📚 建议收藏 | 遇到集群可靠性设计问题,看这一篇就够了
前阵子跟一个做基础设施的哥们聊天,他说了一句话让我印象特别深:“我们产品没有资源搞 K8s,但我们自己写的集群服务三天两头出问题,想参考一下 K8s 的架构,看看它到底是怎么保证可靠的。”
这个问题我太有感触了。很多人以为 K8s 的可靠性就是“多搞几个副本”——节点挂了自动重启 Pod 就完事了。但真拆开看,K8s 从控制平面到数据平面,从组件通信到状态存储,每一层都有自己的一套生存逻辑。
今天我就从 SRE 的视角,把 K8s 集群保障可靠性的核心机制掰开了讲。下文会逐一展开,这里先给你一个全景速查。
🚑 速查急救包
序号 | 核心机制 | 一句话说明 |
1 | 声明式 API + 控制器调谐 | 控制器通过 Informer 监听资源变化,持续对比期望状态 vs 实际状态,自动修复偏差(这是所有自愈能力的根基) |
2 | 工作负载自愈(ReplicaSet/Deployment) | 自动替换失败容器、节点宕机后重新调度工作负载 |
3 | 领导者选举(Lease) | Controller Manager 和 Scheduler 通过 Lease 分布式锁选主,故障后自动切换(经验值约 15~25 秒) |
4 | 控制平面多副本 | API Server、Controller Manager、Scheduler 各部署 ≥3 个实例,消除单点故障 |
5 | etcd 奇数节点集群 | 3 节点可容忍 1 台故障,5 节点可容忍 2 台;生产环境最低推荐版本为 3.4.29+ 和 3.5.11+ |
🎯 核心实体:谁在背后撑着整个集群?
要理解 K8s 怎么保证可靠性,先得搞清楚谁在干活。
K8s 集群分两大部分:控制平面(Control Plane)和工作节点(Worker Node)。
控制平面是集群的“大脑”,包含四个核心组件:
- kube-apiserver:集群的唯一入口,所有操作都经过它
- etcd:分布式键值存储,保存集群所有状态数据
- kube-controller-manager:运行各种控制器(Deployment、ReplicaSet 等)
- kube-scheduler:负责把 Pod 调度到合适的节点
工作节点上运行的是kubelet和kube-proxy,负责执行控制平面下发的指令。
K8s 保障可靠性的核心思路就一句话:每个可能挂掉的组件,都给它配个“备胎”,并且让它们能自动感知故障、自动切换。
🔁 机制一:声明式 API + 控制器模式——这是所有自愈能力的根基
先讲这个,因为它是 K8s 最核心的设计思想,也是很多人理解不到位的地方。
声明式 API的意思是:你告诉 K8s “我想要什么状态”,而不是“你该怎么做”。
比如你写一个 Deployment:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 3 # 我想要 3 个 Pod 一直跑着 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.21你用kubectl apply提交这个 YAML 后,K8s 不会立刻去创建 3 个 Pod 然后就不管了。实际的工作流程是这样的:
Deployment 控制器会创建一个ReplicaSet,然后ReplicaSet 控制器负责维持指定数量的 Pod 副本。这是一个两层控制器的嵌套关系——Deployment 管 ReplicaSet,ReplicaSet 管 Pod。
每个控制器内部都运行着一个调谐循环(Reconcile Loop),不断做三件事:
- 读取“期望状态”(Deployment 里写的 replicas: 3)
- 读取“实际状态”(当前有多少个 Pod 在跑)
- 如果有偏差,执行操作让实际状态向期望状态靠拢
这个循环本质上是一个持续运行的无限循环,但不是盲目地高频轮询。控制器通过Informer 机制监听 API Server 的资源变化事件(Watch),收到事件后触发调谐。同时,Informer 也会定期将本地缓存的对象重新入队(requeue)触发调谐,作为防止事件丢失的安全网——需要注意的是,resync 并不会从 API Server 重新拉取全量数据,只是将本地缓存中的对象重新加入工作队列。
不同控制器的 resync period 配置差异很大。client-go 的sharedInformerFactory默认不启用 resync(defaultResync = 0);controller-runtime 的 Manager 层面默认SyncPeriod为 10 小时,且各控制器之间会有 10% 的抖动以避免同时发起请求;kube-controller-manager 的--min-resync-period默认值为 12 小时。
这就是 K8s 自愈能力的本质:Pod 挂了 → 控制器发现实际 < 期望 → 自动创建新 Pod。节点宕了 → 控制器发现 Pod 不见了 → 在其他健康节点重新调度。
⚠️一个致命误区:很多人以为 K8s 的“自愈”是 K8s 本身会“监控”Pod 然后“重启”它。不对。是控制器模式在背后持续调谐——你只需要声明“我要 3 个副本”,控制器会一直确保这个声明成立。区别在于:前者是“被动响应”,后者是“主动闭环”。
🧩 机制二:工作负载自愈——从 Pod 到节点的全链路保护
基于控制器模式,K8s 实现了多层级自愈:
Pod 级别:
- ReplicaSet:保证指定数量的 Pod 副本一直在运行
- 容器崩溃(OOMKilled、健康检查失败)→ 自动重启
- 配置
livenessProbe和readinessProbe防止流量进入异常 Pod
节点级别:
- 节点宕机 → kube-controller-manager 检测到 Node 失联 → 将 Node 上的 Pod 标记为
Terminating→ 在其他健康节点重建 - 如果 Pod 挂载了 PersistentVolume,K8s 会将卷重新挂载到新节点
调度级别:
- Kubernetes 调度器在调度 Pod 时会综合考虑节点资源、污点容忍、亲和性等因素,确保 Pod 被调度到符合条件的节点上
💡给自建集群的启发:别只做“故障告警”,要做“故障自愈”。K8s 的控制器模式告诉我们:与其等人收到告警再去修复,不如设计一个持续巡检 + 自动修复的闭环系统。哪怕一开始只做一个“挂了自动重启”的 watchdog,也比纯人工响应强。
👑 机制三:领导者选举——多个备胎,只有一个干活
kube-controller-manager和kube-scheduler这两个组件,在高可用集群中会部署多个实例。但同一时间只有一个实例在干活(Leader),其他都是 Standby。
怎么选出来的?K8s 使用Lease API作为轻量级分布式锁:
- 所有实例竞争同一个 Lease 对象
- 谁先成功更新 Lease,谁就成为 Leader
- Leader 定期续约(renew)
- Leader 挂了不续约,Lease 过期后其他实例重新竞争
LeaseSpec 的关键字段包括:
holderIdentity:当前 Leader 的身份标识(如 Pod 名称或基于主机名的字符串)acquireTime:获得领导权时的时间戳renewTime:当前持有者最近一次续约的时间戳leaseDurationSeconds:租约的有效期,候选实例在尝试获取过期租约之前,应等待此时间再加上一小段宽限时间leaseTransitions:领导权发生变更的次数
当 Lease 不存在或已过期(候选者观察到当前时间 > renewTime + leaseDurationSeconds)时,候选实例尝试使用自己的身份更新此 Lease。由于多个候选者可能同时观察到 Lease 过期,Kubernetes 依赖resourceVersion 乐观并发控制来保证只有一个更新成功。
切换时间由三个参数共同决定:
--leader-elect-lease-duration:默认15s(租约有效期)--leader-elect-renew-deadline:默认10s(Leader 续约截止时间)--leader-elect-retry-period:默认2s(候选者重试间隔)
Leader 停止续约后,其他候选者会在retryPeriod(默认 2s)周期内不断尝试获取 Lease。当renewTime + leaseDurationSeconds过期后,候选者即可成功获取。因此实际切换时间通常在15~25 秒左右——主要取决于leaseDuration的 15s,额外的 0~10 秒来自候选者检测到 Leader 停止续约的时间差和重试间隔。
另外,Kubernetes v1.33 将协调领导者选举(Coordinated Leader Election)标记为 Beta 特性,但默认仍为禁用。该特性允许控制平面组件通过协调领导者选举确定性地选择一个领导者。如需启用,需要通过--feature-gates="CoordinatedLeaderElection=true"开启特性门控,同时通过--runtime-config="coordination.k8s.io/v1beta1=true"启用coordination.k8s.io/v1beta1API 组。对于 Kubernetes 1.36,当特性门控和 API 组被启用时,kube-controller-manager和kube-scheduler会自动使用协调领导者选举。当前大多数生产集群运行在 v1.28–v1.32 之间,仍使用传统 Lease 选举机制。
💡给自建集群的启发:如果你需要部署多实例的服务,用分布式锁实现“一主多备”比“多活”简单可靠得多。K8s 用的是 Lease + 乐观锁(resourceVersion 冲突检测),你可以用 Redis 分布式锁或者 etcd 自身的 Lease 机制来实现类似效果。
🔄 机制四:控制平面多副本——消除单点故障
先讲一个我踩过的坑。最早搭实验集群的时候,图省事搞了个1 Master + 3 Worker的结构。跑 demo 没问题,但一上生产就出事了——Master 节点重启,整个集群的控制面直接瘫痪,调度、扩缩容、发布全部中断。
根本原因就四个字:单点故障(SPOF)。
K8s 官方推荐的高可用方案是:控制平面节点 ≥3 个。
这里有个细节需要注意。官方文档的措辞是“控制平面节点为奇数有利于机器故障或者分区故障时重新选举”,用的是can help(有助于),而不是must(必须)。为什么?因为在堆叠 etcd 拓扑中,etcd 运行在控制平面节点上,而 etcd 需要奇数个成员,所以控制平面节点也建议为奇数。如果使用外部 etcd 拓扑,控制平面节点和 etcd 节点可以各自独立配置奇数数量。
多 Master 部署时,客户端(kubectl、worker 节点的 kubelet)需要通过负载均衡器访问 API Server。常用的方案是 HAProxy + Keepalived 提供 VIP,或者直接用云厂商的 LB。
🗄️ 机制五:etcd 高可用——状态存储不能丢
为什么把 etcd 放在控制平面多副本后面讲?因为理解了控制平面需要多节点之后,你才会明白 etcd 为什么必须是奇数——etcd 就运行在控制平面节点上(堆叠拓扑),控制平面节点的数量决定了 etcd 的容错能力。
etcd 是 K8s 的“命根子”——所有集群状态都在这里面。etcd 挂了,整个集群就废了。
官方文档明确:生产环境中运行的 etcd 最低推荐版本为 3.4.29+ 和 3.5.11+。此外,如果计划升级到 etcd v3.6,官方建议先升级到 v3.5.26 或更高版本——etcd v3.5.26 已引入自动同步机制,将 v2store 中的成员信息同步到 v3store,确保受影响的集群在升级到 3.6.x 之前得到修复,避免出现“僵尸成员(zombie members)”问题。
etcd 基于 Raft 共识协议,需要“多数派”(Quorum)才能工作。所谓多数派,是指超过半数(>50%)的节点达成一致,而不是“≥半数”:
- 1 节点:挂 1 个就 0%,无法工作(仅限测试)
- 2 节点:挂 1 个剩 1 个,1 不大于 1,无法达成共识 —— 所以 2 节点不能容错
- 3 节点:可容忍 1 个节点故障(剩 2 个,2 > 1.5)
- 5 节点:可容忍 2 个节点故障(剩 3 个,3 > 2.5)
etcd 集群成员个数应为奇数。官方建议生产环境使用五个成员的集群。
K8s 官方提供两种 etcd 高可用拓扑:
1. 堆叠(Stacked)etcd
- etcd 和控制平面跑在同一批节点上
- 基础设施少,成本低
- 这是 kubeadm 的默认拓扑
- 每个控制平面节点创建一个本地 etcd 成员,这个 etcd 成员只与该节点的 kube-apiserver 通信——API Server 通过
--etcd-servers参数配置的是本地 etcd 地址,etcd 集群内部通过 Raft 协议在成员之间同步数据 - 风险:一个节点挂掉,etcd 成员和控制平面实例同时丢失
2. 外部(External)etcd
- etcd 集群独立部署,与控制平面节点分离
- 可靠性更高,可以独立扩缩容和维护
- 每个 etcd 主机与每个控制平面节点的 kube-apiserver 通信
- 需要两倍于堆叠拓扑的主机数量
生产环境建议etcd 至少 3 节点,并且分布在不同可用区(AZ),这样能容忍单个可用区故障。
检查 etcd 集群健康状态:
ETCDCTL_API=3 etcdctl --endpoints=<ENDPOINTS> endpoint health💡给自建集群的启发:状态存储和业务逻辑要分离。etcd 独立部署这个思路,在你自己的集群服务里同样适用——别把元数据存储和业务节点绑在一起。
📊 一张表看懂 K8s 可靠性机制
层级 | 组件/机制 | 故障场景 | 应对措施 | 恢复时间(经验值) |
接入层 | HAProxy/云 LB + VIP | 单 LB 故障 | 多实例 + 健康检查 | 秒级 |
API 层 | kube-apiserver(≥3 副本) | 单 API Server 宕机 | 负载均衡转发到其他实例 | 秒级 |
调度层 | kube-scheduler(Leader 选举) | Leader 宕机 | Lease 过期后其他实例竞选 | ~15-25 秒 |
控制层 | kube-controller-manager(Leader 选举) | Leader 宕机 | Lease 过期后其他实例竞选 | ~15-25 秒 |
存储层 | etcd(3/5 节点 Raft) | 单 etcd 节点宕机 | Raft 自动切换 Leader | 数秒内 |
工作负载 | Deployment/ReplicaSet | Pod 崩溃/节点宕机 | 控制器调谐重建 Pod | 取决于调度速度 |
📌 恢复时间为经验估算值,实际受集群规模、网络状况、API Server 负载等因素影响,并非官方承诺值。
❓ 常见问题
Q1:2 个控制平面节点行不行?
在堆叠拓扑中不行。etcd 基于 Raft 协议需要多数派(Quorum),2 节点挂 1 个就只剩 50%,1 不大于 1,无法达成共识。最少 3 个,且建议为奇数。
Q2:Controller Manager 和 Scheduler 的多个实例是“多活”吗?
不是。它们是“一主多备”——通过领导者选举,同一时间只有一个 Leader 在执行实际工作,其他 Standby 随时待命。
Q3:协调领导者选举(Coordinated Leader Election)和传统领导者选举有什么区别?
协调领导者选举是 Kubernetes v1.33 标记为 Beta 的特性(默认禁用),允许控制平面组件通过 LeaseCandidate API 确定性地选择领导者。开启该特性需要同时通过--feature-gates启用特性门控,并通过--runtime-config启用coordination.k8s.io/v1beta1API 组。当前大多数生产集群仍使用传统 Lease 选举机制。
Q4:K8s 能保证 100% 可靠吗?
不能。K8s 保障的是“在组件故障时尽快自动恢复”,而不是“永远不会故障”。控制平面 HA 需要至少 3 台机器,etcd 需要 SSD 和合理的性能规划。可靠性是有成本的。
Q5:我把这些机制抄到自己的集群服务里,需要多大的改动量?
看你的现状。如果你现在是一个单 Master 架构,改造成本最高——需要引入分布式锁(Lease 机制)、状态存储高可用(etcd/ZooKeeper 多节点)、接入层负载均衡。如果你已经是多实例部署但缺少故障切换逻辑,加上领导者选举是最快见效的一步。
⚠️ 彩蛋:一个会让你翻车的隐藏细节
在 Kubernetes v1.28+ 中,kube-controller-manager和kube-scheduler的--leader-elect参数默认是true。但很多人不知道的是:如果你部署了多个控制平面节点,却没有正确配置网络插件和节点间网络互通,Controller Manager 可能在选主成功后因为网络问题无法正常工作,导致集群“半死不活”——API Server 能访问,但 Pod 创建不出来。
我见过不止一个团队在这个坑里翻车。检查清单:
- 确认 CNI 插件(Calico/Flannel/Weave)在所有节点上正常运行
- 确认节点间网络互通(
kubectl get nodes能看到所有节点状态为Ready) - 确认
--cluster-cidr与 CNI 插件的配置相匹配 - 确认每个控制平面节点的 etcd 集群通信正常(2379/2380 端口互通)
不要只盯着--pod-cidr这一个参数——实际故障通常是 CNI 未部署、节点间网络不通、--cluster-cidr与 CNI 配置不匹配等多个因素共同导致的。
📝 总结
K8s 保障可靠性的核心就 5 件事:
- 声明式 API + 控制器调谐:通过 Informer 监听资源变化,持续调谐,自动修复偏差(这是所有自愈能力的根基)
- 工作负载自愈:从 Pod 到节点,全链路自动恢复
- 领导者选举:Controller Manager 和 Scheduler 用 Lease 选主,故障约 15~25 秒切换
- 控制平面多副本:≥3 个 Master,消除单点故障
- etcd 奇数节点:3 节点起步(生产环境最低版本 3.4.29+/3.5.11+,升级到 v3.6 前需先升级到 v3.5.26+),Raft 协议保证一致性
给自建集群的三条建议:
- 别在单点上省钱:控制节点至少 3 个,这是分布式系统的最低门槛
- 设计“调谐”而非“告警”:与其等人收到告警去修,不如让系统自己修
- 状态存储独立出来:别把元数据和业务节点绑在一起,独立 etcd/ZooKeeper 集群是正经做法
你现在的集群服务是怎么做高可用的?有没有踩过什么坑?欢迎在评论区聊聊,我也想知道大家的实践方案。
觉得有用的话,转发给团队里负责基础设施的兄弟,省得他再去翻几百页官方文档了。