找回密码
立即注册
搜索
发回帖 发新帖

5337

积分

0

好友

679

主题
发表于 25 分钟前 | 查看: 4| 回复: 0

一、背景与现象

一个自建 K8s 集群,某天上午开发同学反馈:新建 Pod 一直 Pending,kubectl apply 报 etcdserver: mvcc: database space exceeded。同时现有 Pod 正常运行,但任何写操作(创建 / 更新 / 删除)都失败,apiserver 日志猛刷 quota exceeded。

问题不在节点,而是 etcd 后端数据库配额被历史版本撑爆。最坑的是:集群“看起来一切正常”,只是写不进去,所以告警往往比业务反馈来得晚。这类问题在开了高频 reconcile 的集群里尤其常见。

二、排查过程

先确认 etcd 是否真的空间告急:

# 查看每个 etcd 成员 db 大小与配额
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 \
  endpoint status --write-out=table
# 看 dbSize 与 quota(默认 2GB),dbSize 接近 quota 即危险

再看有没有报警被触发:

# 列出当前 etcd alarm
etcdctl alarm list
# 典型输出:
# memberID:xxxx alarm:NOSPACE

NOSPACE 报警一旦挂上,etcd 会拒绝一切写请求,哪怕只是新建一个空 ConfigMap。确认 alarm 后,查看历史版本堆积:

# 看当前修订号,判断堆积量
etcdctl endpoint status --write-out=json | python -m json.tool
# 关注 header.revision 与 dbSize 的增长趋势
# 顺便确认成员健康,排除 leader 切换导致的写入抖动
etcdctl endpoint health --write-out=table

如果想知道是谁在疯狂写,可以用 etcdctl watch 临时抓一下写入热点,或者翻 apiserver 审计日志里 verb=create/update 最频繁的 resource。另一个定位写入源的实用办法是:在 apiserver 审计日志里按 resource 和 verb 统计写操作频次,往往能直接指认 culprit,比如某个 Operator 的 reconcile 循环在空转刷写。

三、根因分析

etcd 是 MVCC 模型,每次写都会生成一个新版本,旧版本不会立即删除,靠压缩(compact)+ 碎片整理(defrag)回收。如果集群开着高频写(如每几秒更新的 Endpoint、kubelet 心跳、Prometheus Operator 频繁 reconcile),而 auto-compaction 没开或配得太松,历史版本会无限堆积,直到触及 quota-backend-bytes(默认 2GB)触发 NOSPACE。

这里有两个容易踩的点:一是用 periodic 模式按时间压缩(如保留 1h),不如 revision 模式按版本数压缩(如保留最近 1000 个修订)来得稳,因为 revisions 的增长速度和写频率直接挂钩;二是只 compact 不 defrag,db 文件大小不会降,空间依旧占着。根因:只写不压(或压得太慢)+ 配额不设防。

落到数字上,etcd 每个 revision 大约几十到几百字节,集群里成千上万个对象、每几秒一次 Endpoint 刷新,一天就能产生几百万个 revision。2GB 配额在高频写入下可能几周就被吃满。这也解释了为什么问题常在集群稳定运行一段时间后才爆发:不是忽然写的变多,而是旧版本一直没人收。对于特别大的 etcd,还可以把 defrag 排进每月一次的运维窗口,作为预防性维护,而不是等 NOSPACE 才救火。

四、解决方案

分三步:解 alarm → 压缩历史 → 碎片整理释放空间。

# 1) 解除 NOSPACE 报警(不解报警,写请求仍被拒)
etcdctl alarm disarm

# 2) 压缩到当前最新修订号(先取 revision)
REV=$(etcdctl endpoint status --write-out=json | \
  python -c "import sys,json;print(json.load(sys.stdin)['status']['header']['revision'])")
etcdctl compact $REV --physical

# 3) 逐个成员做离线碎片整理,真正回收 db 文件空间
etcdctl defrag --cluster

# 验证:dbSize 应明显下降,写操作恢复
etcdctl endpoint status --write-out=table

defrag 会短暂阻塞该成员写,生产建议逐个成员滚动执行,避免 etcd 整体不可用。配额也可临时上调应急:

# 临时调大到 8GB(治标,配合压缩后尽快回落)
# 修改 etcd 静态 Pod 的 --quota-backend-bytes=8589934592 后滚动重启

五、复盘与预防

  • 开启自动压缩,别等手动:--auto-compaction-mode=revision --auto-compaction-retention=1000(保留最近 1000 个修订)。

  • 配额显式设置并监控:--quota-backend-bytes=4294967296(4GB),用 etcd_mvcc_db_total_size_in_bytes 接 Prometheus,超 70% 即告警。下面这条告警规则可直接抄:

    # prometheus alert rule
    - alert: EtcdDBSizeNearQuota
    expr: etcd_mvcc_db_total_size_in_bytes / etcd_server_quota_backend_bytes > 0.7
    for: 10m
    labels: {severity: warning}
    annotations: {summary: "etcd db 已超配额 70%,请尽快 compact + defrag"}
  • 告警联动:把 etcd_server_has_leader、alarm:NOSPACE、defrag 失败都接进夜莺 / Alertmanager。

  • 规范:高频写的控制器(Prometheus Operator、Cert-Manager)单独评估,避免无意义的频繁 reconcile 把 etcd 写爆。

六、小结

mvcc: database space exceeded 是 etcd 的“磁盘配额爆了”信号,根子在历史版本没压缩。口诀:先 alarm disarm 解报警,再 compact + defrag 回收,最后把 auto-compaction 和 quota 固化进集群配置,别让下次重演。apiserver 写不动时,第一反应就该查 etcd 而不是节点。




上一篇:生产环境排错,我常用的 10 条 Linux 命令(网络/磁盘/内核)
下一篇:Godot 碰撞检测入门:字符数组当地图,越界当墙一步搞定
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-11-13 20:25 , Processed in 0.071968 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表