go程序收不到sigterm的根本原因是未运行在pid 1,docker默认shell启动导致信号被拦截;需用exec格式entrypoint、goroutine监听signal channel、shutdown配timeout context、waitgroup等待后台任务、显式关闭第三方资源,并确保k8s terminationgraceperiodseconds足够。

Go 程序收不到 SIGTERM?检查是否被容器 runtime 拦截了
很多 Go 服务在 Docker/K8s 里无法响应 SIGTERM,根本原因不是代码写错了,而是进程没跑在 PID 1。Docker 默认用 shell 启动(/bin/sh -c "your-command"),此时 SIGTERM 发给 shell,不会透传到你的 Go 进程。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在
Dockerfile中改用exec形式启动:ENTRYPOINT ["/app/myserver"],避免 shell wrapper - 验证是否在 PID 1:进容器执行
ps -o pid,comm,确认myserver的 PID 是 1 - K8s 中若用了
livenessProbe或preStop,注意它们不替代信号处理,只是辅助手段
signal.Notify 没生效?别漏掉 goroutine 和 channel 阻塞
signal.Notify 本身不阻塞,但常见错误是注册完就直接退出 main,或者把 signal channel 放在 select 里却没做非阻塞读取,导致信号“丢了”。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 必须用 goroutine 启一个监听循环,不能只注册不消费:
go func() { for range sigChan { /* handle */ } }() - channel 容量至少为 1:
sigChan := make(chan os.Signal, 1),否则并发发多个SIGTERM可能丢信号 - 不要在
select里只等 signal channel 而无 default 或超时,否则 shutdown 逻辑可能卡住
HTTP server Shutdown 超时或 panic?得配对用 context.WithTimeout
http.Server.Shutdown 不会自动设超时,如果请求卡在中间件、DB 查询或外部调用里,它会一直等,最终 hang 住整个停机流程。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 传入带 timeout 的 context:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) -
Shutdown返回后立刻cancel(),避免 context 泄漏 - 注意
Shutdown本身会关闭 listener,但不会中断正在处理的 request —— 所以务必在 handler 内部也检查ctx.Done() - 若用
net/http.Serve(非ListenAndServe),需手动 close listener,否则Shutdown会报ErrServerClosed以外的错误
goroutine 泄漏让停机失败?用 sync.WaitGroup 显式等待关键任务
优雅停机不只是关 HTTP server,还要等后台任务(如消息消费、指标 flush、连接池关闭)完成。靠 sleep 等几秒不可靠,goroutine 可能还在跑。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每个长期运行的 goroutine 启动前
wg.Add(1),退出前wg.Done() - 在 signal 处理函数里调用
wg.Wait(),但要配合 context 控制最大等待时间,避免死等 - 不要依赖
runtime.NumGoroutine()判断是否清空 —— 它包含 runtime 自身的 goroutine,数值不稳定 - 数据库连接池、Redis client 等第三方库一般自带
Close()方法,务必显式调用,且注意它们是否阻塞
最常被忽略的一点:K8s 的 terminationGracePeriodSeconds 必须 ≥ 应用实际 shutdown 耗时,否则 kubelet 会在你还没关完时发 SIGKILL 强杀。这个值不是越长越好,但低于 30 秒对多数服务都偏紧。










