必须分开暴露/healthz和/readyz端点:/healthz仅检查进程存活(端口、goroutine、时钟),要求快稳无副作用;/readyz检查服务就绪(DB、Redis等依赖),需异步预热+超时控制;两者均须返回200或503,且探针参数须与代码耗时对齐。

必须分开暴露 /healthz 和 /readyz 两个端点,不能复用同一接口——这是 K8s 健康管理的语义底线。
为什么不能只写一个 /health?
因为 liveness 和 readiness 在 Kubernetes 中触发的动作完全不同:前者失败会杀容器重启,后者失败只是摘流量。如果共用一个接口,只要数据库挂了,liveness 就会误判并反复重启健康进程,形成“假死循环”。
-
/healthz只检查进程是否活着:监听端口是否可用、主 goroutine 是否卡死、系统时钟是否异常 -
/readyz检查服务是否真能干活:DB 连接池是否 ready、Redis 是否可 ping、配置是否加载完毕、gRPC 客户端是否处于READY状态 - 两者都必须返回标准 HTTP 状态码:
200 OK表示通过,503 Service Unavailable表示失败(K8s 默认只认这个)
/healthz 怎么写才安全?
核心原则是「快、稳、无副作用」:不能调外部依赖,不能写日志,不能做任何可能阻塞或超时的操作。它不是诊断工具,而是生死开关。
- 避免在 handler 里调
db.Ping()或redis.Ping()—— 网络抖动会导致误杀 - 可以检查
time.Since(startTime) (防时钟漂移)、runtime.NumGoroutine() > 10000(防 goroutine 泄漏) - 建议加一个轻量心跳标记,比如启动时启动一个 goroutine 定期更新
atomic.Bool,/healthz只读它
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
if time.Since(startTime) < 0 {
http.Error(w, "clock skew", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
/readyz 怎么避免拖慢请求或误判?
就绪检查天然比存活检查重,但依然不能阻塞主线程。常见错误是每次请求都实时连 DB,结果高并发下探针本身变成性能瓶颈。
立即学习“go语言免费学习笔记(深入)”;
- 所有依赖检查必须带
context.WithTimeout,超时建议 ≤ 2s - 用异步预热 + 状态缓存:启动时开 goroutine 轮询 DB/Redis,把结果存在
atomic.Bool或sync.Map中 - 不要在
/readyz里执行SELECT 1,用连接池的Ping()或Stats().OpenConnections更轻量 - 若依赖多个组件,任一失败就立即返回
503,不要等全部检查完
var dbReady = atomic.Bool{}
go func() {
for i := 0; i < 3; i++ {
if err := db.PingContext(context.Background(), 2*time.Second); err == nil {
dbReady.Store(true)
return
}
time.Sleep(1 * time.Second)
}
}()
http.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
if !dbReady.Load() {
http.Error(w, "db not ready", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("ready"))
})
K8s 探针配置参数怎么设才不翻车?
参数不是随便填的,要和你代码里的耗时、服务启动时间对齐。最常踩的坑是 initialDelaySeconds 设太小,导致容器还没初始化完就被 liveness 杀掉。
-
livenessProbe:initialDelaySeconds: 15(给服务足够启动+预热时间),timeoutSeconds: 3(匹配代码中所有超时设置) -
readinessProbe:initialDelaySeconds: 5(早于 liveness 启动,让流量晚点进来),periodSeconds: 5(高频探测,快速摘除异常实例) - 启动慢的服务(如加载大模型、预热缓存),必须加
startupProbe,否则liveness会抢在它活过来之前把它干掉
最容易被忽略的一点:所有健康检查逻辑必须在服务 启动阶段就注册好,而不是等某个 init 函数跑完才挂路由——否则探针在初始化期间看到的是 404,直接判死。










