Problema classico: Python è avviato come PID 1 all’interno del contenitore, e nei timeout SSH (soprattutto con ProxyCommand/bastion) i figli figliolini rimangono sospesi sul PID 1, che non li attende — da qui i processi zombie. Risolvo con due livelli: installo tini come init all’interno dell’immagine (protegge qualsiasi docker run), e attivo init: true nel compose come segnale esplicito.
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 come PID 1: raccoglie i figli orfani (principale fonte di zombie — ssh con ProxyCommand/bastion, ucciso per ConnectTimeout/SSH_TIMEOUT_SEC, lascia il figlio ssh al bastion sospeso sull'init);
# e propaga correttamente i segnali (SIGTERM/SIGINT) a tutta la group dei processi (flag -g), così che docker stop non impatti per 10 secondi e non lasci code ssh.
ENTRYPOINT ["/usr/bin/tini", "-g", "--", "python3", "/app/server.py", "--host", "0.0.0.0", "--config", "/config/config.yaml"]
restart: always
# Nel caso in cui l'ENTRYPOINT nell'immagine sia stato ridefinito: docker-init (tini)
# raccoglierà i zombie da ssh/ProxyCommand nei timeout.
init: true
ports:
Cosa è cambiato e perché:
-
Dockerfile: Installo il pacchettotinie avvolgo il comando contini -g -- python3 .... Ora il PID 1 ètini, che:- raccoglie i figli orfani (principale fonte di zombie —
sshconProxyCommand/bastion, terminato perConnectTimeout/SSH_TIMEOUT_SEC, lascia il figliosshal bastion sospeso sull’init); - propaga correttamente
SIGTERM/SIGINTa tutta la group dei processi (flag-g), così chedocker stopnon impatti per 10 secondi e non lasci code ssh.
- raccoglie i figli orfani (principale fonte di zombie —
-
docker-compose.yml:init: true— un ulteriore strato di sicurezza, nel caso in cui qualcuno ridefinisca localmenteentrypoint:(allora tini dall’immagine scompare, ma docker-init rimane comunque come PID 1).
Applicare:
docker compose build --no-cache ansible-status
docker compose up -d
Verificare che i zombie non si moltiplichino più (all’interno del contenitore non dovrebbero esserci righe con Z in STAT):
docker compose exec ansible-status sh -c 'ps -e -o pid,ppid,stat,comm | awk "NR==1 || /Z/"'
Se si desidera ancora più cura — si può aggiungere, in un passaggio separato, nelle opzioni di ssh_check_for_target i parametri -o ControlMaster=no -o ControlPath=none e start_new_session=True nel subprocess.run, così che ssh non crei multiplexer a lunga durata e, in caso di TimeoutExpired, si possa terminare tutta la process group, e non solo il processo padre ssh. Ma questo è un miglioramento del comportamento, mentre la causa principale dei zombie — l’assenza di init — è già risolta.