【コンテナだとちょっとややこしい】nginxでPHPを動かす仕組み

nginxもPHPも両方触った事なかったので、調べながら作業した際の記録＆コンテナの仕様関連ではまったりしたのでまとめました。

apacheとnginxの違い
-
主にwebサーバーへクライアントからのアクセスが重なった際の挙動が違うらしい
apache：1つのアクセスに対して1つの対応を処理
nginx：複数のアクセスに対してまとめて処理

参考：
https://www.kagoya.jp/howto/rentalserver/nginx/

また、apacheの場合はPHPを動かす際に必要になるモジュールがデフォルトでインストールされている為、特別な設定をしなくてもPHPを動かせるが、nginxにはそれがない。

###なぜnginxにはPHPモジュールが存在しないか？
モジュール版はWEBサーバーのプロセスとしてPHPが実行されているが、モジュールでは無い場合はWEBサーバーとは別のプロセス（fastcgiサーバーの中のプロセス）でPHPが実行される為、WEBサーバー自体の処理速度の向上が期待できる。

参考：
https://techplay.jp/column/563

処理の流れ
-
リクエスト→WEBサーバー→fastcgi
同じ経路を辿ってエンドユーザーにレスポンスを返す
fastcgiプロセスは処理が終わっても破棄されず継続する

###WEBサーバー→fastcgiの接続方法
以下の3種類になる
・socket
・名前付きパイプ
・TCP

socketをサポートしている言語であれば何でも利用することが可能

php-fpmについて
-
nginxでfastcgiを使用する際にPHPを実際に実行する仕組み
処理の流れはこうなる
リクエスト→WEBサーバー→fastcgi→php-fpm

###プロセス
プロセスの数を予め設定しておいたり（=static)、プロセス数が足りなくなったらプロセスを追加起動できる（=dynamic、追加起動できる数も予め設定できる）
このプロセス1つでCPUの1コアを占有する為、設定はCPUコア数により決める
尚、メモリ消費量はプロセス1つで100M切る模様
dynamicの場合プロセスが肥大化していく（=メモリ消費量の肥大、削除の仕組みがない？）定期的な再起動でメモリのクリアが必要

尚、プロセスの集まりをプールと呼ぶらしい

参考：
https://hackers-high.com/linux/php-fpm-config/

インストール
-
<a href= "https://www.php.net/manual/ja/install.fpm.install.php">公式</a>によると

>FPM を有効にして PHP をビルドするには、configure 時に --enable-fpm を追加します。

との事。

尚コンテナに環境構築する場合、PHP公式のDockerイメージでイメージ名に「fpm」と明記されているものを選ぶと、コンテナ起動時にはfpmがインストールされた状態
（ここから登場する「デフォルト」は全て下記のバージョン前提）

```
/app # /usr/local/sbin/php-fpm -v
PHP 7.4.13 (fpm-fcgi) (built: Dec 17 2020 09:09:15)
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
```


設定ファイルについて
-

設定ファイルは下記にあった
/usr/local/etc/php-fpm.d/www.conf
コメント部分を抜くとこんな中身、なんとなくここまで記載した内容の事が指定されているような

```
[www]
user = www-data                                                                 
group = www-data  
listen = 127.0.0.1:9000  
pm = dynamic 
pm.max_children = 5 
pm.start_servers = 2  
pm.min_spare_servers = 1
pm.max_spare_servers = 3
```
あれ、<a href= "https://www.php.net/manual/ja/install.fpm.install.php">公式</a>にはこうなってたんだけどな（グループも）PHP8のページだからかな？

>--with-fpm-user - FPM のユーザー (デフォルトは nobody)。


とりあえずデフォルトの状態でも使用する事自体はできる。
尚、上記設定ファイルは下記ファイルでインクルードされている（こちらもコメント割愛）
/usr/local/etc/php-fpm.conf

```
[global]
include=etc/php-fpm.d/*.conf     
```

webサーバー側の.conf設定
-
appサーバーとwebサーバーを別々のマシンとして分けたいという状況を元に下記を作成。

```
server {
    listen 80 default_server;
    server_name _;
    root /app/webroot;
    index index.php;

    location ~ ^/(.*)$ {
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param CAKE_ENV local;
        include fastcgi_params;
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }

}
```

ポイントはfastcgi_xxの部分。

####fastcgi_pass
TCPで指定、違うコンテナ間での通信の場合、socketは使用できないというのをどこかで見た為（どこで見たか忘れた）

####fastcgi_index
ディレクトリのリクエストがあった場合に返すファイル

####fastcgi_param
環境変数の設定

最低限、これだけで動くらしいけど、本当はもっといっぱい便利に使える設定とかあるんだろうなぁ。
今時点では全く網羅できない。


php-fpmの動作確認（と落とし穴）
-
設定もなんとなくできたから早速コンテナ起動して動作確認してみよう、と起動するもphpが動作しない。
なぜ、、へこむ。プロセスを確認。

```
/app # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 tail -f /dev/null
   20 root      0:00 /bin/sh
   52 root      0:00 ps aux
```

あれ、php-fpmのプロセスもないけど、PID1が何か、、
全てのプロセスの生みの親はどこへ？

PID1問題
-
https://text.superbrothers.dev/200328-how-to-avoid-pid-1-problem-in-kubernetes/

ここにコンテナの衝撃の仕様が書いてあった
コンテナでコマンド実行するとPID1になるのかーーー


####起こった事象のまとめ（推測含む）

コマンドなしでコンテナ起動した場合、php-fpmのプロセス（PID1）が存在
（fpmイメージにこのプロセスが起動されるように仕込まれているらしい）
docker inspectの結果、下記のようになっていた

```
            "Cmd": [
                "php-fpm"
            ],
```

ちなみにエントリーポイントは下記

```
            "Entrypoint": [
                "docker-php-entrypoint"
            ],
```


これをkillしようとしたりするとできなかったりする
↓
コマンドありでコンテナ起動した場合、PID1がコマンドに上書きされる
結果php-fpmマスタープロセスが消える事でプール全部消える
↓
コマンドが何かのインストールだった場合、インストール処理が終わったら継続するプロセスが何も存在しない事でコンテナも落ちる（プロセスが何であれ継続するプロセスがひとつ存在しなければならない）

今まで深く考えずtail -f /dev/nullしていたけど、掘り下げたらこういう仕組みだったという事ですね。


まとめの内容を踏まえて対応を考える
-
今回のミッションとしては
・APとWEBでコンテナを分けたい
・コンテナ起動時にcomposer installをしたい

こういう理想があるので、それを実現する方法として

```
    command: >
      sh -c "
      composer install --no-interaction &&
      /usr/local/sbin/php-fpm
      "
```

php-fpmが起動しさえすれば、このプロセスが使われてなくても継続されるものなので、インストールが終了した後もコンテナが落ちないという事ですね。

ただ、これだとPID1がphp-fpmになる様子

```
/app # ps
PID   USER     TIME  COMMAND
    1 root      0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
   29 root      0:00 /bin/sh
  318 www-data  0:00 php-fpm: pool www
  320 www-data  0:00 php-fpm: pool www
  321 root      0:00 ps
```

コンテナなのでそもそもしないと思うけど、php-fpmプロセスをkillしたりするときに変な挙動をする可能性が残っている

####composeファイルのinitオプションを使う

この問題を避ける為に、Docker-composeには既にオプションが存在しました！

https://docs.docker.com/compose/compose-file/compose-file-v2/#init

>Run an init inside the container that forwards signals and reaps processes. Set this option to true to enable this feature for the service.

このオプションをtrueにすることで使用することになるinitの設定を変えることもできるみたいだけど、デフォルトのもので動作確認。

```
/app # ps
PID   USER     TIME  COMMAND
    1 root      0:02 /sbin/docker-init -- docker-php-entrypoint sh -c  composer install --no-interac
    6 root      0:03 php-fpm: master process (/usr/local/etc/php-fpm.conf)
   16 www-data  0:00 php-fpm: pool www
   17 www-data  0:00 php-fpm: pool www
   18 root      0:00 /bin/sh
   23 root      0:00 ps
```

PID1がdocker-initというものになった！
これで仮にphp-fpmのプロセスを再起動したい、等の場合対応できるはず

php-fpmの停止＆再起動の方法
-

単にkill+PID（又はプロセス名）だけではコンテナが落ちる。
この辺りは最初PID1のプロセスの際に起こるのかと思っていたけど、PID1の件が解決しても起こる。

参考：
https://www.softel.co.jp/blogs/tech/archives/5196
公式を元にした上記ブログの情報だとkill+PIDで停止できるようだけど、コンテナの場合少し事情が違う？
この辺りはコンテナとphp-fpmの掘り下げが更に必要そう、、


####再起動は可能
上記のブログに記載の内容で停止はできないけど、再起動はできる。
USR2シグナルを使用するらしい。（php-fpmで定義されていると思われる）

```
/app # kill -USR2 6
/app # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/docker-init -- docker-php-entrypoint sh -c  composer install --no-interac
    6 root      0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
   18 root      0:00 /bin/sh
   24 www-data  0:00 php-fpm: pool www
   25 www-data  0:00 php-fpm: pool www
   26 root      0:00 ps
```

こちらにも同じ再起動の方法が記載されていた
参考：
https://cloudpack.media/13479

なんで停止はできないんだろう、、
ちなみにPID1がphp-fpmのマスターの場合でも、上記再起動は可能

```
$ docker-compose exec app ps
PID   USER     TIME  COMMAND
    1 root      0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
    6 www-data  0:00 php-fpm: pool www
    7 www-data  0:00 php-fpm: pool www
    8 root      0:00 ps
$ docker-compose exec app kill -USR2 1
$ docker-compose exec app ps
PID   USER     TIME  COMMAND
    1 root      0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
   18 www-data  0:00 php-fpm: pool www
   19 www-data  0:00 php-fpm: pool www
   20 root      0:00 ps
```


もやっとするけど、必要な設定を備えたコンテナ起動という目的は達成したので、ここの掘り下げはまた時間がある時に。