/healthz和/readyz必须分开:前者仅检查进程存活态(如goroutine数、HTTP监听状态),后者专检业务就绪态(DB/Redis/gRPC连通性);混用会导致滚动更新卡死(因依赖检查阻塞readiness)或误重启(因liveness超时失败)。

为什么/healthz和/readyz必须分开写
混用会导致滚动更新卡死或误重启。Kubernetes的livenessProbe只关心进程是否卡死(比如goroutine泄漏、死锁),而readinessProbe决定能否收流量(比如DB连得上、Redis响应快、下游gRPC已连接)。
-
/healthz里只做轻量快照:检查http.Server是否还在Accept、runtime.NumGoroutine()是否突增、关键chan是否阻塞——不碰任何外部依赖 -
/readyz才调db.PingContext(ctx, 2*time.Second)、redis.Ping(ctx).Err()、grpcConn.GetState() == connectivity.Ready - 别在
/readyz里reload配置或预热缓存——这些该在main()启动阶段完成,否则探针一来就卡住
健康检查结果必须缓存,不能每次请求都真实连一次
高频探针(如每2秒一次)下,每次db.Ping会打爆数据库连接池,并让K8s因超时(默认1秒)反复kill进程。
- 启动一个后台goroutine,用
time.Ticker按固定间隔(如5秒)执行全量依赖检查 - 结果存进线程安全结构体,用
sync.RWMutex保护读写 -
/readyzhandler只读快照,不触发新连接——避免脉冲压力和goroutine泄漏 - 缓存结构体至少包含
status("UP"/"DEGRADED"/"DOWN")、timestamp、各依赖明细(如{"db": "OK", "redis": "failed"})
怎么让Kubernetes正确使用liveness和readiness
K8s探针配置不对,等于白写健康接口。常见错误是把两个probe都指向同一个路径,或者timeoutSeconds设得太短。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
livenessProbe用/healthz,initialDelaySeconds: 10,timeoutSeconds: 1,失败后重启容器 -
readinessProbe用/readyz,initialDelaySeconds: 15(给DB连接池warmup留时间),timeoutSeconds: 3,失败时从Service Endpoint列表剔除 - 务必设置
periodSeconds: 5和failureThreshold: 3,避免网络抖动误判 - 所有依赖探测必须带context超时:
context.WithTimeout(context.Background(), 1500*time.Millisecond),总耗时控制在2s内
滚动更新卡住的真正原因常是goroutine没退出
滚动更新时,Kubernetes先启动新Pod,等其readinessProbe通过后,再终止旧Pod。若旧服务里有未退出的goroutine(比如未关闭的time.Ticker、未close()的channel监听、或阻塞在http.Serve()上的server),SIGTERM信号发过去后进程不会退出,导致更新超时失败。
- 务必在
main()中监听os.Interrupt和syscall.SIGTERM,调用srv.Shutdown()而非srv.Close() - 所有长期运行的goroutine都应接收
context.Context并主动检查ctx.Done(),例如:for { select { case - 别在handler里
new redis.Client或sql.DB实例——复用服务启动时已初始化的全局client,否则每次探针都建连接,fd很快耗尽
最易被忽略的是:健康检查端点必须用独立的http.ServeMux注册,和业务路由完全隔离。中间件(比如Auth、Logger、Recovery)一旦拦截/readyz,探针就拿不到真实状态,K8s会持续摘流量或误杀Pod。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










