优雅下线的关键是主进程正确接收并响应sigterm:需显式注册信号处理器、避免shell导致pid 1无法转发信号、设置合理stop-timeout,并通过docker stop验证退出码为0。

在 Docker 容器中实现优雅下线,关键不是“拦截 SIGTERM”,而是让主进程能正确接收并响应它——Docker 默认会向 PID 1 进程发送 SIGTERM,若该进程不处理或直接忽略,容器就会强制终止(约 10 秒后发 SIGKILL),导致请求被中断、连接未关闭、数据未刷盘等问题。
确保你的应用真正监听并处理 SIGTERM
很多语言运行时(如 Node.js、Python、Java)默认不自动处理 SIGTERM,需显式注册信号处理器:
-
Node.js:用
process.on('SIGTERM', () => { cleanup(); process.exit(0); }),务必调用process.exit(),否则进程不会退出,Docker 会超时强杀 -
Python:用
signal.signal(signal.SIGTERM, lambda s, f: (cleanup(), exit(0))),注意避免在清理中阻塞过久(建议设超时) -
Java(Spring Boot):启用 actuator 的 shutdown endpoint 或配置
spring.lifecycle.timeout-per-shutdown-phase=30s,同时确保 JVM 能响应 OS 信号(默认支持)
避免 PID 1 问题:别让 Shell 启动脚本当 PID 1
如果 Dockerfile 用 CMD ["sh", "-c", "exec myapp"],sh 会成为 PID 1,而大多数 shell 不转发信号。结果:SIGTERM 发给 sh,sh 不传给子进程,你的应用收不到。
✅ 正确做法:
- 用
exec myapp替代sh -c "myapp",让应用直接成为 PID 1 - 或使用轻量级 init(如
tini):在 Dockerfile 中加ENTRYPOINT ["/sbin/tini", "--"],再 CMD 启动应用
设置合理的停止超时时间
Docker 默认等待 10 秒才发 SIGKILL。如果你的应用清理需更久(如批量写入、长连接 draining),启动容器时用 --stop-timeout=30 延长等待窗口。
同时,在应用内也应设置清理阶段的硬性超时(比如最多等 25 秒),防止无限阻塞。
验证是否生效:手动测试 + 日志观察
别等上线才发现失效。本地快速验证:
- 启动容器:
docker run -d --name test-app your-image - 发送停止信号:
docker stop test-app(观察控制台输出是否打印 “Shutting down…” 类日志) - 检查退出码:
docker inspect test-app --format='{{.State.ExitCode}}'应为 0(正常退出),非 137(被 SIGKILL 终止)










