DockerでWordPress環境を用意するのが簡単じゃなかった話

#### はじめに
この記事は、次の記事

「[今更だけどDockerでWordPress環境を用意してみたら超簡単だった](https://qiita.com/maimax/items/5090fd37f9832fe3c5fd)」

へのオマージュです。この記事の通りにやってみた（つもりだった）のだが、全く簡単じゃなかったという話です。

但し、元記事が悪いわけでは決してなく、原因は一重に私の横着に帰するものであることを強調しておかなければなりません。年寄りは経験だけはあるので、マニュアルや指導書をちゃんと読みもせず、こうだろうという思い込みでやってしまうところがあります。それが、現場でベテランのIT技術者が敬遠される理由のひとつなのですが、多いに反省せねばなりません。

この記事では、WordPressの起動の失敗から解決にいたるまでの顛末記を披露します。結局は元記事をよく読めということなのですが、問題点を究明するまでの過程でDockerの構造をよく把握出来ました。苦労は無駄ではなかったようです。Dockerイメージを分析する際などで、この記事は参考になると思います。

なお、ここで使用しているDockerは、DebianのStretchリリースのdocker.ioパッケージであり、アップストリームのバージョンは18.09.1です。あらかじめカレントユーザをdockerグループに所属させていますので、ユーザレベルでdockerの実行が可能です。


#### 使用したコマンド類
結論を得るまでの過程で使用したコマンドを先にあげておきます。以下では短縮形のコマンドを用いています。

|コマンド|短縮形|意味|
|:--|:--|:--|
|docker image pull|docker pull|イメージをダウンロードする|
|docker container run|docker run|コンテナを作成し開始する|
|docker container ls -a|docker ps -a|コンテナをリストする|
|docker image ls|docker images|イメージをリストする|
|docker container exec|docker exec|コンテナのコマンドを実行する|
|docker container exec -it|docker exec -it|ターミナル上でコマンドを実行する|
|docker container cp|docker cp|ホストとコンテナの間でファイルをコピーする|
|docker container stop|docker stop|コンテナを停止する|
|docker container rm|docker rm|コンテナを削除する|
|docker image rm|docker rmi|イメージを削除する|
|docker network create|-|ネットワークを作成する|


#### コンテナ上のコマンドを実行するとき
以下の手順中でコンテナのコマンドを実行する際、2通りの方法を使っています。

1. ホスト上から「docker exec コンテナ名 コマンド名」を使って実行する
2. コンテナのシェルをターミナル付きで起動（「docker exec -it コンテナ名 bash」）して、そのシェルターミナルから実行する

ホストのシェルから実行する場合と、ゲストコンテナのシェル上で実行する場合があるわけです。前者の場合はプロンプトが

```bash
$
```

ですが。後者の場合は、コンテナのシェルがroot権限で起動するため、

```bash
root@f7b37b3a1cd7:/#
```
のようになっています。混同しやすいので、ご注意ください。


#### WordPressの起動に失敗するまで
元記事に従って（そのつもりだった）、MySqlのコンテナを起動します。「docker run」では、イメージが無ければ自動的にダウンロードされますので、「docker pull」は省略します。

```bash
$ docker run --name cnldb -e MYSQL_ROOT_PASSWORD=xxxx -d mysql
```

「cnldb」という名前（コンテナ名）は私の都合です。皆さんは自分の名前をつけてください。

行末の「mysql」はイメージの名前（レポジトリ名）です。タグ指定がありませんので、「mysql:latest」が選択されます（このときには気づかなかったのですが、これが問題でした）。

次に、WordPressのコンテナを起動します。コンテナの名前「cnlwp」も私の都合です。

```bash
$ docker run --name cnlwp --link cnldb:mysql -e WORDPRESS_DB_PASSWORD=xxxx \
    -d -p 8089:80 wordpress
```

「-p 8089:80」は、ホスト側の8089番ポートをコンテナの80番ポートに変換して接続します。ここで8089番ポートを使っているのは、たまたま私のPCで8080番ポートを別に使用しているからです。

ここまで、特に問題がないので、wordpressにブラウザで接続します。

```bash
$ firefox http://localhost:8089/wp-admin/
```

WordPressでは、最初はデータベース（WordPress用の）が存在しませんので、一回目は「/wp-admin/」に接続します。これにより、データベース関係のセットアップが実行されます。ところが、次のようなエラーが出てしまいました。データベースに接続しようとしたが失敗したというエラーです。

![error-database-connection.png](https://qiita-image-store.s3.amazonaws.com/0/52659/a8bafc4c-1ce8-1cd6-7a9e-9dbaaa8e764e.png)

エラーメッセージは、エラーの原因は次のどれかではないかと言っています。

* ユーザ名（データベースのルートユーザ）とパスワードが間違っている
* データベースホスト名が間違っている
* データベースサーバが起動していない

結論から言うと、原因はこのうちのどれでも無かったのですが...


#### DockerイメージのOSは？
上記のエラーを調査する前に、準備として２つのコンテナとイメージを調べます。

```bash
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
efbf039762dc        wordpress           "docker-entrypoint.s…"   4 hours ago         Up 4 hours        0.0.0.0:8089->80/tcp   cnlwp
f7b37b3a1cd7        mysql               "docker-entrypoint.s…"   4 hours ago         Up 4 hours          3306/tcp, 33060/tcp    cnldb
```

2つのコンテナが稼動していることが確認できます。コンテナIDはホスト名としても使用されます（後で出てきます）。

次に、mysqlコンテナのシェルに入ってみます。ターミナルが必要なので「-it」オプションを付けます。

```bash
$ docker exec -it cnldb bash
root@f7b37b3a1cd7:/#
```

「f7b37b3a1cd7」は、データベースのコンテナIDです。OSはLinuxのようなので、ディストリビューションを確認します。

```bash
root@f7b37b3a1cd7:/# vdir /etc/*version*
-rw-r--r-- 1 root root 4 Feb  3 13:01 /etc/debian_version
root@f7b37b3a1cd7:/# cat /etc/debian_version
9.8
```

なんと、mysqlイメージのOSは、Debianでした。実は私のPC（この場合のホスト）もDebianなので、とても好都合です。「9.8」はこの時点（2019-04-01）での最新版です。それならば、aptを使えるのでしょうか。

```bash
root@f7b37b3a1cd7:/# vdir /usr/bin/apt*
-rwxr-xr-x 1 root root 14424 Jan 18 10:42 /usr/bin/apt
-rwxr-xr-x 1 root root 80032 Jan 18 10:42 /usr/bin/apt-cache
-rwxr-xr-x 1 root root 22688 Jan 18 10:42 /usr/bin/apt-cdrom
-rwxr-xr-x 1 root root 22616 Jan 18 10:42 /usr/bin/apt-config
-rwxr-xr-x 1 root root 43168 Jan 18 10:42 /usr/bin/apt-get
-rwxr-xr-x 1 root root 26269 Jan 18 10:42 /usr/bin/apt-key
-rwxr-xr-x 1 root root 43168 Jan 18 10:42 /usr/bin/apt-mark
```

aptのコマンドは一通りそろっています。後々エディタが必要になるでしょう（この記事中では使っていませんが、実際の試行錯誤の過程では多用しました）。おそらくviはインストールされていないと思うので、インストールしてみます。

```bash
# updateから始めます
root@f7b37b3a1cd7:/# apt-get update
Get:1 http://repo.mysql.com/apt/debian stretch InRelease [19.2 kB]
...

root@f7b37b3a1cd7:/# apt-get install vim
...
The following additional packages will be installed:
  vim-common vim-runtime xxd
...
# マニュアル関係のエラーが出ますが無視してかまいません
```

次にwordpressイメージのOSを確認します。mysqlコンテナのシェルを終了した後、Debianかどうか確認してみると、

```bash
root@f7b37b3a1cd7:/# exit
$ docker exec cnlwp cat /etc/debian_version
9.8
```

やはりDebianでした。mysqlイメージと同じです。


#### データベースサーバは起動しているか？
データベース接続エラーの原因を潰していきます。まず、データベースサーバが起動しているかどうかを確認します。

```bash
$ docker exec cnldb ps aux
OCI runtime exec failed: exec failed: container_linux.go:344: \
  starting container process caused \
  "exec: \"ps\": executable file not found in $PATH": unknown
```

あれ？ mysqlイメージには「ps」がありません。ホストでpsが入っているパッケージを確認します（ホストもDebianなのは、こんな場合に有難い）。

```bash
$ dpkg -S /bin/ps
procps: /bin/ps
```

psは、procpsパッケージに入っているようです。インストールします。インストール中に選択を求められた場合を考慮して「-it」オプションをつけておきます。コンテナで初めてのinstallであれば、その前に「apt-get update」が必要ですが、既に実行していますので、今回は必要ありません。

```bash
$ docker exec -it cnldb apt-get install procps
...
```

インストールがうまく行ったら、早速、psを実行します。

```bash
$ docker exec cnldb ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
mysql        1  1.7  2.6 1951164 408052 ?      Ssl  13:39   0:08 mysqld
root       415  0.0  0.0  36636  2768 ?        Rs   13:48   0:00 ps aux
```

なんと、プロセスはmysqldだけです（ps自身を除いて）。Dockerは、とても経済的なんですね。ちょっと感動です。

mysqlサーバが稼働しているならば、ローカルに接続できるはずです。mysqlクライアントを立ち上げてみます。

```bash
$ docker exec -it cnldb mysql -u root -pxxxx
...
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 33
Server version: 8.0.15 MySQL Community Server - GPL
...
mysql> exit; 
```

mysqlクライアントが起動できました。サーバのバージョンは8.0.15のようです（本来は、このあたりで問題点に気づかなればならなかったのですが）。


#### データベースのユーザとパスワードは正しいか？
mysqlコンテナ側には問題がなさそうなので、wordpressコンテナの方を調べます。

データベース接続に用いるユーザとパスワードは、WordPressのwp-config.phpにハードコードされています。確認しましょう。まず、シェルに入ります。

```bash
$ docker exec -it cnlwp bash
root@efbf039762dc:/var/www/html# vdir
...
-rw-r--r--  1 www-data www-data   420 Nov 30  2017 index.php
...
drwxr-xr-x  9 www-data www-data  4096 Mar 13 00:18 wp-admin
...
-rw-r--r--  1 www-data www-data  3076 Apr  1 13:39 wp-config.php
drwxr-xr-x  4 www-data www-data  4096 Mar 13 00:18 wp-content
...
```

wordpressイメージは、カレントディレクトリが「/var/www/html」になっているようです。このディレクトリがWordPressのドキュメントルートのようですね。wp-config.phpがあります。wp-config.phpの内容を見てみます。

```bash
root@efbf039762dc:/var/www/html# cat wp-config.php
...
/** MySQL database username */
define( 'DB_USER', 'root');

/** MySQL database password */
define( 'DB_PASSWORD', 'xxxx');

/** MySQL hostname */
define( 'DB_HOST', 'mysql');
...
```

データベースのルートパスワードは設定した通りになっています。mysqlコンテナでローカル接続に用いたのと同じです。

気になるのは、データベースサーバのホストが「mysql」になっていることです。「cnldb」になるはずですが。

でも実は大丈夫なのです。/etc/hostsの内容を見てみましょう。

```bash
root@efbf039762dc:/var/www/html# cat /etc/hosts
127.0.0.1	localhost
...
172.17.0.2	mysql f7b37b3a1cd7 cnldb
172.17.0.3	efbf039762dc
```

ここで「f7b37b3a1cd7」は、mysqlコンテナのIDです。「f7b37b3a1cd7」、「mysql」、「cnldb」、どの名前を使っても同じホストアドレス「172.17.0.2」に解決されることが分かります。これは、wordpressコンテナを作成する際、「--link cnldb:mysql」オプションを付けた結果です。

なお、「efbf039762dc」はwordpressコンテナのIDですから、このコンテナが「172.17.0.3」のアドレスを持っていることが分かります。


#### wordpressコンテナからPHPで接続してみる
結局、データベースサーバのホスト名にも、ユーザ名にも、パスワードにも問題がなかったわけです。

仕方がないので、ローレベルに（PHPレベルで）データベースへ接続して、どうなるか調べてみることにします。

簡単なPHPプログラムを実行するのですが、wordpressコンテナにはviをインストールしていないので、一旦シェルを終了してから、ホストからコンテナにファイルをコピーします。

```bash
root@efbf039762dc:/var/www/html# exit
$ cat test.php
<?php
$link = mysqli_connect('mysql', 'root', 'xxxx');

if (!$link) {
	die(mysqli_error());
}
print('Success');
mysqli_close($link);

$ docker cp test.php cnlwp:/var/www/html
$ docker exec cnlwp vdir
...
-rw-r--r--  1 www-data www-data  7425 Jan  9 02:56 readme.html
-rw-r--r--  1     1000     1000   133 Mar 31 14:51 test.php
...
``` 

うまくコピーできているようですので、実行します。

```bash
$ docker exec cnlwp php test.php
[01-Apr-2019 15:31:18 UTC] PHP Warning:  mysqli_connect(): \
  The server requested authentication method unknown to the client \
  [caching_sha2_password] in /var/www/html/test.php on line 2
...
```

接続できませんでした。エラーメッセージは、「サーバが『caching_sha2_password』による認証を要求してきたが、クライアントには理解できない」と言っています。やっと原因がつかめそうです。


#### 「caching_sha2_password」による認証
「caching_sha2_password」を検索してみると全てが分かりました。

MySQL8.0では、新たな認証プラグイン「[caching_sha2_password](https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html#upgrade-caching-sha2-password-compatible-connectors)」が導入されました。

そのため、mysql:latestのイメージでは、デフォルトの認証プラグインとして「caching_sha2_password」を採用しています。ところが、wordpressの方では（正確にはwordpressイメージのphpでは） 従来の認証プラグインである「mysql_native_password」を想定しているため、データベースへの接続ができなかったのです。

このあたりについては、Qiitaの[この記事](https://qiita.com/ucan-lab/items/3ae911b7e13287a5b917)でも扱われています。


#### mysqlイメージのダウングレード
現在のところ（2019-04-01）、PHP7系では「caching_sha2_password」をサポートする接続ライブラリがありません。そのため、現状ではwordpressイメージの側では、この問題への対処法がありません。

となると、mysqlイメージをMySql5系に落とすか、mysql側で「mysql_native_password」を有効にするしか解がありません。

元記事をよく読むと、「docker pull」するとき「mysql:5.7.1」をタグ指定しています。これを見落としていました。これが私の犯した失敗の原因です。このために丸一日潰したことになります。

ところで、「mysql:5.7.1」は、現在のDocker Hubにはありません（[参照](https://hub.docker.com/_/mysql)）。2019-04-01現在では、5.7系の最新版は「mysql:5.7.25」です。

しかし、マイナーバージョンは頻繁に変わりますので、タグ指定するなら「mysql:5.7」か「mysql:5」の方が良いでしょう。私は後者を採用しました。


#### ついでに、deprecatedな--linkオプションを止める
元記事ではwordpressコンテナを作成する際、「--link」オプションを使用しています。現在のところ、これでも問題はないのですが、「--link」オプションは、[Docker公式でdeprecatedとされています](https://docs.docker.com/network/links/)。そのため、いつかは使えなくなるはずです。

上記で見たように、「--link」オプションは、/etc/hostsの内容を変更しますので、TCP/IP接続を行っていることになります。

この代わりに利用するのが「network」です。これは、mysqlイメージとwordpressイメージの間に仮想的なLANケーブルを引くことに相当します。このネットワークはブリッジ接続ですのでIPアドレスは使いません。

後述のように「docker network create cnlnet」した後で（「cnlnet」は適当につけたネットワークの名前です）、ネットワークをリストすると、cnlnetが付加されていることが分かります。

```
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
57d6b3902bbe        bridge              bridge              local
2e19ba960b22        host                host                local
7020cd347646        none                null                local
6b947460b7ef        cnlnet          bridge              local
```

ネットワークを作成した後、「docker run」コマンド実行の際、「--net」オプションを付加します。この結果、同じネットワークを付加されたコンテナ同士でブリッジ接続することが出来ます。


#### 結論
結局、以下のようにインストールすれば良いという結論になりました。

```bash
$ docker network create cnlnet
$ docker run --name cnldb --net cnlnet \
    -e MYSQL_ROOT_PASSWORD=xxxx -d mysql:5
$ docker run --name cnlwp --net cnlnet \
    -e WORDPRESS_DB_HOST=cnldb -e WORDPRESS_DB_PASSWORD=xxxx \
    -d -p 8089:80 wordpress
```

「--link」オプションをやめたため、WORDPRESS_DB_HOSTを設定する必要があります。

これまでの経過を順番にたどってきた場合は、次のように既存のコンテナの削除とmysql:latestイメージの削除を、事前に実行しておくことが必要です。

```bash
$ docker stop cnlwp cnldb
$ docker rm cnlwp cnldb
$ docker rmi mysql
```

さて、/wp-admin/にアクセスしてみます。

```
$ firefox http://localhost:8089/wp-admin/
```

成功です。

![initial-wordpress-admin.png](https://qiita-image-store.s3.amazonaws.com/0/52659/946399ee-0e93-633a-a0a5-17772662b81a.png)


#### おわりに
私の不注意のため、DockerのWordPress環境を作成するのに失敗しました。問題の究明に時間を使ってしまいましたが、そのかわり、Dockerに慣れました。十分に元を取った気分です。

問題究明の過程で、インストールされているパッケージや、コンテナ上で稼動しているプロセスを調べました。Dockerイメージがとても合理的に構成され、必要最低限のリソースしか使っていないことに感動しました。メンテナの皆さんの努力を讃えたいと思います。

個人的には、サーバもクライアントも共にDebianを使う私としては、Dockerイメージの多くがDebianで構築されているのは、たいへん親しみが持てます。そもそも、Debianの目標のひとつは、リソースの少ない環境でも稼動することです。Debianが多く使われるのも納得できます。

いずれ、PHPの認証ライブラリがMySql8.0に対応するでしょう。そうなれば、こんな問題もなくなるはずですが、いつになるのでしょうか。


#### 補遺
MySql8系のイメージを用いてWordPressを接続することも不可能ではありません。イメージの再作成が必要となりますが、興味がある方は次の記事などを参考にしてください。

* [【Docker】MySQL8.0系を使う時に発生する問題について](https://blog.websandbag.com/entry/2018/05/17/121730)


#### 変更履歴
2020-02-24: [些細な修正] 記事中に記載したDebianのリリース名が間違っていました。当時（2019-04-01）は、BusterでなくStretchでした