最近完全に自転車ブログと化していましたが、久しぶりに技術ネタです。
このサイトのWordPressで使用しているPHP-FPMとMariaDBをコンテナ化しました。
目次
なぜコンテナ化したか
WordPressで使用している「EWWW Image Optimizer」というプラグインが古いPHPのバージョンをサポートしなくなったからです。
今まで使用していたPHPのバージョンは5.4で、今の最新正式版は7.2。どうせなら最新まで上げたいですが、CentOSの古いバージョンなのでパッケージ管理ツールであるyumでは単純にバージョンアップできません。
それならばPHP-FPMだけコンテナ化し、最新PHPで動くようにすれば簡単なんじゃね?と思いやってみる事にしました。
構成
普通、WordPressをDocker(どっかー)化というとNginx + PHP-FPM + MySQLで全部をコンテナ化みたいなパターンが多いですが、うちの場合SSLやWAFで使っているNginx関係をコンテナ化するのはやや骨が折れると思ったので、PHP-FPMのみをコンテナ化するつもりではじめました(結局その後ろにあるMariaDBもコンテナにしていますが)。
再掲になりますがこんな感じです↓
WordPressのPHPファイルや記事にアップした画像などはコンテナの外、つまりホストのディレクトリを参照させてコンテナと一緒に消失しないように永続化しています。
MariaDBのデータベースファイルも同様ですね。
今回、こんな感じでコンテナとホストに分かれた構成にしてしまったので、いろいろはまりました。いっそ全部コンテナだったら、スムーズだったのに。
完成形の設定
最初に完成形の設定を書いておきます。
今回、複数のコンテナを起動させるのでdocker-composeを使っています。
以降、はまったポイントなどを書いていきます。
ホストのNginxとコンテナのPHP-FPMが繋がらない
ここが一番ハマりました。とにかく繋がらない。ろくなログも出なくて(出し方がわからなくて)迷宮入り寸前でした。
以前、Kubernetes(くーばねいてぃす)で動かしていたときはcentosのイメージでyumで必要モジュールをインストールしていたのですが、今回は軽さを重視してAlpine Linuxベースのイメージを使ったのも大変さの要因の一つだったかもしれません。
Google Container Engineに移行した その3
Alpine Linux(あるぱいん りなっくす)だとイメージサイズがかなり小さくなるので、軽量さを求められるコンテナではベースのイメージとして使われている事が多いです。
ただ、Busyboxという組み込み向けワンバイナリのLinuxで構築されており、bashが入ってなかったり、apkというパッケージ管理ツールを使わなければいけなかったり、自分で素のOSからPHPなどの環境を構築しようとすると、それなりの知識が必要だったりします。
私も「素」のAlpine Linux 3.8でapkコマンドをつかってphpやphp-fpmやmysqlドライバインストールしていったのですが、ホストのNginxからphp-fpmへ接続するとphp-fpmがエラーも残さずに接続を切ってしまう現象に遭遇しどうにもなりませんでした。
次にやったのがPHP7.2のイメージにPHP-FPMをインストールするやり方ですが、これも似たようなもの。
[error] 35165#0: *3 upstream prematurely closed connection while reading response header from upstream, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "localhost"
というエラーが発生し、頑張ったのですが繋がるところまで行き着きませんでした。
結局、公式のphp:7.2-fpm-alpineを使う事でNginxとの通信はできるようになりました。難しい。時間があればDockerfileを読み解いて原因を理解しておきたいところ。
phpファイルがPHP-FPMで読めない
通信はPHP-FPMに届くようになりましたが、今度はPHP-FPMが.phpファイルを見つけられない。
調べてみるとNginx側の設定で、
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
こういう設定をいれているんだけど、これでNginxからPHP-FPM側に「PHPファイルへのパス」が引き渡される。PHP-FPMはそのパスに置いてあるPHPファイルを読んで処理する。
つまりNginxから/usr/local/var/wwwにPHPファイルが置いているのが見えるならば、コンテナの中のPHP-FPMでも同じ/usr/local/var/wwwのパスにPHPファイルが見えるように設定しないとうまく動作しない。
これはdocker-compose.ymlに
volumes:
- /var/contents:/var/contents
という形で設定するとよい。「:」より左がホスト側の共有するディレクトリのパス、右がコンテナ側のパスになります。今回は同じパスに見えないといけないので両方に同じパスが書かれています。
PHP-FPMが権限にひっかかってスクリプトを処理できない
今度はPHP-FPMでパーミッションエラー。
php-fpm_1 | 172.xxx.xxx.xxx - 30/Sep/2018:02:38:21 +0000 "GET /blog/index.php" 403
php-fpm_1 | [30-Sep-2018 02:38:21] WARNING: [pool www] child 6 said into stderr: "ERROR: Unable to open primary script: /xxxxx/index.php (Permission denied)"
コンテナにはいってlsでコンテンツのディレクトリの権限を見ると、ユーザーとグループが数字表示。つまりコンテナに存在しないユーザーの権限になっている。これでは権限エラーになるのはあたりまえ。
php-fpmの公式イメージはwww-dataというユーザーでPHP-FPMが起動するようになっている。一方ホスト側に置いてあるコンテンツはNginxユーザーが所有者になっている。
今回、コンテナ側にホストと同じuidとgidでユーザーを作り、php-fpmの起動ユーザーを書き換える処理をDockerfileに入れた。
これの23行目から25行目ですね。
-gと-uの後ろのxxxxはホストのnginxユーザーのIDを調べて入れる形になります。環境依存でイマイチですけどね。うまくやればそのあたりも自動化できるかもしれない。
このあたりはこちらを参考にさせていただきました。ありがとうございます!
備忘録:DockerのPHP公式のfpmイメージでどハマりしたこと – のめしこきの日々
ここまででとりあえずWordPressの初期設定画面は表示されるようになりました。
MariaDB接続関数が見つからない?
WordPressでDB設定の画面から先に進もうとすると今度は画面真っ白現象に遭遇。
docker-compose logs -fやってもPHP-FPMは500エラーとしかログが出てなくてまったくもって原因不明。WordPressのデバッグモードをオンにしても画面には何も表示されず途方に暮れる。
もしかしたら権限の関係でwp-config.phpファイルが作成できないのでは?と考え、手動でwp-config.phpを作るとビンゴ。エラーが画面に表示されるようになりました。
Fatal error: Uncaught Error: Call to undefined function mysql_connect() in /usr/local/var/www/html/blog/wp-includes/wp-db.php:1562 Stack trace: #0
mysql_connect関数の定義が見つからないだと?
PHP7で、mysql_connect()が無いと怒られる件 (undefined function mysql_connect) – Qiita
なんとPHP5.5以降ではmysql_系の関数が使えなくなり、mysqli_系の関数に置き換わってるらしい。
え?どうすんの、これ。
GitHubに情報ありました。
RUN docker-php-ext-install mysqli
を実行すればmysqli対応になるんですって。さっきのDockerfileの2行目ですね。
MariaDB接続でディレクトリが見つからない?
一難去ってまた一難。艦長!エラー発生です!(?)
Warning: mysqli_real_connect(): (HY000/2002): No such file or directory in /usr/local/var/www/html/blog/wp-includes/wp-db.php on line 1531
DBとの接続なのにディレクトリが見つからないとは、これ如何に?
MariaDBとの接続にはTCP/IPでの接続とUnixドメインソケットが使用できます。tmpディレクトリの下にxxxx.sockというファイルがあるの見た事無いですか?あれがUnixドメインソケットでああいう感じでファイル見えしています。今回の場合もUnixドメインソケットでつなげようとしたけど、MariaDBのsockが見当たらなくてこんなエラーになってるんですね。
Unixソケットドメインの方が早いし、DockerのVolume指定でsockファイルが見えるようにしてもいいんですが、Volumeだらけでなんか気持ち悪いのでTCP/IPの接続に切り替えます。
設定はWordPressのwp-config.phpにある…
define('DB_HOST', 'localhost');
の設定を、ホストのIPアドレスに変える。同じマシンだからといって127.0.0.1を指定してもコンテナ自身を指す事になるのでダメ。
define('DB_HOST', '172.xxx.xxx.xxx');
一応、これでも繋がるんだけど、WordPressの設定ファイルに今後変わるかもしれないホストの内部IPを指定するのは嫌。
だったら、MariaDBもコンテナ化すればコンテナの名前でDNSがひけるようになるじゃないか、ということでDBもコンテナ化する方向にしました。
MariaDBに接続拒否される
MariaDBのコンテナはすんなり起動したが、接続しようとしたらこんなエラー。
Warning: mysqli_real_connect(): (HY000/1130): Host '172.xxx.xxx.xxx' is not allowed to connect to this MariaDB server in /var/contents/a-tak.com/html/blog/wp-includes/wp-db.php on line 1531
IPの部分はDockerのコンテナのIPが出てくる。
MariaDBはユーザー毎、DB毎にどこからの接続を許可するか設定します。今の設定はmysqlコマンドで以下のクエリ投げると確認できます。
select user,host from mysql.user;
今までホスト自身からの接続しか許可していなかったので当然コンテナのIPからの接続は拒否されたというわけ。とはいえ、そのままだとコンテナのIPは変動するので、dockerのネットワーク機能を使って固定IPを設定した。
dockerのコンテナに固定IPを振る(compose file version 3の注意点) – Qiita
docker-compose.ymlの10行目や25行目が固定IPの設定で、28行目にコンテナのIP範囲などのグローバルな設定を入れている。
php-fpmのコンテナだけではなく、dbのコンテナにも設定を入れないと別のネットワークに属していると見なされるようで、コンテナ名で名前解決できなくなる。
これでIPが固定化されたのでMariaDBで以下のような感じで権限指定すればよし。
grant all privileges on db名.* to 'ユーザー名'@'172.100.1.2' identified by 'パスワード';
flush privileges;
PHP-GDも入ってなかった
これで動かしてみたら記事一覧は問題ないけど、記事単独ページに行くと画像が表示されない問題にぶつかる。
PHP Fatal error: Uncaught Error: Call to undefined function imagecreatetruecolor() in /xxxx/wp-content/plugins/siteguard/really-simple-captcha/siteguard-really-simple-captcha.php:178
うちで使っているSiteGuardというプラグインがGDをwith freetypeオプション付きでインストールしてないと動かないことがわかった。
こちらを参考にDockerfileを作り直した。
Dockerfileの冒頭にapkでいくつかのdevライブラリ追加してるのと、docker-php-ext-configureとdocker-php-ext-installで必要モジュールを追加しているのがそれです。
思ったより大変だった
かなり時間を費やしたけど、なんとかこれでDocker化出来ました。
他にもいろんな罠に実際ははまりまくっていて書き切れないぐらいなんですが、今回のことでDockerがだいぶ分かってきました。
これでPHPのバージョンアップもコンテナを最新で作り直すだけで簡単になるはずですが、性能はだいぶ落ちました。GCPのグラフを見ると定期的にCPUが100%まで跳ね上がってます😓。今までは10%程度の負荷だったのですが、20%は当たり前で急に100%に跳ね上がる的な動きをしています。
まだPHP-FPM側のチューニングはやってないので、それ次第で安定するかもしれません。