クラシックな問題:コンテナ内で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:
変更点とその理由:
Dockerfile:tiniパッケージをインストールし、ENTRYPOINTをtini -g -- python3 ...に変更。これによりPID 1はtiniとなり:- 子プロセス(主なゾンビ原因:ProxyCommand/バストionのSSH、
ConnectTimeout/SSH_TIMEOUT_SECで終了した場合、子のSSHプロセスがinitに残る)を再起動。 SIGTERM/SIGINTをすべてのプロセスグループに正しく伝達(-gフラグ)。これによりdocker stopが10秒間停止せず、SSHの残骸も残らない。
- 子プロセス(主なゾンビ原因:ProxyCommand/バストionのSSH、
docker-compose.yml:init: 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=noneとstart_new_session=Trueを追加し、subprocess.runでSSHが長生きするマルチプレクサーを生成しないようにし、TimeoutExpired時にプロセスグループ全体を終了できるようにする。これはゾンビの原因(initの欠如)を解消するものではなく、動作の改善です。