Zombie-Probleme in Docker-Containern

Ein klassisches Problem: Python läuft als PID 1 im Container, und bei SSH-Timouts (insbesondere mit ProxyCommand/Bastion) bleiben Enkelprozesse an PID 1 hängen, der sie jedoch nicht adoptiert — daraus entstehen Zombiprozesse. Ich behebe das mit zwei Schichten: Ich setze tini als init im Image selbst (schützt jeden docker run), und aktiviere init: true in docker-compose.yml als explizites Signal.

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 als PID 1: adoptiert verwaiste Enkel (Hauptquelle von Zombis — ssh mit ProxyCommand/Bastion, getötet durch ConnectTimeout/SSH_TIMEOUT_SEC, lässt den Kinderssh-Prozess an init hängen)
# und leitet Signale korrekt an den Python-Server weiter.
ENTRYPOINT ["/usr/bin/tini", "-g", "--", "python3", "/app/server.py", "--host", "0.0.0.0", "--config", "/config/config.yaml"]

restart: always
# Falls der ENTRYPOINT im Image überschrieben wurde: docker-init (tini)
# greift die Zombis von ssh/ProxyCommand bei Timeouten auf.
init: true
ports:

Was geändert wurde und warum:

  • Dockerfile: Ich installiere den Paket tini und umhüllen die Kommandozeile mit tini -g -- python3 …. Nun ist PID 1 tini, der:
    • verwaiste Enkelprozesse reapernt (Hauptquelle von Zombis — ssh mit ProxyCommand/Bastion, getötet durch ConnectTimeout/SSH_TIMEOUT_SEC, lässt den Kinderssh-Prozess an init hängen);
    • SIGTERM/SIGINT korrekt an die gesamte Prozessgruppe weiterleitet (Flag -g), sodass docker stop nicht 10 Sekunden hängt und keine ssh-„Schwänze“ zurücklässt.
  • docker-compose.yml: init: true — zusätzliche Sicherungsschicht, falls jemand lokal entrypoint: überschreibt (dann verschwindet tini aus dem Image, aber docker-init nimmt trotzdem PID 1 ein).

Anwendung:

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

Überprüfen, ob keine Zombis mehr entstehen (im Container sollten keine Zeilen mit Z in STAT auftreten):

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

Wenn man noch genauer sein möchte — kann man in ssh_check_for_target zusätzlich die Optionen -o ControlMaster=no -o ControlPath=none und start_new_session=True in subprocess.run hinzufügen, damit ssh keine langlebigen Multiplexoren erzeugt und bei TimeoutExpired die gesamte Prozessgruppe abgeschaltet werden kann, und nicht nur der Haupt-ssh. Aber das ist eine Verbesserung des Verhaltens — die eigentliche Ursache der Zombis, nämlich das Fehlen von init, ist bereits behoben.