Kubernetes 控制面高可用:组件职责、选主与故障影响
“控制面挂了,已有 Pod 还能跑吗?”这个问题需要先拆开:是一个 API Server 不可达,还是所有 API Server 都停止?Scheduler、Controller Manager 和 etcd 还正常吗?工作节点、应用依赖和数据路径是否仍然可用?
已有进程继续运行、已有请求继续成功、API 还能写入、新副本能够创建并调度,是不同的能力。控制面高可用的目标,是让这些协调能力在约定故障范围内继续工作,而不是保证任何一次组件故障都对每个请求透明。
控制面不是一个统一的主备服务
API Server 可以同时有多个副本处理请求,通常由稳定的负载均衡入口提供访问。请求最终面对的是共享的集群状态,API Server 副本之间不靠选出一个唯一主节点来串行处理所有请求。入口本身的可用性、后端健康检查和客户端重连,都是这条路径的一部分。
健康检查还要区分“进程活着”与“可以接收请求”。API Server 的 livez 用于存活判断,readyz 用于就绪判断,启动或退出过程中的实例可能仍存活却未就绪。负载均衡后端摘除应按就绪能力设计,不能只以端口能建立 TCP 连接作为全部依据。installReadyz 与 installLivez 分别安装处理器,addReadyzShutdownCheck 还把退出信号纳入就绪检查;具体配置要兼顾检查器的认证与访问权限。
Scheduler 和 Controller Manager 的常规高可用部署则使用选主:多个进程存在,但选主保护的工作由持有 Lease 的实例负责。Scheduler 决定未绑定 Pod 的节点位置;Deployment、ReplicaSet、Node 等控制器负责让当前状态接近期望状态。多副本存在不代表它们都在同时执行同一组调谐工作。
etcd 自身又有 Raft leader 和多数派提交规则。这个 leader 与 Scheduler 的 leader、Controller Manager 的 leader 没有必须位于同一个节点的关系。把某台机器叫作“主 Master”,容易把这几种不同角色混在一起。
sequenceDiagram
participant C as 客户端
participant L as API 访问入口
participant A as API Server 副本
participant E as etcd 多数派
participant S as Scheduler leader
participant M as Controller Manager leader
C->>L: 写入 Deployment
L->>A: 转发健康后端
A->>E: 持久化对象
E-->>A: 确认提交
A-->>M: Watch 观察到期望状态
M->>A: 创建或调整 ReplicaSet 与 Pod
A-->>S: Watch 观察到未绑定 Pod
S->>A: 写入 Pod 绑定
Note over S,M: 各自通过 API Lease 协调选主
图中的 Controller Manager 汇总了多个控制器的职责,省略 ReplicaSet 控制器之间的观察过程。关键是控制器和调度器通过 API 对象协作,不直接把指令从 Deployment Controller 发给 Scheduler。
Lease 如何决定谁开始工作
以 Kubernetes v1.35.1 的 client-go 选主实现为依据,实例通过读取与更新锁对象竞争领导权。kubeadm 常规部署在 kube-system 中使用 kube-scheduler、kube-controller-manager Lease,可以观察 holderIdentity、续租时间和领导权切换。
LeaderElector.Run先完成 acquire,才启动 OnStartedLeading 回调;随后持续 renew,退出时调用 OnStoppedLeading。tryAcquireOrRenew在发现其他实例的租约仍有效时拒绝接管,再通过 API 更新竞争新的租约状态。
故障检测和接管需要时间。租约期限、重试周期、API 延迟和后端存储状态都会影响切换窗口,不能把一次实验里测到的时间写成固定恢复上限。这个库也不提供外部副作用的 fencing 保证,控制器仍需正确处理重复执行和并发更新;相关设计见 Controller 调谐机制。
故障影响取决于哪一层停止推进
单个 API Server 故障且入口还能把请求路由到其他健康副本时,API 服务可以继续。客户端已有连接可能断开,故障检测前的请求可能失败;多副本不自动保证请求恰好执行一次,写入超时后还要判断操作是否已经生效。
所有 API Server 不可用时,客户端无法正常修改期望状态,控制器和节点也无法继续通过 API 同步。已有节点上的容器不会仅因为 API 暂时不可达就被统一杀死,已经建立的数据路径也可能继续工作。但应用如果每次请求都依赖 Kubernetes API、需要动态发现新端点,或恰好遇到进程与节点故障,业务结果会不同。
Scheduler 全部停止时,API 仍可能接受新 Pod,但没有其他调度器负责的 Pod 无法获得节点绑定。已经绑定的 Pod 不需要重新经过 Scheduler 才能继续运行。
Controller Manager 全部停止时,用户仍可能修改 Deployment 的副本数,但对应控制器无法继续创建或删除所需对象。节点上的 kubelet 仍可处理已有 Pod 的本地容器重启,这与“新建一个替代 Pod”是两种恢复路径。
失去 etcd 多数派时,即使 API Server 进程仍在,也无法据此保证 API 写入正常。存储请求、一致读、选主和状态更新都可能受到影响,详见 etcd 一致性与恢复。
实验一:停止全部 Scheduler 和 Controller Manager
实验运行于 zj,使用 kind v0.33.0、Kubernetes v1.35.1,专用集群名为 article-control。集群包含三个控制面和一个 worker,后续检查使用匹配的 kubectl v1.35.1。实验移出自己节点中的指定静态 Pod 清单,等待对应运行时容器确实停止,退出时恢复清单。
停止三个 Scheduler 后,通过 API 创建一个新 Pod,立即读回对象,确认 spec.nodeName 为空;恢复 Scheduler 后,等待这个 Pod Ready。脚本记录:
| |
停止全部 Controller Manager 后,把已有 Deployment 的期望副本数从 1 改为 3。API 接受修改,但当次读取仍只有原来的 1 个 Pod;恢复控制器后,等待 rollout 完成:
| |
这两组用可恢复的因果干预区分了 API 写入、控制器调谐和调度绑定。瞬时读回只是证据片段,停止全部对应进程及恢复后的收敛检查,才共同支撑职责判断。
实验二:暂停一个控制面节点,观察接管与切换窗口
集群使用 stacked etcd,每个控制面节点同时承载 API Server、Scheduler、Controller Manager 和一个 etcd 成员。暂停其中一个节点,会同时影响多种组件;这组实验验证的是一个控制面节点失效后的整体恢复,不用于单独测量某个组件的接管时间。
首次暂停原控制面节点时,第一个 ConfigMap 写请求返回:
| |
这个失败不能从记录中删掉。它说明后端选主、入口与连接切换期间,单次请求仍会失败。恢复节点后,补充实验按当前 Scheduler Lease 找到 leader 所在节点,再暂停该节点,以有截止时间的重试确认 API 写入恢复;随后把 Deployment 扩到 4 个副本,等待全部 Available,并记录新的 Lease 持有者。
这次扩容完成,四个业务 Pod 都位于独立 worker。结果支持“三个 etcd 成员剩余两个可通信,并且剩余控制面能够接管后,集群继续创建与调度副本”,不支持“所有单次请求从未受影响”。
实验三:所有 API Server 停止后,直接访问已有 Pod
在 worker 上创建一个简单 HTTP Pod,记录 Pod UID、容器 ID 和 Pod IP。移出三个 API Server 的静态清单,等待三个 API Server 容器全部停止,确认 kubectl 请求失败;此时从 worker 节点直接访问这个 Pod IP 的 HTTP 端口,得到:
| |
恢复 API Server 后,脚本读回 Pod,核对 UID 与容器 ID 均未改变。这次请求没有依赖 Kubernetes API,也没有经过 DNS、Ingress 或 Service,证明的是这条已建立的直接 Pod 网络路径和原进程仍然工作。它没有覆盖所有生产流量路径,也没有验证控制面长时间失联后的节点与网络行为。
配置、脚本、证据与清理方法见 k8sdev/article-control-ha。先按该目录 README 的固定提交准备步骤创建独立工作目录,再运行实验。
API 停机实验对直接 HTTP 请求及恢复后的对象、容器身份分别取证,具体检查见实验脚本。
高可用拓扑要覆盖实际故障域
stacked etcd 把控制面组件与 etcd 成员部署在相同节点,机器故障会同时减少两类副本;外置 etcd 则可以分别安排故障域和扩容,但增加独立运维对象。官方 kubeadm 高可用拓扑说明列出了两种结构。
本实验的所有 kind 节点都在同一台 zj 上,它们只是隔离进程环境,不能证明物理机或可用区故障下的高可用。生产设计还要核对 API 入口是否单点、etcd 多数派能否跨预期故障域保留、剩余节点是否有足够工作负载容量,以及客户端是否正确重连。
回答控制面故障时,应先明确故障对象,再说明保留下来的能力和停止推进的状态。例如“已有进程继续运行,但新 Pod 无法调度”远比“控制面故障不影响业务”准确,也能直接指向接下来应检查的组件。