就绪探针配错比不配更危险:一次新版本发布流量全打的排查复盘
发布新版本后五分钟内,监控告警群炸了:所有请求的 P99 延迟从 80ms 飙到 3 秒,错误率 40%。回滚之后一切恢复正常。第一反应是代码问题,但对比发布前后的指标,CPU、内存、连接数全都正常。最后定位到的原因很反直觉:新版本的 Pod 明明没准备好接流量,却因为就绪探针配置不当,被 Service 当成了健康实例。这篇复盘讲清楚就绪探针的常见配置错误,以及为什么"配错了"比"没配"更危险。
一、事故链路还原
当时的发布配置大致是这样:就绪探针指向 /healthz,间隔 5 秒,超时 1 秒,失败阈值 3 次;而 /healthz 这个接口在新版本里被改成了"进程存活即返回 200"。问题就出在这里:新版本启动后需要预热本地缓存(约 40 秒),预热期间所有业务请求都会因为缓存未命中而打到下游数据库,造成雪崩。
探针返回 200,kubelet 认为 Pod 就绪,Endpoint Controller 把它加进 Service 的后端列表,流量按比例分流过来——而此时应用根本不具备服务能力。"进程活着"和"能处理请求"是两件事,/healthz 把它们混为一谈了。
二、两类探针的分工必须严格
很多团队只配了一种探针,或者两种探针指向同一个接口,这是最常见的埋雷方式。正确的分工是:
存活探针(livenessProbe)回答"进程是否卡死",失败触发重启。它应该只检查进程最基本的运行能力,绝不能依赖外部资源——如果存活探针去 ping 数据库,数据库抖动会导致所有 Pod 被连环重启,把局部故障放大成全局故障。
就绪探针(readinessProbe)回答"这个实例现在能不能接流量",失败只是把 Pod 摘出 Service 后端,不会重启。它应该覆盖真正的服务依赖:缓存预热状态、核心依赖的连接池、必要的初始化任务是否完成。
这次事故的修复方式很直接:给就绪探针换成专门的业务就绪接口,应用在缓存预热完成之前返回 503,预热完成才返回 200。
三、三个容易被忽略的参数
第一是 initialDelaySeconds 与启动慢的问题。如果应用启动要 30 秒,探针 5 秒就开始探测,前几次必然失败。对就绪探针来说失败无害(只是不接流量),但对存活探针来说会触发误重启。慢启动的应用要么调大 delay,要么改用 Kubernetes 1.22+ 的启动探针(startupProbe),让存活/就绪探针在启动完成后再开始工作。
第二是 periodSeconds 与探测成本。探测间隔太密,对本身有开销的健康检查(比如查一次依赖)会形成额外负载;太疏则故障实例最多要在后端多挂一个周期。一般 5~10 秒是合理区间,关键要和 failureThreshold 一起算"最长摘流时间"。
第三是 优雅终止与就绪的配合。滚动更新时,旧 Pod 收到 SIGTERM 后还有一段宽限期会继续收到流量,前提是 preStop 里先把 Pod 从 Endpoint 摘除并等待传播(通常 sleep 5~10 秒),否则会出现"旧 Pod 已经在关连接、新请求还在进来"的窗口。
四、验证方式:用发布演练代替上线赌运气
这次之后我们在预发环境加了一个固定演练步骤:压测流量打到 Service 的同时执行滚动发布,观察四条曲线——请求错误率、Endpoint 变化(kubectl get endpoints -w)、探针失败次数、P99 延迟。健康的滚动发布应该是:Endpoint 先减后增、错误率无抖动、延迟平滑过渡。任何一条曲线出现毛刺,都说明探针或终止配置还有洞。
结语
就绪探针的价值在于"让不具备服务能力的实例不出现在流量路径上",但配置错误会让它给出虚假的承诺。记住三个原则:存活探针只管进程、就绪探针管真实服务能力、发布时用 Endpoint 变化验证摘流是否及时。探针配置是典型的"低成本高杠杆"项,值得在每次新服务上线前认真过一遍。
- 点赞
- 收藏
- 关注作者
评论(0)