Kubernetes滚动更新失败主因是探针误用:/livez与/readyz混用导致就绪判断失真,livenessProbe不应检查外部依赖,readinessProbe必须真实反映业务就绪(含DB/Redis/gRPC连通性),且依赖探测需缓存、去抖、设超时。

Go服务健康检查不能只靠/health返回200——它必须区分进程存活和业务就绪,且所有依赖探测必须带超时、去抖、缓存,否则Kubernetes会反复杀Pod,Consul会持续转发故障流量。
为什么/livez和/readyz必须分开写
混用会导致滚动更新卡死或误重启。Kubernetes的livenessProbe只关心进程是否卡死(比如goroutine泄漏、死锁),而readinessProbe决定能否收流量(比如DB连得上、Redis响应快、下游gRPC已连接)。
-
/livez里只做轻量快照:检查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"})
TCP层心跳比HTTP探针更准、更快
HTTP探针要走完整握手+TLS+HTTP解析,延迟高、易被反向代理拦截;而net.Conn直连只要三次握手成功,就能确认节点TCP可达。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 对每个目标节点启独立
goroutine,用net.DialContext(ctx, "tcp", addr),ctx设为600ms超时(覆盖DNS解析+connect全链路) - 连接成功后立刻
conn.Close(),不发任何数据——建连即代表存活 - 失败不重试,但状态变更需去抖:连续3次失败且最近一次在1秒内才标
Down;恢复需连续2次成功且间隔>500ms才标Up - 禁用
http.Client做心跳——它的连接池和重试逻辑会掩盖真实连通性问题
Consul注册后服务始终显示critical的根因和解法
Consul默认认为所有服务“不健康”,除非你显式配health_check字段。没配=永远不进服务发现列表,不是bug,是设计。
- 注册时
Checks字段不能为空,哪怕只加个最简HTTP探针:HTTP: "http://127.0.0.1:8080/readyz"、Interval: "10s"、DeregisterCriticalServiceAfter: "90s" - 别用
TCP: "127.0.0.1:8080"——端口通≠服务可用,DB挂了它照样返回healthy - Client必须连本地Consul Agent(
http://127.0.0.1:8500),别直连远程集群——网络抖动会导致注册失败或健康上报延迟 -
/readyz响应体建议带明细,虽然Consul不消费,但运维排查时能省半小时
真正难的不是写/health接口,而是把依赖状态从“实时探测”变成“受控快照”,把网络判断从“单次成败”变成“去抖稳态”。漏掉任意一环,监控系统看到的就只是绿色假象。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










