/livez仅检查进程存活,禁用外部调用;/readyz需缓存依赖探测结果;HTTP服务器须设读写超时;探针路径、端口、响应体须严格规范。

/livez 必须只查进程自身状态,禁止任何外部调用
只要 /livez 里出现一次 db.PingContext、http.Get 或 os.ReadFile,就等于给 Kubernetes 埋了个定时重启开关。它不判断“服务能不能干活”,只回答“这个 Go 进程是不是还活着”。常见错误是把 /livez 和 /readyz 写成同一个 handler,或者在 /livez 中加了日志中间件、JWT 鉴权逻辑——这些都会拖慢响应或引入 panic 风险。
正确做法是用裸 http.HandleFunc 注册,绕过所有中间件和业务包:
- 检查
runtime.NumGoroutine()是否异常(比如 > 1000) - 读取原子变量
atomic.LoadInt32(&isRunning),该变量由主 goroutine 初始化后置 1 - 用
select+time.After(10ms)防死锁,超时直接返回 500 - 响应体必须非空:
w.Write([]byte("ok")),部分 kubelet 版本会把空 body 当失败 - 绝不记录日志,也不调
log.Printf——高频探针日志会撑爆磁盘 I/O
/readyz 要并行探测依赖,但结果必须缓存
/readyz 的目标不是“此刻连得上 DB 吗”,而是“此刻能不能安全接流量”。如果每次请求都实时执行 db.PingContext,连接池会被轮询打爆,DB 本身也可能被压垮。更糟的是,短暂网络抖动会导致 readiness 瞬间失效,Service endpoint 被摘掉又马上加回,引发客户端 503 波动。
真正可靠的做法是后台定期探测、前台只读快照:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 启动时起一个 goroutine,每 5 秒调一次
db.PingContext+redis.Ping+grpcConn.Ready(),共用同一个context.WithTimeout(ctx, 300 * time.Millisecond) - 结果写入
sync.RWMutex保护的结构体,如type readinessState struct { DB bool; Redis bool; GRPC bool } -
/readyzhandler 只做mu.RLock()读值,任一为false就返回http.StatusServiceUnavailable - 避免在 handler 里做任何阻塞操作,包括
time.Sleep、无缓冲 channel 发送、未设 timeout 的 HTTP 调用
HTTP Server 配置必须显式设超时,不能依赖默认
Go 默认的 http.Server 没有设置 ReadTimeout 和 WriteTimeout,一旦遇到 GC STW 或内核收包延迟,/livez 响应可能卡住超过 1 秒——而 Kubernetes livenessProbe 默认超时就是 1 秒,触发重启后形成 CrashLoopBackOff。
必须手动配置:
- 使用
http.NewServeMux()替代http.DefaultServeMux,避免其他包意外注册路由污染 - 显式初始化 server:
&http.Server{Addr: ":8081", Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second} - 监听端口要和 Kubernetes
livenessProbe.httpGet.port严格一致,否则探针永远 404 - 若服务同时暴露业务端口和健康端口,确保健康端口不被防火墙或 Service 的
port/targetPort映射错位
initialDelaySeconds 和 failureThreshold 需匹配 Go 启动行为
Go 服务冷启动常需加载大配置、建立 gRPC 连接、预热缓存,若 livenessProbe.initialDelaySeconds 设太小(如 5s),而程序实际 12s 才完成初始化,Kubernetes 就会在它还没 ready 时就 kill 掉容器。
合理配比的关键是看真实启动耗时:
- 在 main 函数开头打时间戳,init 完毕后 log 启动耗时,取 P95 值作为
initialDelaySeconds下限 -
livenessProbe.failureThreshold至少设为 3,配合periodSeconds: 10,容忍单次 GC 或短时调度延迟 -
readinessProbe的initialDelaySeconds可略小于 liveness(如 8s),但绝不能小于主服务真正就绪的时间点 - 两个探针路径必须不同:
livenessProbe.httpGet.path: "/livez",readinessProbe.httpGet.path: "/readyz",混用会导致语义混乱
最易被忽略的一点:健康端点的响应体内容类型和非空性。即使只是返回 "ok",也要显式设 Content-Type: text/plain,且不能只调 w.WriteHeader(200) 就结束——某些旧版 kubelet 会因 body 为空判定探针失败。

















