gin 默认不处理 sigterm,因其仅为路由框架,不封装服务器生命周期管理,底层依赖标准库 http.server;必须手动调用 srv.shutdown() 配合信号监听与超时 context 才能实现优雅停机,否则仅捕获信号而无 shutdown 将导致请求被立即中断。

必须手动调用 http.Server.Shutdown(),Gin 本身不提供自动优雅停机逻辑;光靠 signal.Notify 捕获 SIGTERM 而不调用 Shutdown(),等于没做任何事。
为什么 Gin 默认不处理 SIGTERM?
Gin 是一个 HTTP 路由框架,它不封装服务器生命周期管理——底层仍依赖 Go 标准库的 http.Server。Kubernetes 发送 SIGTERM 时,进程若未注册信号处理器,会直接退出;即使注册了,也必须显式触发 srv.Shutdown() 才能停止接收新请求并等待活跃连接结束。
-
srv.ListenAndServe()是阻塞调用,不响应信号 - 仅用
signal.Notify(quit, syscall.SIGTERM)+os.Exit(0)属于“伪优雅”,请求会被立即中断 - Go 1.8+ 的
http.Server.Shutdown()是唯一标准路径,Gin 无替代方案
http.Server.Shutdown() 的超时必须短于 terminationGracePeriodSeconds
Kubernetes 给 Pod 的终止宽限期(terminationGracePeriodSeconds)是硬上限;而 Shutdown() 的 context 超时必须比它小至少 2–5 秒,否则会因超时失败后被强制 SIGKILL。
- 若 K8s 配置
terminationGracePeriodSeconds: 30,建议context.WithTimeout(..., 25*time.Second) - 超时过长(如设为 30 秒)会导致
Shutdown()还在等请求完成,K8s 已发SIGKILL——此时日志里会出现context deadline exceeded - 超时过短(如 5 秒)可能误杀慢请求,需结合接口 P99 延迟评估(例如支付回调类接口通常需 ≥15 秒)
preStop Hook 不是可选项,而是关键协同点
Kubernetes 的 preStop 生命周期钩子,在 SIGTERM 发出前同步执行,是确保流量不再打入的最后防线。它和应用内 Shutdown() 必须配合使用,否则存在竞争窗口。
- 错误做法:只在代码里监听
SIGTERM,但没配preStop→ Service 可能在Shutdown()启动前还在转发流量 - 推荐配置:
preStop.exec.command: ["sh", "-c", "sleep 0.1"](极轻量,只为触发就绪探针快速失效) - 更稳妥做法:在
preStop中调用本地健康检查端点(如curl -f http://localhost:8080/healthz),确认服务已进入“拒绝新请求”状态后再退出 -
preStop执行时间会计入terminationGracePeriodSeconds总时长,不能超过它
资源释放顺序不能错:HTTP Server 必须最先 Shutdown
数据库、Redis、Kafka 等资源的 Close() 必须在 http.Server.Shutdown() 完成之后执行。否则,正在处理的请求可能因连接池关闭而 panic 或返回错误。
- 正确顺序:
srv.Shutdown()→db.Close()→redis.Close()→kafka.AsyncClose() - 反例:先
db.Close(),再srv.Shutdown()→ 正在执行的 HTTP handler 调用db.Query()会 panic - 所有
Close()调用建议加log.Println(),便于排查停机卡点(例如日志停在 “关闭数据库” 就不动了,说明db.Close()阻塞)
真正容易被忽略的是:K8s 的就绪探针(readinessProbe)必须在 preStop 触发后快速失败——否则流量还会持续打进来,直到探针下一次检测周期(默认 periodSeconds=10)。这意味着你的健康接口在收到 SIGTERM 后,应立刻返回非 200 状态,而不是等 Shutdown() 开始才变。这个状态切换时机,比 Shutdown 本身更关键。











