Go服务/readyz端点必须返回真实就绪状态,否则流量涌入导致panic;根本原因是initialDelaySeconds设为0、periodSeconds过小或handler未用原子变量缓存状态,造成Kubernetes探测时依赖尚未初始化完成。

Go 服务的 /readyz 端点必须返回真实业务就绪状态,否则流量一来就 panic —— 这不是探针配置问题,是逻辑缺陷。
就绪探针为什么总返回 200 却收不到流量?
根本原因不是 Kubernetes 没识别到响应,而是 readinessProbe 配置和 Go handler 之间存在「时间差漏洞」:Pod 启动后,Kubernetes 立即开始探测(initialDelaySeconds: 0 默认值),但此时 DB 连接可能刚建一半、gRPC client 还没注册成功、配置热加载仍在进行。
常见表现:kubectl get pods 显示 Running,kubectl get endpoints 却始终为空或延迟数分钟才出现 IP。
- 检查
readinessProbe.initialDelaySeconds是否设为 0 —— 对 Go HTTP 服务,至少设为5,给http.ListenAndServe和依赖初始化留出缓冲 - 确认
readinessProbe.periodSeconds不小于 handler 实际耗时;若db.PingContext平均耗时 800ms,periodSeconds: 2就容易触发误判 - 用
kubectl describe pod <name>查看 Events,重点找Readiness probe failed后是否紧跟着ContainerStatusChanged,这说明探针在反复失败/恢复
Go 的 /readyz handler 怎么写才不翻车?
不能只返回 200 OK,也不能每次请求都重连 DB —— 前者导致流量误入,后者压垮数据库。
立即学习“go语言免费学习笔记(深入)”;
核心原则:状态可变、检查轻量、超时可控。
- 用
sync.RWMutex+ 全局isReady bool变量缓存就绪状态,/readyzhandler 只读不查 - 启动时异步执行依赖检查(DB、Redis、下游 gRPC),成功后原子更新
isReady = true;失败则保持false并打日志 - 所有外部调用必须套
context.WithTimeout(r.Context(), 1500*time.Millisecond),超时值建议设为timeoutSeconds的 60%~80% - 收到
SIGTERM时立刻设isReady = false,避免新流量进入;不要等 handler 执行完再改状态 - 返回码严格限定:就绪 →
200,未就绪 →503 Service Unavailable(别用404或500)
readinessProbe YAML 参数怎么配才稳?
参数之间有强耦合,单改一个容易引发连锁反应。比如把 failureThreshold 设成 3,但 periodSeconds 是 2,等于允许连续 6 秒失败才摘流量 —— 对高敏感服务太长了。
-
timeoutSeconds至少设为5:Go 中一次 DB Ping 可能卡住 3~4 秒,设1就注定失败 -
periodSeconds推荐10:太小(如2)会让 kubelet 频繁发请求,尤其多副本时易触发应用限流 -
failureThreshold设为1最安全:只要一次失败,立即从 Endpoints 移除,避免脏流量漏入 - 别依赖
exec或tcpSocket:对 Go HTTP 服务,tcpSocket只确认端口监听,不确认路由层就绪;exec在容器里curl localhost:8080/readyz可能因 DNS/CNI 未 ready 而失败
为什么加了就绪探针,缩容时还是有请求进来?
因为 Kubernetes 不会主动通知 Service “这个 Pod 要下线了”,它只靠探针轮询发现状态变化。如果探针间隔是 10s,而 preStop 里 sleep 2s 再发 curl /readyz?down=true,那中间最多有 10 秒窗口期继续收流量。
-
preStop必须做两件事:先设isReady = false,再sleep等待残留请求完成(通常 10~30s) - 确保
terminationGracePeriodSeconds≥preStop中 sleep 时间 + 预估最长请求耗时 - 在 handler 里加
if isShuttingDown { http.Error(w, "", http.StatusServiceUnavailable); return },双重保险 - 验证方式:缩容时用
while true; do curl -s -w "%{http_code}\n" -o /dev/null http://svc/health; sleep 0.5; done观察状态切换是否及时
最常被忽略的一点:就绪状态不是二元开关,而是需要与依赖生命周期对齐的动态信号 —— 它既不能太激进(导致频繁断流),也不能太保守(导致请求失败)。写死的 200 和裸写的 db.Ping() 都是半成品。


















