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

Dockerのコンテナを起動する際に何らかのデーモンを起動させたい、という要求は結構多い(というか実務的にはむしろそれが主目的ではないか)と思うのだが、「何らかのデーモン」がバックグラウンド・プロセスとして動作する代物だと、若干ハードルが上がるようだ。

デーモンがフォアグラウンドで動作するならば、教科書通りDockerfileのCMDにデーモン起動のコマンドを記述すればよい。例えば次のような内容だ。

FROM amazonlinux:latest

# 色々と準備(省略)

# コンテナ起動時に自作デーモン my-daemon を起動する。
CMD /usr/local/sbin/my-daemon -l /usr/local/etc/my-daemon.conf

DockerfileのENTRYPOINTCMDは、フォアグラウンドで動作するアプリケーションを前提としている節がある。ENTRYPOINTないしCMDに記述されたコマンドは、コンテナ起動時にPID=1のプロセスとして実行される。フォアグラウンドで動作するアプリケーションならば、自身のプロセス(PID=1のプロセス)は実行状態のままとなり、コンテナを停止するまで生存し続ける。

※より正確に書くと、docker run実行時にオプション--initを付与しなかったコンテナにおいては、ENTRYPOINTCMDexec形式で記述されたコマンドが、PID=1のプロセスとして実行される。シェル形式で記述した場合、そのコマンドは/bin/sh -cの引数として実行されるが、この時PID=1のプロセスは/bin/sh -cであり、その子プロセスとして「ユーザが記述したコマンド」が実行される。

ところで、非コンテナ環境で運用してきた秘伝のデーモンを組み込もうとした時、例えば/etc/init.dの起動スクリプト経由でバックグラウンド・プロセスとして起動させる、みたいな構成*1であることが結構多い。

起動スクリプト内でdeamon(8)を使っているならまだしも、中にはソースコード内でdaemon(3)を使って自前でバックグラウンド化している(しかもフォアグラウンドで実行させる術がない)アプリケーションもあったりする。

残念ながら、CMDの内容が「バックグラウンド動作するデーモンを起動する」だけであると、コンテナ自体が起動した直後に停止してしまう。例えば次のような内容だと、ユーザの意図した動作にならない。

FROM amazonlinux:latest

# 色々と準備(省略)

# コンテナ起動時に秘伝のデーモン magic-daemon を起動する。
# ==> デーモンは起動するが、コンテナ自体が直ぐに停止してしまう。
CMD /etc/init.d/magic-daemon start

CMDの内容に従ってデーモン起動のコマンドを実行した直後に、その処理を行ったPID=1のプロセスが終了してしまう。そのため、コンテナ自体が直ぐに停止してしまうのだ。

ではどうすればよいかといえば、CMDに「直ぐには終了しない処理」を記述すればよい。デーモン起動のコマンドを実行した後に、例えば以下のようなコマンドを実行するのだ*2

  • /bin/sh
    • 対話型コマンドなので、直ぐには終了しない。
    • ただし/bin/bashへのシンボリックリンクだったりすると、ほぼ使われないプロセスなのにVSZやRSSが大きくなりがちで、微妙な気分になる。
  • sleep infinity
    • 厳密には有限だが、事実上問題にならないほど長い時間sleep(1)する。
    • なお、この書き方はLinux限定で、他のUnix系OSでは使えない……はず。でもDockerだからLinux以外の環境を考慮する意味はない。
  • tail -f /dev/null
    • これも無限待ち状態になるが、他者に意図が伝わりにくい書き方だと思う。
  • while : ; do sleep 1; done
    • 実はこれが一番手堅い方法かもしれない。

CMDに複数のコマンドを記述するには、ラッパーとなるシェルスクリプトを用意しても良いが(というか個人的にはそれを推奨するが)、シェル形式を用いて次のように記述するのもひとつの手だろう。例えば次のような塩梅だ。

FROM amazonlinux:latest

# 色々と準備(省略)

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

ところで上記の書き方は、コンテナを停止する際に問題を引き起こす可能性がある。回避するためには、ラッパーとなるシェルスクリプトを用意する必要がある。

(次回に続く)

*1:要は、システム起動時に SysVinit / Upstart / Systemd などを使用して起動する、という前提で実装されたデーモンだということ。

*2:ここでは、比較的どのDockerイメージにも入っていそうなコマンドを選択している。