系统学习 故障排查学习路径:从方法论到系统、网络、数据库与 K8s

incident-etcd-quorum

三节点 etcd 在两台成员先后不可用后失去多数派,apiserver 随之大面积超时;这次故障真正的救援对象是 quorum,而不是 apiserver。

现象

某业务集群的控制面在短时间内出现成片超时。kubectl get、kubectl apply 频繁卡住后报 etcdserver: request timed out,apiserver 的 5xx 比例快速上升,部分控制器日志出现 lost leader、failed to renew lease。与此同时,已经运行的业务 Pod 仍在提供服务,数据面流量没有立即中断,这让值班一度以为只是“kubectl 客户端或网络的问题”。

控制面的异常有明显层次:apiserver 的 /livez 还能返回,/readyz 却开始失败;apiserver 日志里反复出现连接 etcd 超时;etcd 自身日志出现 no leader、心跳发送失败和 slow fdatasync。这些信号说明 apiserver 只是受害者,真正失去健康的是它依赖的 etcd 集群。

kubectl get --raw='/livez?verbose'
kubectl get --raw='/readyz?verbose'
kubectl get --raw='/readyz?verbose&exclude=etcd'
etcdctl --endpoints=https://<node-a>:2379,https://<node-b>:2379,https://<node-c>:2379 \
  --cacert=<ca> --cert=<cert> --key=<key> \
  endpoint health
etcdctl --endpoints=<same> --cacert=<ca> --cert=<cert> --key=<key> \
  endpoint status -w table
etcdctl --endpoints=<same> --cacert=<ca> --cert=<cert> --key=<key> \
  member list --consistency=s -w table

故障期间,endpoint health 对所有端点都返回失败:失去多数派后,连仍可连接的成员也无法完成需要 quorum 的读。member list --consistency=s 仍能列出全部成员,但 endpoint status 显示没有稳定的 leader。到这里可以确认:集群已经失去多数派,任何依赖 etcd 写入的操作都会失败。

时间线

影响面

控制面写入能力中断约 30 分钟,apiserver 从出现 5xx 到完全恢复约 40 分钟。受影响的主要是“需要改变集群状态”的操作:发布、扩缩容、更新 Service 与 Ingress、证书轮换、创建 Pod、更新 ConfigMap 和 Secret,以及依赖 apiserver 选主的控制器。已经调度好的业务 Pod 继续运行,已有连接不受直接影响,因此用户侧不一定立刻感知,但故障期间的发布和故障转移全部失效,风险被推迟到恢复之后。

监控和排障本身也受到影响。依赖 apiserver 查询的告警面板出现空白,部分事件记录缺失;值班只能依赖 etcd 自身日志和节点指标。恢复后出现了短暂的 Endpoint 状态陈旧:服务端已经恢复,但网关仍按旧的后端列表转发,直到控制器重新同步。整个过程没有观察到 etcd 数据丢失,但丢失多数派期间无法排除少数派成员数据落后的可能,因此恢复后仍需核对成员状态和快照完整性。

误导线索

第一个误导是 apiserver 的 CPU 和 5xx。监控上 apiserver 的 CPU 确实升高,日志里也全是超时,看起来像 apiserver 自身过载。但 apiserver 是无状态组件,它的读写都依赖 etcd;重启 apiserver 不会创造 quorum,只会让客户端重连更频繁。/readyz?verbose 把 etcd 检查项单独暴露出来后,才把“apiserver 故障”与“etcd 不可用”区分开。

第二个误导是“还有一个 etcd 进程活着”。端口能连上、进程仍在运行,容易让人以为集群仍然可用。可达不等于有 quorum:endpoint health 执行的是需要多数派的读,失去多数派后连可达成员也会失败;判断 quorum 要看 endpoint status 的 leader 状态和 etcd 日志。实际证据是:唯一可达的成员也无法完成写入,endpoint status 显示没有 leader,日志明确报告没有 leader。

第三个误导来自变更节奏。计划内重启在变更系统里有记录,但没有和 etcd 健康检查关联;执行人默认“三节点坏一个没关系”,没有注意到另一个成员已经因为磁盘慢而处于不健康状态。真正推翻“单节点维护无害”的证据,是 etcd 日志在维护开始前就出现了 slow fdatasync 和心跳超时,说明容错额度已经被消耗掉了。

根因

集群是三成员 etcd,只能容忍一个成员失效。成员 A 的 WAL 与容器日志共用同一块盘,业务日志高峰把磁盘 IOPS 打满,etcd 的 fsync 延迟升高,心跳无法按时发送,成员逐渐被判为不健康。这个状态没有触发独立告警,因为监控只盯 apiserver 的 5xx,没有盯 etcd 的 leader 和 fsync。

随后成员 B 所在节点按计划重启。执行人看到集群“还有两个成员”,没有先确认 A 是否健康,也没有检查当前 leader 分布。B 停机后,A 已经不健康,只剩 C 一个成员,达不到三节点所需的两个票,集群失去多数派。apiserver 的所有写请求都在 etcd 层等待超时,控制面进入雪崩。

更深一层的问题是缺少维护闸门。三节点集群的容错额度只有一个,任何一次维护都必须以“其余成员全部健康且有稳定 leader”为前提;这次维护只检查了节点数量,没有检查健康状态。磁盘规划也有责任:etcd 对 fsync 极其敏感,却和大量日志共享 IOPS,容量规划只看了空间,没有看延迟和 IOPS。

修复

第一步不是重启所有组件,而是恢复一个数据完整的成员。优先恢复刚刚维护的成员 B,因为它的数据目录完整、停机时间短;观察它能否追上日志并与 C 重新选出 leader。此时不要对 etcd 执行 --force-new-cluster,也不要把多个成员的数据目录清空重建,否则可能造成不可逆的数据丢失或脑裂。

etcd 的部署方式决定日志入口:systemd 管理的外部 etcd 用 systemctl 和 journalctl,kubeadm 的静态 Pod 用 kubectl -n kube-system logs etcd-<node> 或 crictl logs。不要因为某条命令没有输出就判断进程不存在。

systemctl status etcd
journalctl -u etcd --since '30 min ago' | tail -100
df -h /var/lib/etcd
iostat -x 1 5

如果恢复 B 后仍无法达到多数派,再检查 A 的磁盘和进程。若 A 只是暂时 I/O 饱和,先降低磁盘压力、等待其恢复;若 A 的数据目录损坏且集群已经恢复多数派,才在健康集群上执行成员摘除与重新加入:

etcdctl --endpoints=<healthy-endpoints> --cacert=<ca> --cert=<cert> --key=<key> \
  member list -w table
etcdctl --endpoints=<healthy-endpoints> --cacert=<ca> --cert=<cert> --key=<key> \
  member remove <member-id>
etcdctl --endpoints=<healthy-endpoints> --cacert=<ca> --cert=<cert> --key=<key> \
  member add <name> --peer-urls=https://<new-node>:2380

新成员必须以 --initial-cluster-state=existing 加入,并按 member add 输出的成员列表配置 --initial-cluster,数据目录保持为空,由集群同步数据。任何成员摘除、重建之前,先对健康成员执行 snapshot save 并校验;如果全部成员都已丢失数据,只能从快照恢复到新集群,而不是强行拼回旧集群。恢复多数派后,再验证 apiserver 的 /readyz、控制器选主、EndpointSlice 和网关后端,必要时触发一次控制器重新同步。

预防

把“能否安全维护”做成脚本而不是口头约定。维护前必须满足:endpoint health --cluster 全部健康、member list 成员齐全、存在稳定 leader、最近没有 leader 切换、fsync 延迟在阈值内;任何一项不满足就拒绝停机。三成员集群一次只允许停一个成员,且维护窗口内冻结其他控制面变更。

监控要覆盖 etcd 的“内部健康”,而不只是 apiserver 的外部表现。重点指标包括 leader 是否存在、leader 切换次数、WAL fsync 延迟、peer 网络往返、成员数量、磁盘 IOPS 与延迟。给 etcd 使用独立磁盘,WAL 与日志分盘,限制节点上的日志写入;对控制面节点设置系统保留资源,避免业务 Pod 与 etcd 抢 I/O。定期做快照恢复演练,确认备份可用、恢复步骤不需要临场发挥。

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. apiserver 大面积 5xx 时,为什么不能先重启 apiserver?
  2. 只有一个 etcd 进程/端口仍然可达,为什么仍可能没有 quorum?
  3. 恢复多数派时,为什么优先恢复数据完整的旧成员,而不是直接清空数据重建?
答案
  1. apiserver 无状态,读写依赖 etcd。若 etcd 已经失去多数派,重启 apiserver 只会增加重连和选主抖动,不能恢复写入能力。应先用 /readyz、etcd 端点和成员列表(--consistency=s)确认 quorum 状态。
  2. quorum 看的是集群中能参与投票的成员数量,不是单个进程是否存活或端口是否可达。一个成员可达但其余成员不可达时,它无法单独完成写入和选举,endpoint status 的 leader 状态和 etcd 日志才是有效证据。
  3. 旧成员的数据目录完整,重新加入后可以直接追日志,风险最低。清空重建会触发成员摘除与重新加入,只有在集群已恢复多数派、确认原数据不可用时才可执行,否则可能扩大数据丢失范围。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令