INCIDENT-20260916 — etcd 失去多数派导致控制面完全不可用¶
本文档是台账与交接记录,用于复盘与行动项跟踪。 变更是"关闭两台控制面虚拟机",属人为操作;故障是"etcd 成员数不足导致 quorum 丢失"。
1. 概要¶
| 项 | 内容 |
|---|---|
| 发生时间 | 2026-09-16,故障起始时刻无直接日志留存;处置动作在 12:24:34(有证据) |
| 等级 | P1(控制面完全不可用):集群 API 全停,无法创建/修改/删除任何资源 |
| 影响面 | 全部 6 台节点;已有 Pod 未受影响(kubelet 与业务容器继续运行),但集群失去管理能力 |
| 数据影响 | 无数据损坏、无数据丢失(etcd 数据完整,仅因成员数不足无法达成共识) |
| 当前状态 | 已恢复。集群运行正常,且已按新架构缩容为单控制面(见 SOP 5.10) |
| 直接原因 | 3 成员 etcd 中被关闭 2 台,仅剩 1 个成员 < 多数派 2,etcd 无法选举 leader |
2. 影响范围¶
- 受影响:
kubectl全部操作、控制面组件(apiserver/controller-manager/scheduler)、依赖 API 的所有自动化 - 未受影响:已在运行的业务 Pod(不含被关闭节点上的 Pod)、CNI 数据面转发
- 被关闭节点上的 Pod:随虚拟机一起停止(
calico-node、kube-proxy等 DaemonSet 容器) - 无影响:etcd 数据文件本身完整;无 etcd 数据目录损坏,无集群重建需求
3. 时间线¶
时间取自节点与工程的可核验记录;无法取证处已明确标注为推断。
| 时间(CST) | 事件 | 证据来源 |
|---|---|---|
| 09-16 02:56:08 | 部署流程安装 containerd(运行时就绪) | systemctl show containerd -p ActiveEnterTimestamp |
| 09-16 03:07:48 | keepalived 启动,VIP 就绪 | systemctl show keepalived -p ActiveEnterTimestamp |
| 09-16 03:34 | 部署工程最后一次执行(3 成员 etcd HA 集群建成) | 工程目录 backups/20260916_033456/ |
| 03:34 至 12:24 之间 | 人为关闭 master-02、master-03 虚拟机,etcd 由 3 成员降至 1 成员,失去多数派 | 推断(无直接日志留存,依据 member 变更与后续现象) |
| 同期 | apiserver 反复退出与重启 | crictl ps -a:apiserver 容器 ATTEMPT 18(当前 Running)、ATTEMPT 17(Exited) |
| 同期 | 客户端侧 kubectl 报连接失败(TLS 握手超时 / context deadline exceeded) |
现场实测 |
| 09-16 12:24:34 | 处置:启动 master-02 恢复多数派,并重启 master-01 的 kubelet 触发静态 Pod 重建 | systemctl show kubelet -p ActiveEnterTimestamp |
| 09-16 12:24 之后 | apiserver 重建完成,控制面恢复 | 现场实测 curl -sk https://192.168.31.8:16443/healthz 返回 ok |
| 09-16 16:00 起 | 进一步缩容为单控制面(受控操作,见 SOP 5.10) | 工程 backups/20260916_163436/、170705/ |
4. 根因¶
etcd 集群失去多数派(quorum lost),导致无法选出 leader,apiserver 因存储层不可用而反复退出。
etcd 采用 Raft 共识:3 成员集群的多数派为 2,任何写操作与 leader 选举都需 ≥2 个成员达成一致。 当 2 台被关闭后只剩 1 个成员,永远无法满足多数派,集群进入无限选举循环。
证据¶
证据 1:apiserver 反复重启
ssh root@192.168.31.10 "crictl ps -a | grep kube-apiserver"
4f841ad00fbda ... 3 days ago Running kube-apiserver ATTEMPT 18
7dad052463f04 ... 3 days ago Exited kube-apiserver ATTEMPT 17
ATTEMPT 18 表示该容器已被重新拉起 18 次,是控制面持续崩溃重启的直接记录。
证据 2:etcd 反复发起选举、客户端请求超时(故障现场实时抓取)
{"level":"warn","msg":"waiting for ReadIndex response took too long, retrying"}
{"level":"warn","msg":"23d6928b1e4bc362 is starting a new election at term 5"}
{"level":"warn","msg":"etcdserver: no leader"}
etcd-client: rpc error: code = DeadlineExceeded desc = context deadline exceeded
no leader + 反复 starting a new election 是 quorum 不足的典型特征——有候选者、无多数票。
证据 3:apiserver 无法读写 etcd,进程 Fatal 退出
kube-apiserver ... failed to contact etcd
PostStartHook "wait for APIServer" failed ... context deadline exceeded
(现场抓取的 kubelet 侧记录;此后 apiserver 容器被反复重建,形成证据 1 的 18 次重启。)
证据 4:etcd 进程本身始终健康
ssh root@192.168.31.10 "crictl ps -a | grep etcd"
38758502c2508 ... 3 days ago Running etcd ATTEMPT 0
ATTEMPT 0 说明 etcd 容器自创建起从未崩溃重启——故障不在 etcd 进程,而在成员数量。
排除项:不是磁盘故障、不是证书过期、不是网络分区(节点间二层可达)、不是 etcd 数据损坏。
5. 处置过程¶
| 步骤 | 动作 | 说明 |
|---|---|---|
| 1 | 确认 etcd 成员数与健康度 | etcdctl member list 显示 3 成员但仅 1 个可达 |
| 2 | 确认控制面容器状态 | apiserver 反复重启、etcd 未重启(定位为 quorum 问题) |
| 3 | 止血:启动 k8s-master-02 |
virsh start k8s-master-02(宿主机上执行),成员数回到 2,满足多数派 |
| 4 | 等待 etcd 选出 leader | 观察选举结束后 endpoint health 转为健康 |
| 5 | 重启 master-01 的 kubelet | systemctl restart kubelet,触发已退出的 apiserver 静态 Pod 重建 |
| 6 | 验证控制面恢复 | healthz 返回 ok、kubectl get nodes 正常 |
未做的事(有意为之):未执行 kubeadm reset、未重建集群、未从快照恢复——数据完好,
仅需恢复成员数即可,任何重建动作都会带来不必要的数据风险。
6. 恢复验证¶
curl -sk https://192.168.31.8:16443/healthz # 预期:ok(实测通过)
kubectl get --raw='/readyz' # 预期:ok(实测通过)
kubectl get nodes # 预期:全部 Ready(实测通过)
kubectl get pods -A | grep -Ev 'Running|Completed' # 预期:无输出(实测通过)
以上四项在现场处置后均实测通过;随后集群稳定运行,直至 09-16 16:00 起按计划缩容为单控制面。
7. 复盘¶
有效的做法
- 从"apiserver 反复重启 + etcd 从未重启 + member 列表完整"三点迅速锁定是 quorum 问题, 没有误判为 etcd 数据损坏或证书问题,避免了走重建集群的弯路
- 只在必要时动作(启动一台虚机 + 重启 kubelet),未做多余操作
- 恢复后立即做了幂等复跑与成员数核对
拖慢恢复的地方
- 没有第一时间怀疑"节点被关闭":起初按"网络/证书"方向排查,多绕了几步;
应先核对 etcd 成员可达性(
endpoint health而非member list——后者对关机成员仍显示started) - 缺乏外部监控:故障从发生到被发现依赖人工操作时才暴露,期间无人知晓
member list的 STATUS 列有误导性:它表示"是否曾启动过",不反映实时健康- 无人值守期间关闭多数派节点,本身缺少前置约束(没有"最多只能关一台控制面"的护栏)
根因归类:配置/操作失误(人为关闭多数派节点),叠加"无告警、无护栏"的运维缺口。
8. 行动项¶
| # | 事项 | 责任人 | 期限 | 状态 |
|---|---|---|---|---|
| 1 | 缩容为单控制面,从根本上消除"误关多数派"场景(etcd 单成员 quorum=1) |
用户 | 2026-09-16 | ✅ 已完成(SOP 5.10) |
| 2 | master-02/03 关闭 libvirt autostart,避免宿主机重启后自动拉起"残缺控制面" | 用户 | 2026-09-16 | ✅ 已完成 |
| 3 | 在部署工程前置校验中加入"etcd 多数派"检查,成员不足时拒绝缩容 | 运维 | 2026-09-16 | ✅ 已完成(control_plane_scale.py pre_check) |
| 4 | 部署工程加入 hostname 与时间同步托管,减少人工准备项 | 运维 | 2026-09-16 | ✅ 已完成 |
| 5 | 为集群接入外部监控与告警(控制面可用性、节点 Ready、etcd 健康) | 用户 | 待定 | ⬜ 未开始(可复用 monitoring_upgrade/ 工程) |
| 6 | 实施 etcd 定时快照并做一次恢复演练 | 用户 | 待定 | ⬜ 未开始(当前仅有手工快照) |
| 7 | 若日后扩回 3 控制面,需建立"任何时刻只操作一台控制面"的书面约束 | 用户 | 待定 | ⬜ 未开始(已在 RUNBOOK 第 1 章写明铁律) |
责任人一栏中"用户"指环境所有者(本环境为单人学习环境,无团队分工); 期限"待定"表示尚未排期,不是已放弃。
来源:
/home/rain/Documents/k8s_ins/k8s_136_cluster_deploy/docs/INCIDENT-20260916-etcd-quorum-lost.md| 最后更新:2026-09-19 16:19(由build/sync.py同步,请勿在站点侧修改)