こんにちは、突然だけど、PHP-FPMをGracefulにreloadしたくないですか?
僕はしたい。
今回はそんな感じになったときにやったことをすごく短くメモ的に残していく回。
そもそもGracefulってなに
恥ずかしながら、自分は働き出すまでGracefulって言葉を知らなかった。なので初見のときには優雅じゃんみたいな気持ちになった。
で、実際Graceful 〇〇 (ここにはshutdownとかrestartが入る) を自分がシュッと調べた限り「雑にプロセスを殺したりせず、今行っている処理をちゃんと始末してからいい感じにプロセスがシャットダウンしたりリスタートしたりする事」のようだった。
環境
[homirun@rise ~]$ php-fpm -v
PHP 8.0.19 (fpm-fcgi) (built: May 10 2022 08:07:35)
Copyright (c) The PHP Group
Zend Engine v4.0.19, Copyright (c) Zend Technologies
PHP-FPMのデフォルトの挙動
結論から書くと、デフォルトではGracefulにリロードしない。
PHP-FPMの公式ページには
緩やかな (graceful) 停止/起動 機能を含む高度なプロセス管理
https://www.php.net/manual/ja/install.fpm.php
みたいなことも書いてあるが、何故かGracefulにはならない。
具体的にはstop、restart、reloadでGracefulにならない。つまり全部。
Gracefulなreloadをするには
基本的にこちらの記事を参考にさせていただいた。
process_control_timeout
の値をデフォルト値の0からPHPのコネクションのタイムアウト時間付近に設定することでGracefulなreloadをしてくれるようになる。
[global]
process_control_timeout=40
[www]
user = nginx
group = nginx
listen = 9000
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
request_terminate_timeout=60s
pm = ondemand
pm.max_children = 4
上に挙げた記事ではrestartもGracefulになるようだったが、自分の環境では即プロセスが落ちてしまった。
一応PHP-FPMのmanコマンドの結果もシュッと覗いてみた。
If your installation has no appropriate init script, launch php-fpm with no arguments. It will launch as a daemon (background process) by default. The file /var/run/php-fpm.pid determines
whether php-fpm is already up and running. Once started, php-fpm then responds to several POSIX signals:
SIGINT,SIGTERM immediate termination
SIGQUIT graceful stop
SIGUSR1 re-open log file
SIGUSR2 graceful reload of all workers + reload of fpm conf/binary
一部抜粋してあるが、みたところSIGUSR2を投げるとgraceful reloadされてSIGQUITを投げるとGraceful stopするようだった。
そこで、php-fpm.serviceもみた。中ではExecReloadで/bin/kill -USR2 $MAINPID
、ExecStopは定義されていなかった。これを見たときに勝ちを確信した。ExecStopでSIGQUIT渡せばいいだろうと。
しかし、そんなに甘くはなく普通に即プロセスが死んだ。
嘘つき。
実験
さて、せっかくなのでprocess_control_timeout
=40を設定した上でちょっとした実験を行った。
1. 長いsleepは待ってくれるのか
とりあえず無難にsleepが抜けてから(process_control_timeout
=40なので40秒待って)reloadされるのかを確認する。
<?php
sleep(30);
echo "finish: wait 30";
?>
上のコードを実行してすぐにreloadをしてみる。期待する挙動は30秒待ったあとに「finish: wait 30」が描画されることだ。
結果
さて、実際に実行している途中でreloadを行ってみると30秒待たずに即echoが実行された。Gracefulなreloadする際は、sleepはすぐ抜けるのかなと言う感想。
2. sleepを挟んだloopは待ってくれるのか
次に、1つ目の実験でsleepを待ってくれない疑惑が出ているので、forの中にsleepを入れ込んで見たらどうなるんだろうと思ったので以下のコードを用意して確認する。
<?php
for ($i = 1; $i <= 10; $i++) {
echo $i;
sleep(1);
}
echo 'hoge'
?>
期待する動作はすべてのsleepが無視されて即「12345678910hoge」が返ってくることだ。
結果
これも意外なことに10秒(1秒のsleepを10回)しっかり待っているようだった。なんもわからん。
3. 無限loopはprocess_control_timeoutまで待ってくれるのか
<?php
for (;;) {
echo 'loop'
}
echo 'hoge'
?>
これに期待する動作はprocess_control_timeoutもしくは、php.ini等で設定されたタイムアウトの時間でreloadされること。
結果
流石に途中でreloadされて503が返ってきた。最後はちゃんと期待した動作をしてくれて嬉しい。
おわりに
結構思ったのと違う挙動をしていて普通にびっくりした。manコマンドにも公式サイトにも「Gracefulなrestartができる!!!」と書いてあったので、一部の環境だけで起きることなのかもしれないなとは思っている。
実はもっといい感じに解決できる方法を知っている人いたら教えて下さい。