Running、Ready与业务成功是不同的问题
一句话总结
Running 是 Pod 生命周期的概括,Ready 是关于现在是否可以把请求交给它的条件。 还要同时看重启次数和真实的 HTTP 响应,才能区分“进程还活着”和“服务在工作”。
为什么需要它
快递仓库里员工来上班了,并不意味着发货准备就绪。员工活着、在走动,但也许发票打印机没有连上。 确认有没有来上班的问题,和决定要不要分配新订单的问题是不同的。服务器也是一样, 在进程运行期间,它可能还在读取配置或者恢复连接。
如果运维人员只看画面上的 Running,就判断没有故障,用户的失败就无法得到解释。 反过来,如果因为一次响应慢就不断杀掉进程,请求就会涌向仍然正常的实例, 而那个实例也会再次变慢。健康信号不是制造更多绿灯的功能,而是约定把观测结果 与什么行动连接起来。
工作原理
Pod 没有一个“健康分数”。status.phase、status.conditions 和各容器的状态,
分别回答不同的问题。kubectl get pods 的简短表格只是概括,所以要先确认自己看的是哪一列。
发生过重启,而现在是 Running,并不意味着过去的失败也消失了。
Running 阶段是指 Pod 被分配到节点、容器被创建之后,至少有一个正在运行,
或者正在启动、重启中的情况也包括在内。这并不表示所有容器的业务都正常。
另外,表格 STATUS 中看到的 CrashLoopBackOff 与 API 的 status.phase 不是同一个字段。
与其把一个概括字符串背下来当作健康判定,不如把各容器的状态也展开来读。
| 观测 | 回答什么问题 | 仅凭它无法知道的事 |
|---|---|---|
| phase=Running | Pod 是否进入了运行阶段 | 业务请求是否成功 |
| Ready 条件 | 现在是否准备好接收流量 | 所有用户场景是否成功 |
| restartCount | 这个容器重启了多少次 | 是哪个故障导致了重启 |
| 真实的 HTTP 响应 | 这条路径的这次请求是否成功 | 其他路径、其他时间点是否也成功 |
readiness 检查由 kubelet 执行,其结果会反映到 Pod 条件和 Service 的转发目标上。
在一般的 Service 中,未就绪的 Pod 不会被用作新请求的正常目标。
这时,不要只看 EndpointSlice 中的地址是否字面上消失了。地址保留着,
conditions.ready=false 也是有可能的。必须同时对照 Pod UID、地址和条件,才算是在看同一个目标。
publishNotReadyAddresses=true 的 Service 是另一种行为,所以本实验中明确把它设为 false。
假设 Pod A 和 B 在同一个 Service 后面。只让 A 的 readiness 失败,A 仍然可以是 Running。 可以比较这样的情形:直接发给 A 的请求能连上,但业务响应失败,而通过 Service 的新请求由 B 处理。 这里重要的证据不只是“看到了一次 B 的响应”。还必须同时确认 A 的 Ready=false、 EndpointSlice 中 A 的条件,以及 A 原有的容器 ID 和重启次数保持不变。
而 liveness 是关于是否让容器继续活下去的检查。超过所设置的连续失败标准后, kubelet 会终止该容器,并遵循重启策略。在同一个 Pod 内容器重新启动的 情况下,Pod UID 相同,容器 ID 会改变。这与 Deployment 替换 Pod 的情况是不同的事件。 如果漏掉这个区分,就会得出“Pod 还是原来的,所以没有重启”这样的错误结论。
在现场相遇的样子
第一种情况是暂时的依赖服务故障。如果应用本身活着,并且可以重新尝试连接, 暂时停止分配请求可能是合适的。依赖服务恢复后,同一个进程会 再次变为 Ready。靠杀死进程,是无法修复对方服务的故障的。
第二种情况是应用内部无法推进。如果即使接收新请求,处理也完全没有进展, 并且是可以通过重启进程来恢复的状态,liveness 就可能有帮助。不过,本实验中的故障 标记并不是真正的死锁状态,而是为了比较恢复行为而设的合成故障。不能据此声称 检测到了生产应用的死锁。真实应用的检查路径要测量什么,必须另行设计。
连接到下一课时
下一篇教材会看检查本身设计错误时产生的错觉。区分 TCP 连接成功与 HTTP 业务成功, 以及缓慢的初始化与无法恢复的失败。在后面的实验中,会记录 Pod 和容器的身份, 分别制造 readiness 失败和 liveness 失败,然后确认正常的对比组是否没有受到影响。
官方依据:Pod 生命周期与 phase · Pod 健康检查 · EndpointSlice 条件。