
滚动更新时 Pod 还在处理请求就被终止了
根本原因是 terminationGracePeriodSeconds 默认只有 30 秒,而你的 HTTP 服务没来得及 drain 连接。K8s 发送 SIGTERM 后,只要超时或进程退出,就立刻发 SIGKILL 强杀。
Go 程序必须自己监听 SIGTERM,并阻塞 shutdown 流程直到活跃请求完成。别依赖 http.Server.Shutdown() 自动等——它只等正在处理的 handler 返回,不等长连接、流式响应或未完成的 write。
- 在
main()开头启动 signal.Notify 监听os.Interrupt和syscall.SIGTERM - 收到信号后,调用
server.Shutdown(),但要配合context.WithTimeout()控制最大等待时间(比如 45 秒) - 务必在 handler 内部检查
ctx.Done(),尤其是涉及数据库查询、RPC 调用或文件写入的路径 - 避免在 handler 里启 goroutine 后直接返回——这会让
Shutdown()认为请求已结束,实际后台还在跑
Readiness Probe 配置不当导致新 Pod 提前被流量打爆
很多团队把 readinessProbe 设成和 livenessProbe 一样简单地 ping HTTP 端口,结果新 Pod 一启动就进 Service Endpoints,但 DB 连接池还没建好、配置还没加载完、缓存还没预热。
Readiness 不是“进程活着”,而是“准备好收请求”。它必须反映真实业务就绪状态。
- Probe 路径建议独立为
/readyz,和/healthz分开 - 在 handler 里检查关键依赖:DB ping、Redis 连通性、必要 config 文件是否存在、gRPC 后端是否 ready
- 设置
initialDelaySeconds: 10,避免容器刚起来就狂 probe;periodSeconds: 5足够,太频繁增加负载 - 别用
exec执行 curl 或 wget —— 容器里可能没装,且引入 shell 启动开销
Deployment 的 maxSurge/maxUnavailable 没对齐业务吞吐能力
默认 maxSurge: 25% + maxUnavailable: 25% 在低副本场景下会出问题。比如你只有 2 个 Pod,更新时先启 1 个新 Pod(surge),再删 1 个旧 Pod(unavailable),瞬间变成 2→3→2,但新 Pod 可能还没通过 readiness check,Service 就已把流量切过去。
更危险的是高并发写场景:旧 Pod 正在 flush buffer,新 Pod 还没 load warm data,这时切流等于丢请求或读到脏数据。
- 对写敏感服务,设
maxUnavailable: 0,确保旧 Pod 全退完再启新 Pod(滚动变蓝绿) - 对读多写少、有缓存的服务,可设
maxSurge: 1+maxUnavailable: 0,平衡速度与安全 - 永远不要设
maxSurge: 0—— 那会卡住整个 rollout,新 Pod 启不来 - 用
kubectl rollout status deploy/<code> 实时观察,别只看 <code>READY列
Go 里没关掉 HTTP/2 Server Push 或 Keep-Alive 导致连接残留
Go 1.12+ 默认启用 HTTP/2,而 HTTP/2 的 server push 和长连接复用机制,在 Shutdown() 时不会自动中断所有 stream。客户端可能还在发 DATA frame,服务端却已 close listener。
现象是:日志显示 shutdown 已完成,但 netstat -an | grep :8080 仍看到大量 TIME_WAIT 或 ESTABLISHED 连接,甚至偶发 connection reset。
- 显式禁用 HTTP/2:启动 server 前加
server.TLSConfig = &tls.Config{NextProtos: []string{"http/1.1"}} - 缩短 keep-alive:设
server.ReadTimeout = 30 * time.Second、WriteTimeout = 30 * time.Second、IdleTimeout = 60 * time.Second - 如果必须用 HTTP/2,要在
Shutdown()前主动调用server.Close()并等待所有 active connections 关闭(需自己维护 conn map) - 用
curl -v --http1.1 http://pod-ip:port/测试,确认没走 h2
事情说清了就结束。真正难的不是写那几行 shutdown 代码,而是把 readiness 的判断逻辑和业务生命周期对齐——比如配置中心变更、缓存重建、分片路由表加载,这些都得变成 /readyz 里的可测点。漏掉一个,滚动更新就可能变成线上抖动。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











