根本原因是pid 1进程未注册信号且未用exec启动,导致sigterm被内核丢弃、容器假死;推荐用tini作为轻量init自动转发信号并回收僵尸,或让应用自身作为pid 1并监听信号,运行时可加--init临时修复。
根本问题是 pid 1 进程没做信号注册,又没用 exec 替换启动方式,导致 sigterm 被内核丢弃,容器无法优雅退出,表现为“假死”——进程还在、端口还占着、健康检查也通,但实际已不响应新请求。
确认是否真被忽略
别猜,直接验证:
- 执行 docker exec kill -TERM 1,观察容器是否退出或日志是否有清理动作
- 进容器查 ps -o pid,ppid,comm,state,看 PID 1 是不是你的应用,还是 sh/bash 占着
- 如果 PID 1 是 shell(如 dash、sh),基本可以断定信号没传下去
推荐方案:用 tini 做轻量 init
tini 是 Docker 官方推荐的解决方案,不到 100KB,专治 PID 1 信号和僵尸问题:
- 在 Dockerfile 中加一句:ENTRYPOINT ["tini", "--"],再接原有 CMD
- 或构建时启用 --init:docker build --init -t myapp .
- tini 会自动转发 SIGTERM/SIGINT 给子进程,并持续 waitpid(-1) 回收僵尸
代码层修复:让应用自己当 PID 1 并监听信号
适合 Go/Python/Node.js 等能主动处理信号的语言:
- Go:用 signal.Notify(c, syscall.SIGTERM, syscall.SIGINT),收到后执行清理再 os.Exit(0)
- Python:用 signal.signal(signal.SIGTERM, handler) 注册回调
- Node.js:监听 process.on('SIGTERM', () => { server.close(); })
- 关键前提是启动命令必须是 exec 形式,比如 CMD ["exec", "python", "app.py"],不能套 sh -c
临时救急:运行时加 --init 参数
不用改镜像,适合测试或紧急上线:
- docker run --init -d myapp
- 它会在容器内注入一个类似 tini 的 init 进程,自动接管信号转发和僵尸回收
- Kubernetes 中可通过 securityContext 设置 initProcess,或使用 initContainer 模拟










