コンテナー内でのマルチプロセス実行

コンテナーの主となる実行プロセスは、Dockerfileの最終部分に指定されるENTRYPOINTCMDです。 1 つのコンテナーには 1 つのサービスを割り当てるということにすれば、気にかける箇所が絞られるので、これがベストプラクティスです。 ただそのサービスからは、複数のプロセスがフォークされることもあります(たとえば Apache ウェブサーバーでは複数のワーカープロセスが起動されます)。 マルチプロセスとなることは、まったく問題ありません。 一方で、アプリケーションが持ついくつもの役割を 1 つのコンテナーに持たせることは、Docker の優れた機能を利用する観点からは避けるべきです。 コンテナーを複数にするのであれば、ユーザー定義のネットワークや共有ボリュームを利用して接続します。

コンテナーのメインプロセスは、コンテナーそのものが起動させるプロセスすべてを管理するためにあります。 メインプロセスが十分に機能していないことが原因で、コンテナー終了時に子プロセスを適切に停止できないことがあります。 起動プロセスがこの手の事態に陥った場合は、コンテナー起動時に --init オプションを指定してみてください。 この --init フラグは、コンテナーのメインプロセスとして、非常に小さな初期化プロセスを埋め込みます。 この小さなプロセスが、コンテナー終了時の子プロセス停止を受け持つことになります。 子プロセスの扱いをこのようにするのは、本格的な初期化プロセス、たとえば sysvinitsystemd に比べて、コンテナー内部のプロセスのライフサイクルを適切に扱うことができるからです。

1 つのコンテナー内に複数のサービスを起動させる必要があるなら、方法はいくつかあります。

ラッパースクリプトの利用

実行するコマンドをすべてラッパースクリプトに含めます。 あらかじめテストやデバッグは行っておきます。 そしてこのラッパースクリプトを CMD として実行します。 以下は簡単な例です。 まずはラッパースクリプトを生成します。

#!/bin/bash

# 1つめのプロセスを起動
./my_first_process &

# 2つめのプロセスを起動
./my_second_process &

# いずれかが終了するのを待つ
wait -n

# 最初に終了したプロセスのステータスを返す
exit $?

次は Dockerfile です。

# syntax=docker/dockerfile:1
FROM ubuntu:latest
COPY my_first_process my_first_process
COPY my_second_process my_second_process
COPY my_wrapper_script.sh my_wrapper_script.sh
CMD ./my_wrapper_script.sh

Bash のジョブコントロールの利用

1 つのメインプロセスを起動させたら、そのまま起動し続ける場合です。 一時的に別のプロセスをいくつか起動する(そしておそらくはメインプロセスと通信を行う)とします。 この場合は bash のジョブコントロールの機能を利用します。 まずはラッパースクリプトを生成します。

#!/bin/bash

# ジョブコントロールを有効にします。
set -m

# 1つめのプロセスをバックグラウンドで実行します。
./my_main_process &

# ヘルパープロセスを実行します。
./my_helper_process

# この my_helper_process は自分の処理を開始して終了するためには、
# 1つめのプロセスの動きを知っておく必要があるかもしれません。


# ここで1つめのプロセスをフォアグラウンド実行に戻して
# そのままとします。
fg %1
# syntax=docker/dockerfile:1
FROM ubuntu:latest
COPY my_main_process my_main_process
COPY my_helper_process my_helper_process
COPY my_wrapper_script.sh my_wrapper_script.sh
CMD ./my_wrapper_script.sh

プロセスマネージャーの利用

Use a process manager like supervisord. This is more involved than the other options, as it requires you to bundle supervisord and its configuration into your image (or base your image on one that includes supervisord), along with the different applications it manages. Then you start supervisord, which manages your processes for you.

The following Dockerfile example shows this approach. The example assumes that these files exist at the root of the build context:

  • supervisord.conf
  • my_first_process
  • my_second_process
# syntax=docker/dockerfile:1
FROM ubuntu:latest
RUN apt-get update && apt-get install -y supervisor
RUN mkdir -p /var/log/supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY my_first_process my_first_process
COPY my_second_process my_second_process
CMD ["/usr/bin/supervisord"]

If you want to make sure both processes output their stdout and stderr to the container logs, you can add the following to the supervisord.conf file:

[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0

[program:app]
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true