根本原因在于容器中 pid 1 进程不具备信号处理与僵尸进程回收能力;linux 内核要求其必须主动捕获 sigterm 并调用 wait() 回收子进程,否则信号被丢弃、子进程变僵尸(z 状态),推荐使用 tini 作为轻量 init 进程自动转发信号并收割僵尸。

根本原因在于容器中 PID 1 进程不具备信号处理与僵尸进程回收能力。Linux 内核对 PID 1 有特殊要求:它必须主动捕获 SIGTERM 等信号,并调用 wait() 回收已退出的子进程。若应用直接作为 PID 1 启动(如 python app.py),又未实现这些逻辑,就会出现信号被丢弃、子进程变僵死(Z 状态)的问题。
用 tini 替代默认入口点
tini 是 Docker 官方推荐的轻量 init,仅约 100KB,专为容器设计:
- 自动将收到的 SIGTERM、SIGINT 等信号原样转发给子进程
- 持续调用
waitpid(-1, ...),自动收割所有僵死子进程 - 子进程退出后,tini 自身也退出,容器自然终止
- Debian/Ubuntu 镜像中安装:
RUN apt-get install -y --no-install-recommends tini - Alpine 镜像中安装:
RUN apk add --no-cache tini - 在 Dockerfile 中声明:
ENTRYPOINT ["/sbin/tini", "--"],再配正常 CMD
启用 Docker 内置 --init 选项
无需修改镜像,运行时注入 init 进程即可:
-
docker run --init my-app会自动使用 tini(Docker 1.13+ 默认集成) - 等效于手动设置
ENTRYPOINT ["/dev/init", "--"] - 适合测试、CI 或无法改 Dockerfile 的场景
- 注意:该选项不能替代多进程管理,仅解决信号与僵死问题
确保应用自身成为 PID 1(单进程适用)
若容器只跑一个前台进程,且该进程能响应信号、不 fork 子进程,可跳过 init:
- 避免 shell 封装:
CMD ["sh", "-c", "python app.py"]错误 —— shell 是 PID 1,不转发信号 - 改用 exec 替换 shell:
CMD ["sh", "-c", "exec python app.py"] - 或直接写二进制路径:
CMD ["python", "app.py"](Docker 会以 exec 形式启动) - 验证方式:进入容器执行
ps -o pid,ppid,comm,确认应用 PID 为 1 且 PPID 为 0
检查并清理已有僵死进程
修复配置后,还需清理历史残留:
- 进入容器:
docker exec -it <container> sh</container> - 查看僵死进程:
ps aux | grep 'Z'或ps -eo pid,ppid,stat,comm | grep 'Z' - 若发现僵死进程,说明当前 PID 1 仍未正确收尸,需回退检查 tini 是否生效或 exec 是否到位
- 重启容器是最稳妥的清理方式;宿主机上无法直接 kill 僵死进程(它们已无父进程可响应)











