みなさんこんにちは。最近コストコでSinghaビールをたくさん買った山口です。
Singhaは氷を入れて飲むとおいしいと思います。
さて、みなさんApacheは好きですか?私はNginxの方が好きです。
今回はApacheのgraceful stop(緩やかな停止)に関する雑談です。
動機
Apacheのドキュメントを読んでいると、「緩やかな停止」のシグナルにWINCHが割り当てられている記載がありました。
https://httpd.apache.org/docs/current/stopping.html#gracefulstop
シグナルWINCH(SIGWINCH)は本来、端末サイズの変更(Window Change)を表すシグナルです。しかしApacheでは、そのシグナルをgraceful stopに割り当てています。
「端末のリサイズ用シグナルでApacheを止める」というのは直感に反するので、なぜこうなっているのか気になりました。
Apacheの停止・再起動シグナル一覧
まず整理のため、UnixのApacheが使う主なシグナルをまとめます。
| 操作 | シグナル | コマンドの例 |
| 停止 | TERM | apachectl -k stop |
| 再起動 | HUP | apachectl -k restart |
| 緩やかな再起動 | USR1 | apachectl -k graceful |
| 緩やかな停止 | WINCH | apachectl -k graceful-stop |
graceful restart(USR1)とgraceful stop(WINCH)は似ていますが、後者は親プロセス自身も最終的に終了します。
なんでSIGWINCHなの?
公式ドキュメントでは、SIGWINCHを採用している理由は明記されていません。
Stack Overflowでは「Unixのシグナルが不足しているため、仕方なくSIGWINCHを使っているのだろう」という説明がありました。
https://stackoverflow.com/questions/780853/what-is-in-apache-2-a-caught-sigwinch-error
But unfortunately apache2 process poorly misuses this signal (in the way they divert its first meaning), but for their defense, they seems not to have a choice and had to resort to this due to a lack of signal (see bug report). One of their assumption is that apache2 process is always in background.
Unixでは、TERM, HUP, USR1, QUIT あたりが制御によく使われると思います。Apache 2.xではUSR1がgraceful restartに固定され、graceful stop用のシグナルを別途確保する必要がありました。WINCHは「端末に紐づいたフォアグラウンドプロセス」向けのシグナルで、デーモンとしてバックグラウンドで動くApacheには通常届きません。そのため、WINCHが割り当て先として選ばれた、というのが実情のようです。
ソースの話
graceful stopは include/mpm_common.h で次のように定義されています。
https://github.com/apache/httpd/blob/trunk/include/mpm_common.h#L85
#define AP_SIG_GRACEFUL_STOP SIGWINCH
一方、graceful restartはUSR1です。
#define AP_SIG_GRACEFUL SIGUSR1
gitの履歴
gitのログを辿ると、SIGWINCHとApacheの関係は段階的に変わっています。
- 2001年: Prefork MPMがgraceful restartにSIGUSR1の代わりにSIGWINCHを使っていた
https://github.com/apache/httpd/commit/da4502c070611095e30f6a5f0aa7bfd7e817867b - 2005年: graceful restartのシグナルをUSR1に固定し、SIGWINCHをgraceful stop用に空けた
https://github.com/apache/httpd/commit/395896ae8d19bbea10f82b1d40e16f4721d316b7 - 2005年: `AP_SIG_GRACEFUL_STOP` のマクロ定義が追加された
https://github.com/apache/httpd/commit/bbb8aeee0237265af89587728a9bde0be7a1e96a
上記の2001年のコミットは「graceful stopがUSR1からWINCHに変わった」というより、「当時はgraceful restartにWINCHを使っていた」ようです。
現在の役割分担(USR1 = restart、WINCH = stop)は2005年頃に整理されました。
SIGUSR2を使うビルドオプション
Apacheのビルド時に --enable-sigusr2 オプションを指定すると、graceful stopにSIGUSR2を使えます。
https://github.com/apache/httpd/blob/trunk/configure.in#L844
$ cd apache/httpd # httpdのディレクトリへ移動
$ configure --enable-sigusr2
configure.in ではデフォルトがWINCH、オプション指定時にUSR2に切り替わるようになっています。
Apacheを自分でビルドしてインストールすることは滅多にないと思うので忘れて良いと思います。
graceful stopの内容
graceful stopを送ると、親プロセスは子プロセスに「処理中のリクエストを終えたら終了しろ」と伝え、自身はPidFileを削除してポートのlistenを止めます。子プロセスがすべて終了するか、GracefulShutdownTimeout で指定した時間が経過するまで親プロセスは待ち続け、その後自身も終了します。
httpd -X などでApacheを端末上で動かしていると、ウィンドウをリサイズしただけで graceful stop が実行されます。デバッグ中に不意に止まる原因になり得ます(このあと実際にやってみます)。
ApacheにSIGWINCHしてgraceful stopしてみよう!
今回はApacheをフォアグラウンドで実行、ウィンドウサイズを変更し、graceful stopをしてみます。
-X オプションでフォアグラウンド実行すれば、端末に紐づいた状態になるので、ウィンドウをリサイズするだけで SIGWINCH が届きます。
手順
今回はDockerの公式イメージを利用します。
-X はデバッグ用のシングルプロセスモードで、端末を占有したまま httpd を実行できます。
# Apacheを実行する端末
$ docker run -it --rm --name httpd-test -p 8080:80 httpd:2.4 httpd-foreground
httpd が起動したら、httpd を動かしている端末のサイズを変更します。カーネルが SIGWINCH を送り、Apache はそれを graceful stop として処理します。ログに次のようなメッセージが出れば成功です。
[mpm_event:notice] [pid 1:tid 1] AH00492: caught SIGWINCH, shutting down gracefully
参考: 運用でgraceful stopする場合
多くの環境ではApacheをデーモンで実行しているので、apachectlやsystemctlといったマネージャを使ってgraceful stopを実行します。
# 内部で kill -WINCH を親プロセスに送信している
$ sudo apachectl -k graceful-stop
PidFile の親プロセスに直接WINCHを送り、graceful stopすることもできます。
普通にapachectlやsystemctlを使ってください。
$ kill -WINCH "$(cat /path/to/httpd.pid)"
まとめ
Apacheのgraceful stopがSIGWINCHなのは、Unixのシグナル割り当ての歴史的経緯に起因するようです。
端末リサイズ用のシグナル名を使うのは紛らわしいですが、デーモンとして動くApacheには通常届かないため、実運用上の問題はないと考えられます。
Blog