readinessProbe 不能替代自定义健康检查,因其仅控制 Endpoint 加入 Service,不参与 Pod 生命周期决策;需结合 SIGTERM 处理、可降级 /healthz 端点及 http.Server.Shutdown() 实现无损滚动更新。

为什么 readinessProbe 不能直接替代自定义健康检查
因为 readinessProbe 是 kubelet 调用的,只控制 Endpoint 是否加入 Service,不参与 Pod 生命周期决策(比如滚动更新时旧 Pod 的退出时机)。Kubernetes 默认在新 Pod 变为 Ready 后就立即对旧 Pod 发送 SIGTERM,此时即使它还在处理请求,也会被强制终止——这就是“有损”的根源。
真正无损的关键,在于让旧 Pod 主动等待所有活跃连接完成后再退出。这需要两层配合:一是容器内服务自己监听 SIGTERM 并进入“拒绝新请求 + 等待旧请求结束”状态;二是通过一个可编程的健康端点,告诉 Kubernetes “我还没准备好下线”。
-
readinessProbe在 SIGTERM 后应返回 503,让 Service 摘除该实例 - 但仅靠它不够:kubelet 不会因 readiness 变 false 而延迟删除 Pod,它只等
terminationGracePeriodSeconds - 所以必须在程序内实现“优雅关闭触发 → 健康检查降级 → 连接 draining → 退出”闭环
如何用 net/http 实现可切换的 /healthz 端点
Go 标准库的 http.ServeMux 不支持运行时动态替换 handler,硬编码 http.HandleFunc("/healthz", ...) 会导致状态无法同步。推荐用原子变量控制响应逻辑:
var (
isShuttingDown = atomic.Bool{}
)
func healthzHandler(w http.ResponseWriter, r *http.Request) {
if isShuttingDown.Load() {
http.Error(w, "shutting down", http.StatusServiceUnavailable)
return
}
// 其他检查:DB 连通性、依赖服务可达性等
w.WriteHeader(http.StatusOK)
}
- 在
signal.Notify捕获SIGTERM后,立刻调用isShuttingDown.Store(true) - 确保
healthzHandler是注册到主http.Server的,而非另起 goroutine 启动独立 server - 不要在 handler 里加锁或阻塞操作,否则会拖慢 kubelet 探针超时(默认 1s)
怎样让 HTTP Server 真正等待连接关闭再退出
http.Server.Shutdown() 是 Go 1.8+ 提供的优雅关闭机制,但它不会自动等待长连接(如 WebSocket、HTTP/2 流、或未及时 Close 的 TCP 连接)。必须配合 context 和超时控制:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
srv := &http.Server{Addr: ":8080", Handler: mux}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// 收到 SIGTERM 后
-
srv.Shutdown()会先关闭 listener,不再接受新连接;然后等待已有连接主动 close 或超时 - 如果业务中用了
http.TimeoutHandler或自定义ReadTimeout,要确保它们不早于 Shutdown 超时,否则可能提前中断请求 - 注意:gRPC Server 需单独调用
GracefulStop(),不能复用同一个 http.Server
Kubernetes Deployment 配置里哪些字段影响无损效果
光写对 Go 代码还不够,YAML 中几个参数错配会让整个流程失效:
-
terminationGracePeriodSeconds: 45—— 必须 ≥ Go 代码里Shutdown的 context timeout(比如上面的 30s),留出缓冲 -
readinessProbe.initialDelaySeconds设太小(如 1)可能导致 Pod 还没完全初始化就被认为 Ready,进而触发旧 Pod 终止 -
minReadySeconds: 10在 RollingUpdate 策略中很重要:新 Pod 必须连续 10s Ready 才算“稳定”,才能开始删旧 Pod -
strategy.rollingUpdate.maxSurge: 1和maxUnavailable: 0组合能保证“先扩后缩”,避免流量缺口
最容易被忽略的是:如果你用了 Istio 或其他 service mesh,它的 sidecar 注入和健康检查默认行为可能覆盖你写的 /healthz,得额外配置 readinessProbe.httpGet.path 指向你的端点,并确认 mTLS 不拦截该路径。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










