Casual Developers Note

エンジニアやデザイナー向けの技術情報・英語学習情報・海外留学情報・海外旅行情報を提供中。世界を旅して人生を楽しもう。

  • ホーム
  • 技術 Tips & Tutorials
  • 技術塾
  • ライフハック
  • 海外留学
  • 英語学習
  • コラム
  • お問い合わせ
現在の場所:ホーム / アーカイブdocker

2019年5月20日 By KD コメントを書く

DockerコンテナのThe PID 1 Problemとその解決策(NodeJS編)

DockerコンテナのThe PID 1 Problemとその解決策(NodeJS編)

Dockerでアプリケーションを構築する場合に発生するメジャーな問題である「The PID 1 Problem」とその解決策を紹介します。

はじめに

アプリケーションをDocker化するにあたり、プロダクション用のDockerfileでは開発用のDockerfile以上にいくつか気をつける点があります。その中の一つが今回紹介する「The PID 1 Problem」の対応です。Dockerをプロダクションに適用する場合は、理解しておく必要があります。それでは、問題の内容と解決策を見ていきましょう。

The PID 1 Problemとは?

「The PID 1 Problem」とは、この記事で指摘されているDockerコンテナのPID 1に関する問題です。一般的にPID 1は「initプロセス」と呼ばれており、システムが起動した際に最初に起動するプロセスです。このinitプロセスの役割は、ゾンビプロセスの除去とサブプロセスへのシグナルの伝搬です。何の対策もせずにアプリケーションをDocker上で起動した場合、そのアプリケーションの起動プロセスがコンテナ上の最初のプロセスになってしまうため、ゾンビプロセスが残ったり、シグナルが正しく処理されないという問題が発生します。開発者として明確に困る点は、Docker化したアプリケーションが正しく停止しないことです。

tiniとは?

「tini」とは、コンテナ向けに作られたシンプルなinitプログラムです。Dockerコンテナの起動コマンドで使用することで、PID 1の本来の役割を果たし、「The PID 1 Problem」を回避できます。

解決策

それではNodeJSの簡単なDockerアプリケーションを作って、「The PID 1 Problem」を解決してみましょう。

サンプルNodeJSアプリケーションの作成

単にメッセージを返すだけのアプリケーションを作ります。

$ mkdir dockerized-app-fixed-pid1
$ cd dockerized-app-fixed-pid1/
$ yarn init -y
$ yarn add express
$ touch app.js
$ touch Dockerfile
$ touch .dockerignore

コードは以下のようにします。

app.js

const express = require('express');

const app = express();

app.get('/', (req, res) => {
  res.send('Hey, dockerized app!');
});

app.listen(3000, () => {
  console.log('Dockerized app is up...');
});

Dockerfile

FROM node:12.2-alpine

ENV NODE_ENV=production

WORKDIR /node
COPY package.json yarn.lock ./
RUN mkdir app && chown -R node:node .
USER node
RUN yarn install && yarn cache clean --force

WORKDIR /node/app
COPY --chown=node:node . .

EXPOSE 3000

CMD ["node", "app.js"]

.dockerignore

node_modules/

試しにビルドして実行してみましょう。

$ docker build -t dockerized-app .
$ docker run -p 3000:3000 dockerized-app
Dockerized app is up...

動作確認してから停止します。

$ curl localhost:3000/
Hey, dockerized app!
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS                    NAMES
a3b8c29a79c6        dockerized-app      "node app.js"       About a minute ago   Up 59 seconds       0.0.0.0:3000->3000/tcp   quizzical_heyrovsky
$ docker top a3b
PID                 USER                TIME                COMMAND
55419               1000                0:00                node app.js
$ docker stop a3b
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                       PORTS               NAMES
a3b8c29a79c6        dockerized-app      "node app.js"       About a minute ago   Exited (137) 4 seconds ago                       quizzical_heyrovsky
$ docker rm a3b

注目点として、このコンテナはControl+Cやdocker stopで停止しようとしてもすぐに停止することはなく、正しい停止処理で失敗して強制終了する形で停止しています。

補足として、NodeJSにおけるExit Codeの見方ですが、公式サイトのExit Codeのページを見ると書かれています。つまり、NodeJSのSignal ExitはUnixのSignal Numberに128を足した数字になります。上記の場合は、Exit Codeは「137」なので、128+9ということであり、「9」は「SIGKILL」のシグナルを示しているので、強制終了されていることが分かります。

--initによる対応(一時的な起動)

Dockerの「--init」オプションと使うことでDockerに同封されているinitプロセスを有効にできます。開発環境など一時的にこの問題を解決したい場合に適しています。

それではこのオプションを付けて起動します。

$ docker run --init -p 3000:3000 dockerized-app
Dockerized app is up...

停止します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAM
ES
200687bc9992        dockerized-app      "node app.js"       38 seconds ago      Up 37 seconds       0.0.0.0:3000->3000/tcp   vig
ilant_hofstadter
$ docker top 200
PID                 USER                TIME                COMMAND
55642               1000                0:00                /dev/init -- node app.js
55677               1000                0:00                node app.js
$ docker stop 200
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                       PORTS               NAMES
200687bc9992        dockerized-app      "node app.js"       54 seconds ago      Exited (143) 7 seconds ago                       vigilant_hofstadter
$ docker rm 200

停止はすばやく実行され、Exit Code「143」(128+15なので「SIGTERM」)で正常に終了しています。

tiniによる対応(永続的な起動)

「tini」をDockerfile上に追加することでプロダクション用にこの問題を解決できます。今回はtiniの「-e」オプションを使い、Exit Code「143」を「0」にマップすることにします。

Dockerfile

FROM node:12.2-alpine

ENV NODE_ENV=production

RUN apk add --no-cache tini

WORKDIR /node
COPY package.json yarn.lock ./
RUN mkdir app && chown -R node:node .
USER node
RUN yarn install && yarn cache clean --force

WORKDIR /node/app
COPY --chown=node:node . .

EXPOSE 3000

ENTRYPOINT ["/sbin/tini", "-e", "143", "--"]
CMD ["node", "app.js"]

それでは変更したDockerfileでビルドして起動します。

$ docker build -t dockerized-app .
$ docker run -p 3000:3000 dockerized-app
Dockerized app is up...

停止します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  
  NAMES
c7ebb0d93ce7        dockerized-app      "/sbin/tini -e 143 -…"   10 seconds ago      Up 9 seconds        0.0.0.0:3000->3000/tcp 
  cocky_haslett
$ docker top c7e
PID                 USER                TIME                COMMAND
55904               1000                0:00                /sbin/tini -e 143 -- node app.js
55938               1000                0:00                node app.js
$ docker stop c7e
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
c7ebb0d93ce7        dockerized-app      "/sbin/tini -e 143 -…"   36 seconds ago      Exited (0) 5 seconds ago                       cocky_haslett

Exit Code「0」で想定通りに終了しています。

最後に

いかがでしたか?これでDockerコンテナのメジャーな問題である「The PID 1 Problem」を解決できるようになったと思います。プロダクションでDockerを適用する際は注意しましょう。それでは。

環境

  • Docker: 18.09.2
  • NodeJS: v12.2.0
  • Yarn: 1.16.0

カテゴリ : 技術 Tips & Tutorials タグ : docker, nodejs

2019年3月4日 By KD コメントを書く

Go言語のRESTアプリケーションを開発用にオートリロード可能な状態でDocker化する方法(fresh編)

Go言語のRESTアプリケーションを開発用にオートリロード可能な状態でDocker化する方法(fresh編)

Go言語によるRESTアプリケーションの開発を加速するために、Docker化し、freshでオートリロードを可能にする方法を紹介します。

はじめに

以前の記事でGo言語でRESTアプリケーションを実装する方法を紹介しました。今回は、開発環境を簡単に構築できるようにするために、Docker化し、その際に、「fresh」というオートリロード用のモジュールを導入する方法を紹介します。それでは、始めましょう。

前提

以下の準備が完了している必要があります。

  • Go言語がインストールされていること
  • Dockerがインストールされていること
  • 以前の記事で実装したRESTアプリケーションが準備されていること(いつも通りチュートリアル形式で進めていきますが、内容を参考にするだけで良いなら不要です)

詳しくは「環境」を参照してください。

Go言語のRESTアプリケーションを開発用にオートリロード可能な状態でDocker化する

必要なファイルとフォルダを準備する

以前の記事で実装したRESTアプリケーションを準備して、そのプロジェクト内に必要なファイルとフォルダを作成します。

$ cd golang-rest-api/
$ touch Dockerfile.dev
$ touch docker-compose.yml
$ mkdir data
$ touch wait-for-it.sh

今回はローカルにPostgreSQLのデータを残すためにdataフォルダも作成します。また、PostgreSQLのデータベースが起動するのを待つために「wait-for-it」を使います。「wait-for-it.sh」にはGitHub上のスクリプトをコピペしてください。

Dockerファイルを作成する

Dockerファイル内でオートリロードのためのモジュール「fresh」を設定するようにします。

Dockerfile.dev

FROM golang:1.11.4-stretch

LABEL maintainer="YOU"

WORKDIR /app

COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go get github.com/pilu/fresh
RUN go get -u github.com/go-delve/delve/cmd/dlv

COPY . .

EXPOSE 3000

CMD fresh main.go

Docker Composeファイルを作成する

docker-compose.yml

version: '3'

services:
  app:
    container_name: my_app
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
    depends_on:
      - db
    command: >
      bash -c "chmod +x ./wait-for-it.sh &&
        ./wait-for-it.sh db:5432 -t 30 -- fresh main.go"
  db:
    container_name: my_postgres
    image: postgres:11.1
    restart: always
    environment:
      - "TZ: Etc/UTC"
      - POSTGRES_USER=puser
      - POSTGRES_PASSWORD=ppassword
      - POSTGRES_DB=testdb
    ports:
      - 5432:5432
    volumes:
      - ./data:/var/lib/postgresql/data
      - ./initdb:/docker-entrypoint-initdb.d

RESTアプリケーション内のPostgreSQLの接続先をDocker Composeの設定に合わせて変更する

接続先を「localhost」から「my_postgres」に変更します。

main.go

package main

import (
    "database/sql"
    "golang-rest-api/controllers"
    "log"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/lib/pq"
)

func main() {
    pgURL, err := pq.ParseURL("postgres://puser:ppassword@my_postgres:5432/testdb?sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }

    db, err := sql.Open("postgres", pgURL)
    if err != nil {
        log.Fatal(err)
    }

    controller := controllers.Controller{}

    router := mux.NewRouter()

    router.HandleFunc("/api/tweets", controller.GetTweets(db)).Methods("GET")
    router.HandleFunc("/api/tweets/{id}", controller.GetTweet(db)).Methods("GET")
    router.HandleFunc("/api/tweets", controller.AddTweet(db)).Methods("POST")
    router.HandleFunc("/api/tweets/{id}", controller.PutTweet(db)).Methods("PUT")
    router.HandleFunc("/api/tweets/{id}", controller.RemoveTweet(db)).Methods("DELETE")

    log.Println("Server up on port 3000...")
    log.Fatal(http.ListenAndServe(":3000", router))
}

動作確認

では動作確認してみましょう。

まずは、Docker化したRESTアプリケーションが正しく動くか確認します。

$ docker-compose up -d
Creating network "golang-rest-api_default" with the default driver
Creating my_postgres ... done
Creating my_app      ... done
$ docker-compose ps
   Name                  Command               State           Ports         
-----------------------------------------------------------------------------
my_app        bash -c chmod +x ./wait-fo ...   Up      0.0.0.0:3000->3000/tcp
my_postgres   docker-entrypoint.sh postgres    Up      0.0.0.0:5432->5432/tcp
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -X POST -d '{"content":"This is my first tweet.","user_name":"@keidrun","comment_num":5,"star_num":15,"re_tweet_num":25}' http://localhost:3000/api/tweets | jq
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -X POST -d '{"content":"Golang is my favorite language!","user_name":"@superdeveloper","comment_num":22,"star_num":222,"re_tweet_num":2222}' http://localhost:3000/api/tweets | jq
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -X POST -d '{"content":"I am nothing. Just an ordinary guy.","user_name":"@person"}' http://localhost:3000/api/tweets | jq
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets | jq
...
[
  {
    "id": 1,
    "content": "This is my first tweet.",
    "user_name": "@keidrun",
    "comment_num": 5,
    "star_num": 15,
    "re_tweet_num": 25
  },
  {
    "id": 2,
    "content": "Golang is my favorite language!",
    "user_name": "@superdeveloper",
    "comment_num": 22,
    "star_num": 222,
    "re_tweet_num": 2222
  },
  {
    "id": 3,
    "content": "I am nothing. Just an ordinary guy.",
    "user_name": "@person",
    "comment_num": 0,
    "star_num": 0,
    "re_tweet_num": 0
  }
]

次に、オートリロードが動作するか確認するために、エラーメッセージを変更してみます。

$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets/1 | jq
...
{
  "id": 1,
  "content": "This is my first tweet.",
  "user_name": "@keidrun",
  "comment_num": 5,
  "star_num": 15,
  "re_tweet_num": 25
}
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets/num | jq
...
{
  "message": "\"id\" is wrong"
}
(ここでソースコード上のエラーメッセージを変更する)
$ curl -v -H "Accept:application/json" http://localhost:3000/api/tweets/num | jq
...
{
  "message": "\"id\" is absolutely wrong"
}

正しく動くことが確認できました。

おまけ

プロダクション用にDocker化する

Go言語はコンパイル言語なので、プロダクション用にはコンパイルして実行可能な状態にする必要があります。alpineで軽量のDockerイメージにする場合のDockerファイルは以下のようになります。

Dockerfile

FROM golang:1.11.4-stretch AS builder
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server

FROM alpine:3.8
LABEL maintainer="YOU"
WORKDIR /app
EXPOSE 3000
COPY --from=builder /app/server /app/
ENTRYPOINT ["/app/server"]

最後に

いかがでしたか?これでGo言語のRESTアプリケーションをDocker上で開発できるようになったことでしょう。それでは。

環境

  • Go: go1.11.4
  • Docker: 18.09.1, build 4c52b90
  • fresh: v0.0.0-20170301142741-9c0092493eff

カテゴリ : 技術 Tips & Tutorials タグ : auto-reload, docker, fresh, golang, hot-reload, rest-api

2019年2月25日 By KD コメントを書く

WordPressのHeadless CMSのREST APIを使う開発環境をDocker上に構築する方法(JWT認証編)

WordPressのHeadless CMSのREST APIを使う開発環境をDocker上に構築する方法(JWT認証編)

WordPressのフロントエンドを使わず、WordPressをバックエンドのAPIとして構築するHeadless CMSという手法が人気を集めています。今回は、WordPressで、Headless CMSのREST APIを使う開発環境をDocker上に構築し、JWT認証でCRUDする方法を紹介します。

はじめに

従来は、WordPressでCMSを構築する場合、フロントエンドとバックエンドを分けることなく、外観はCSSとPHPでテーマとして作成する方法が一般的でした。しかし、最近では、フロントエンドが多様化しており、WordPressだけであらゆるフロントエンドに対応することが困難であるため、WordPressをバックエンドのAPIとして動作させるHeadless CMSという手法が人気になっています。最新のWordPressはデフォルトで複数のフォーマットのAPIをサポートしており、簡単にHeadless CMSとして利用できるようになっています。(詳しくは公式ドキュメントを参照してください。)

今回は、WordPressをHeadless CMSのREST APIのバックエンドとしてDocker上に開発環境として構築する方法を紹介します。REST APIにした場合に必須の認証としてJWT認証の導入方法も紹介します。それでは、やっていきましょう。

Headless CMSとは?

ヘッドレスCMS(Headless CMS)とは、フロントエンドで提供しているコンテンツを配信する機能と、バックエンドで提供しているコンテンツを管理する機能を分離して、バックエンドに特化したCMSです。WordPressは従来から両方の機能を提供していますが、APIの機能をもたせることでバックエンドに特化させて機能させることができます。少し前はAPI用のプラグインが必要でしたが、今ではデフォルトでWordPressのAPIが利用可能です。

WordPressのHeadless CMS環境を構築する

前提

事前に以下を準備しておく必要があります。

  • Dockerがインストールされていること

詳しくは「環境」を参照してください。

必要なファイルとフォルダの準備

必要なファイルとフォルダを作成します。

$ mkdir wordpress-headless-cms
$ cd wordpress-headless-cms/
$ mkdir wordpress
$ touch wordpress/000-default.conf
$ touch wordpress/ports.conf
$ touch wordpress/.htaccess
$ touch wordpress/Dockerfile
$ touch docker-compose.yml
$ tree -a
.
├── docker-compose.yml
└── wordpress
    ├── .htaccess
    ├── 000-default.conf
    ├── Dockerfile
    └── ports.conf

Apacheの設定ファイルを作成する

今回はルートを「/var/www/html/wordpress」として、8080ポートでリッスンするようにします。

ports.conf

Listen 8080

<IfModule ssl_module>
    Listen 443
</IfModule>

<IfModule mod_gnutls.c>
    Listen 443
</IfModule>

000-default.conf

<VirtualHost *:8080>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html/wordpress

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    <Directory /var/www/html/wordpress>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

.htaccess

<IfModule mod_rewrite.c>
# JWT
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
</IfModule>

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress

WordPress用のDockerfileを作成する

今回はApacheベースのPHPの公式イメージにWP-CLIでWordPressを入れることにします。(補足ですが、ほぼ同じ設定がWordPressの公式イメージを使えば最初からできます)

Dockerfile

FROM php:7.3-apache

ARG APACHE_RUN_USER=daemon
ARG WP_LOCALE=ja
ARG WP_DB_NAME=wordpress
ARG WP_DB_USER=wordpress
ARG WP_DB_PASS=wordpress
ARG WP_DB_HOST=db
ARG WP_DB_PORT=3306
ARG WP_DB_PREFIX=wp_
ARG WP_DB_CHARSET=utf8

WORKDIR /var/www/html/wordpress

RUN apt-get update -y && apt-get install -y \
  libpng-dev \
  libmagickwand-dev \
  sendmail \
  mysql-client \
  vim

# Apache
RUN a2enmod rewrite

# PHP
RUN docker-php-ext-install mysqli
RUN docker-php-ext-install gd bcmath exif
RUN pecl install imagick && docker-php-ext-enable imagick
RUN cp /usr/local/etc/php/php.ini-development /usr/local/etc/php/php.ini
RUN chmod 666 /usr/local/etc/php/php.ini

# WordPress
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
RUN php wp-cli.phar --info
RUN chmod +x wp-cli.phar && mv wp-cli.phar /usr/local/bin/wp
RUN wp --info
RUN chown -R $APACHE_RUN_USER:$APACHE_RUN_USER /usr/sbin
RUN chown -R $APACHE_RUN_USER:$APACHE_RUN_USER /var/www/html/wordpress

USER $APACHE_RUN_USER
RUN wp core download --locale=$WP_LOCALE
RUN wp config create --dbname=$WP_DB_NAME --dbuser=$WP_DB_USER --dbpass=$WP_DB_PASS --dbhost=$WP_DB_HOST:$WP_DB_PORT --dbprefix=$WP_DB_PREFIX --dbcharset=$WP_DB_CHARSET --skip-check

EXPOSE 8080

Docker Composeを作成する

WordPressとMySQLの開発環境をDocker Composeで作成します。

docker-compose.yml

version: "3"

services:
  web:
    container_name: web
    build: ./wordpress
    ports:
      - "8080:8080"
    volumes:
      - "./wordpress/ports.conf:/etc/apache2/ports.conf:ro"
      - "./wordpress/000-default.conf:/etc/apache2/sites-available/000-default.conf:ro"
      - "./wordpress/.htaccess:/var/www/html/wordpress/.htaccess"
    depends_on:
      - db
  db:
    container_name: db
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    ports:
      - "3306:3306"
    volumes:
      - "./data:/var/lib/mysql"

WP-CLIでWordPressを初期設定する

今回のDockerイメージにWP-CLIをインストール済みなので、それを使ってWordPressの最小限の設定をします。

$ docker-compose up --build -d
$ docker-compose ps
Name              Command               State                 Ports              
---------------------------------------------------------------------------------
db     docker-entrypoint.sh --def ...   Up      0.0.0.0:3306->3306/tcp, 33060/tcp
web    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:8080->8080/tcp  
$ docker exec web wp core install --url=http://localhost:8080 --title='WordPress REST API' --admin_user=wordpress --admin_password=wordpress --admin_email=info@example.com
$ docker exec web wp rewrite structure '/%postname%/'
$ docker exec web wp language core update
$ docker exec web wp plugin update --all
$ docker exec web wp plugin install classic-editor --activate
$ docker exec web wp plugin install health-check --activate
$ docker exec web wp plugin install wp-api-menus --activate
$ docker exec web wp plugin install jwt-authentication-for-wp-rest-api --activate

パーマリンクは分かりやすくポスト名を設定しています。

プラグインは最低限として以下を入れています。

  • Classic Editor
  • Health Check & Troubleshooting
  • WP API Menus
  • JWT Authentication for WP REST API

ブラウザで「http://localhost:8080/wp-admin/」にアクセスしてWordPressの管理画面にログインし、ヘルスチェックを確認します。

NewImage

NewImage

上記のように「REST API の利用」の項目が「REST API は使用可能です。」となっていれば、WordPressはHeadless CMSとして利用可能になっています。

JWT認証用の変数をWordPressに追加する

JWT用のプラグインの設定の通りにWP-CLIで変数を設定します。「JWT_AUTH_SECRET_KEY」は適当にランダム値を設定することにします。

$ docker exec web wp config set JWT_AUTH_SECRET_KEY '=~MWjpLDCv/x46d7saHklnP)[W4K|qmp?InD});2t|kX{9Kq{.Th8jfJ0-^PE|U~'
$ docker exec web wp config set JWT_AUTH_CORS_ENABLE true --raw
$ docker exec web wp config list | grep JWT_AUTH
JWT_AUTH_SECRET_KEY =~MWjpLDCv/x46d7saHklnP)[W4K|qmp?InD});2t|kX{9Kq{.Th8jfJ0-^PE|U~    constant
JWT_AUTH_CORS_ENABLE    1   constant

なお、「.htaccess」の設定はすでに完了しています。

WordPressのREST APIの動作確認をする

それでは、構築したHeadless CMSのREST APIのCRUDの動作を確認してみましょう。

初期データの削除

WordPressに最初から入っている記事と固定ページのデータを削除し、一応メニューのデータも削除します。

$ docker exec web wp post delete $(docker exec web wp post list --post_type=page,post --format=ids)
$ docker exec web wp menu delete $(docker exec web wp menu list --format=ids)
$ curl http://localhost:8080/wp-json/wp/v2/posts | jq
[]
$ curl http://localhost:8080/wp-json/wp/v2/pages | jq
[]
$ curl http://localhost:8080/wp-json/wp-api-menus/v2/menus | jq
[]

JWTトークンの取得

まずはユーザ名とパスワードからJWTのトークンを取得します。

$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -X POST -d '{"username":"wordpress","password":"wordpress"}' http://localhost:8080/wp-json/jwt-auth/v1/token | jq
{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8",
  "user_email": "info@example.com",
  "user_nicename": "wordpress",
  "user_display_name": "wordpress"
}
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X POST http://localhost:8080/wp-json/jwt-auth/v1/token/validate | jq
{
  "code": "jwt_auth_valid_token",
  "data": {
    "status": 200
  }
}

このトークンを使って記事、固定ページをCRUDしましょう。

記事の作成

記事を3つ作成して公開します。

$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X POST -d '{"title":"What is WordPress Headless CMS?","content":"Now, WordPress is a great headless cms","status":"publish"}' http://localhost:8080/wp-json/wp/v2/posts | jq
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X POST -d '{"title":"Why Do I love WordPress?","content":"WordPress is really awesome.","status":"publish"}' http://localhost:8080/wp-json/wp/v2/posts | jq
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X POST -d '{"title":"Everyone Must Know JWT","content":"JWT is one of the most popular authorization.","status":"publish"}' http://localhost:8080/wp-json/wp/v2/posts | jq

記事の取得

作成した記事を取得します。

$ curl http://localhost:8080/wp-json/wp/v2/posts | jq
[
  {
    "id": 10,
...
    "title": {
      "rendered": "Everyone Must Know JWT"
    },
    "content": {
      "rendered": "<p>JWT is one of the most popular authorization.</p>\n",
      "protected": false
    },
...
  },
  {
    "id": 9,
...
    "title": {
      "rendered": "Why Do I love WordPress?"
    },
    "content": {
      "rendered": "<p>WordPress is really awesome.</p>\n",
      "protected": false
    },
...
  },
  {
    "id": 8,
...
    "title": {
      "rendered": "What is WordPress Headless CMS?"
    },
    "content": {
      "rendered": "<p>Now, WordPress is a great headless cms</p>\n",
      "protected": false
    },
...
  }
]
$ curl http://localhost:8080/wp-json/wp/v2/posts/9 | jq
{
  "id": 9,
...
  "title": {
    "rendered": "Why Do I love WordPress?"
  },
  "content": {
    "rendered": "<p>WordPress is really awesome.</p>\n",
    "protected": false
  },
...
}

記事の更新

記事を1つ更新します。

$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X PUT -d '{"title":"Why Do I love JavaScript?","content":"JavaScript is really awesome.","status":"publish"}' http://localhost:8080/wp-json/wp/v2/posts/9 | jq
$ curl http://localhost:8080/wp-json/wp/v2/posts/9 | jq
{
  "id": 9,
...
  "title": {
    "rendered": "Why Do I love JavaScript?"
  },
  "content": {
    "rendered": "<p>JavaScript is really awesome.</p>\n",
    "protected": false
  },
...
}

記事の削除

記事を1つ削除します。

$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X DELETE http://localhost:8080/wp-json/wp/v2/posts/8 | jq
$ curl http://localhost:8080/wp-json/wp/v2/posts | jq
[
  {
    "id": 10,
...
    "title": {
      "rendered": "Everyone Must Know JWT"
    },
    "content": {
      "rendered": "<p>JWT is one of the most popular authorization.</p>\n",
      "protected": false
    },
...
  },
  {
    "id": 9,
...
    "title": {
      "rendered": "Why Do I love JavaScript?"
    },
    "content": {
      "rendered": "<p>JavaScript is really awesome.</p>\n",
      "protected": false
    },
...
  }
]

固定ページの作成

固定ページを3つ作成して公開します。

$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X POST -d '{"title":"Welcome","content":"Welcome to this blog!","status":"publish"}' http://localhost:8080/wp-json/wp/v2/pages | jq
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X POST -d '{"title":"Privacy Policy","content":"We protect your privacy.","status":"publish"}' http://localhost:8080/wp-json/wp/v2/pages | jq
$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X POST -d '{"title":"Contact us","content":"Contact us if you have any questions.","status":"publish"}' http://localhost:8080/wp-json/wp/v2/pages | jq

固定ページの取得

作成した固定ページを取得します。

$ curl http://localhost:8080/wp-json/wp/v2/pages | jq
[
  {
    "id": 15,
...
    "title": {
      "rendered": "Contact us"
    },
    "content": {
      "rendered": "<p>Contact us if you have any questions.</p>\n",
      "protected": false
    },
...
  },
  {
    "id": 14,
...
    "title": {
      "rendered": "Privacy Policy"
    },
    "content": {
      "rendered": "<p>We protect your privacy.</p>\n",
      "protected": false
    },
...
  },
  {
    "id": 13,
...
    "title": {
      "rendered": "Welcome"
    },
    "content": {
      "rendered": "<p>Welcome to this blog!</p>\n",
      "protected": false
    },
...
  }
]
$ curl http://localhost:8080/wp-json/wp/v2/pages/14 | jq
{
  "id": 14,
...
  "title": {
    "rendered": "Privacy Policy"
  },
  "content": {
    "rendered": "<p>We protect your privacy.</p>\n",
    "protected": false
  },
...
}

固定ページの更新

固定ページを1つ更新します。

$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X PUT -d '{"title":"Public Policy","content":"We publish everything in public.","status":"publish"}' http://localhost:8080/wp-json/wp/v2/pages/14 | jq
{
  "id": 14,
...
  "title": {
    "rendered": "Public Policy"
  },
  "content": {
    "rendered": "<p>We publish everything in public.</p>\n",
    "protected": false
  },
...
}

固定ページの削除

固定ページを1つ削除します。

$ curl -v -H "Accept:application/json" -H "Content-Type:application/json" -H "Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImlhdCI6MTU1MDU3Mjg2MywibmJmIjoxNTUwNTcyODYzLCJleHAiOjE1NTExNzc2NjMsImRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.fx4eJabYaPNdxZQIzuFdD_bJ8OUkRHV1I9fuHCWAbb8" -X DELETE http://localhost:8080/wp-json/wp/v2/pages/14 | jq
$ curl http://localhost:8080/wp-json/wp/v2/pages | jq
[
  {
    "id": 15,
...
    "title": {
      "rendered": "Contact us"
    },
    "content": {
      "rendered": "<p>Contact us if you have any questions.</p>\n",
      "protected": false
    },
...
  },
  {
    "id": 13,
...
    "title": {
      "rendered": "Welcome"
    },
    "content": {
      "rendered": "<p>Welcome to this blog!</p>\n",
      "protected": false
    },
...
  }
]

メニューの取得

メニューは取得のみになるので、WP-CLIでデータ作成して、取得します。

$ docker exec web wp menu create "Main menu"
$ docker exec web wp menu create "Footer menu"
$ curl http://localhost:8080/wp-json/wp-api-menus/v2/menus | jq
[
  {
    "term_id": 3,
    "name": "Footer menu",
    "slug": "footer-menu",
    "term_group": 0,
    "term_taxonomy_id": 3,
    "taxonomy": "nav_menu",
    "description": "",
    "parent": 0,
    "count": 0,
    "filter": "raw",
    "ID": 3,
    "meta": {
      "links": {
        "collection": "http://localhost:8080/wp-json/wp-api-menus/v2/menus/",
        "self": "http://localhost:8080/wp-json/wp-api-menus/v2/menus/3"
      }
    }
  },
  {
    "term_id": 2,
    "name": "Main menu",
    "slug": "main-menu",
    "term_group": 0,
    "term_taxonomy_id": 2,
    "taxonomy": "nav_menu",
    "description": "",
    "parent": 0,
    "count": 0,
    "filter": "raw",
    "ID": 2,
    "meta": {
      "links": {
        "collection": "http://localhost:8080/wp-json/wp-api-menus/v2/menus/",
        "self": "http://localhost:8080/wp-json/wp-api-menus/v2/menus/2"
      }
    }
  }
]
$ curl http://localhost:8080/wp-json/wp-api-menus/v2/menus/2 | jq
{
  "ID": 2,
  "name": "Main menu",
  "slug": "main-menu",
  "description": "",
  "count": 0,
  "items": [],
  "meta": {
    "links": {
      "collection": "http://localhost:8080/wp-json/wp/v2/menus/",
      "self": "http://localhost:8080/wp-json/wp/v2/menus/2"
    }
  }
}

うまくいきましたね。

おまけ

WordPressでHeadless CMSをDocker上で構築する場合に発生する可能性のあるエラーと対処法を紹介します。

「http://localhost:8080/wp-json/wp/v2/」にアクセスした時に404 Not Foundになる場合の対処法

  • パーマリンクを「pretty permalinks」に変更する。(公式ドキュメントに書かれている通り、デフォルトは「non-pretty permalinks」と呼ばれており、デフォルト以外に変更する必要がある。デフォルトでは「http://localhost:8080/?rest_route=/wp/v2/」ならアクセスできる。)
  • Apacheに「mod_rewite」を入れる。(PHPの公式Dockerイメージにはデフォルトで入っていないので、「a2enmod rewrite」でインストールする必要がある。「.htaccess」はWordPressで自動で作成される。)

パーマリンク変更後に記事の更新などで失敗する場合の対処法

  • 「Classic Editor」のプラグインを導入する。(新しいエディタ「Gutenberg」の不具合の場合がある)

外観のテーマの編集の更新で失敗する場合の対処法

  • Dockerのホスト側ポートとコンテナ側ポートが同じになるように設定する(今回の場合は「docker-compose.yml」のports、「ports.conf」のListenポート、「000-default.conf」のVirtualHostを8080に統一しました。)

最後に

いかがでしたか?これでWordPressのHeadless CMSのREST APIを使う開発環境が構築できましたね。それでは。

環境

  • Docker: 18.09.1, build 4c52b90
  • WordPress: 5.0.3
  • PHP: 7.3.2
  • MySQL: 8.0.15

カテゴリ : 技術 Tips & Tutorials タグ : docker, headless-cms, jwt, rest-api, wordpress

  • « 前のページ
  • 1
  • 2
  • 3
  • 4
  • …
  • 7
  • 次のページ »

ブログ更新情報や海外の関連情報などを配信する無料メルマガ

Sponsored Links

About Author

KD

世界を旅し日本を愛するエンジニア。大学でコンピュータサイエンスの楽しさを学び、日本の大手IT企業で働く中で、新しい技術やスケールするビジネスが北米にある事に気づく。世界に挑戦するための最大の壁が英語であったため、フィリピン留学およびカナダ留学を経て英語を上達させた。現在は日本在住でエンジニアとして働きつつ、次の挑戦に備えて世界の動向を注視している。挑戦に終わりはない。このブログでは、エンジニアやデザイナー向けの技術情報から、海外に留学したい人向けの留学情報、海外に興味がある人向けの海外旅行情報など、有益な情報を提供しています。

https://casualdevelopers.com/

最近の投稿

  • 2020年JS周辺のバックエンド寄りの注目技術!ネクストNodeJSの「Deno」と分散型パッケージレジストリの「Entropic」の紹介

    2020年JS周辺のバックエンド寄りの注目技術!ネクストNodeJSの「Deno」と分散型パッケージレジストリの「Entropic」の紹介

    2020年1月13日
  • 今さら聞けないJavaによる関数型プログラミング入門 ~ラムダ式、ストリーム、関数型インターフェース~

    今さら聞けないJavaによる関数型プログラミング入門 ~ラムダ式、ストリーム、関数型インターフェース~

    2019年11月4日
  • ReactのためのEslintおよびPrettierの設定方法 ~Airbnb JavaScript Style Guideの適用~

    ReactのためのEslintおよびPrettierの設定方法 ~Airbnb JavaScript Style Guideの適用~

    2019年10月30日
  • BashからZshに移行する方法(Mac編)

    BashからZshに移行する方法(Mac編)

    2019年10月21日
  • Create React Appを使わないでゼロからReactの開発環境を構築する方法(Webpack/Docker編)

    Create React Appを使わないでゼロからReactの開発環境を構築する方法(Webpack/Docker編)

    2019年9月30日

カテゴリ

  • 技術 Tips & Tutorials (100)
  • 技術塾 (6)
  • ライフハック (26)
  • 海外留学 (12)
  • 英語学習 (3)
  • コラム (6)

アーカイブ

最高の学習のために

人気記事ランキング

  • MySQLで「ERROR 2003 (HY000): Can't connect to MySQL server」と怒られた時の対処法
    MySQLで「ERROR 2003 (HY000): Can't connect to MySQL server」と怒られた時の対処法
  • SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
    SAKURAのメールボックスで独自ドメインのメールを設定し、Gmail経由で送受信する方法
  • Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
    Jupyter Notebookで「The kernel appears to have died. It will restart automatically.」というエラーが出た場合の原因と対処法
  • Expressで「Cannot set headers after they are sent to the client」と怒られた時の対処法
    Expressで「Cannot set headers after they are sent to the client」と怒られた時の対処法
  • [tips][perl] Perlで文字コードをいい感じに処理する方法
    [tips][perl] Perlで文字コードをいい感じに処理する方法
  • Amazon EC2インスタンスにSSHできなくなった時の対処法
    Amazon EC2インスタンスにSSHできなくなった時の対処法
  • SLF4JとLogbackによるJavaのロギング入門(SLF4J + Logback + Lombok)
    SLF4JとLogbackによるJavaのロギング入門(SLF4J + Logback + Lombok)
  • AWS ECRとECSの入門(EC2編) ~ ECSのEC2版を使ってReactのDockerアプリケーションをAWS上で稼働させる方法 ~
    AWS ECRとECSの入門(EC2編) ~ ECSのEC2版を使ってReactのDockerアプリケーションをAWS上で稼働させる方法 ~
  • PythonでWebスクレイピング入門(Scrapy+Selenium編)
    PythonでWebスクレイピング入門(Scrapy+Selenium編)
  • [tips][Sublime Text] Sublime Text 3で文字化けしない方法
    [tips][Sublime Text] Sublime Text 3で文字化けしない方法

Bitcoin寄付 / BTC Donation

Bitcoinを寄付しよう

BTC
Select Payment Method
Personal Info

Donation Total: BTC 0.0010

このブログの運営のためにBitcoinでの寄付を募集しています。お気持ち程度の寄付を頂けると管理者の励みになります。

Bitcoin寄付について知りたい方はこちらの記事へ

ビットコイン取引ならここ

  • ホーム
  • 技術 Tips & Tutorials
  • 技術塾
  • ライフハック
  • 海外留学
  • 英語学習
  • コラム
  • サイトマップ
  • タグ一覧
  • プライバシーポリシー
  • お問い合わせ

Copyright © 2023 KD - Casual Developers Notes