使用 dumb-init 解决容器孤儿进程回收瓶颈,关键在于确保其作为 pid 1 进程运行、进程树干净、信号路径通畅、cgroups 归属准确;推荐用 docker run --init 启动,避免 shell 中间层,直接 exec 或 entrypoint 主程序,并验证 cgroup 所属与僵尸进程数为 0。
用 dumb-init 解决容器内孤儿进程回收瓶颈,核心在于让它真正成为 pid 1,并接管所有子进程的生命周期——不是“加了就行”,而是要确保进程树结构干净、信号路径通畅、cgroups 归属准确。
让 dumb-init 稳稳坐实 PID 1
直接使用 docker run --init 是最稳妥的方式。Docker 1.13+ 内置支持,会自动注入 dumb-init(或 tini)作为初始进程,无需手动安装或改镜像。它启动后自动收养后续所有 fork 出的子进程,包括后台任务、shell 启动的守护进程等。
- 推荐命令:
docker run --init -d my-app:latest - 避免自行下载二进制再写 ENTRYPOINT,除非有定制信号重写需求(如
--rewrite SIGTERM=SIGQUIT) - 进入容器后执行
ps -o pid,comm | head -2,第一行必须是dumb-init或tini,否则没生效
切断 shell 中间层,防止进程“脱管”
很多孤儿进程其实源于启动脚本里多了一层 shell。比如 /bin/sh -c "python app.py",此时 python 是 shell 的子进程;shell 一退出,python 就被转交给宿主机 PID 1,彻底逃出容器 cgroups。
- 正确做法:用
exec替换当前 shell 进程,例如/bin/sh -c "exec python app.py" - 更优写法:直接运行主程序,不套 shell,如 Dockerfile 中设
ENTRYPOINT ["python", "app.py"] - 若必须用脚本启动,确保最后一句是
exec "$@",把控制权完整交出去
验证是否真正在 cgroups 下受控
光看进程树不够,得确认资源归属。孤儿进程逃逸的本质是脱离容器 cgroup,导致 OOM Killer 不杀它、docker stats 不统计它。
- 查任意子进程的 cgroup 路径:
cat /proc/$(pgrep python)/cgroup,输出中应含docker/xxx或system.slice/docker-xxx.scope - 对比宿主机视角:
cat /sys/fs/cgroup/memory/docker/*/memory.usage_in_bytes 2>/dev/null | sort -n | tail -3,数值应与docker stats显示内存基本一致 - 观察僵尸数:
ps aux | awk '$8 ~ /Z/ {count++} END {print count+0}',有 dumb-init 时应长期为 0
脚本编写配合 init 的关键习惯
dumb-init 能兜底收养和 wait,但脚本本身别主动制造“不可回收”场景:
- 避免在脚本里用
wait不带参数——它只等直系子进程,而 dumb-init 等的是所有被收养进程,交给它更可靠 - 后台任务(如
sleep 10 &)无需自己 wait,dumb-init 会自动回收 - 信号处理交给 dumb-init 转发即可,应用代码不必额外注册 SIGCHLD 或反复调用 wait










