Dockerのコンテナ停止時にバックグラウンド・プロセスとして動作しているデーモンを適切に終了させたい

続き。

前回、下記のような書き方だとコンテナを停止する際に問題を引き起こす可能性がある、と書いた。

FROM amazonlinux:latest

# 色々と準備(省略)

# コンテナ起動時に秘伝のデーモン magic-daemon を起動する。
# その後、コンテナを終了させないようにする。
CMD /etc/init.d/magic-daemon start; tail -f /dev/null

docker stopなどでコンテナを停止させる時、コンテナ内のPID=1のプロセスにSIGTERMが送信されるのだが、PID=1はCMDの内容を実行している/bin/sh -cだ。

つまり、バックグラウンド・プロセスとして動作しているデーモンそのものにはSIGTERMが届かない。デーモンは依然として動作したままとなり、一定時間後(既定では10秒後)にSIGKILLが送信されて、強制的に終了させられる。

待ち時間はdocker stopのオプション-tで変更できるものの、ついうっかりオプションを忘れて10秒待つのはイライラの元であるし、何より「本来意図されているだろう終了処理が行われない」というのも気持ち悪い。「気持ち悪い」だけで済めば良いのだが、例えば複数同時起動を回避するためにロック・ファイルを作成するシステムでは、強制終了によりロック・ファイルが残されたままとなり、次回以降のコンテナ起動時にデーモン起動に失敗する――といった具合に、何かしらの問題が引き起こされるものである。

この問題に対応するには、以下の内容を実行するラッパーのシェルスクリプトか何かを用意しておき、CMDにてexec形式で当該スクリプトを呼び出せば良い。

  1. デーモン起動のコマンドを実行する。
  2. SIGTERMを受信するまで無限待ちする。
  3. SIGTERMをトラップして、デーモン終了のコマンドを実行する。

例えば次のような内容のスクリプトdaemon-runner.shを用意しておく。

#!/bin/sh
# -*- coding: utf-8-unix -*-
# vim:fileencoding=utf-8:ff=unix
#
# daemon-runner.sh
# 秘伝のデーモンの起動/終了を制御するスクリプト。

trap '/etc/init.d/magic-daemon stop; exit 0' TERM

/etc/init.d/magic-daemon start

# SIGTERM が届くまで待ち続ける。
# trap(1p) で指定したコマンドが即座に実行されるようにするため、
# バックグラウンドで sleep(1) させてフォアグラウンドで wait(1p) する。
while : ; do sleep 86400 & wait $!; done

このスクリプトをコンテナにコピーして、CMDで実行するようにしておく。

FROM amazonlinux:latest

# 色々と準備(省略)

# ラッパースクリプトをコンテナにコピーして、実行権限を付与する。
# いずれ COPY のオプションで --chmod=0755 みたく書けるようになるはず。
COPY ./daemon-runner.sh /usr/local/sbin/
RUN chmod 755 /usr/local/sbin/daemon-runner.sh

# コンテナ起動時に秘伝のデーモンを起動する。
CMD ["/usr/local/sbin/daemon-runner.sh"]

こうしておけば、コンテナ起動時にデーモンを起動した後は無限待ちとなるのでプロセスが生存し続ける。またexec形式で記述したことでPID=1のプロセスとして動作するので、コンテナ停止時にSIGTERMが届くから、trap(1p)でシグナルをフックしてデーモン終了のコマンドを実行することが可能となる。

蛇足

なお、秘伝のデーモンがシグナル受信だけで適切に終了する(つまり本稿で言うなら「/etc/init.dスクリプトで停止」しなくても問題ない)場合、もしかしたら冒頭に挙げたCMD /etc/init.d/magic-daemon start; tail -f /dev/nullのような書き方をした上で、docker runの時にオプション--initを付与するだけで事足りるかもしれないが、あまり試していないので真偽は不明である。

バックグラウンド・プロセスが/bin/sh -cの子プロセスの場合には効果があるようだが、deamon(8)などを使ってバックグラウンド化したプロセスの場合にどうなるのだろうか?