Go服务需捕获SIGTERM,用server.Shutdown()优雅关闭HTTP服务器,配合K8s的preStop钩子与readinessProbe联动实现先断流再停服,并手动管理WebSocket等长连接生命周期。

Go 服务如何响应 SIGTERM 并停止 HTTP Server
Kubernetes 在删除 Pod 前会向容器主进程发送 SIGTERM,Go 程序必须捕获该信号并主动关闭监听器,否则连接可能被强制中断。关键不是“等几秒再退出”,而是“等正在处理的请求完成,且不再接受新请求”。
常见错误是只调用 server.Close() 而没做 graceful shutdown 控制,导致 ListenAndServe() 立即返回错误、连接被 reset;或完全忽略信号,靠 K8s 的 terminationGracePeriodSeconds 强杀。
- 使用
server.Shutdown()(Go 1.8+),它会阻塞直到所有活跃连接完成或超时 - 用
signal.Notify()监听os.Interrupt和syscall.SIGTERM - 启动 HTTP server 时建议用
server.Serve(lis)而非server.ListenAndServe(),便于复用 listener 并提前关闭 - 超时时间建议设为略小于 K8s 的
terminationGracePeriodSeconds(如后者是 30s,这里设 25s)
K8s 中 readinessProbe 与 preStop hook 如何配合下线
仅靠 Go 层关 server 不够:K8s 的 endpoint controller 可能还没来得及把 Pod 从 Service 的 Endpoints 中摘除,新流量仍会被转发进来。必须让 K8s 主动“先断流、再停服”。
readinessProbe 失败会让 K8s 把 Pod 从 endpoints 列表中移除,但默认行为是 probe 连续失败若干次才触发,存在窗口期;preStop 则是在发 SIGTERM 前同步执行的钩子,可用于立即拒绝新请求。
立即学习“go语言免费学习笔记(深入)”;
- 在
preStop中执行一个快速 HTTP 请求(如curl -X POST http://localhost:/shutdown),让 Go 服务切换到“拒绝新连接”状态 - 同时将
readinessProbe配置为探测同一接口(如/healthz),并在收到 shutdown 信号后立即返回 503 - 注意
preStop默认有 30 秒超时,需确保其执行快于terminationGracePeriodSeconds,否则会被跳过 - 不要在
preStop中执行耗时操作(如等待 DB 连接池 drain),应由 Go 主程序自己处理
HTTP Server 关闭时如何等待长连接(如 WebSocket、流式响应)
http.Server.Shutdown() 默认只等待普通 HTTP 连接关闭,对升级后的 WebSocket 连接或 text/event-stream 响应不感知——它们底层是复用的 TCP 连接,但 net/http 不跟踪其生命周期。
这意味着即使调用了 Shutdown(),只要还有活跃的 WebSocket 客户端,这些连接不会自动关闭,也**不会计入 Shutdown 的等待逻辑**,可能导致超时强制退出、连接异常中断。
- 需自行维护活跃连接列表(如用
sync.Map存储*websocket.Conn) - 在 upgrade 后添加连接,在
Close()或ReadMessage()返回 error 时删除 - 在 shutdown 流程中遍历该 map,调用每个 conn 的
Close(),并等待 goroutine 退出 - 若使用第三方 WebSocket 库(如
gorilla/websocket),注意其WriteDeadline和 ping/pong 设置,避免 close 时卡住
为什么 livenessProbe 不能替代优雅下线逻辑
有些团队误以为只要配置了 livenessProbe,K8s 就会在下线前“健康检查失败 → 重启 Pod”,从而绕过优雅关闭。这是危险误解:livenessProbe 失败触发的是 **kill + restart**,不是 stop + cleanup;而且失败判定有延迟,无法控制下线节奏。
更严重的是,若在 shutdown 流程中主动让 livenessProbe 返回失败(比如删掉 /healthz handler),K8s 可能在 Go 还没关完 server 时就 kill 掉进程,造成双重中断。
-
livenessProbe只用于检测崩溃,不参与发布/缩容流程 -
readinessProbe才是控制流量进出的开关,必须和 shutdown 状态联动 - 两者 probe path 应分离:
/healthz(liveness)只检查进程存活;/readyz(readiness)检查依赖(DB、cache)+ 是否处于 shutdown 模式 - 切勿在 shutdown 中修改 livenessProbe 行为,否则可能引发级联驱逐
真正难的不是写几行 signal 处理代码,而是把「连接生命周期管理」「K8s 探针语义」「应用内部资源释放(DB 连接池、gRPC client、后台 goroutine)」三者串成一条无竞态、可观察、可测试的链路。漏掉任意一环,都可能在线上出现 5xx 爆增或连接重置。










