Go程序需捕获SIGTERM并等待请求完成:用signal.Notify监听信号,调用http.Server.Shutdown配合带超时的context;PreStop应触发轻量操作如HTTP请求而非kill;Shutdown失效常因handler未受context控制;日志需在超时前Sync或Flush,且shutdown超时须小于terminationGracePeriodSeconds。

Go 程序如何响应 SIGTERM 并完成正在处理的请求
Go 进程默认收到 SIGTERM 会立即退出,导致正在处理的 HTTP 请求、数据库事务或消息消费被强行中断。关键不是“能不能停”,而是“等正在跑的活干完再停”。
核心做法是用 signal.Notify 捕获 SIGTERM(和 SIGINT),配合 http.Server.Shutdown 或自定义的 shutdown 逻辑:
- 启动一个
http.Server后,别直接server.ListenAndServe(),改用server.Serve(lis)配合自定义 listener,方便后续关闭 - 在收到信号后,调用
server.Shutdown(ctx),传入带超时的context.WithTimeout—— 超时时间必须 > 你最长请求耗时,否则会被强制 kill - Shutdown 不会主动等待 goroutine,如果你有独立运行的 worker(比如后台消费 Kafka),得自己加
sync.WaitGroup或context.WithCancel控制生命周期
Kubernetes PreStop Hook 中执行 Go 程序的 shutdown 命令要注意什么
PreStop 是容器终止前由 kubelet 触发的钩子,它本身不传递信号给主进程,只是执行一条命令。常见错误是写成 kill -TERM 1 却没让 Go 进程真正监听它。
更稳妥的做法是:让 Go 程序监听 SIGTERM,同时在 PreStop 里只做轻量触发(比如发个 HTTP 请求或 touch 文件),避免依赖信号传递的时序问题:
立即学习“go语言免费学习笔记(深入)”;
- 不要在
PreStop里写sleep 30 && kill 1—— 容器可能在 sleep 期间就被强制终止(默认 terminationGracePeriodSeconds=30) - 推荐用
exec类型的PreStop执行curl -X POST http://localhost:8080/shutdown,Go 端暴露一个带鉴权的 shutdown handler - 如果必须用
signal,确保 Go 进程 PID=1(即没用sh -c包裹),否则kill 1杀不到你的程序,而是杀掉 shell
Go 的 http.Server.Shutdown 为什么有时不生效
现象是调用了 Shutdown,但连接立刻断开,或者超时后仍卡住。根本原因常是:HTTP handler 里有没受 context 控制的阻塞操作。
比如数据库查询、第三方 API 调用、time.Sleep,这些不会因 ctx.Done() 自动中断:
- 所有
http.HandlerFunc必须从r.Context()拿 context,并传给下游调用(如db.QueryContext(ctx, ...)) - 避免在 handler 里起无管控 goroutine,例如
go sendToKafka(...)—— Shutdown 不会等它,得用WaitGroup显式同步 - 如果用了
net/http/httputil.ReverseProxy,它默认不传播 context,需手动 wrapDirector和重写Transport
容器终止时日志丢失或来不及刷盘怎么办
Go 默认日志(log 包)是行缓冲的,但容器终止时 stdout/stderr 可能被截断,尤其用 log.SetOutput(os.Stderr) 直接写文件描述符时。
解决重点不在“怎么记”,而在“什么时候记完”:
- 在
Shutdown的 context 超时前,显式调用log.Sync()(如果用了支持 sync 的 logger,如zap的Sync()) - 避免在 shutdown 流程中做重 IO,比如压缩日志归档 —— 它可能拖过 grace period,触发 SIGKILL
- 如果用结构化日志库(如
zerolog),确认其 writer 是os.Stdout或os.Stderr,而非带 buffer 的bufio.Writer;若用了 buffer,shutdown 前要Flush()
最易被忽略的是:Shutdown 的 context 超时时间,和 Pod 的 terminationGracePeriodSeconds 必须对齐,且前者要略小(比如设为 25s,后者 30s),否则 kubelet 会在 Go 还没完成 shutdown 时就发 SIGKILL。










