绿灯的含义与缓慢启动的时间预算
一句话总结
探针成功,只表示所选的检查条件为真。如果把端口已打开解读为业务成功, 或者把缓慢的初始化解读为必须立即终止的故障,自动恢复反而会制造故障。
为什么需要它
能打通电话的餐厅和能接受点单的餐厅是两回事。店员会接电话,但后厨可能已经停摆。 在监控中把“连接成功”读成“点单成功”的那一刻,绿色画面和客户投诉就会同时存在。 服务器的 health endpoint 也不会因为名字里有 health 就自动成为正确的检查。 还要看成功条件是谁、用什么实现的。
工作原理
TCP 检查确认能否连接到指定端口。HTTP 检查观察指定路径的响应码。 例如,即使 Web 服务器对所有订单都返回 503,接受 TCP 连接的套接字也可能是打开的。 这时 TCP readiness 会成功,而用户收到的却是失败响应。
但这并不意味着 TCP 检查对所有应用都是错的。如果检查目的是连接能否被接受,那它问的就是正确的问题。 问题在于,给这个结果附加了它并不保证的业务含义。HTTP 检查同样如此, 如果是一条始终返回 200 的路径,就发现不了数据库查询失败。反过来,如果每次健康检查都执行沉重的查询, 检查本身就可能给服务造成负担。
| 路径示例 | 想确认的事 | 不应随意混淆的事 |
|---|---|---|
/startupz |
这次进程的初始化是否已完成 | 启动之后每次都再执行同样的初始化 |
/livez |
让它继续运行是否还有意义 | 所有外部依赖服务的暂时性故障 |
/readyz |
能否把新的业务请求交给它 | 保证用户完整流程全部成功 |
| 真实业务路径 | 真实请求的结果与响应内容 | 把仅有的一次成功扩大为可用性保证 |
startup probe 是保护缓慢启动的独立检查。在它成功之前,liveness 和 readiness 不会执行。 如果对初始化耗时较长的应用一开始就应用激进的 liveness, 就可能出现应用还没完成启动就不断被终止的情况。 这就是要把允许启动的时间和检测运行中不可恢复状态的时间分开处理的原因。
例如,实验应用启动后 20 秒内返回“正在初始化”的响应,之后返回正常响应。 如果 startup 的检查周期为 1 秒、连续失败上限为 45,那么相对于 20 秒的初始化,这是留有余地的设置。 这个数值并不保证在所有环境中都恰好在 45.000 秒后终止。由于存在执行、调度和检查的耗时, 实际的状态转换必须通过观测来确认。在考试中,比起背数字, 更重要的是能否说明哪个预算保护了哪种失败。
初始化结束后,readiness 暂时失败,并不会让 startup 从头再跑一遍。 反过来,容器一旦重启,新进程就有了新的启动阶段。这时不能把旧进程的成功记录 当作新进程的证据重复使用。这就是为什么不仅要记录 Pod UID, 还要同时记录容器 ID 和应用的运行标识符。
在现场相遇的样子
假设新版本部署之后,连接检查成功,但只有真实的 API 失败。首先,不要去掉 probe, 也不要改成让 Service 连未就绪的地址也暴露出去。要分别调查业务路径与检查路径的差异、 Pod 条件、EndpointSlice 的对象,以及真实的请求结果。然后修改检查, 使其表达该服务所承诺的就绪状态。检查中的红灯消失,和故障得到解决,是两回事。
修改检查时也必须尊重对象的生命周期。对于正在运行的独立 Pod,其 probe 字段不能随意修改。 要修改声明,并确认哪个托管对象会创建新的 Pod,以及在替换期间是否有可用的对比组。 不要把教材中用于一次性单个 Pod 的替换流程,直接当作生产 Deployment 的零停机部署流程。
下一项实验要做什么
让两个正常 Pod 中的一个 readiness 失败,观察在没有重启的情况下,是否只有请求分配发生变化。 接着通过合成的 liveness 故障观察容器重启,并比较另一个通过 TCP readiness 却返回 HTTP 503 的 Pod。最后,把缓慢启动期间的状态与初始化完成之后的状态,连接到真实的记录上。 状态转换可能需要时间,所以在反复覆盖设置之前,先查看同一个对象的进展。
官方依据:探针配置与注意事项。