影响Kubernetes控制平面可用性的隐藏故障路径

影响Kubernetes控制平面可用性的隐藏故障路径

💡 原文英文,约2500词,阅读约需9分钟。
📝

内容提要

本文讨论Kubernetes大规模集群控制平面的可用性风险:组件即使不崩溃,也可能通过共享元数据、同步启动或过度健康检查放大局部故障。文中分析三个案例:扩展API发布无效的OpenAPI v2模式,导致无关Helm部署失败;DaemonSet代理同时重启引发API请求风暴;存活探针将KMS依赖故障误判为进程故障,导致反复重启。建议验证实际模式、限制informer范围、区分依赖检查与能力检查。

🔎

延伸解读

共享模式文档的故障放大效应

OpenAPI v2 将核心 API 与所有聚合扩展 API 的模式合并为单一文档,因此任一扩展发布无效定义,都可能让只使用标准资源的客户端(如 Helm)在请求到达 API 服务器前就报错。这种共享故障域意味着扩展的升级风险会外溢到无关工作负载。文章建议升级后实际拉取并解析 /openapi/v2 与 /openapi/v3 文档,用会做模式校验的客户端验证,而不是仅检查进程健康。

恢复操作可能制造同步请求风暴

DaemonSet 的 maxUnavailable 只约束更新期间不可用的 Pod 数量,但已崩溃的 Pod 本就不算可用,其替代者可能几乎同时启动。每个新代理的 informer 都会对同一集合发起初始 list,在数千节点规模下形成同步请求高峰。文章指出,即使 resourceVersion=0 走 watch 缓存,旧版本可能忽略 limit 并返回全部对象,消耗大量内存与 CPU。缩小 informer 监听范围、使用 metadata-only 或 EndpointSlices 可降低单次启动成本。

存活探针应检查能力而非依赖可达性

案例中,KMS 提供者的存活探针每次执行真实加解密,网络中断时探针失败并触发重启;而 API 服务器本可依靠内存中的缓存加密材料继续服务。更严重的是,/healthz 聚合了该提供者状态,导致可用的 API 服务器被替换,新实例又因无缓存而反复重启。文章区分了依赖检查与能力检查:依赖不可达适合告警,存活探针应判断进程是否卡死或无法服务本地接口,避免把暂时性依赖故障升级为控制平面容量损失。

❓

Q&A

为什么一个扩展 API 发布无效的 OpenAPI v2 模式会导致无关的 Helm 部署失败?

因为 OpenAPI v2 是一个合并了核心 API 和所有聚合扩展 API 定义的单一文档。如果某个扩展发布了无效的模式(例如模型名包含斜杠),客户端在解析该文档时会失败,从而阻止后续请求,即使请求的资源属于其他 API。Helm 在应用 chart 前会获取 OpenAPI v2 文档进行验证,因此使用标准资源的 chart 也会因无关扩展的无效模式而失败。

DaemonSet 代理同时重启为什么会引发 API 请求风暴?

DaemonSet 在每个节点上运行一个 Pod,大规模集群中代理重启时,许多新 Pod 几乎同时启动。每个代理都会创建 informer,informer 初始 list 请求使用 resourceVersion=0,可能从 watch 缓存返回全部对象(忽略 limit 参数),导致 API 服务器需要为数千个同时请求构建大量完整响应,消耗大量内存和 CPU,从而拖慢整个控制平面。

存活探针检查外部 KMS 依赖为什么反而会导致控制平面不可用?

当存活探针执行需要访问外部 KMS 的操作时,如果 KMS 暂时不可达,探针失败会触发 kubelet 重启组件。重启后的组件无法建立新的 KMS 会话,于是反复重启。如果该组件是 API 服务器请求路径的一部分,且 API 服务器的 /healthz 检查包含其状态,那么重启会替换掉仍能利用缓存加密材料工作的 API 服务器,新实例没有缓存又依赖不可达的 KMS,最终导致控制平面服务能力丧失。

如何避免因 OpenAPI 模式问题导致的大面积故障?

在升级扩展 API 后,应实际获取并验证客户端收到的模式文档:使用 kubectl get --raw /openapi/v2 和 /openapi/v3/apis/<group>/<version> 获取文档,解析并验证其有效性,并在生产启用前用会验证请求的客户端进行测试。同时,优先使用 OpenAPI v3 以缩小影响范围,并确保扩展 API 正确声明模型包注解。

如何限制 DaemonSet 中 informer 对 API 服务器的启动压力?

应评估 informer 所需的最小资源集合:如果只需特定命名空间的 Pod,就不要全集群 list;如果只需 Pod 元数据,考虑使用 metadata-only informer;如果关注服务后端,可使用 EndpointSlices 替代全量 Pod。此外,利用 Kubernetes 1.31 修复的 API Priority and Fairness 列表成本估算和 WatchList 机制,并注意重试策略可能同步启动成本。

在设计控制平面组件的健康检查时,如何区分依赖检查和能力检查?

存活探针应只检查组件自身是否卡住或无法提供本地接口,而不是检查外部依赖是否可达。外部依赖的不可用应通过独立的监控告警来通知运维,而不应自动触发重启。因为组件可能仍能利用缓存状态(如缓存的加密材料、已建立的连接)继续提供有用服务,重启反而会丢弃缓存并加剧依赖压力。

🏷️

标签

➡️

继续阅读