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 写法没兜住。
实操建议:
- 用
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
<h3>initialDelaySeconds 设太小,等于亲手把 Pod 杀了</h3>
<p>Go 应用冷启动常要加载配置、连 DB、预热缓存,这些不是瞬间完成的。而 <code>livenessProbe</code> 默认从容器 <code>Started</code> 那一刻就开始倒计时。如果 <code>initialDelaySeconds</code> 设成 5 秒,但你的 DB 连接实际要 8 秒,那 probe 在第 5 秒就失败,Kubelet 第 6 秒杀 Pod —— 你永远等不到第 8 秒的成功。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6460" title="Golang Naming"><img
src="https://img.php.cn/upload/skill/000/000/081/179094616043400.jpg" alt="Golang Naming" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="overflowclass">Golang Naming</a>
<p class="overflowclass">Go(Golang)命名规范 — 包括包、构造函数、结构体、接口、常量、枚举、错误、布尔值、接收器、getter/setter、函数等。</p>
</div>
<a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<p>使用场景:</p>
- 简单 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 还在等请求结束,白白多扛几秒流量。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










