跳到主要内容

故障排查与最佳实践

先看什么​

etcd 出问题时,第一反应不应该是翻日志,而是先确认「集群现在是什么状态」。按这个顺序过一遍,能快速定位到问题大类:

# 1. 每个端点还活着吗
etcdctl endpoint health --cluster

# 2. 谁有 leader、raft 进度差多少、db 多大
etcdctl endpoint status -w table

# 3. 有没有 NOSPACE / CORRUPT 之类的告警
etcdctl alarm list

# 4. 成员列表和 peer 地址
etcdctl member list -w table

再看日志里的关键词:

journalctl -u etcd --since "1 hour ago" | grep -iE \
'no leader|leader changed|timed out|slow|space|corrupt|fsync|heartbeat|handshake'
现象优先怀疑
所有请求超时,endpoint health 全红多数派丢失、网络分区
只有一台红,其余正常该成员进程挂了或磁盘满
读正常、写报 database space exceededNOSPACE,db 超配额
leader 反复切换,日志有 heartbeat 超时慢盘、网络抖动、CPU 饱和
apiserver 报 too old resource versionwatch 断流后重新 list,或压缩过猛
启动报 cluster ID mismatch数据目录和集群对不上

下面按最常见的四类故障展开:多数派丢失、NOSPACE、慢盘、watch 泄漏。

多数派丢失​

现象​

典型表现是所有写请求卡住或超时,etcdctl endpoint health 返回不健康,日志里反复出现:

{"level":"warn","msg":"failed to get leader"}
{"level":"error","msg":"etcdserver: no leader"}
{"level":"warn","msg":"lost the leadership"}

apiserver 会跟着报 context deadline exceeded、etcdserver: request timed out,kubectl get 也可能卡住。K8s 里已经运行的 Pod 不受影响,但任何需要读写 apiserver 的操作都会失败。

原因​

多数派丢失,顾名思义就是「能正常工作的成员数不到一半以上」。常见触发:

  • 两台机器同时宕机、断电、内核 panic。
  • 交换机或安全组把 2380 端口封了,成员之间互相看不见。
  • 某个成员磁盘满、fsync 卡死,虽然进程还在,但无法参与投票。
  • 时钟漂移严重,证书校验失败导致 peer 连接断开。
  • 误操作把成员的 initial-cluster 改错,重启后自己起了一个新集群。

先排查「是不是能救回来」:机器还能不能启动、网络通不通、磁盘有没有满。只要能恢复多数派,优先恢复成员,不要急着做灾难恢复。

恢复策略​

按优先级从高到低:

策略一:把挂掉的成员拉起来。 修好机器、清理磁盘、恢复网络,然后 systemctl start etcd。成员重启后会从 leader 同步落后的日志,追上后集群自动恢复。这是最安全、数据最完整的路径。

策略二:用快照重建集群。 如果挂掉的机器彻底没了(比如云主机释放、磁盘损坏),而手上有多数派成员的最后快照,可以参考部署篇的恢复流程,用同一份快照重建三个成员。恢复后 revision 会回到快照时刻,快照之后的数据会丢。

策略三:--force-new-cluster 单节点强启。 这是最后手段,只在「没有可用快照、又必须把集群拉起来」时用。3.5 还保留这个参数,3.6 起官方更推荐走快照恢复。

操作步骤:

# 1. 停掉所有 etcd 成员,先备份各自的数据目录
sudo systemctl stop etcd
sudo cp -a /var/lib/etcd /var/lib/etcd.bak-$(date +%F-%H%M)

# 2. 选数据最全的一个成员(raft index 最大),用原配置加 --force-new-cluster 启动
sudo -u etcd etcd --config-file=/etc/etcd/etcd.conf.yml --force-new-cluster

# 3. 确认它变成单节点集群,能读写
etcdctl endpoint status -w table
etcdctl member list -w table

# 4. 把其他成员的旧数据清掉,重新以 existing 状态加入
etcdctl member add etcd-02 --peer-urls=https://10.0.0.12:2380
force-new-cluster 的代价

它会丢弃其他成员上、还没同步到这个节点的日志;如果选错了成员,可能丢掉已经提交的数据。不要把多个成员同时用 --force-new-cluster 启动,那会得到几个互不相干的单节点集群,比原来更难收拾。操作前必须备份数据目录,并明确告诉业务方可能回退到哪个时间点。

预防​

  • 3 节点起步,跨机架或跨可用区部署,别把三台放在同一台宿主机上。
  • 监控 etcd_server_has_leader 和 leader_changes_seen_total,多数派丢失前通常有选主抖动。
  • 定期做快照,并验证恢复流程。多数派丢失时,快照是最后的底牌。
  • 变更 initial-cluster、防火墙、证书前,一次只动一个成员。

NOSPACE​

现象​

写请求返回:

etcdserver: mvcc: database space exceeded

etcdctl alarm list 会看到:

memberID:8e9e05c52164694d alarm:NOSPACE

这时候读还能用,写全部失败。K8s 表现为新 Pod 创建不了、Deployment 扩缩容卡住、Service 更新不生效,但已有 Pod 继续运行。这是 etcd 的保护机制:db 超过配额后拒绝写入,避免把磁盘彻底写满导致数据损坏。

原因​

  • 没有开自动压缩,历史 revision 一直堆积。
  • 删除的 key 只是逻辑删除,空间没释放,长期没有 defrag。
  • K8s 里 Event、旧 ReplicaSet、已完成 Job 等对象太多。
  • 有人往 etcd 里写了大量业务数据或大对象。
  • 配额本身设得太小(默认只有 2 GiB)。

处理步骤​

先看 db 大小和配额:

etcdctl endpoint status -w table
etcdctl endpoint status -w json | jq '.[0].Status | {dbSize, dbSizeInUse}'
etcdctl alarm list

# 配额只在 /metrics 里,HTTP 场景直接抓;HTTPS 要加 --cacert/--cert/--key
curl -s http://127.0.0.1:2379/metrics | grep '^etcd_server_quota_backend_bytes'

然后按「压缩 → 碎片整理 → 解除告警」的顺序处理:

# 1. 压缩到当前 revision,释放历史版本
REV=$(etcdctl endpoint status -w json | jq -r '.[0].Status.header.revision')
etcdctl compact $REV

# 2. 逐台 defrag,回收磁盘空间
etcdctl --endpoints=https://10.0.0.11:2379 \
--cacert=/etc/etcd/certs/ca.crt \
--cert=/etc/etcd/certs/client.crt \
--key=/etc/etcd/certs/client.key \
defrag --command-timeout=600s

# 3. 三台都整理完,解除 NOSPACE 告警
etcdctl alarm disarm

# 4. 验证写入恢复
etcdctl put /demo/recovered ok
etcdctl get /demo/recovered

几个关键点:

  • compact 是集群级操作,对一个成员执行即可;defrag 是成员级操作,三个成员都要做,一台一台来。
  • 删除 key 不会立刻释放空间。del 之后还要 compact + defrag,db 文件才会真正变小。
  • defrag 需要额外磁盘空间。如果磁盘已经 100% 满,先扩容或清掉无关文件,否则 defrag 会失败。
  • 解除告警前确认 db 已低于配额,否则写入很快又会触发 NOSPACE。
紧急情况下的临时扩容

如果磁盘还有空间,但 compact/defrag 需要时间,可以把所有成员的 --quota-backend-bytes 临时调大并滚动重启,再执行 alarm disarm,先恢复写入,然后安排治理。调大配额是止血,不是治疗;db 继续涨,下一次还是同样的结局。

预防​

  • 开 --auto-compaction-mode=periodic --auto-compaction-retention=1h。
  • 配额按 8 GiB 设置,db 到 80% 就告警。
  • 每月检查一次碎片率,超过 50% 安排 defrag。
  • 定期清理 K8s 里不用的对象,尤其是 Event 和 Completed 的 Job。
  • 不要把 etcd 当业务 KV 用,大对象放对象存储或数据库。

慢盘导致 leader 抖动​

现象​

日志里高频出现:

{"level":"warn","msg":"failed to send out heartbeat on time (exceeded the 100ms timeout for 1s)"}
{"level":"warn","msg":"leader failed to send out heartbeat; took too long"}
{"level":"warn","msg":"apply request took too long"}

etcd_server_leader_changes_seen_total 持续增长,apiserver 请求间歇性超时,但进程没挂、端口也在。这种「看起来活着但一直抖」的问题,十有八九是磁盘。

排查​

先看 etcd 自己的指标:

# WAL fsync P99,超过 10ms 就要警惕(按成员看)
histogram_quantile(0.99, sum by (instance, le) (rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])))

# backend commit P99
histogram_quantile(0.99, sum by (instance, le) (rate(etcd_disk_backend_commit_duration_seconds_bucket[5m])))

# 慢 apply 速率
rate(etcd_server_slow_apply_total[5m])

# 心跳发送失败速率
rate(etcd_server_heartbeat_send_failures_total[5m])

再上机器看 IO:

# 磁盘利用率和 await
iostat -x 1

# 看 etcd 进程的读写情况
pidstat -d 1 -p $(pidof etcd)

# 看是不是有别的进程在抢 IO
iotop -o

如果 iostat 的 await 长期高于 10ms、%util 接近 100%,基本可以确定磁盘扛不住。云盘还要看 IOPS 和吞吐是否被规格限制,突发额度用完后的性能会断崖式下跌。

处理​

  • 换盘:把数据目录迁到独立 SSD/NVMe,或者升级云盘规格。这是最有效的办法。
  • 隔离 IO:别让日志采集、镜像拉取、业务写入和 etcd 共用一块盘。K8s 控制面节点尤其容易犯这个错。
  • 降负载:减少不必要的 apiserver 写入,关掉高频的控制器重试,清理 Event 风暴。
  • 调时间参数:跨可用区、网络 RTT 较大的环境,可以适当加大心跳和选举超时,例如:
heartbeat-interval: 250
election-timeout: 2500

这会降低误选主概率,但故障切换也会变慢。先确认磁盘和网络没问题,再考虑调参数;拿调参去掩盖慢盘,只是把问题推迟。

不要盲目加大选举超时

选举超时从 1000ms 调到 5000ms,故障切换时间也会变成 5 秒以上,apiserver 的请求超时可能先触发。调参要配合压测和监控,改完观察至少一周。

预防​

  • etcd 独占 SSD,容量留 20% 以上余量。
  • 控制面节点不要跑重 IO 业务,容器运行时和镜像目录也尽量分盘。
  • 监控 fsync P99,超过 10ms 就发告警,别等 leader 开始抖。
  • 云环境选带稳定 IOPS 的盘,注意突发性能额度。

watch 泄漏与连接数​

现象​

  • etcd 内存持续上涨,etcd_debugging_mvcc_watcher_total 只增不减。
  • etcd_debugging_mvcc_slow_watcher_total 增长,日志出现 watcher channel is full 或 slow watcher。
  • 宿主机 2379 的 ESTABLISHED 连接数远高于正常水平。
  • apiserver 间歇性报 etcdserver: too many requests 或 watch 断流。

原因​

watch 是长连接,正常客户端的 watcher 数量应该跟「对象数量 × 控制器数量」大致成比例,并且随控制器退出而回收。持续上涨通常是:

  • 客户端创建 watch 后没有 Cancel,进程重启也没释放。
  • 自定义控制器在错误重试循环里反复建 watch,旧的不关。
  • apiserver 的 watch 缓存失效,大量客户端同时重连、重新 list,形成风暴。
  • 慢客户端消费不过来,etcd 端缓冲打满,watch 被取消后客户端立刻重连,循环往复。

排查​

# 当前 watcher 数(HTTPS 场景加 --cacert/--cert/--key 并换成 https)
curl -s http://127.0.0.1:2379/metrics | grep -E 'etcd_debugging_mvcc_(watcher|slow_watcher)_total'

# 连接数
ss -tn state established '( sport = :2379 )' | tail -n +2 | wc -l

# 按来源 IP 排序,找出连接最多的客户端
ss -tn state established '( sport = :2379 )' | tail -n +2 \
| awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head

如果是 apiserver 发起的连接,去查 apiserver 日志里的 watch chan error、too old resource version;如果是某个自定义控制器,查它的重启次数和 goroutine 数。

处理​

  • 先止血:重启泄漏的客户端(通常是 apiserver 或某个控制器),watcher 数会立刻回落。
  • 再定位:对比重启前后的连接来源,找到持续建连的组件,修代码或升级版本。
  • 处理 410 Gone:apiserver 报 too old resource version 时,客户端应该重新 list 再 watch;如果压缩太激进,watch 起点频繁失效,可以适当放宽 --auto-compaction-retention,给客户端留重连时间。
  • 调 FD 上限:连接数确实大时,确认 systemd 的 LimitNOFILE=65536 已生效,os_fd_used 不要逼近 os_fd_limit。
watch 数量没有硬上限

etcd 不会因为 watcher 太多直接拒绝,而是靠内存和事件缓冲扛着。等到开始 OOM 或大量慢 watcher,已经很危险了。把 watcher 数和连接数纳入监控,比设一个拍脑袋的上限更实用。

其他高频故障​

报错 / 现象常见原因处理
x509: certificate has expired证书过期续签并滚动重启成员,检查 SAN
transport: authentication handshake failedCA 不匹配、客户端没带证书对齐 CA,检查 --client-cert-auth 和 etcdctl 证书
cluster ID mismatch数据目录属于另一个集群找回原数据目录,或清空后以 existing 加入
request timed outleader 过载、慢盘、提案积压查 fsync、pending proposals、网络
snapshot apply in progress新成员或落后成员在同步快照等同步完成,别急着重启
database space exceededNOSPACE按上文 compact + defrag + disarm
no space left on device磁盘真的满了清日志/扩容,检查 WAL 和 db 目录
时间跳变、选主异常NTP 不同步配置 chrony,校准所有成员

最佳实践清单​

把前面几篇的结论收成一份可以照着核对的清单:

  1. 规模选 3 或 5,奇数节点,跨机架/可用区部署,永远不要 2 或 4 节点。
  2. 磁盘用独立 SSD/NVMe,fsync P99 控制在 10ms 以内,容量留 20% 余量。
  3. 生产必须开 TLS,客户端和 peer 两套证书,client-cert-auth 和 peer-client-cert-auth 都打开。
  4. 证书纳入监控,剩余 30 天告警;kubeadm 集群记得定期 kubeadm certs renew。
  5. 配额设 8 GiB,db 到 80% 告警;不要把配额当成解决增长的手段。
  6. 开自动压缩,--auto-compaction-mode=periodic --auto-compaction-retention=1h。
  7. 定期 defrag,逐台执行、低峰操作、leader 最后,碎片率超过 50% 就安排。
  8. 每小时快照 + 异地保存,每季度做一次恢复演练,没演练过的备份不算数。
  9. 监控四件套:leader 切换、fsync 延迟、db 配额、NOSPACE 告警;再加 watch 数和连接数。
  10. 变更一次只动一个成员,改配置、换证书、扩缩容都一样,动完确认健康再动下一个。
  11. 不要把 etcd 当业务库,不存大对象、不存日志、不存图片,单 value 控制在 KB 级。
  12. 控制面节点保持干净,别和重 IO 业务混部,NTP 必须开。
  13. 灾难恢复优先快照,--force-new-cluster 是最后手段,操作前备份数据目录并评估数据回退。
  14. 告警要演练,阈值先观察基线再收紧,避免误报导致整组告警被静音。
一份最小应急卡片

出事先做三件事:endpoint health --cluster 看谁活着,alarm list 看有没有 NOSPACE,journalctl -u etcd 搜 leader 和 timeout。这三步能覆盖八成以上的 etcd 故障,剩下的再按本篇场景往下查。

小结​

  • 排查顺序:健康检查 → 成员状态 → alarm → 日志关键词,先判断是可用性问题还是容量问题。
  • 多数派丢失优先恢复成员,其次快照重建,--force-new-cluster 是最后手段且有数据丢失风险。
  • NOSPACE 的解法是 compact → defrag → alarm disarm;删除 key 不会自动释放磁盘,预防靠自动压缩和配额告警。
  • leader 抖动先查 fsync 和磁盘 IO,换盘、隔离 IO 比调大选举超时更根本。
  • watch 泄漏看 watcher 数、慢 watcher、连接来源,先重启泄漏客户端止血,再修代码。
  • 最佳实践的核心就三句话:磁盘要快、变更要慢、备份要能恢复。

到这里 etcd 系列的正篇就结束了。附录里整理了 etcdctl 命令速查和官方资源,日常排查时可以随手翻。