はじめに
- php で作ったキューワーカーのようなサービスをコンテナで実行させる
- サービスはシグナルをハンドリングしていわゆるに Graceful に終了したい
- サービスが終了したときは普通にコンテナも終了する
- ただしローカルの開発環境では特定のシグナルを受けたときはコンテナを終了させずにサービスだけリスタートする
- そのためにシェルスクリプトを書いてサービスはそのシェルスクリプトから実行する
実装例その1
コンテナの init プロセスとして dumb-init を使って「dumb-init -> sh -> サービス」のようなプロセスツリーになるなら、次のようにシンプルに実装できそうです。
#!/bin/sh trap restart=1 HUP trap restart= INT trap restart= QUIT trap restart= TERM while :; do restart= "$@" status=$? if [ -z "$restart" ]; then exit "$status" fi done
dumb-init がシグナルを受けると(-c
オプションが指定されていなければ)直接の子プロセスだけでなく孫プロセスにもまとめてシグナルが転送されます。ので、シェルスクリプトからサービスへシグナルを送る必要はなく、単にサービスが終了したときにリスタートするかどうかの条件として最後に受けたシグナルを使えば良いだけです。
この方法の問題点は↑で列挙しているシグナル(HUP/INT/QUIT/TERM)以外のシグナルを受けると、例えサービスでそれをハンドリングしていても sh のプロセスが落ちてしまい、直下のプロセスが落ちたことで dumb-init も終了してコンテナ自体が終了するため、サービスのプロセスも突然死します。ただ、サービスでハンドリングしているシグナルを列挙しておけば良いだけなので、問題ではありません。
他の問題点をあげるとすれば・・dumb-init からシグナルが複数のプロセスに送られた後、各プロセスでシグナルハンドラが実行されるタイミングは決まっていないだろうので、HUP シグナルの後に sh の trap で restart=1 が実行される前に、サービスのプロセスが終了して sh の while ループを抜けてしまう可能性がゼロでは無いかもしれません(未確認)。ただリスタートしたいのはローカル環境だけのことでプロダクション環境で使いたいわけではないので、この程度の問題は目を瞑ります。
実装例その2
別の実装案。dumb-init が無くても動きます。また、シェルスクリプトが予期しないシグナルなどで終了するときもサービスのプロセスの終了を一定時間待ちます。ただ予期しないシグナルでも trap EXIT を発動させるために bash が必要です(※)。
#!/bin/bash on_exit() { if [ -n "$pid" ]; then kill -TERM "$pid" for x in 0 1 2 3 4 5 6 7 8 9; do if ! kill -0 "$pid" 2>/dev/null; then exit fi sleep 1 done kill -KILL "$pid" fi } trap 'restart=1 ; [ -n "$pid" ] && kill -HUP "$pid"' HUP trap 'restart= ; [ -n "$pid" ] && kill -INT "$pid"' INT trap 'restart= ; [ -n "$pid" ] && kill -QUIT "$pid"' QUIT trap 'restart= ; [ -n "$pid" ] && kill -TERM "$pid"' TERM trap 'on_exit' EXIT while :; do restart= "$@" & pid=$! while :; do wait if kill -0 "$pid" 2>/dev/null; then continue fi pid= if [ -z "$restart" ]; then exit fi break done done
この実装だとサービスの終了コードが何であってもシェルスクリプトの終了コードは 0 になってしまいます。
さいごに
特定のシグナルでリスタートしたいことがあるのはローカル環境だけで、プロダクション環境では素朴に dumb-init の直下で実行すればよかったので、それならローカル環境でのみ runit とか supervised とかでリスタートさせればよいか・・と思ったのですがそれだけのために runit やら supervised やらを出してくるのは仰々しい気がしたので、シェルスクリプトを書いてみました。