srv.Shutdown() 必须与 readiness 下线同步触发,否则会丢请求或被 LB 继续转发流量;因其不感知 K8s 生命周期,仅调用会导致 LB 仍发请求、连接被立即中断、资源无法释放。

srv.Shutdown() 必须和 readiness 状态下线同步触发,否则会丢请求或被 LB 继续转发流量。
为什么只调 srv.Shutdown() 不够
Go 的 http.Server 默认不感知 Kubernetes 的生命周期——它不会自动响应 SIGTERM、不会等 readiness 摘流、也不会主动通知依赖组件退出。只调 srv.Shutdown() 会导致两个问题:
• 负载均衡器还在往这个实例发新请求(因为 readiness 端点仍返回 200)
• srv.Shutdown() 默认不设 IdleTimeout,连接可能被立即中断,正在处理的请求直接失败
• RegisterOnShutdown() 回调不会自动运行,数据库连接池、gRPC 客户端、消息消费者等资源无法释放
readiness 状态必须原子切换且立刻生效
readiness 下线不是“等几秒再关”,而是收到信号后立刻让端点返回非 200。Kubernetes 的 Endpoint Controller 通常在 1–3 秒内摘除实例,延迟取决于 probe 配置(periodSeconds 和 timeoutSeconds)。
• 用 atomic.StoreInt32(&isReady, 0) 控制状态,避免锁或 channel 阻塞 handler
• readiness handler 中直接 if atomic.LoadInt32(&isReady) == 0 { http.Error(w, "not ready", http.StatusServiceUnavailable); return }
• 不要加日志、中间件、鉴权——这些都可能拖慢响应,导致 K8s 探针超时误判
• 确保 readinessProbe 的 path 和代码中注册路径完全一致(比如都是 /readyz)
srv.Shutdown() 前后必须配齐三件事
优雅停机不是单个函数调用,而是一组必须同时满足的条件:
• 设置 srv.IdleTimeout(推荐 30s),否则默认为 0,Shutdown() 会立即返回,不等活跃连接结束
• 在 signal.Notify() 收到 SIGTERM 后,先切 readiness 状态,再调 srv.Shutdown(),顺序不能反
• 用 srv.RegisterOnShutdown() 注册清理逻辑,比如 db.Close()、consumer.Stop()、grpcClient.Close(),但不要在里面做耗时操作(如等待消息队列积压清空)
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
最容易被忽略的:探针配置与代码行为必须对齐
Kubernetes 不会读你代码注释,它只信探针返回的状态码和耗时。
• 如果 readinessProbe 的 timeoutSeconds: 1,但你的 /readyz handler 里做了 DB 查询且没设 ctx 超时,1 秒后就失败,实例被提前摘流
• 如果 livenessProbe 指向 /healthz,但该端点里调了 redis.Ping(),DB 抖动就会触发重启,而不是摘流
• startupProbe 必须覆盖服务真实就绪时间(比如初始化配置+建连+预热缓存),否则 K8s 可能在服务还没 ready 时就杀掉容器重试
立即学习“go语言免费学习笔记(深入)”;

















