docker×nginx×php-fpmでwordpress複数台を負荷分散させつつ動かしてみました。

特別Wordpressでブログを出しているような身ではないのですが、少し変わった環境を構築してみたいかもという興味本位でやってみました。
Wordpressサイト運用でプラグイン入れたりバージョン上げたりしたら動かないなんてことも聞くので、こうやって仮想環境を導入してローカル検証の手段を確保したり、将来的にそのままECSなどクラウドコンテナ管理サービスにデプロイして運用したりするといいのかもと思ってる最近です。

いくつかヒットした日本語サイトの情報が古いもので参考にできなかったので、公式ドキュメントのみ参照して2021年最新のもののみで作成しました。将来のバージョンアップも考慮して使用バージョンも残してまとめています。

同じようなことをしたい方に参考にしていただければ幸いです。


## 環境
- Windows 10 (Docker Desktopインストール済)


## ディレクトリ構成

```
project/
  ├ .docker/
  │   ├ env_files/
  │   │   ├ mysql.env
  │   │   └ wordpress.env
  │   └ nginx/
  │       └ default.conf
  └ docker-compose.yml
```


## 使用したdockerイメージ

パッチバージョンまで指定すると後で使えなくなった時に困るので、マイナーバージョンまでの指定でイメージを使用するのをお勧めします。バージョン違いで動かないことも稀にあるので指定は必ず行いましょう。

以下に使用したイメージと特記事項を残しています。

#### nginx:1.21-alpine
- 特記事項なし

#### wordpress:5.7-php7.4-fpm-alpine
- 7.4-fpmを土台にしたphpイメージのため、9000番ポートがデフォルトでexportされている([参考ページ](https://github.com/docker-library/php/blob/368453e2918b0a4a5ce488cc987c59c30e4ae4be/7.4/buster/fpm/Dockerfile))

#### mysql:8.0
- デフォルトで3306番ポート、33060番ポートがexportされている
- MySQL8.0以上ではデフォルトの認証方式が5.7以前と異なるため注意


## 具体的な実装方法

### コード

```yml:project/docker-compose.yml
version: "3.9"

services:
  nginx:
    image: nginx:1.21-alpine
    ports:
      - "3000:80"
    volumes:
      - ./.docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./html:/var/www/html
    depends_on:
      - wp_first
      - wp_second
      - wp_third

  wp_first:
    image: wordpress:5.7-php7.4-fpm-alpine
    volumes:
      - ./html:/var/www/html
    env_file:
      - ./.docker/env_files/wordpress.env
    depends_on:
      - mysql

  wp_second:
    image: wordpress:5.7-php7.4-fpm-alpine
    volumes:
      - ./html:/var/www/html
    env_file:
      - ./.docker/env_files/wordpress.env
    depends_on:
      - mysql

  wp_third:
    image: wordpress:5.7-php7.4-fpm-alpine
    volumes:
      - ./html:/var/www/html
    env_file:
      - ./.docker/env_files/wordpress.env
    depends_on:
      - mysql

  mysql:
    image: mysql:8.0
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=mysql_native_password
    volumes:
      - ./.docker/mysql:/var/lib/mysql
    env_file:
      - ./.docker/env_files/mysql.env

```

```nginx:project/.docker/nginx/default.conf
upstream wp_fastcgi_passes {
    least_conn;
    server wp_first:9000;
    server wp_second:9000;
    server wp_third:9000;
}

server {
    listen 80;

    root /var/www/html/;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass wp_fastcgi_passes;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }
}
```

```:project/.docker/env_files/mysql.env
MYSQL_ROOT_HOST=%
MYSQL_ROOT_USER=root
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=wordpress
```

```:project/.docker/env_files/wordpress.env
WORDPRESS_DB_HOST=mysql
WORDPRESS_DB_USER=root
WORDPRESS_DB_PASSWORD=root
WORDPRESS_DB_NAME=wordpress
```

### docker関連の説明

#### Wordpressファイルの自動生成場所をマウントしておく

基本的にwordpressコンテナを立ち上げると1分くらいで`/var/www/html`に生成されるので、wordpressコンテナのvolumeに`./html:/var/www/html`を指定しておきます。こうすると、今は無い「html」というディレクトリがいつの間にか出来上がります。

#### 静的ファイルを配信する準備をしておく

nginxコンテナのvolumeにも`./html:/var/www/html`を指定しておきます。wordpressを動かしているfpmはphpのみをさばく役割のみ担っているため、cssやjavascript、画像などの静的ファイルはnginxから配信します。

#### mysql8.0の起動方法に気を付ける

上述した通りで認証方法が変わっているので、mysqlのcommandに`--default-authentication-plugin=mysql_native_password`を指定しておきましょう。


### nginx関連の説明

#### rootとindexのphpファイルを指定する

nginxにドキュメントがどこにあるのか、「/」のリクエスト(=index)が飛んできたときに見に行くファイルがindex.htmlでなくindex.phpのリクエストであると指定しておきましょう。何も指定していないとphpファイルを探してくれません。

#### phpファイルが来た時の流し方を指定する

`location ~ \.php$`で、phpファイルへのリクエストの時にはfastcgiに処理を流すように指定してください。こうすることでwordpressコンテナにphpの処理を任せられるので、nginxは静的ファイルの配信とphpリクエストの誘導のみ行うようになります。

#### 複数台構成のfastcgiへの負荷分散を指定する

upstreamで`(コンテナ名):(exposeしているポート番号)`を複数指定すれば、あとはnginxの方で勝手に負荷分散(ロードバランシング)してくれます。これをfastcgi_passに使うことでnginx1台でfpm複数台へ処理を流す設定完了です。


## 実際にやってみる

### mysqlの初期化

最初はデータベースの初期化(1~2分)が必要なので、進捗確認目的もかねてバックグラウンド実行はせずに立ち上げてください。終わったらCtrl+Cを2回くらい押すと停止します。

```bash
$ docker-compose up mysql
```

### Wordpressファイルの生成

いきなりWordpress複数台立ち上げるとそれぞれのコンテナでWordpressファイルを生成し始めちゃうので、最初は必ず1つだけ立ち上げてください。こちらは進捗がコマンドに出ないので、`wp-admin`, `wp-content`, `wp-includes`が出そろってしばらくファイル生成がなければ停止してください。

```bash
$ docker-compose up wp_first
```

### 一気に全部立ち上げましょう

負荷分散を実感するため、今回は`docker-compose up -d`を使用していません。wp_firstやwp_second、wp_thirdそれぞれにリクエストの処理が割り振られているのを確認できると思います。

```bash
$ docker-compose up
```

### ブラウザで確認

`localhost:3000`へアクセス。以上！


## 参考

- [wordpress - Docker Hub](https://hub.docker.com/_/wordpress)
- [wordpress/Dockerfile at 1d90641dc2075168fe59df2f02502df068cc5531 · docker-library/wordpress · GitHub](https://github.com/docker-library/wordpress/blob/1d90641dc2075168fe59df2f02502df068cc5531/latest/php7.4/fpm/Dockerfile)
- [php/Dockerfile at 368453e2918b0a4a5ce488cc987c59c30e4ae4be · docker-library/php · GitHub](https://github.com/docker-library/php/blob/368453e2918b0a4a5ce488cc987c59c30e4ae4be/7.4/buster/fpm/Dockerfile)
- [Nginx upstream module - Nginx.org](http://nginx.org/en/docs/http/ngx_http_upstream_module.html)