问题背景
生产 Kubernetes 集群在 2026-09-08 凌晨 03:12 左右开始出现间歇性 API 请求失败,Prometheus 告警显示 apiserver_request_total{code="5xx"} 突增,持续约 40 分钟后回落。集群规模约 180 节点、3200+ Pod,核心组件(kube-apiserver、etcd、controller-manager)均运行在独立高性能 NVMe 盘上。
集群自 7 月底以来已按官方推荐开启了 etcd 自动压缩(--auto-compaction-retention=1h),并配置了每日 03:00 的全量快照。理论上压缩后 etcd 磁盘占用应趋于稳定,但监控显示最近 7 天内 etcd WAL 目录持续以 180-220MB/天的速度增长,远超预期。
本次故障正发生在压缩窗口后 12 分钟,疑似与压缩后的 WAL 清理/重写流程存在竞争或 I/O 放大。
故障现象
- API Server 5xx 集中爆发:
kube-apiserver日志中大量出现etcdserver: request timed out与context deadline exceeded,对应客户端请求返回 503/504。 - etcd WAL fsync 延迟飙升:
etcd_disk_wal_fsync_duration_secondsP99 从常态的 8-12ms 突增至 180-420ms,持续约 8-15 秒后回落,周期性出现。 - WAL 文件异常增长:压缩完成后
member/wal目录下仍出现多个 128MB+ 的 WAL segment,etcd --wal-dir实际占用从 1.8GB 涨至 2.4GB。 - 磁盘 I/O 抖动:
iostat -x 1显示nvme0n1的%util在 03:24-03:31 期间多次达到 92-98%,await从 0.8ms 飙升至 45-120ms。 - 无数据丢失:etcd
mvcc_db_total_size保持在 6.1GB 左右,leader 选举正常,集群健康检查(etcdctl endpoint health)全部通过。
排查过程
步骤 1:确认 5xx 与 etcd 延迟的关联
通过 Loki 查询 kube-apiserver 在 03:12-03:52 的错误日志,提取 request.*etcd 相关行,发现 87% 的 5xx 请求在 etcd 客户端侧等待超过 2.5s。
|
|
同时用 etcdctl alarm list 确认无 NOSPACE 告警,排除空间不足。
步骤 2:定位 WAL 增长源头
|
|
用 etcdctl endpoint status 查看 dbSize 与 dbSizeInUse 差距仅 180MB,说明压缩已生效,但 WAL 未同步释放。
步骤 3:分析 fsync 延迟与磁盘 I/O
在故障节点上抓取 bpftrace 脚本追踪 fsync:
|
|
输出显示 etcd 进程的 fsync 延迟集中在 03:24、03:27、03:30 三次,每次持续 6-11 秒。
用 iotop -o -d 1 进一步确认:etcd 进程在这些时间点 DISK WRITE 达到 380-520MB/s,远超 NVMe 盘的常态写入带宽(~280MB/s)。
步骤 4:WAL 段文件与压缩窗口的时序关系
检查 etcd 日志中压缩触发点:
|
|
而 WAL 异常增长的 segment 创建时间集中在 03:12-03:31,正好落在压缩完成后 12-31 分钟。
进一步用 filefrag -v 检查 WAL 文件的物理碎片:
|
|
发现单个 WAL segment 在物理盘上被分配到 1843200-1875967 连续块,但相邻的另一个 segment 却散布在 1920000+ 区域,存在跨 zone 的分配。
步骤 5:复现与验证
在测试环境用相同 etcd 版本(v3.5.12)与磁盘类型重放:
- 写入 50 万条 key,触发 1h 压缩;
- 持续写入 2000 req/s 模拟业务;
- 观察到压缩后 8-15 分钟内 WAL fsync P99 同样出现 90-180ms 尖峰。
确认问题与「压缩后 WAL 重写 + 业务持续写入」的 I/O 竞争直接相关。
解决方案
立即止血(已执行)
|
|
同时在 kube-apiserver 端增加 --etcd-compaction-interval=0(禁用客户端侧自动压缩触发),把压缩完全交给 etcd 自身调度,避免双重压缩竞争。
根治措施
-
调整 WAL 段大小与清理策略
1 2 3 4# etcd 启动参数增加 --wal-keep-size=256M \ --max-snapshots=3 \ --max-wals=5wal-keep-size限制 WAL 保留上限,超过后会触发提前清理,防止单个 segment 过大导致 fsync 耗时。 -
分离 WAL 与 DB 物理磁盘
原配置中 WAL 与
member/snap/db共用同一 NVMe 盘。改为:- WAL:
/data/etcd-wal(独立 400GB NVMe,--wal-dir指向) - DB:
/data/etcd-db(另一块 NVMe,--data-dir指向)
避免 WAL fsync 与 DB compaction 的 I/O 争抢。
- WAL:
-
引入 WAL 预分配与 direct I/O
在 etcd 配置中启用实验性参数(需 v3.5.13+):
1 2--experimental-wal-preallocate=true \ --experimental-wal-fsync-policy=direct预分配可减少运行时 extend 操作,direct I/O 绕过 page cache 降低延迟抖动。
-
监控增强
新增告警规则:
1 2 3 4 5- alert: EtcdWALFsyncP99High expr: histogram_quantile(0.99, etcd_disk_wal_fsync_duration_seconds_bucket) > 0.05 for: 2m annotations: summary: "etcd WAL fsync P99 > 50ms,疑似磁盘 I/O 瓶颈"同时暴露
etcd_wal_segment_count与etcd_wal_total_size_bytes两个新指标,用于提前发现 WAL 膨胀。
根因分析
根本原因是 etcd 压缩后的 WAL 重写窗口与业务高峰写入存在 I/O 竞争,且 WAL segment 默认 64MB 在高写入场景下增长过快,单个 fsync 操作需刷盘 128MB+ 数据,叠加 NVMe 盘在 zone 分配时的轻微碎片,导致 P99 延迟偶发飙升。
压缩本身是正确的(dbSizeInUse 下降),但 WAL 清理滞后 + 磁盘未分离 + 缺少 wal-keep-size 限制,形成了「压缩后反而更忙」的反直觉现象。
预防措施
- WAL 与 DB 物理分离:所有 etcd 节点必须在下次滚动重启时完成磁盘分离,预计 2026-09-15 前落地。
- WAL 保留策略收紧:
--wal-keep-size=256M+--max-wals=5已写入 etcd systemd unit,需在 9 月底前全量生效。 - 引入 etcd 版本升级窗口:计划 Q4 将 etcd 升级至 v3.5.15(含 WAL 预分配与 direct I/O 正式支持),提前在 staging 环境验证。
- 每月 WAL 健康巡检:在
auto_review.py中增加etcd_wal_segment_count > 12的告警项,防止 WAL 段文件无限累积。 - 变更 checklist 补充:任何涉及 etcd 存储层的变更,必须在「变更前 checklist」中增加「确认 WAL 与 DB 物理磁盘分离」「确认 wal-keep-size 已生效」两项门禁。
总结
本次故障再次印证了「etcd 压缩不是银弹」的观点:压缩解决了 DB 膨胀,但 WAL 的生命周期管理同样关键。生产环境必须把 WAL 视为独立的一等公民——物理隔离、尺寸限制、I/O 优先级、监控告警,四者缺一不可。
故障处理过程中,团队也积累了「压缩窗口后 30 分钟是 WAL 延迟高发期」的经验,后续可据此调整业务低峰维护窗口,避免再次踩坑。
最终状态:集群已恢复平稳,etcd WAL fsync P99 回落至 15ms 以内,API Server 5xx 告警清零。预计通过磁盘分离与 WAL 策略收紧,可将同类问题发生概率降低 90% 以上。