Go 服务优雅停机:K8s 发 SIGTERM 后不是立刻消失
Go 服务优雅停机:K8s 发 SIGTERM 后不是立刻消失
Go 服务部署在 K8s 后,优雅停机经常被忽略。Pod 被滚动更新时会收到 SIGTERM,如果服务没有停止接收新请求、等待进行中请求结束、关闭连接池,就可能产生一批莫名其妙的 502 或处理中断。
优雅停机不是形式主义,它决定发布时用户是否被无辜打断。
一、停机链路要清楚
flowchart TD A[K8s SIGTERM] --> B[Stop Accepting] B --> C[Drain Requests] C --> D[Close Resources] D --> E[Exit]K8s 不会读心。应用收到信号后,需要自己处理生命周期。
二、Go HTTP Server 支持 Shutdown
srv := &http.Server{Addr: ":8080", Handler: router} go func() { if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatal(err) } }() ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() _ = srv.Shutdown(ctx)Shutdown会停止接收新连接,并等待已有请求完成,直到 context 超时。
三、K8s 配置要配合
terminationGracePeriodSeconds: 30 lifecycle: preStop: exec: command: ["sh", "-c", "sleep 5"]preStop 可以给负载均衡摘流量一点时间。具体配置要结合网关和服务发现机制测试。
四、长请求要有上限
如果请求可能跑几分钟,停机就会拖很久。服务应该设置请求超时,并对长任务异步化。
shutdown_policy: max_request_time: 15s async_jobs: resumable force_exit_after: 30s优雅停机不是无限等待。超过窗口仍要退出,否则发布会卡住。
还要处理 readiness。收到 SIGTERM 后,可以先让 readiness 失败,让流量入口停止转发,再等待已有请求完成。
var shuttingDown atomic.Bool func readiness(w http.ResponseWriter, r *http.Request) { if shuttingDown.Load() { w.WriteHeader(http.StatusServiceUnavailable) return } w.WriteHeader(http.StatusOK) }这样服务不是一边准备退出,一边继续接新请求。生命周期信号要和探针配合。
五、总结
Go 服务在 K8s 中要处理 SIGTERM、停止接收新请求、等待已有请求、关闭资源,并与 terminationGracePeriod 和 preStop 配合。
Pod 不是立刻消失,但也不会永远等你。生命周期写清楚,滚动发布才真的平滑。
上线前可以做一次演练:压测中滚动发布,观察 5xx、请求耗时和连接关闭情况。优雅停机只有被验证过,才不是纸面配置。
