etcd 的性能调优:磁盘、压缩、配额与告警处理
etcd-tuning 讲的是怎么让 etcd 这个 Kubernetes 唯一的数据库不要成为集群的瓶颈。etcd 的写入路径是"提案 → Raft 复制 → WAL fsync → 应用到 bbolt 后端",其中任何一步都要落盘,所以它的性能几乎完全由磁盘决定;而它的可用性由多数派决定,网络抖动会直接变成选举。
调优可以拆成三件事:
--quota-backend-bytes=0 # 默认配额 2GiB(0 表示用内置默认值);官方建议不超过 8GiB
--snapshot-count=100000 # 触发一次快照的已应用条目数
--heartbeat-interval=100 # leader 心跳间隔(毫秒)
--election-timeout=1000 # 选举超时(毫秒),须 >= 10 倍成员间 RTT
--auto-compaction-mode=periodic # periodic 或 revision
--auto-compaction-retention=0 # 0 表示关闭自动压缩
--max-request-bytes=1572864 # 单次请求上限 1.5MiB
--max-snapshots=5 # 保留的快照文件数
--max-wals=5 # 保留的 WAL 文件数
--backend-bbolt-freelist-type=map # bbolt 空闲页管理方式
--experimental-watch-progress-notify-interval=10m
注意 --quota-backend-bytes 的 flag 默认值是 0,但 etcd 的实际默认配额是 2GiB(官方 limits 文档原文:"The default storage size limit is 2 GiB, configurable with --quota-backend-bytes flag")。看到 0 不要误以为"无限制"。
etcd 官方给出的硬件口径(见参考链接中的 hardware 页):
| 项 | 要求 |
|---|---|
| 顺序 IOPS(最低) | 50(约等于一块 7200 转机械盘) |
| 顺序 IOPS(重负载推荐) | 500(本地 SSD 或高性能云盘) |
| 带宽 | 通常 10MB/s;大集群建议 100MB/s(15 秒恢复 1GB 数据) |
| 介质 | SSD 优先;机械盘至少 15000 转,并做 RAID 0 |
| 内存 | 通常 8GB 够用;watcher 上千、键百万级建议 16–64GB |
| CPU | 通常 2–4 核;客户端上千、QPS 上万时建议 8–16 核 |
| 网络 | 常见部署 1GbE 足够;大集群 10GbE 可缩短恢复时间 |
云盘要注意:厂商标称的 IOPS 往往是并发 IOPS,可能是顺序 IOPS 的十倍,别拿它当依据,用 fio 实测。磁盘优先级也要调,官方给出的做法是:
# 提高 etcd 进程的 IO 优先级(降低其他进程抢盘的影响)
sudo ionice -c2 -n0 -p $(pgrep etcd)
# CPU 调频器设为 performance,避免省电模式带来的抖动
sudo cpupower frequency-set --governor performance
# 网络层面:让 Raft 成员间流量(2380)优先于客户端流量(2379)
sudo tc qdisc add dev eth0 root handle 1: prio bands 3
sudo tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip sport 2380 0xffff flowid 1:1
sudo tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 2380 0xffff flowid 1:1
这是最容易混淆的一对概念:
| 动作 | 做什么 | 何时做 |
|---|---|---|
| compaction(压缩) | 删除某个 revision 之前的历史版本,逻辑上释放键空间 | 持续做(自动压缩或 apiserver 触发) |
| defrag(整理) | 重建 bbolt 数据库文件,把释放出来的空洞还给文件系统 | 压缩之后、磁盘占用明显大于实际数据时 |
关键点:压缩后空间只是"etcd 内部可重用",宿主机看到的文件不会变小;只有 defrag 才会让它变小。而且 defrag 会阻塞该成员的读写,必须逐成员执行。
# 1. 查看当前大小与实际使用量(差值就是待回收的碎片)
etcdctl endpoint status --write-out=table --cluster
# 2. 压缩到指定 revision(通常用最新 revision)
rev=$(etcdctl endpoint status --write-out=json | jq -r '.[0].header.revision')
etcdctl compact $rev
# 3. 逐个成员整理(不要同时做);--cluster 会依次处理所有成员
etcdctl defrag --cluster
# 或者指定单个成员
etcdctl --endpoints=https://10.0.0.11:2379 defrag
# 4. 离线整理(成员已停止时使用数据目录)
etcdutl defrag --data-dir /var/lib/etcd
# 自动压缩:按时间窗口保留历史
--auto-compaction-mode=periodic --auto-compaction-retention=10h
# 或者按 revision 数保留
--auto-compaction-mode=revision --auto-compaction-retention=1000
官方给出的经验值:写入极其频繁的键用 1h 或 30m;很少更新的用 24h/48h/72h;通用场景 10h。
当后端数据超过配额时,etcd 会抛出集群范围的 NOSPACE 告警,集群进入"只接受键读取与删除"的状态 —— 表现就是所有写操作都失败,报 mvcc: database space exceeded。
# 1. 查看告警
ETCDCTL_API=3 etcdctl alarm list
# memberID:13803658152347727308 alarm:NOSPACE
# 2. 压缩 + 逐个 defrag(见上一节),否则解除后很快会再次触发
rev=$(ETCDCTL_API=3 etcdctl endpoint status --write-out=json | jq -r '.[0].header.revision')
ETCDCTL_API=3 etcdctl compact $rev
ETCDCTL_API=3 etcdctl defrag --cluster
# 3. 解除告警
ETCDCTL_API=3 etcdctl alarm disarm
# 4. 确认大小已回落
ETCDCTL_API=3 etcdctl endpoint status --write-out=table
# 磁盘:写路径延迟,最核心的两个
etcd_disk_wal_fsync_duration_seconds
etcd_disk_backend_commit_duration_seconds
# 集群健康
etcd_server_has_leader
etcd_server_leader_changes_seen_total
etcd_server_proposals_failed_total
# 数据规模(注意区分"文件大小"和"实际使用")
etcd_mvcc_db_total_size_in_bytes
etcd_mvcc_db_total_size_in_use_in_bytes
# 常用告警表达式的形状
histogram_quantile(0.99, rate(etcd_disk_backend_commit_duration_seconds_bucket[5m])) > 0.025
rate(etcd_server_leader_changes_seen_total[1h]) > 3
rate(etcd_server_proposals_failed_total[5m]) > 0
etcd_mvcc_db_total_size_in_bytes / etcd_mvcc_db_total_size_in_use_in_bytes > 2
etcd_mvcc_db_total_size_in_bytes 与 ..._in_use_in_bytes 的比值反映碎片程度,长期大于 2 说明该做 defrag 了。(v3.4 起,原来的 etcd_debugging_mvcc_db_total_size_in_bytes 被重命名为 etcd_mvcc_db_total_size_in_bytes。)
# 成员必须是奇数:1 / 3 / 5
ETCDCTL_API=3 etcdctl member list --write-out=table
# 每个成员的状态:leader、DB 大小、配额、Raft 索引
ETCDCTL_API=3 etcdctl endpoint status --write-out=table --cluster
# 健康检查(带延迟)
ETCDCTL_API=3 etcdctl endpoint health --write-out=table --cluster
--snapshot-count 的默认值是 100000,不是 10000。etcd 官方 tuning 页面里仍写着"default 10,000 changes",与同一站点 configuration 页的 '100000' 以及代码里的默认值不符 —— 以 configuration 页与 etcd --help 的实际输出为准。--cluster 在业务高峰期执行,也不要在多个成员上并行执行。alarm disarm)之前必须先 compact + defrag,否则写入很快又会撞上配额,进入"告警—解除—再告警"的循环。--heartbeat-interval 与 --election-timeout 必须全集群一致,取值也应一致。选举超时至少要大于 10 倍成员间 RTT(跨机房部署要按最慢链路算),否则容易出现 leader 反复被踢。--max-request-bytes 限制(默认 1.5MiB)。apiserver 侧 --max-request-bytes 默认是 3MiB,留了 JSON 转 protobuf 的余量;若把 etcd 侧调大而 apiserver 侧没同步(或反之),会出现"某些对象就是写不进去"的糊涂账,两边要一起改。--backend-bbolt-freelist-type 之类的参数改动会影响存储格式的兼容性,升级/回滚时可能出问题;除非有明确的压测数据,否则保持默认。--etcd-servers-overrides=/events#https://... 指定,可以显著降低核心 etcd 的写入压力与碎片增速。etcdctl snapshot save 产生的是 MVCC 一致快照,而直接拷贝 /var/lib/etcd 目录拿到的是一份不一致的镜像。备份要连同 /etc/kubernetes/pki/etcd/ 证书一起保存。etcd — 集群数据存储kube-apiserver — 集群 API 服务器,唯一直接读写 etcd 的组件kubeadm — Kubernetes集群安装工具crictl — 容器运行时调试工具kube-apiserver-tuning — apiserver 侧的 etcd 相关参数