etcd 运维与性能调优:备份恢复、碎片整理与配额

系统讲解 Kubernetes 背后 etcd 的运维要点:key 布局与 MVCC revision、Raft 选主与奇数节点拓扑、snapshot save/restore 的完整备份恢复流程、compaction 与 defrag 的区别及自动化、quota-backend-bytes 与 NOSPACE 告警处理、磁盘 fsync 与网络参数调优,以及核心监控指标与故障定位。

etcd 是 Kubernetes 的唯一持久化存储——集群里所有的 Pod、Service、Secret、CRD 实例、RBAC 规则、Lease 对象都存在这里。它一旦不可用,API Server 只能读缓存、无法写入,整个控制面等于瘫痪;它一旦数据损坏且没有备份,集群只能重建。然而 etcd 的运维门槛恰恰在于:它不是一个普通数据库,它的性能瓶颈几乎全在磁盘 fsync 延迟上,而它的容量问题表现为一个容易被忽略的 NOSPACE 告警。本文覆盖从拓扑设计、备份恢复、碎片整理到配额与调优的完整运维面。

1. etcd 的角色与数据模型

1.1 唯一数据存储

kubectl / 控制器 / kubelet
        ↓ (只与 API Server 通信)
   kube-apiserver
        ↓ (唯一直接读写 etcd 的组件)
      etcd 集群(3 或 5 节点)

这条链路决定了两个结论:① 没有任何组件能绕过 API Server 直接改 etcd(否则会破坏 watch cache 一致性);② etcd 的延迟会 1:1 放大成整个集群的写入延迟。

1.2 key 布局

etcd 里的 key 是扁平的字节串,Kubernetes 按资源类型分组:

# 常用查询(在 etcd 容器内用 etcdctl)
etcdctl get /registry/pods/default/nginx --keys-only
etcdctl get /registry/ --prefix --keys-only | head
/registry/pods/<namespace>/<name>
/registry/services/specs/<namespace>/<name>
/registry/secrets/<namespace>/<name>
/registry/events/<namespace>/<name>.<uid>
/registry/apiextensions.k8s.io/customresourcedefinitions/<name>
/kubernetes.io/leases/kube-node-lease/<node>        # 节点心跳 Lease

Event 与 Lease 是量最大的两类 key:Event 默认保留 1 小时(--event-ttl),Lease 每个节点每秒刷新一次。大集群的 etcd 增长,80% 来自这两者。

1.3 MVCC 与 revision

etcd 使用 MVCC(多版本并发控制),每次写都会让全局 revision 单调递增,旧版本保留直到被压实(compaction):

etcdctl endpoint status --write-out=table
# 输出含:DB SIZE、IS LEADER、RAFT INDEX、RAFT TERM、RAFT APPLIED INDEX、REVISION

DB SIZE 包含历史版本,所以**「删了对象但 DB 没变小」是正常的**——需要 compaction + defrag 才会真正回收空间。

一句话:etcd 的空间由「历史版本 + Event/Lease」共同撑大,只看对象数量无法判断容量风险,要看 DB SIZE 与 REVISION。

2. 集群拓扑与选主

2.1 为什么必须是奇数节点

etcd 基于 Raft 共识算法,写操作需要**多数派(quorum = N/2 + 1)**确认:

节点数多数派容忍故障数
110
321
532
743

偶数节点(如 4)的多数派是 3,容忍度与 3 节点相同(1),但成本更高——所以永远用奇数。生产推荐 3(中小集群)或 5(大规模 / 跨 AZ)。

2.2 Raft 写入流程

客户端 → Leader
  Leader 追加日志(本地 WAL 落盘 + fsync)
  Leader 并行发 AppendEntries 给 Follower
  Follower 落盘后 ACK
  多数派 ACK → Leader 提交(apply 到 KV 存储)→ 返回客户端

每一次写入至少两次 fsync(Leader 一次、多数 Follower 各一次)。所以 etcd 的延迟下限由最慢的那个节点的磁盘 fsync 决定——这就是为什么 etcd 必须用 SSD/NVMe,且不能与高 IO 负载混部。

2.3 learner 与成员变更

# 查看成员与健康
etcdctl member list -w table
etcdctl endpoint health --cluster
etcdctl endpoint status --cluster -w table

新增成员时用 learner 模式先同步数据、再提升为投票成员,避免新节点加入瞬间拖慢集群:

etcdctl member add etcd-4 --learner --peer-urls=https://10.0.1.14:2380
# 等 learner 追上后
etcdctl member promote <member-id>

3. 备份与恢复

3.1 快照备份

# 在任一 etcd 节点上执行(etcdctl v3 API)
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  snapshot save /backup/etcd-$(date +%Y%m%d%H%M).db

# 校验快照完整性(重要!)
etcdctl snapshot status /backup/etcd-20261007.db -w table
# 输出:hash、revision、total keys、total size

要点:snapshot save 是在线操作,对集群影响很小(它会读一份一致性的快照),但要确保备份期间 Leader 没有发生切换(切换会导致快照失败)。建议对每个 etcd 节点都跑一次或至少固定一台。

3.2 恢复流程

恢复不是把文件拷回去,而是一个正式流程:

# 1) 停止所有 etcd 与 apiserver(否则会写入不一致数据)
systemctl stop kubelet
# 用静态 Pod 部署时,移动 manifest 即可
mv /etc/kubernetes/manifests/etcd.yaml /tmp/

# 2) 在每个节点上执行 restore(注意 initial-cluster 参数)
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-20261007.db \
  --name=etcd-1 \
  --initial-cluster=etcd-1=https://10.0.1.11:2380,etcd-2=https://10.0.1.12:2380,etcd-3=https://10.0.1.13:2380 \
  --initial-cluster-token=etcd-cluster-1 \
  --initial-advertise-peer-urls=https://10.0.1.11:2380 \
  --data-dir=/var/lib/etcd-restore

# 3) 替换数据目录并重启
rm -rf /var/lib/etcd && mv /var/lib/etcd-restore /var/lib/etcd
mv /tmp/etcd.yaml /etc/kubernetes/manifests/

最容易踩的坑:

坑后果对策
restore 时 --name/--initial-advertise-peer-urls 写错成员身份错乱每个节点用自己的一组值
忘了 --initial-cluster 包含全部成员集群起不来三个节点用同一份 initial-cluster
数据目录权限不对etcd 启动失败chown -R etcd:etcd
只恢复一个节点就启动全部数据分叉三个节点都 restore 后再一起启动

3.3 定期备份方案

# 用 CronJob 或集群外的定时任务做备份(推荐放在控制面外)
apiVersion: batch/v1
kind: CronJob
metadata:
  name: etcd-backup
  namespace: kube-system
spec:
  schedule: "0 */2 * * *"          # 每 2 小时
  jobTemplate:
    spec:
      template:
        spec:
          hostNetwork: true
          nodeSelector:
            node-role.kubernetes.io/control-plane: ""
          tolerations:
            - operator: Exists
          containers:
            - name: backup
              image: registry.k8s.io/etcd:3.5.9-0
              command:
                - /bin/sh
                - -c
                - |
                  etcdctl --endpoints=https://127.0.0.1:2379 \
                    --cacert=/etc/kubernetes/pki/etcd/ca.crt \
                    --cert=/etc/kubernetes/pki/etcd/server.crt \
                    --key=/etc/kubernetes/pki/etcd/server.key \
                    snapshot save /backup/etcd-$(date +%s).db
          restartPolicy: OnFailure

备份必须异地存储并定期演练恢复——没有演练过的备份等于没有备份。集群级的灾难恢复策略可参考 Kubernetes 备份与灾难恢复 。

一句话:etcd 备份是「快照 + 异地 + 演练」三件套,恢复是「全停 → 逐节点 restore → 一起启动」的正式流程,任何一步偷懒都会让集群回不来。

4. 碎片整理与压缩

4.1 compaction 与 defrag 的区别

这是 etcd 运维里最常混淆的一对概念:

操作作用是否释放磁盘是否影响服务
compaction删除某个 revision 之前的历史版本否(逻辑删除)几乎无感
defrag重整 boltdb 文件,回收空洞是阻塞该节点读写

顺序必须是先 compaction、再 defrag——直接 defrag 无法回收仍被引用的历史版本。

4.2 手动执行

# 1) 查看当前 revision
etcdctl endpoint status -w table

# 2) 压缩到某个 revision 之前(保留最近 1000 个版本)
etcdctl compact $(($(etcdctl endpoint status --write-out=json | jq -r '.[0].Status.header.revision') - 1000))

# 3) 碎片整理(逐个节点做,不要同时)
etcdctl defrag --endpoints=https://10.0.1.11:2379
etcdctl defrag --endpoints=https://10.0.1.12:2379
etcdctl defrag --endpoints=https://10.0.1.13:2379

# 4) 观察 DB SIZE 是否下降
etcdctl endpoint status --cluster -w table

4.3 自动压缩

etcd 支持自动压缩,避免历史版本无限堆积:

# etcd 启动参数
--auto-compaction-mode=periodic
--auto-compaction-retention=1h        # 保留 1 小时内的历史版本
模式参数含义适用
periodic按时间保留(如 1h、30m)通用推荐
revision按版本数保留(如 10000)写入量稳定

注意:自动压缩只做 compaction,不做 defrag。所以 DB 文件仍会缓慢增长(空洞不回收),需要定期(如每周)做一次 defrag 或滚动重启 etcd 节点。许多发行版在 etcd 启动时自动执行 defrag(--defrag-on-start 之类),这也是「重启 etcd 后 DB 变小」的原因。

一句话:compaction 删逻辑版本,defrag 回收物理空间——自动压缩解决 90% 的增长问题,剩下的空洞靠定期 defrag 或滚动重启释放。

5. 配额与 NOSPACE 告警

5.1 quota-backend-bytes

etcd 默认配额是 2GiB,超过后整个集群变成只读:

--quota-backend-bytes=8589934592      # 8GiB
配额说明
2 GiB(默认)小集群够用,大集群危险
4~8 GiB生产推荐
> 8 GiB谨慎:etcd 恢复(restore/replay WAL)时间会显著变长

配额不是越大越好:DB 越大,etcd 启动时重放 WAL 的时间越长,故障恢复越慢。业界经验是控制在 8GiB 以内,超过说明该做 compaction/defrag,或该清理 Event。

5.2 NOSPACE 告警的处理

# 症状:API Server 报 etcdserver: mvcc: database space exceeded
etcdctl alarm list
# 输出:memberID:xxx alarm:NOSPACE

# 处理三步:
# 1) 压缩到最新 revision
rev=$(etcdctl endpoint status --write-out=json | jq -r '.[0].Status.header.revision')
etcdctl compact $rev

# 2) 逐节点 defrag
etcdctl defrag --endpoints=<每个节点>

# 3) 解除告警
etcdctl alarm disarm

只做 alarm disarm 而不压缩/defrag 是无效的——告警会立刻回来,因为空间并没有释放。

5.3 容量监控

关键指标(Prometheus):
  etcd_mvcc_db_total_size_in_bytes          # DB 物理大小
  etcd_mvcc_db_total_size_in_use_in_bytes   # 逻辑使用量(两者差距=碎片)
  etcd_server_quota_backend_bytes           # 配额上限
  etcd_debugging_mvcc_db_compaction_pause_duration_milliseconds

告警阈值:db_total_size_in_bytes / quota_backend_bytes > 70% 就应预警,> 85% 必须处理。另外监控 in_use / total 的比值,若长期 < 0.5 说明碎片严重,该 defrag 了。

6. 性能调优

6.1 磁盘是第一瓶颈

etcd 对磁盘的要求几乎苛刻:

指标推荐值说明
WAL fsync 延迟(p99)< 10ms超过 25ms 集群开始不稳
磁盘类型本地 SSD/NVMe禁止网络盘、禁止与日志盘共用
顺序写吞吐> 50MB/sRaft 日志写入
IOPS> 500峰值场景
# 用 fio 实测 fsync 延迟(etcd 官方推荐方法)
fio --rw=write --ioengine=sync --fdatasync=1 --directory=/var/lib/etcd \
    --size=22m --bs=2300 --name=etcd-perf
# 关注输出中的 fsync 部分(p99)

最常见的性能事故:把 etcd 数据目录放在网络存储(EBS gp2、NFS)上,或与高写入的日志服务混部。前者 fsync 延迟随负载抖动,后者直接抢 IO。

6.2 核心参数

--heartbeat-interval=100        # 心跳间隔(ms),默认 100
--election-timeout=1000         # 选举超时(ms),默认 1000
--snapshot-count=10000          # 触发快照的已提交事务数
--max-request-bytes=1572864     # 单请求最大字节(默认 1.5MiB)
--max-snapshots=5               # 保留的快照数
--max-wals=5                    # 保留的 WAL 数
场景调整
跨 AZ 高延迟heartbeat-interval=250、election-timeout=2500
大对象(如大 CRD)调大 --max-request-bytes(同时要调 apiserver 的 --max-request-bytes)
磁盘慢优先换盘,不要靠调大 timeout 掩盖

6.3 网络与资源

- etcd 节点间必须低延迟(同 AZ 内 < 1ms,跨 AZ < 10ms);
- 单独网卡/独立带宽,避免与业务流量争抢;
- 不要与 apiserver 抢 CPU(etcd 对 CPU 敏感度中等,但 fsync 受 CPU 调度影响);
- 建议为 etcd 进程设置 CPU 亲和或独占核。

7. 监控与故障定位

7.1 核心监控项

指标含义告警线
etcd_server_has_leader是否有 Leader= 0 立即告警
etcd_server_leader_changes_seen_totalLeader 切换次数持续增长需排查
etcd_disk_wal_fsync_duration_secondsWAL fsync 延迟p99 > 25ms
etcd_disk_backend_commit_duration_seconds后端提交延迟p99 > 25ms
etcd_network_peer_round_trip_time_seconds节点间 RTTp99 > 50ms
etcd_mvcc_db_total_size_in_bytesDB 大小> 70% 配额
etcd_server_proposals_failed_total提案失败持续增长

7.2 常见故障对照

症状根因处理
集群只读 + database space exceeded达到配额compact → defrag → disarm
Leader 频繁切换磁盘慢 / 网络抖动查 fsync 延迟与 RTT
API Server 写入超时etcd 提交慢etcd_disk_backend_commit_duration_seconds
某节点一直落后磁盘差 / 网络分区换盘,或用 learner 重建
恢复后数据「回退」用了旧快照确认快照 revision
启动极慢WAL 重放 / DB 过大defrag、调小配额
# 集群健康一键检查
etcdctl endpoint health --cluster -w table
etcdctl endpoint status --cluster -w table
etcdctl alarm list

7.3 与集群运维的联动

etcd 的健康直接影响 Kubernetes 集群升级 :升级前必须做一次快照,升级中要观察 leader_changes,升级后要确认所有成员版本一致。当集群出现「API 变慢、kubectl 超时」时,第一站应该是 etcd 的 fsync 指标而不是 apiserver。更完整的控制面排障流程见 Kubernetes 集群排障与诊断 。

8. 小结

主题关键结论
拓扑奇数节点,3 或 5;跨 AZ 用 learner 加节点
备份snapshot save 在线快照 + 异地存储 + 定期演练
恢复全停 → 逐节点 restore → 一起启动,参数别写错
空间compaction 删版本,defrag 回收空间,顺序不能反
配额默认 2GiB 太小,建议 4~8GiB,>70% 预警
性能磁盘 fsync 是唯一真瓶颈,用 fio 实测
监控has_leader、fsync p99、DB size 三件套

一句话记住:etcd 是 Kubernetes 的「心脏」,它的健康不取决于对象数量,而取决于磁盘 fsync 延迟与 DB 大小。把备份做成习惯、把 compaction/defrag 做成周期任务、把配额与 fsync 做成告警,集群的控制面才有真正的下限保障——因为 etcd 出问题,不是「某个服务挂了」,而是「整个集群失去了记忆」。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「云原生」更多文章

  1. 证书管理与自动轮换:cert-manager 与 kubelet 证书轮换
  2. Sidecar 模式与原生边车容器:init 容器、生命周期与重启策略
  3. CoreDNS 与服务发现:插件链、NDOTS 与 Headless Service