Kubernetes健康检查生效需严格区分/livez与/readyz、绕过中间件、控制超时并缓存依赖状态;否则即使/healthz秒级返回200,也会因DB调用、JWT拦截、空响应或日志撑爆等导致误判和反复重启。

/healthz 端点本身可以秒级就绪,但“接入 Kubernetes 健康检查”是否真正生效,取决于你有没有绕过中间件、区分 /livez 和 /readyz、控制超时、缓存依赖状态——这些漏掉任何一项,K8s 都可能误判或反复重启。
为什么写个 http.HandleFunc("/healthz", ...) 还是被 K8s 杀掉
常见现象是:本地 curl http://localhost:8080/healthz 返回 200,但 Pod 一直 CrashLoopBackOff。
- K8s 的
livenessProbe不看 body 内容,只认 HTTP 状态码;返回 200 但 handler 里调了db.PingContext(),DB 一抖动就卡住 → 超时 → K8s 杀进程 - 用了 Gin/Echo 却把
/healthz注册在全局中间件之后,结果被 JWT 验证拦截,返回 401 → 探针直接判失败 - 没设
Content-Type或返回空 body,某些 Ingress(如 Nginx)会截断响应,kubelet 收到空包 → 当作失败 - handler 里写了
log.Println("healthz hit"),高频探针撑爆 stdout/stderr → 容器 OOM 或日志卷满
/livez 必须在 1 秒内完成,且不能碰任何外部依赖
这是最常被当“可选优化”忽略的硬约束。K8s 默认 timeoutSeconds: 1,超过即失败。
- 只检查进程自身:用
atomic.LoadInt32(&healthy)、runtime.NumGoroutine()、http.Server.ActiveConn(需提前注册) - 禁用所有 I/O:不调
os.ReadFile、不查redis.Ping()、不发http.Get、不 sleep - 显式写响应体:
w.Write([]byte("ok")),避免空 body 被 kubelet 误判 - 启动时设好 server 超时:
http.Server{ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second}
/readyz 不能每次请求都真实探测依赖
每 10 秒一次的 readinessProbe 若每次都连 DB + Redis + gRPC,不仅延迟高,还会压垮下游。
- 后台用
time.Ticker每 5 秒并发检查一次,结果缓存在sync.RWMutex保护的结构体中 - handler 里只读快照:
if !atomic.LoadBool(&dbHealthy) { w.WriteHeader(http.StatusServiceUnavailable); return } - DB 检查优先走
db.Stats().OpenConnections > 0快速过滤,再执行SELECT 1,ctx 超时严格 ≤ 2s - 非关键依赖(如日志上报服务)失败不参与判定,静默忽略
路由注册和中间件必须绕开
健康端点不是业务接口,它需要“裸奔”——没有日志、没有鉴权、没有 CORS、没有 panic recover。
- 用独立
http.ServeMux,别往http.DefaultServeMux上注册,防其他包冲突 - Gin 中必须
r.GET("/readyz", readyHandler).SkipMiddleware = true(Echo 同理) - 禁止在 handler 里调
viper.GetString或os.Getenv—— 配置加载失败会导致整个探针失败 - 确保路径与 K8s YAML 完全一致:
livenessProbe.httpGet.path: /livez就不能写成/healthz
真正耗时的从来不是写那几行 http.HandleFunc,而是把“进程活着”和“能收流量”拆开、把探测逻辑从请求线程挪到后台、把中间件链彻底砍断——这些细节没对齐,K8s 就永远在猜你是不是还活着。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











