Dockerコンテナのプロセスでゾンビプロセスの問題

クラシックな問題:コンテナ内でPythonがPID 1として起動しており、SSHのタイムアウト(特にProxyCommand/バストionの場合)で子プロセスがPID 1に再接続されますが、PID 1はそれらを監視していません。これによりゾンビプロセスが発生します。これを2層で修正します:Dockerイメージ自体にtiniをinitとしてインストールし(docker runのすべてに適用)、docker-compose.ymlではinit: trueを明示的に指定して信号を強化します。

RUN apt-get update \\
    && apt-get install -y --no-install-recommends openssh-client \\
    && apt-get install -y --no-install-recommends openssh-client tini \\
    && rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["python3", "/app/server.py", "--host", "0.0.0.0", "--config", "/config/config.yaml"]

# tiniをPID 1として設定:OSIROTEVSHCHIH VNUKOV(ProxyCommand/バストion、SSHタイムアウトで終了したsshプロセスがinitに残る)を再起動し、
# SIGTERM/SIGINTをPythonサーバー全体に正しく伝達します。
ENTRYPOINT ["/usr/bin/tini", "-g", "--", "python3", "/app/server.py", "--host", "0.0.0.0", "--config", "/config/config.yaml"]

restart: always
# ENTRYPOINTがローカルで上書きされた場合の保険:docker-init(tini)がPID 1として立ち上がり、ssh/ProxyCommandのタイムアウトによるゾンビを拾います。
init: true
ports:

変更点とその理由:

  • Dockerfiletiniパッケージをインストールし、ENTRYPOINTtini -g -- python3 ...に変更。これによりPID 1はtiniとなり:
    • 子プロセス(主なゾンビ原因:ProxyCommand/バストionのSSH、ConnectTimeout/SSH_TIMEOUT_SECで終了した場合、子のSSHプロセスがinitに残る)を再起動。
    • SIGTERM/SIGINTをすべてのプロセスグループに正しく伝達(-gフラグ)。これによりdocker stopが10秒間停止せず、SSHの残骸も残らない。
  • docker-compose.ymlinit: trueは追加の保険。ローカルでentrypoint:が上書きされた場合(tiniがイメージから消える)、docker-initがPID 1として立ち上がり、ゾンビを拾います。

適用方法:

docker compose build --no-cache ansible-status
docker compose up -d

ゾンビプロセスが生成されなくなったか確認(コンテナ内にSTAT列にZが含まれる行がないか):

docker compose exec ansible-status sh -c 'ps -e -o pid,ppid,stat,comm | awk "NR==1 || /Z/"'

さらに丁寧にしたい場合は、ssh_check_for_targetのオプションに-o ControlMaster=no -o ControlPath=nonestart_new_session=Trueを追加し、subprocess.runでSSHが長生きするマルチプレクサーを生成しないようにし、TimeoutExpired時にプロセスグループ全体を終了できるようにする。これはゾンビの原因(initの欠如)を解消するものではなく、動作の改善です。