Problema de zumbis nos processos do container Docker

Problema clássico: o Python está rodando como PID 1 dentro do container, e durante timeouts do SSH (especialmente com ProxyCommand/bastion), os filhos ficam pendurados no PID 1, que não os espera — daí os zumbis. Solução em dois níveis: instalo o tini como init dentro do próprio imagem (protege qualquer docker run), e ativo init: true no compose como sinal explícito.

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 como PID 1: recolhe processos orfãos (principal fonte de zumbis — SSH com ProxyCommand/bastion, terminado por ConnectTimeout/SSH_TIMEOUT_SEC, deixa o ssh filho pendurado no init);
# e encaminha corretamente sinais para o servidor Python.
ENTRYPOINT [\"/usr/bin/tini\", \"-g\", \"--\", \"python3\", \"/app/server.py\", \"--host\", \"0.0.0.0\", \"--config\", \"/config/config.yaml\"]
    restart: always
    # Caso o ENTRYPOINT na imagem tenha sido sobrescrito localmente: docker-init (tini) ainda assumirá o PID 1 e capturará zumbis de ssh/ProxyCommand em timeouts.
    init: true
    ports:

O que mudou e por quê:

  • Dockerfile: Instalo o pacote tini e envolvo a execução com tini -g -- python3 …. Agora, o PID 1 é o tini, que:

    • reapára processos orfãos (principal causa de zumbis — ssh com ProxyCommand/bastion, terminado por ConnectTimeout/SSH_TIMEOUT_SEC, deixa o ssh filho pendurado no init);
    • encaminha corretamente SIGTERM/SIGINT para toda a grupo de processos (flag -g), para que docker stop não fique travado por 10 segundos e não deixe resíduos de ssh.
  • docker-compose.yml: init: true — camada adicional de segurança, no caso de alguém sobrescrever localmente entrypoint: (então o tini da imagem desaparece, mas o docker-init ainda assumirá o PID 1).

Como aplicar:

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

Verificar que os zumbis não estão mais sendo gerados (dentro do container, não deve haver linhas com Z no campo STAT):

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

Se quiser ainda mais cuidado — é possível adicionar, em um passo separado, nas opções de ssh_check_for_target, os parâmetros -o ControlMaster=no -o ControlPath=none e start_new_session=True no subprocess.run, para que o ssh não crie multiplexadores longevos e, em caso de TimeoutExpired, possamos matar toda a group de processos, e não apenas o processo principal ssh. Mas isso é uma melhoria no comportamento, e a causa raiz dos zumbis — ausência de init — já foi resolvida.