うちの会社でひとつサーバを立てて、WordPressのサイトを立ち上げることになりました。OSはCentOS7、WebサーバはNginxで、php-fpmを使用します。
まあ、昨今WordPressを動かすだけでよければ、パッケージをガンガンガンとインストールして適当に設定すればハイ出来上がり、なのですが。今回はちょっと野望を抱いて、php-fpmをDockerで動かしてみることにしました。
Dockerを使う理由は、脆弱性が次々と発見されるPHPを手軽にアップデートできるようにしたかったからです。ホストにベタでインストールすると、他のアプリケー ションとの依存性があったりパッケージのアップデートが遅かったりしたりで、なかなかおいそれとバージョンを上げるわけにはいきません。ですが、 Dockerで動かしていればphp-fpmのみを独立してコントロールできるので、アップデートも手軽に行なうことができます。
実は、理由はも うひとつあって、WordPressをPHP7で動かしたらどうなるか、というのを検証してみたかったのです。WordPress公式ではPHP5.6以 上を推奨しているので、手堅く行くなら5.6台で動かすのがまずよかろうと。ですが、巷では「PHP7でWordPressが速くなった」という情報が多 数見られるので、PHP7でも動かしてみたい。だったら、PHP5.6のfpmとPHP7のfpmを用意して使い比べてみればいいではないか、と。そうい うわけで、ふたつのphp-fpmを独立して導入するために、Dockerで動かすことにしたわけです。
といった次第で、Docker Engineをインストールして、PHP公式のfpmイメージ(php:5.6-fpm 、php:7.0-fpm)をpullして、さて動かしてみるか……と、気軽に設定を始めたわけなのですが。これがまあ、見事な泥沼でして。まともに動かせるようになるまで、筆舌に尽くしがたい労苦を重ねることになってしまいました。以下、その泥沼っぷりについて記録しておきたいと思います。
1.設定ファイルの扱いがややこしい
まずそもそも、コンテナの中にphp.iniが見当たりません。Dockerfileを見ると、置き場所として/usr/local/etc/php なんつうところが指定されているのですが、そこにはありません。探してみると、configureを行なった/usr/src/phpの下に「php.ini-production」というのがあったので、これをdocker cpでホスト側にコピーして、しかるべき/etc/php.iniとして使うことにしました。いやはや、パッケージでインストールしていれば当たり前についてくるものなのに……そのありがたみを痛感しました(大袈裟?)。
それと、php-fpm自体の設定ファイルの場所も/usr/local/etc/以下に指定されているので、これをホストからマウントするのがめんどくさい。具体的にはこんな感じでマウントします。
-v /etc/php.ini:/usr/local/etc/php/php.ini
-v /etc/php-fpm.conf:/usr/local/etc/php-fpm.conf
-v /etc/php-fpm.d/www.conf:/usr/local/etc/php-fpm.d/www.conf
2.ログ採りは大事です
Dockerコンテナからのログの採取はアプリケーションによっては実に悩ましい問題なのですが、幸い(?)php-fpmはrootで動いているので、適当な場所(今回はなぜか/home)にログを書くように設定ファイルで指定して、そこをホストからマウントするようにしました。ホスト側のディレクトリもroot所有なのでパーミッションのこじれが起きないのがありがたいです(MySQLとかだとこうはいかない)。ちなみに、何も設定しないとログは標準出力&標準エラー出力に書き出されるので、docker logsとかで見るようになるのでしょう。正直、docker logsは運用的には扱いづらい仕組みです。
なお、ログを採る場合は時間の同期が大事です。Docker内部はデフォルトだとUTCで動いているので、その時刻でログを書かれるとホスト側から見た時に混乱してしまいます。解決策としては、ホストの/etc/localtimeをコンテナ側にマウントするという方法が手軽なので、そのようにしました。
結果、こんな感じにマウントを追加しています。
-v /var/log/php-fpm:/home
-v /etc/localtime:/etc/localtime:ro
3.Webのコンテンツもマウントしましょう
Webサーバのドキュメントルートになるディレクトリもコンテナ側にマウントします。でないと、php-fpmがどのファイルを処理していいのかわからないままになってしまいますので。理屈としては当たり前なんですが、これに気づくまで、けっこう時間がかかりました(恥)。
-v /usr/share/nginx/html:/usr/share/nginx/html
4.ソケットも大事です
Nginxからfastcgi_passする先として、php-fpmのソケットをホスト側から参照できるようにしておかなければなりません(TCPポート経由でもいいのですが、多少なりと速度を稼ぐため)。まあ、これはコンテナ側のソケットの置き場所を設定ファイルで指定して、それをマウントするという形で問題なく動きます。
が、もうひとつ。MySQLを「localhost」としてデータベースに接続を行なう場合(wp-config.phpでDB_HOSTを「localhost」とする場合)は、MySQLのソケットもコンテナから見えるようにしておかなければなりません。MySQLは自身を「localhost」とする接続に対しては、ソケットによる接続を行なうからです(ちなみに「127.0.0.1」と指定してきた接続に対しては、TCPポートでの接続を行ないます)。この点についてはMySQLのクセというしかなくて、これを理解するまでこれまた相当の時間がかかりました。
そういうわけで、さらにマウントが増えます。
-v /var/run/php-fpm:/var/run
-v /var/lib/mysql:/var/lib/mysql
5.そのままではMySQLに接続できません
……これが、一番の泥沼でした。phpinfo()はちゃんと動くのに、なぜかWordPressのインストールが動かない。ブラウザは真っ白、Webサーバのログでは200で処理されているのに、なぜ……???
これもどうしていいのか散々調べたりしてかなりの時間をかけたのですが、解決の糸口は2つありました。
・php-fpmのプール設定で「catch_workers_output」を「yes」にして、エラーログにPHPのエラーを吐かせる
・WordPressをデバッグモードで動かす(wp-config.phpで「define(‘WP_DEBUG’, true);」とする)
すると、こんなメッセージが。
Fatal error: Call to undefined function mysql_connect() in /usr/share/nginx/html/wordpress/wp-includes/wp-db.php on line 1520
mysql_connect()が、無い!?
まさか、そんなことが……と思って再度Dockerfileを確認したところ、configureのパラメータに「–enable-mysqlnd」があるものの、「–with-mysqli」がありません。つまり、MySQL拡張が使えないということです。そ、そんな……orz……あまりに予想外な事態に、頭を抱えました。
ですが、Dockerfileをよく読んでみると、/usr/local/binに「docker-php-ext-install」というスクリプトが置いてあることがわかります。制作者の意図としては、どうやらこれを使って必要な拡張を適宜インストールしてくれたまえ、ということらしいです。というわけで、php-fpmのコンテナを起動してから
docker exec php-fpm /usr/local/bin/docker-php-ext-install mysqli
と実行して、WordPressのインストールに再度挑戦……動きました……(泣)
全く、うれしいやら腹が立つやら悔しいやら。こんなんだったら、公式イメージなんか使わずに自分でDockerfile書いてbuildするんだった、散々悩んだ時間を返してくれ……と叫びたくなりました。
以上のような行程を経て、ひととおりWordPressは動くようにはなったのですが、問題はまだもうひとつあるのです。
6.nginx名義で動かすには
WordPress本体のファイルの所有はWebサーバの実行アカウント(Nginxであれば「nginx:nginx」)にするのが通例です。これに従って、php-fpmはnginxの権限で動かす必要があります。そうでないと、例えばプラグインの更新などを行なう場合にファイルを更新する権限が無いので、更新が失敗してしまいます。
ホストに直接インストールしたphp-fpmであればユーザ情報が共通していますから、プールの設定ファイルでuserとgroupをnginxに設定するだけで済むのですが、Dockerのコンテナの場合はそう簡単にはいきません。コンテナの中でホスト側のnginxユーザと同じUID、GIDを持ったnginxユーザを作成して、その権限でphp-fpmを動作させなければならないからです。しかし、そのユーザを作成するためには、まずコンテナを起動させなければなりません。そのためにはプール設定ファイルのuserとgroupの設定を一旦コンテナの既存のアカウント(初期設定ではwww-dataになっている)にしておいて、コンテナを起動→nginxユーザをコンテナ内に作成→プール設定ファイルを書き換える→コンテナを再起動する、という実に面倒くさい手続きを踏まなければなりません。
こんな面倒なことはいちいち人手でやるのは大変ですから、(先のmysqli拡張をインストール部分も含めて)一連の動作を自動的に行なってphp-fpmのコンテナを起動するスクリプトを作成しました。大量のマウントオプションもこのスクリプトで定義します。
#!/bin/bash
docker run --name php-fpm \
-v /etc/php.ini:/usr/local/etc/php/php.ini \
-v /etc/php-fpm.conf:/usr/local/etc/php-fpm.conf \
-v /etc/php-fpm.d/www.conf:/usr/local/etc/php-fpm.d/www.conf \
-v /var/log/php-fpm:/home \
-v /etc/localtime:/etc/localtime:ro \
-v /usr/share/nginx/html:/usr/share/nginx/html \
-v /var/run/php-fpm:/var/run \
-v /var/lib/mysql:/var/lib/mysql \
-d php:5.6.21-fpm
docker exec php-fpm /usr/local/bin/docker-php-ext-install mysqli
DATA=`id nginx`
if [[ $DATA =~ ^uid=([0-9]*).*gid=([0-9]*).*$ ]]; then
USER=${BASH_REMATCH[1]}
GROUP=${BASH_REMATCH[2]}
fi
docker exec php-fpm groupadd -g $GROUP nginx
docker exec php-fpm useradd -u $USER -g $GROUP nginx
sed -i -e 's/www-data/nginx/g' /etc/php-fpm.d/www.conf
docker restart php-fpm
これで、公式のfpmイメージを「使える」状態で動かすことができるようになりました。やれやれ。まったくもって、やれやれです。
正直、公式には頼らずに独自にDockerfileを作ってビルドした方がいろいろとスマートだと思うのですが……まあ、それはもう少し知識を積んだ上で、時間ができてからやってみたいと思います。
はあ、疲れた……。