Kubernetes健康检查失败主因是Go服务未适配外部探测模型,需用独立http.ServeMux暴露/healthz和/readyz端点,绕过业务中间件,严格返回2xx状态码,并为依赖调用设置显式超时。

Go服务在Kubernetes里健康检查失败,90%不是探针配错了,而是Go代码没对齐K8s的外部探测模型——它不看你有没有log,只认HTTP状态码、TCP连通性或exec退出码。
为什么/healthz返回200但K8s还在杀Pod
常见现象是:curl localhost:8080/healthz能拿到{"status":"ok"},但livenessProbe仍反复触发重启。根本原因是K8s探针和你的HTTP handler之间存在“中间层干扰”:
- 用了Gin/Echo等框架的全局中间件(比如JWT校验、日志拦截),导致
/healthz被重定向、加Header、甚至超时卡住 - handler里调了
db.Ping()但没设上下文超时,DB短暂抖动就阻塞整个探针请求 - 返回了302或503,而K8s只接受200–399范围内的成功响应(严格来说,只有2xx才算success)
- 用
http.Redirect把/healthz转到/api/health,但K8s不会跟重定向
必须用原生net/http单独暴露探针端点
别把/healthz塞进主路由树。正确做法是起一个独立的http.ServeMux,绑定到同一端口的子路径,绕过所有业务中间件:
go
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
if atomic.LoadInt32(&isReady) == 0 {
http.Error(w, "not ready", http.StatusServiceUnavailable)
return
}
// 只查轻量依赖:如redis.PingContext(ctx, 500*time.Millisecond)
if err := redisClient.Ping(ctx).Err(); err != nil {
http.Error(w, "redis unavailable", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ready"})
})
<p>srv := &http.Server{Addr: ":8080", Handler: mux}</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8661" title="Golang Uber Dig"><img
src="https://img.php.cn/upload/skill/000/000/081/179215391842073.jpg" alt="Golang Uber Dig" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8661" title="Golang Uber Dig" class="overflowclass">Golang Uber Dig</a>
<p class="overflowclass">使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等</p>
</div>
<a rel="nofollow" href="/xiazai/skill8661" title="Golang Uber Dig" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 不要用
router.GET("/healthz", ...),哪怕你用的是Echo或Gin -
/healthz只做进程存活检查(比如atomic.LoadInt32(&healthy)),不查DB、不查下游 -
/readyz才查真实依赖,且每个依赖必须带明确超时(timeoutSeconds - 1) - 两个端点共用同一个
http.ServeMux,避免额外监听端口或goroutine泄漏
livenessProbe和readinessProbe参数怎么设才不打架
配错参数比没写探针更危险。典型翻车场景:DB连接池卡住2秒,periodSeconds=5 + failureThreshold=1 → 每次失败立刻重启,形成雪崩。
-
initialDelaySeconds:Go程序启动慢(比如加载大配置、初始化gRPC client),livenessProbe至少设30,readinessProbe可设10,但不能小于应用真正就绪时间 -
timeoutSeconds:必须比探针handler内最慢依赖的超时多1秒,比如redis.PingContext(ctx, 900*time.Millisecond),这里就设1 -
failureThreshold:设为3或5,容忍瞬时抖动;periodSeconds建议10~15,别低于5 - 绝对不要让两个探针指向同一个handler:它们语义不同,混用等于放弃流量控制能力
SIGTERM来了,怎么让readinessProbe主动失效
K8s发SIGTERM后,你的服务得先拒绝新流量,再等老请求结束。靠readinessProbe返回非200就能自动从Endpoints摘掉实例:
- 声明一个原子变量:
var isReady int32 = 1 - 在
SIGTERM信号处理里执行:atomic.StoreInt32(&isReady, 0) -
/readyzhandler开头加判断:if atomic.LoadInt32(&isReady) == 0 { http.Error(...); return } - 别依赖
http.Server.Shutdown()来控制就绪态——它只管关闭server,不改探针行为
最容易被忽略的一点:Go里没有“动态修改探针配置”的API。K8s永远按YAML里写的路径和参数去调,所以探针逻辑必须全部实现在handler内部,靠原子变量、context取消、显式错误返回来驱动行为变化。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










