supervisor 默认不转发 sigterm 到孙子进程,需设 killasgroup=true 和 stopasgroup=true;tini 是轻量 pid 1 init,负责信号透传与僵尸回收;python 自行处理信号无法覆盖整个进程树,必须依赖 tini 或 dumb-init。

supervisor 启动进程后收不到 SIGTERM?
因为 supervisor 默认不把信号转发给子进程的子进程(即孙子进程),只发给直接子进程。你用 supervisord 跑一个 shell 脚本,脚本再启动 python app.py,那 kill -TERM 只会到 shell,不会透传到 Python 进程——结果就是容器停不干净,超时被强制 kill。
实操建议:
- 在
supervisord.conf中为每个 program 设置killasgroup=true和stopasgroup=true,强制按进程组发信号 - 避免用
shell=True或写command=/bin/sh -c "python app.py",改用直接执行:command=python app.py - 如果必须用 shell 封装,加
startsecs=0+autorestart=false防止异常重启掩盖问题
tini 是什么,为什么 Docker 官方镜像里总带它?
tini 不是进程管理器,是个极简 init 系统(PID 1),唯一任务:当主进程退出时,收掉所有孤儿进程;同时把收到的信号(比如 SIGTERM)原样转发给子进程——这正是容器里缺失的关键链路。
常见错误现象:没加 tini 的镜像,Python 进程 fork 出子进程(如多进程日志轮转、multiprocessing.Pool),主进程一停,子进程变僵尸或继续跑,docker stop 卡住。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- Dockerfile 里加
ENTRYPOINT ["/sbin/tini", "--"],再接你的主命令,比如["/sbin/tini", "--", "python", "app.py"] - 别用
tini -v或带多余参数,Docker 1.13+ 自带tini,可用ENTRYPOINT ["tini", "--"]直接调用 - 确认你的 Python 进程没忽略
SIGTERM:检查是否调用了signal.signal(signal.SIGTERM, ...)但没做清理就 exit
dumb-init 和 tini 有啥区别?能随便换吗?
两者定位完全一致:轻量 PID 1 init,解决信号转发和僵尸回收。区别在于实现和默认行为:dumb-init 默认开启 --rewrite(重写进程名),tini 更保守,纯转发。
使用场景差异:
- 如果你的 Python 日志里看到进程名全是
tini或dumb-init,影响排查,选tini(不重写)或dumb-init --no-rewrite -
dumb-init对SIGHUP默认转成SIGTERM,tini不做任何转换——如果你依赖SIGHUP触发 reload,别用dumb-init默认配置 - 镜像体积上,
tini静态链接二进制更小(~60KB),dumb-init稍大(~120KB),但差别不大
Python 进程里自己处理信号够不够?
不够。即使你在 Python 里写了 signal.signal(signal.SIGTERM, cleanup),也只管得了自己的进程。fork 出来的子进程、subprocess.Popen 启的外部命令、C 扩展里创建的线程,都不会自动继承这个 handler。
更麻烦的是:容器里 PID 1 没有 init 功能,子进程 exit 后没人 wait,变成僵尸;SIGTERM 到不了子进程,它们卡在 running 状态。
所以关键点不是“Python 能不能响应信号”,而是“整个进程树能不能被干净终止”。这时候靠 tini 或 dumb-init 做 PID 1,比在 Python 里堆 signal 处理逻辑更可靠。
容易被忽略的地方:很多人加了 tini,但 Python 主进程又用 os.execv 替换了自身,导致 tini 失去对后续进程的控制——这种情况下,tini 已经退出,后面全是裸奔进程。










