Kubernetes健康检查必须区分/livez和/readyz:/livez仅做轻量快照(如goroutine数、HTTP服务状态),严禁查DB/Redis以防超时重启;/readyz需并发探测依赖并缓存结果,返回200及JSON状态体,确保流量精准路由。

Go微服务健康检查不是“返回200就行”,而是必须区分 /livez 和 /readyz,否则 Kubernetes 滚动更新会卡死、Consul 会持续转发故障流量。
为什么 /livez 不能查数据库和 Redis
liveness 探针只负责判断进程是否卡死,比如 goroutine 泄漏、死锁、HTTP server 停止 Accept。一旦它去连 DB 或 Redis,就可能因下游超时(哪怕设了 1s)触发反复重启——Kubernetes 默认 10 秒探测一次,但超时后立即重试,连续失败 3 次就 kill Pod。
-
/livez里只做快照:调runtime.NumGoroutine()(阈值建议 ≤ 5000)、检查关键 channel 是否阻塞、确认http.Server还在运行 - 绝对不调
db.Ping()、redis.Ping()、grpcConn.GetState() - 不带任何 context 超时控制,因为本意就是“快”;耗时超过 200ms 就该怀疑进程本身有问题
/readyz 必须并发探测依赖且缓存结果
readiness 探针决定能否收流量,所以必须验证 DB、Redis、gRPC 等真实连通性。但每次请求都真实拨号会打爆连接池、触发雪崩——高频探针(如每 2 秒一次)下,db.Ping() 可能瞬间建立数百个空闲连接。
- 用
time.Ticker启后台 goroutine,每 5 秒执行一次全量依赖探测 - 结果存进线程安全结构体,用
sync.RWMutex保护读写 -
/readyzhandler 只读缓存快照,不发起新连接 - 每个依赖必须独立超时:
db.PingContext(ctx, 2*time.Second)、redis.Ping(ctx).Err()、grpcConn.WaitForStateChange(ctx, connectivity.Ready)
HTTP 健康端点返回什么状态码和响应体
Kubernetes、Consul、Nacos 全部只认 200 为健康,503 不是“降级”,而是“直接剔除实例”。返回 JSON 时若缺 Content-Type,某些 Ingress(如 Traefik)会截断响应,导致探针判定失败。
- 状态码必须是
200,永远不要用503表示依赖异常 - 响应体至少含
status(值为"UP"或"DEGRADED")、timestamp、service - 必须设置
w.Header().Set("Content-Type", "application/json; charset=utf-8") - 别用
log.Fatal或 panic 处理探测中的错误——应捕获并写入components字段
TCP 层探活比 HTTP 更准,但不能替代 /readyz
HTTP 探针要走完整 TLS 握手 + HTTP 解析,容易被反向代理拦截或超时;而 net.DialContext 直连只要三次握手成功,就能确认节点网络可达。但它只能说明“TCP 层活着”,无法验证业务逻辑、DB 连接池、gRPC 认证等。
- 对下游服务做 TCP 心跳时,用
ctx, cancel := context.WithTimeout(context.Background(), 600*time.Millisecond) - 连接成功后立刻
conn.Close(),不发任何数据 - 失败不重试,但需去抖:连续 3 次失败且最近一次在 1 秒内才标
Down - TCP 探活适合做 sidecar 或监控 agent 的前置探测,不能代替
/readyz中的语义级检查
最常被忽略的是缓存探测结果的时间粒度和去抖逻辑——没缓存,探针频率一高就拖垮依赖;没去抖,网络抖动就会误判服务下线。

















