TT Lab
开始
学习 学习路径 课程

由我来搞坏 — 先写假设的混沌实验室

干掉一个,会有多少人受影响

在 TT Lab 中继续学习

一句话总结

杀掉一个 Pod 时会有多少用户受影响,终止流程的影响比副本数大得多。

为什么需要它

“已经增加了副本,所以不会中断服务”这句话只对了一半。即使有三个副本,也常有服务在每次部署时漏出几个失败请求。因为每次只有几个,没人报告,于是这种情况多年不变。要弄清这些漏出的请求来自哪里,就必须一步一步观察 Pod 被终止的过程。

工作原理

收到删除 Pod 的请求后,有两件事会同时开始。一件是控制平面把这个 Pod 从 Service 的 EndpointSlice 中移除,另一件是 kubelet 停止容器。Pod 生命周期文档明确写道,这两件事是并行的,而不是有先后顺序。正在终止的端点,其 ready 状态始终为 false,因此会从负载均衡的对象中剔除,但这一事实要传播到节点的 iptables 规则上需要一段时间。

问题就出在这里。在规则尚未传播开的这短暂时间里,新连接仍会不断进入即将消失的 Pod,而该 Pod 的进程已经收到 TERM 并关闭了套接字。请求因连接被拒绝而失败。应用是正常的,Kubernetes 也是正常的,只有用户的请求失败了。

解决的关键是让进程再多撑一会儿。容器生命周期钩子中的 preStop 在向容器发送 TERM 信号之前执行,这个钩子结束后信号才会发出。用钩子拖住几秒钟,端点移除就能在这段时间内传播开,新连接便只会进入仍然存活的 Pod。关键在于,钩子运行期间应用仍在继续提供服务。

잘못된 기대                     실제 순서
  1. 엔드포인트에서 뺀다          엔드포인트 제거 ─┐ 동시에 시작
  2. 그다음 TERM 을 보낸다        TERM 전송 ──────┘
                                  preStop 이 있으면 TERM 만 뒤로 밀린다

时间预算由 terminationGracePeriodSeconds 决定。默认值为 30 秒,超过这个时间容器仍然存活的话,运行时会用 KILL 将其终止。并且,宽限期的倒计时在 preStop 钩子开始之前就已经开始了。如果打算让钩子占用 5 秒,宽限期就必须比这宽裕得多。

还有一点需要区分。中断(disruption)文档把中断分为自愿中断和非自愿中断。PodDisruptionBudget 只介入自愿中断(排空节点、升级集群)。我们手动执行 kubectl delete pod,或者进程自己崩溃,PDB 都拦不住。这就是“已经配置了 PDB,所以很安全”这种想法的危险之处。

如果客户端会复用连接,情况还要再复杂一层。端点列表只决定新连接会去哪里,并不会把已经打开的连接迁走。用 keep-alive 保持连接的客户端,会一直与已经从端点中移除的 Pod 通信,等这个 Pod 被终止的瞬间就会失败。因此,要把终止处理做好,除了用 preStop 争取时间之外,应用还必须有在收到 TERM 后整理已打开的连接并退出的代码。本实验的负载生成器每个请求都新建连接,就是为了有意排除这个变量。

在现场相遇的样子

在排空一台节点的操作中,这三者会一起暴露出来。没有 PDB 时,同一服务的 Pod 会同时全部被移走,造成完全中断;有 PDB 但没有终止处理时,每移走一个 Pod 就会漏出失败请求。副本数决定受影响的规模有多大,终止流程决定到底有没有影响。

部署策略也沿着同一条轴来理解。滚动更新是通过一次最多允许下线多少个(maxUnavailable)和临时最多允许多启动多少个(maxSurge)来调节爆炸半径(blast radius)的机制。相反,Recreate 会先把旧 Pod 全部下线,再启动新 Pod,因此一定会出现中断。在实验环境中使用 Recreate,是为了只保留一个原因的受控条件,并不是生产环境的推荐值。如果旧 Pod 还在,新配置的效果就会被旧 Pod 的响应掩盖,数字会变得模糊。

下一项测验要确认什么

确认端点移除与发送 TERM 的先后关系,preStop 钩子推迟的到底是什么,以及 PodDisruptionBudget 只适用于哪类中断。