第 16 章 高可用与控制面演化:管高可用的系统怎么管自己

墨:老哥,前面的章节把 K8s 拆成十几个组件。这章问一个递归的问题——这些”管集群”的组件自己挂了,谁来管?一个负责高可用的系统,怎么保证自己高可用? 而且更妙:有些组件能多副本,有些不能。你先猜,哪个不能多副本、为什么。


16.1 控制面组件清单与”谁能多活”

控制面(control plane)核心组件:API Server、etcd、调度器(kube-scheduler)、控制器管理器(kube-controller-manager)、云控制器管理器(cloud-controller-manager)。外加每个节点的 kubelet、kube-proxy。

墨:哪些能直接跑多个副本同时干活(多活),哪些不行? 分两类:

  • 可多活(stateless / 只读共享存储):API Server、kube-proxy(每个节点一个,互不冲突);
  • 需选主(有写动作、会冲突):调度器、控制器管理器、etcd(etcd 是集群本身,特殊)。

为什么调度器/控制器不能随便多活?因为它们都往 etcd 写状态(调度器写 nodeName、控制器写对象状态)。若两个调度器同时跑,都把 Pod A 调度给不同节点、都写 nodeName——冲突(第 3.6 的并发问题放大版)。所以这类”有写入决策权”的组件,必须同一时刻只有一个在干活(active),其余待命(standby)。这就是 Leader Election(选主)。


16.2 Leader Election:用 etcd 自己选老大

墨:调度器要选主,那”选主”这件事本身靠谁?不能又靠另一个调度器吧(鸡生蛋)。 靠 etcd(或等价一致存储)的租约(lease)。具体:

  • 多个调度器实例启动,都去 etcd 抢一个 lease(如 /leader 键,带 TTL);
  • 谁抢到(写成功)谁当 Leader,定期续租(heartbeat);
  • 其他实例抢不到,进入 standby,不断尝试抢(等 Leader 租约过期);
  • Leader 崩溃/网络断 → 租约 TTL 到期没续 → etcd 删键 → 某个 standby 抢到,变身 Leader。

墨:这不就是第 5.7 的 lease 机制吗? 对!选主复用 etcd 的 lease——用”临时凭证自动失效”实现”自动让位”。Leader 挂了不需要”通知别人”,租约超时自动失效,系统自愈。又是 level-triggered + lease 的组合(全书的老朋友)。

为什么不用”多活 + 冲突重试”而用”选主单活”? 因为调度/控制的写是有状态决策(“这个 Pod 归 node-3”),多活会产生大量冲突和来回,且难以保证”全局视图一致”。单活最简单:一个大脑做所有决策,绝无冲突。代价是 Leader 挂到 standby 接棒的秒级空窗(期间调度暂停,但已运行 Pod 不受影响)。这个空窗可接受——etcd 选主通常几秒内完成。

设计取舍:有写入决策权的组件选”单活+自动选主”,而非”多活+冲突解决”。 因为单活在”正确性”上最简单可靠,代价只是短暂的决策暂停(且不影响已运行负载)。这是”正确性优先、可用性靠快速切换”的取向,呼应 etcd 选 C、StatefulSet 求有序(全书一贯)。


16.3 API Server:无状态、多副本、前面挂 LB

API Server 是无状态的(它不持久化任何东西,状态全在 etcd;它不自己做调度/控制决策)。所以它可以放心多副本:

  • 起 3~5 个 API Server 实例,前面挂一个 负载均衡器(LB);
  • 客户端(kubectl、kubelet、控制器)连 LB,LB 分发到某个实例;
  • 某个实例挂了,LB 把它摘掉,流量走别的——无感知;
  • 每个实例都直连 etcd(读走 etcd、写走 etcd,靠 etcd quorum 保证一致)。

墨:多副本 API Server 都写 etcd,会冲突吗? 写冲突由 etcd 的乐观锁(resourceVersion)兜底(第 4.6):两个写同一对象,只有一个基于最新版本成功,另一个重试。且 API Server 本身不做”决策型写”(不决定 Pod 去哪),只是”把用户/控制器的合法写持久化”,所以多副本并行写 etcd 是安全的(etcd 保证串行一致)。这正说明:API Server 能多活,是因为它无状态 + 写靠 etcd 串行化;而调度器不能多活,是因为它的写是带决策的(决定分配),etcd 串行化救不了”两个决策相互矛盾”。

演化看点: API Server 的水平扩展(加副本)是 K8s 控制面扛压的主要手段——大集群多跑几个 API Server 实例即可。但 etcd 不能靠加副本提写吞吐(第 5.11),所以控制面扩展的不对称:无状态层(API Server)易扩展,有状态核心(etcd)是写瓶颈、难扩展。这决定了大集群的瓶颈在 etcd,不在 API Server。


16.4 etcd:奇数节点集群,quorum 保命

第 5 章详讲:etcd 跑 3/5/7 奇数节点,Raft quorum 保证一致与容错。它是控制面的”心脏”,也是最不能丢的。高可用做法:

  • 3 节点 etcd 容忍 1 挂,5 节点容忍 2 挂;
  • etcd 节点不和 API Server 混部太多(生产建议独立节点,避免资源争抢影响心跳);
  • 定期快照备份(第 5.9)——etcd 是集群唯一真相源,备份=备份整个 K8s;
  • 磁盘用低延迟 SSD(fsync 延迟直接决定写性能与稳定性,第 5.5)。

墨:etcd 全部挂了会怎样? API Server 写不进、读可能部分(看命中哪个节点),调度停止、控制器无法更新状态,但已运行 Pod 继续跑(kubelet 本地管理)。恢复 etcd(从备份或重启节点),集群自愈。所以 etcd 全挂是”控制面僵死”而非”业务全灭”——又是第 5.2 的 CAP 取舍在运维层面的体现。


16.5 节点健康与自愈:kubelet 是节点的”哨兵”

墨:控制面组件有选主/多副本保活,那工作节点(跑 Pod 的机器)挂了,谁管? kubelet 管,但它自己就在那台机器上——机器挂了 kubelet 也哑了。所以”节点健康”靠kubelet 主动上报 + 控制面判定:

  • kubelet 周期向 API Server 上报节点状态(通过 node lease + status);
  • 若某节点 kubelet 超过 node-monitor-grace-period(默认 40s)没上报,API Server 把 Node 对象标记为 NotReady;
  • 节点控制器(node controller) 看到 NotReady,启动计时;超过 pod-eviction-timeout(旧默认 5min),把该节点上的 Pod 标记为待驱逐,调度器把 Pod 调度到其他节点重建。

墨:为什么不是”节点一 NotReady 就立刻删 Pod”? 因为可能是网络抖动导致 kubelet 短暂失联,节点其实好好的。若立刻删 Pod,会造成”误杀+全集群 Pod 大迁移”的风暴。所以设宽限期(grace period)——宁可多等几十秒确认节点真死了,也不误杀。这是”用延迟换正确”的经典:在”可能假死”和”真死”之间,选择”多观察一会儿”,避免误判引发雪崩。又是 level-triggered 思维:不靠”失联那一刻”的事件,靠”持续观察状态超时”判定。


16.6 组件崩溃谁拉起:静态 Pod 与 systemd

墨:控制面组件(API Server/etcd/scheduler/controller-manager)自己崩了,谁重启它们? 两种主流:

  • kubeadm 自建集群:这些组件是静态 Pod(第 8.9),由 kubelet 在节点上管。kubelet 自己是 systemd 服务,由 OS 拉起。所以链路:OS(systemd) → kubelet → 静态 Pod(API Server 等)。kubelet 发现静态 Pod 没了就重建。kubelet 自己崩?systemd 拉起它;
  • 云托管集群(EKS/GKE/AKS):控制面由云厂商管,你摸不到这些进程,云厂商用他们的管控系统保证控制面高可用(你只管工作节点)。

墨:为什么控制面用静态 Pod 而不是普通 Deployment 管? 鸡生蛋:API Server 还没起,谁调度 Deployment?静态 Pod 不依赖 API Server 调度(kubelet 本地清单直接拉),所以它能”自举”控制面(第 8.9)。Deployment 管控制面会陷入循环依赖。这是”自举必须不依赖被自举的系统”的铁律。

16.6.1 自愈的分层

你会发现 K8s 的自愈是分层的:

  • 容器崩 → kubelet 按 restartPolicy 重启(第 8.5);
  • Pod 被删/节点挂 → 控制器按期望重建(第 3 章);
  • 节点挂 → 节点控制器驱逐 + 调度器重调度(16.5);
  • 控制面组件崩 → 静态 Pod/systemd 拉起 或 Leader 切换(16.2/16.6);
  • etcd 崩 → 备份恢复 + 剩余节点 quorum 继续(第 5 章)。

每一层都有”谁在管上一层”的兜底,形成自愈链。 这就是”用声明式期望 + 各层控制器”实现的系统级自愈——没有总指挥,但每层都有自己的”守门员”。


16.7 控制面自身的滚动升级

墨:K8s 自己要升级版本(如 1.28→1.29),控制面能无缝吗? 能,且要小心顺序:

  • etcd 先升(或保持兼容):etcd 版本要兼容 API Server,先确保存储层 OK;
  • API Server 多副本轮流升:先加新版本副本、摘旧版本,保证”始终有多数 API Server 在线”(避免和 etcd 一起不可用);
  • 控制器/调度器升:它们是选主单活,升时是”先升 standby、再让 Leader 让位给新 standby、再升原 Leader”——滚动切换,不停服;
  • kubelet 逐个节点升:节点排空(drain)→ 升 kubelet → 恢复(uncordon),Pod 被调度走再回来(或原地升);
  • 版本倾斜规则:API Server 可比 kubelet/控制器新一个版本(如 API 1.29 兼容 kubelet 1.28),反之不行——因为新 API 能力旧组件不懂。

演化/运维看点: 升级是”先数据层、再无状态层、再选主层、最后节点层”,且始终保持”多数在线”。这背后的纪律是兼容性与顺序——和 K8s 的”慢废弃长过渡”(第 4.6)一致:升级也要保证”旧组件短期共存”,不能一刀切。


16.8 演化:自建 vs 托管——把最不能错的部件交给云

墨:你公司要上 K8s,控制面自己搭(kubeadm)还是用云托管(EKS/GKE)? 这是近年的主流演化:托管控制面(Managed Control Plane)成为默认选择。

  • 自建(kubeadm/kops):你管 etcd 备份、API Server 高可用、升级、证书轮转。控制力强,但运维重、易出错(etcd 被玩崩的团队不少);
  • 托管(EKS/GKE/AKS):云厂商管控制面(多副本、etcd 托管、自动升级、备份),你只管工作节点(甚至节点也托管成无节点/Serverless)。

演化趋势的本质: 把”最不能错、最难运维的核心(etcd/API Server)“交给专业云厂商,用户专注”业务负载”。这和第 5.9(etcd 备份交给云)、第 12.3(密钥交 Vault)、第 9 章(网络交 CNI 插件)是同一哲学的不同体现:把专业、高危、通用的底层,外包给专业的提供者;自己只做差异化的业务层。 云原生的整体趋势就是”能力持续下沉/外包给平台,用户上移专注业务”。

墨:那托管是不是”把灵魂交出去”? 是,也有代价:你失去对控制面的掌控(出问题是找云厂商)、被绑定云(控制面 API 行为云可能微调)、数据出口合规要考虑。所以金融等强合规场景可能仍选自建或专属云。又是”便利 vs 控制”的权衡——K8s 自己内部也反复出现这题,现在轮到你在”用 K8s 的方式”上答。


16.9 本章演化线小结

  • 组件分两类:无状态可多活(API Server/kube-proxy)vs 有写决策权需选主(调度器/控制器);
  • Leader Election 借 etcd lease 实现”自动让位”,单活保正确、秒级空窗可接受;
  • API Server 无状态多副本 + LB,写靠 etcd 串行化安全;扩展不对称(etcd 是写瓶颈);
  • etcd 奇数集群 + quorum + 快照备份,全挂=控制面僵死非业务灭;
  • 节点健康靠 kubelet 上报 + 宽限期判定 NotReady + 驱逐重调度,防误杀雪崩;
  • 自愈分层:容器→Pod→节点→控制面→etcd,各层有守门员;
  • 控制面升级按”数据层→无状态→选主→节点”顺序滚动,保持多数在线;
  • 演化:托管控制面成默认,把高危核心外包云厂商,便利换控制。

16.10 本章思考题

🔧 思考题 1

你跑 3 副本调度器,靠 etcd lease 选主。Leader scheduler-A 所在节点发生网络分区(A 还能跑但连不上 etcd),standby scheduler-B 抢到 lease 变 Leader。此时若 A 的网络恢复、误以为自己还是 Leader 继续调度,会发生什么?etcd lease 机制怎么防止”双 Leader”?

【参考答案】 关键在”抢 lease 的原子性”:etcd 的 lease 是单一键 + TTL,同一时刻只有一个客户端持有有效 lease(写成功=拿到,TTL 内别人写被拒)。scheduler-A 网络分区连不上 etcd → 无法续租 → lease TTL 到期,etcd 自动删键(A 的租约失效,即使 A 自己以为还活着,etcd 那边的真相是租约没了)。scheduler-B 抢到新 lease 变 Leader。A 网络恢复后若还尝试”以 Leader 身份写 nodeName”——它写操作要带 lease 校验/或它下一轮续租会失败(etcd 告诉它 lease 已不在),B 才是当前 holder,A 的写会被拒(且 A 应能检测到自己失去 lease 主动降级为 standby)。所以etcd 单一 lease 保证”严格一个 Leader”,不存在真正的双 Leader 同时被 etcd 认可——A 的”自以为 Leader”在它下次和 etcd 交互时立即破产。这题考你对”选主靠外部一致存储的单一租约,而非组件自我认知”的理解:真相在 etcd,不在本地内存。

🔧 思考题 2

一个工作节点宕机(硬件故障),上面跑了 10 个 Pod(都是 Deployment 管)。描述从宕机到这 10 个 Pod 在别处恢复可用的完整时间线与各组件角色,并指出哪个环节的延迟最影响恢复速度、怎么缩短。

【参考答案】 时间线:(1) kubelet 停止上报(节点宕机瞬间);(2) API Server 在 node-monitor-grace-period(默认40s) 内标记 Node NotReady;(3) 节点控制器开始计时 pod-eviction-timeout(旧默认5min,新版用更细的速率控制);(4) 超时后,节点上 Pod 被标记为驱逐,API Server 把 Pod 的 nodeName 清掉(或标记为驱逐);(5) 调度器看到这些 Pod 无节点,过滤+打分选新节点,写 nodeName;(6) 目标节点 kubelet 拉镜像、起容器(受镜像大小/启动耗时影响);(7) 就绪探针通过,Service 后端更新,流量切入。最影响恢复速度的环节:(a) 宽限期(40s+5min 量级)——为防误杀设的长等待,是”恢复延迟”的主来源,可适当调小(但冒误杀风险);(b) 镜像拉取——大镜像拉取慢,用镜像预热/节点池常驻镜像缩短;(c) 应用启动+就绪探针**——优化启动速度、合理设探针。结论:恢复速度 = 宽限确认 + 调度 + 拉镜像 + 启动,其中宽限和启动常是瓶颈;缩短靠”调小宽限(权衡误杀)、镜像预热、应用快启”。这题把第 5.10/15.7/6/8 串成一次真实故障恢复。

🔧 思考题 3

为什么 API Server 可以多副本无脑水平扩,而 etcd 加副本反而可能更慢?从”写路径”角度解释这种扩展不对称性,并说明大集群瓶颈在哪、怎么缓解(不能只加 etcd 节点)。

【参考答案】 写路径:API Server 多副本,每个都直连 etcd 做”读 + 把写转发给 etcd”,自身无状态、不持久化、不做决策,所以加副本只是加”转发入口”,互不冲突、吞吐线性增(受 LB 和 etcd 上限约束)。etcd 写入要走 Raft:每个写需 Leader 复制日志到多数派(quorum) + 多数派 fsync 落盘才提交。副本越多(如 5→7),quorum 越大,每次写要确认的节点越多、网络 round-trip 和 fsync 累积越久,单写延迟上升、写吞吐不增反降。所以 etcd “加副本提升容错但不提升写性能”,甚至略降。大集群瓶颈在 etcd 写吞吐(所有状态变更都过它)。缓解(不能只加 etcd 节点):(1) 减少写量:status 子资源分离(kubelet 频繁更 status 不污染 spec watch,第 5.10)、只上报变化字段、降低无关对象变更频率;(2) 拆分 etcd:event 单独存一个 etcd(事件量大可丢)、不同资源组放不同 etcd;(3) API Server 缓存扇出减少直击 etcd 的 watch(第 4.7);(4) 更低延迟磁盘(NVMe SSD 降 fsync 延迟);(5) 降采样调度/减少对象爆炸(EndpointSlice 第 11 章)。本质:etcd 是”写串行点”,优化靠”少写 + 分摊写”,而非”加副本”。这题呼应第 5.11,考你理解”有状态核心的扩展天花板”。

🔧 思考题 4(进阶)

自建(kubeadm)vs 托管(EKS/GKE)控制面,从”事故面""合规""锁定""成本”四个维度对比,给出一个”什么团队该选自建、什么该选托管”的决策框架。

【参考答案】 四维度:(1) 事故面:自建=你负责 etcd 崩/证书过期/升级翻车,事故你自己救(能力要求高);托管=控制面事故归云厂商(SLA 保障),你省心但失控时只能等厂商。(2) 合规:金融/政务等数据不出私域、强审计,可能必须自建(或专属云/本地托管)以完全掌控数据位置与访问;托管在公有云,数据出口需评估。(3) 锁定(lock-in):托管各家控制面行为有微调(如 EKS 的某些默认、IRSA 鉴权),且把控制面交出去后迁移成本高(工作负载 YAML 大多可移植,但控制面配置/云特定 CRD 绑定);自建完全自主、无云绑定。(4) 成本:自建省控制面费用但有运维人力成本(隐性高);托管付控制面费但省人力。决策框架:选托管——绝大多数公司(尤其非强合规、团队<K8s 专家、要快上线);选自建——强合规/数据不出域、有专职平台团队、需深度定控制面、或已用裸金属无法用云托管。中间态:用托管控制面 + 自建节点池(如 EKS 管控制面、节点用自管裸金属),兼顾。结论:除非”合规或专业团队”两条占其一,默认托管——把高危核心外包是云原生主流,也是第 16.8 说的演化趋势。你做选择时,先问”我团队配不配运维 etcd”,不配就托管。


16.11 小结

  • 组件分可多活(API Server/kube-proxy 无状态)与需选主(调度器/控制器有写决策);
  • Leader Election 借 etcd lease 实现严格单活+自动让位,单活保正确、秒级空窗可接受;
  • API Server 多副本+LB,写靠 etcd 串行化;扩展不对称——etcd 是写瓶颈,加副本不提速;
  • etcd 奇数集群+quorum+快照,全挂=控制面僵死非业务灭;
  • 节点健康靠 kubelet 上报+宽限期防误杀+驱逐重调度;
  • 自愈分层(容器→Pod→节点→控制面→etcd),各层有守门员;
  • 控制面升级按”数据→无状态→选主→节点”滚动,保持多数在线;
  • 演化:托管控制面成默认,把高危核心外包云厂商,便利换控制。

下一章进可观测性与排障心智——分布式之后,一个问题散在几十个组件里,你怎么在迷宫里定位一根坏线?这章给你一套”从 Pod 到节点到控制面”的系统排查方法,不是命令清单,是心智模型。