livenessProbe 仅是重启触发器而非自动修复,需 Go 程序自行定义轻量无依赖的 /healthz 端点,并用 context 超时与 panic 恢复兜底,避免探针误判。

livenessProbe 不是“自动修复”,只是重启触发器
Kubernetes 的 livenessProbe 本身不会修你的 Go 程序,它只做一件事:发现不健康就杀掉容器,让 Kubelet 按 restartPolicy(通常是 Always)拉起新实例。真正的“健康”必须由 Go 程序自己定义和暴露——否则探针再准,也只会反复重启一个根本没初始化完、或卡在死锁里的进程。
常见错误现象:
- Pod 启动几秒后就被
Kill,日志里看不到 panic,但kubectl describe pod显示Liveness probe failed - 探针返回 200,但业务 HTTP server 根本没起来,流量 503 —— 因为
livenessProbe和业务端口/路由没对齐 - 用
/readyz当livenessProbe目标,DB 一抖就重启,把短暂抖动升级成服务雪崩
实操建议:
-
livenessProbe应指向轻量、无外部依赖的端点,比如/healthz,只检查 goroutine 主循环是否存活、内存未 OOM、关键 channel 未阻塞 - 不要在 handler 里调
db.Ping()或redis.Client.Ping()—— 这属于readinessProbe职责 - 超时时间
timeoutSeconds必须大于 handler 内部context.WithTimeout的设定(推荐设为 probe timeout 的 60%),避免 K8s 先超时杀进程,而 Go 还在等 context cancel
Go 代码里怎么写 /healthz 才不被 probe 杀掉
默认 http.HandleFunc 没有超时控制,也没 recover,一次慢查询或 panic 就会让整个进程退出,K8s 探针收不到响应,直接判定失败。这不是配置问题,是 handler 写法没兜住。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 用
context.WithTimeout(r.Context(), 1*time.Second)包裹所有可能耗时操作,哪怕只是select判断主 goroutine 是否还在运行 - 加
defer func() { if r := recover(); r != nil { http.Error(w, "panic", http.StatusInternalServerError) } }(),防 handler panic 导致进程崩溃 - 别在
/healthzhandler 里启新 goroutine 并立即返回 —— 探针不等你后台做完,只看 HTTP 响应码和 body - 示例精简写法:
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 800*time.Millisecond)
defer cancel()
defer func() {
if r := recover(); r != nil {
http.Error(w, "panic", http.StatusInternalServerError)
return
}
}()
select {
case <-ctx.Done():
http.Error(w, "timeout", http.StatusServiceUnavailable)
return
default:
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
})
initialDelaySeconds 设太小,等于亲手把 Pod 杀了
Go 应用冷启动常要加载配置、连 DB、预热缓存,这些不是瞬间完成的。而 livenessProbe 默认从容器 Started 那一刻就开始倒计时。如果 initialDelaySeconds 设成 5 秒,但你的 DB 连接实际要 8 秒,那 probe 在第 5 秒就失败,Kubelet 第 6 秒杀 Pod —— 你永远等不到第 8 秒的成功。
使用场景:
- 简单 HTTP API(无依赖):
initialDelaySeconds: 10 - 带 DB + Redis + gRPC client 初始化的服务:
initialDelaySeconds: 30起步,上线前用time curl -o /dev/null -s -w "%{http_code}\n" http://localhost:8080/healthz实测冷启动时间再加 2–3 秒缓冲
性能影响:
- 设太大:Pod 起来后长时间不接受流量(如果 readiness 也依赖同个延迟),资源闲置
- 设太小:滚动更新卡住、Pod 处于
CrashLoopBackOff状态,监控告警狂响
注意:periodSeconds 别设太密(如 2 秒),高频 probe 会增加 Kubelet 负载,也放大瞬时抖动误判;推荐 10,配合 failureThreshold: 3,即连续 30 秒失败才重启。
为什么不能复用 /readyz 当 livenessProbe 目标
/readyz 是给流量调度用的,语义是“我准备好接请求了吗?”;/healthz 是给进程生命周期用的,语义是“我还活着吗?”。混用会导致两个严重后果:一是外部依赖故障(如 DB 临时不可用)触发不必要的 Pod 重启;二是掩盖真正致命问题(比如 goroutine 泄漏导致 CPU 100%,但 DB 还通,/readyz 一直返回 200)。
实操建议:
-
livenessProbe→/healthz:只查进程级状态,例如原子变量atomic.LoadInt32(&live)、主事件循环 channel 是否可 send、runtime.NumGoroutine() 是否异常飙升 -
readinessProbe→/readyz:查依赖链,但必须加严格超时(≤ 500ms),且失败只摘流量,不重启 - 两者监听同一端口、同一
http.Server实例 —— 如果你开了独立健康 server(如 :8081),务必确认readinessProbe的port字段填的是业务端口,不是健康端口,否则 K8s 认为“就绪”,其实 endpoint 根本没注册到 Service
最容易被忽略的一点:Go 程序收到 SIGTERM 后,得先 atomic.StoreInt32(&isReady, 0),再 sleep 几秒让 Endpoint Controller 摘掉实例,最后才 srv.Shutdown()。否则 K8s 已经把 Pod 从 endpoints 列表里删了,你的 shutdown 还在等请求结束,白白多扛几秒流量。


















