/healthz必须用http.HandleFunc直注册而非框架路由,因其需绕过日志、鉴权等中间件以确保≤1秒响应;混入中间件易超时触发K8s误重启,且语义上/livez与/readyz须分离——前者仅查进程状态,后者可检查DB等依赖。

为什么 /healthz 必须用 http.HandleFunc 而不是中间件
很多 Go 服务用了 Gin、Echo 或自定义中间件链,结果把 /healthz 套进日志、鉴权、panic recover 里——这会让探针变慢、被拦截,甚至返回 500。Kubernetes 的 livenessProbe 默认超时 1 秒,中间件多走一圈就容易超时重启。
真正可靠的写法是绕过所有中间件,用标准库直注册:
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
- 不要用
router.GET("/healthz", handler)(Gin)或e.GET("/healthz", handler)(Echo) - 不要在 handler 里调
ctx.Abort()或ctx.Next(),这些会触发中间件执行 - 如果主服务必须用框架,单独起一个
http.Server绑定健康端口(如 :8081),完全隔离
livez 和 readyz 怎么分开写才不互相干扰
共用一个 /healthz 是最常见错误:DB 暂时连不上,livenessProbe 就失败 → Pod 重启 → 再连不上 → 无限 CrashLoopBackOff。必须语义分离。
-
/livez:只检查进程是否卡死。例如用select { case 检测主 goroutine 是否响应 -
/readyz:可查 DB、Redis、配置加载状态,但必须设 context 超时 ≤500ms,失败只返回503,不杀进程 - 两个端点必须监听同一端口(K8s 不支持跨端口探针),且路径不能拼错——
path: /livez对应http.HandleFunc("/livez", ...)
探针配置参数怎么填才不踩坑
Kubernetes 探针不是“设了就行”,几个关键字段配错会导致滚动更新卡住、Pod 频繁重启或流量误切。
立即学习“go语言免费学习笔记(深入)”;
-
initialDelaySeconds:Go 程序启动慢(比如加载大配置、初始化 gRPC client),livenessProbe至少设15,readinessProbe设10,但不能比实际就绪时间短 -
timeoutSeconds必须 periodSeconds:若设timeoutSeconds: 2+periodSeconds: 5,DB 抖动 2.1 秒就会失败;建议统一用1或2 -
failureThreshold别设成1:网络抖动一次就摘流量或重启太激进,3~5更稳 - 别让两个探针指向同一个 handler:它们的 HTTP path、port、scheme 必须严格匹配代码中注册的路径
原子变量控制就绪状态为什么不能少
收到 SIGTERM 后立刻关掉 /readyz,才能让 Kubernetes 在 优雅终止前先摘掉流量。靠硬编码返回 200 是静态的,没法动态响应生命周期事件。
- 定义全局原子变量:
var isReady int32 = 1 -
/readyzhandler 中加判断:if atomic.LoadInt32(&isReady) == 0 { http.Error(w, "shutting down", http.StatusServiceUnavailable); return } - 主逻辑收到信号后:
atomic.StoreInt32(&isReady, 0),再 sleep 等待活跃请求完成 - 注意:不要用普通 bool 或 int,goroutine 并发读写会竞态
/livez 和 /readyz 的语义边界始终清晰——哪怕业务逻辑里加了一个新依赖,也要立刻决定它该放进哪个探针,而不是全塞进 /healthz 然后祈祷别崩。


















