根本解法是让容器 PID 1 具备 init 职责:主动回收子进程、正确转发信号;默认 shell 不处理 SIGCHLD 导致僵尸进程,可用 --init 启用 tini 或在 Dockerfile 中固化 ENTRYPOINT ["/sbin/tini", "--"] 并确保 exec 模式启动。

根本解法是让容器 PID 1 具备 init 职责:主动回收子进程、正确转发信号。默认的 shell(如 /bin/sh)不处理 SIGCHLD,子进程退出后就会变成僵尸(Z 状态),长期积累可能耗尽 PID 表项,甚至触发 fork: Cannot allocate memory 错误。
用 --init 参数启用 tini(最简有效)
Docker 1.13+ 原生支持 --init,底层自动注入轻量可靠的 tini:
- 运行时启用:
docker run --init -d myapp,无需改镜像,适合测试或临时修复 - tini 作为 PID 1 持续调用
waitpid(-1, )清理所有子进程退出状态 - 将
SIGTERM、SIGINT等信号完整转发给主应用,保障docker stop正常生效
在 Dockerfile 中固化 tini(生产推荐)
构建可复现、就绪的镜像,避免运行时依赖外部参数:
使用 Python 脚本下载 Docker 镜像并打包为 tar 文件。用户说“下载镜像”“拉取镜像”“pull xxx”“帮我下载 nginx 镜像”等时触发。支持 SOCKS5 代理直连(推荐)和国内镜像站加速两种模式。
- Debian/Ubuntu 镜像:
RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/* - Alpine 镜像:
RUN apk add --no-cache tini - 设置入口点:
ENTRYPOINT ["/sbin/tini", "--"],后续CMD将作为 tini 的子进程被托管
避免启动方式绕过 init 管理
即使用了 tini,错误写法仍会让 shell 抢占 PID 1:
- ❌ 错误(shell 模式):
CMD ./start.sh→ 实际执行/bin/sh -c "./start.sh",/bin/sh成为 PID 1 - ✅ 正确(exec 模式):
CMD ["./start.sh"];或脚本末尾用exec "$@"替换当前 shell - Java/Node.js 等应用中,避免用
Runtime.exec()或child_process.spawn()启动子进程却不监听exit事件
验证是否真正生效
进入运行中的容器快速确认:
- 查 PID 1 是什么:
ps -p 1 -o comm=—— 应显示tini或dumb-init,而非sh、bash - 查僵尸数量:
ps axo stat | grep -c '^Z'—— 稳定为 0 才算有效 - 压力测试:
for i in $(seq 50); do sh -c 'sleep 0.01' & done,再检查 Z 进程数是否归零

















