APISXI 造成 ETCD revision 异常
** 只有写操作才会让 revision 增加并撑大 DB,这个理解是对的。** 在 APISIX 场景下,“异常的写操作”基本都可以归到这几类:
- 通过 APISIX Admin API / Dashboard / 脚本反复写配置(包括 Ingress Controller)。
- APISIX Ingress Controller 定时全量同步,把内存里的 APISIX 配置“倒回” etcd,即使没有变更也会写。
- etcd 不稳定或超时,导致客户端重试逻辑频繁写。
- 极少数情况下,自己写的插件/脚本在后台定时写 etcd。
下面我按“谁在写、什么时候写”帮你拆开说明,并顺带给你一些排查思路。
1. 正常情况下,谁会写 etcd?
1.1 APISIX Admin API(最常见)
APISIX 把路由、上游、证书、消费者、全局规则等所有配置都存在 etcd。所有“写”操作都是通过 Admin API 进行的:PUT / DELETE / PATCH 这些方法会真正写入 etcd。
典型写操作包括:
PUT /apisix/admin/routes/{id}:创建/更新路由PUT /apisix/admin/upstreams/{id}:创建/更新上游PUT /apisix/admin/ssl/{id}:证书PUT /apisix/admin/consumers/{id}:消费者PUT /apisix/admin/global_rules/{id}:全局规则DELETE /apisix/admin/...:删除对应资源PATCH /apisix/admin/...:部分字段更新
正常情况下:
只有你通过脚本 / Dashboard / Ingress Controller 调 Admin API 时,才会产生写操作,revision 按业务需求缓慢增长。
1.2 APISIX Ingress Controller
如果你部署了 APISIX Ingress Controller,它主要做两件事:
- 监听 K8s 里的 CRD(ApisixRoute、ApisixUpstream 等);
- 把这些 CRD 翻译成 APISIX 的配置,通过 Admin API 写入 APISIX(从而写入 etcd)。
所以:
- 每当你
kubectl apply一个 ApisixRoute,Ingress Controller 就会调用 Admin API 写一次 etcd; - 删除 / 更新 CRD 时,也会触发对应的写/删除操作。
问题来了:
Ingress Controller 并不一定只在 CRD 变更时才写 etcd,下面几种情况就可能“异常写”。
2. 容易导致“异常写”的几种典型情况
2.1 Ingress Controller 定时全量同步(已知典型问题)
官方 issue 里已经有人报过类似问题:
- 现象:
- etcd 大小持续快速增长;
- 用
etcdctl watch看,发现 ingress-controller 在没有 CRD 变化的情况下,仍以固定间隔持续更新 etcd 里的 key; - 最终磁盘耗尽。
原因:
- Ingress Controller 内部有“定时同步”逻辑,会定期把内存里的 APISIX 配置同步到 APISIX(写 etcd);
- 如果实现不当,就会变成:即使配置没变,也会反复做 PUT / txn,导致 revision 暴涨。
这和你说的“某个时间点突然空间不足”高度吻合。
2.2 Ingress Controller 把“同步事件”都当成 Add 事件写 etcd
另一个 issue 提到:
controller 会间隔一段时间做一次全局 sync,每次 sync 都作为 Add 事件最终执行 etcd 的 create,
也就是本来应该是 update,却一直用 create,导致重复写入。
这说明:
- Ingress Controller 在从 K8s 获取资源列表后,会把所有资源当作“新增”写入 etcd,而不是先判断 key 是否存在再决定 update;
- 如果你的 CRD 数量很多,这个“全量 sync”就会产生大量写操作。
2.3 etcd 不稳定 / Admin API 超时 → 重试风暴
APISIX 自身有个问题:当 etcd 不稳定时,APISIX 的健康检查、watch 等定时任务会报错并不断重试。这时:
- APISIX 不会直接写 etcd,但会频繁调用 etcd 的 Range / Watch 等读请求;
- 如果同时有 Ingress Controller 在通过 Admin API 写 APISIX,
- 这些写请求会变慢;
- 客户端(Ingress Controller)更容易超时;
- 很多客户端会在超时后重试,导致短时间内多次写入同一份数据。
结果就是:
- 看起来是“异常写”,本质是不稳定 + 重试叠加;
- revision 会在某个时间点突然暴涨,配合历史版本没压好,DB 就被撑爆。
2.4 自己写的脚本/插件在后台写 etcd
还有一种情况比较隐蔽:
- 你自己写了脚本(比如定时同步路由、监控上报);
- 或者自己写插件,在
ngx.timer里定期调用 Admin API / 直接操作 etcd;
这些脚本如果逻辑有 bug(比如死循环、条件判断错误),也会变成持续写 etcd 的“异常源”。
3. 用一个图帮你梳理:谁在什么时候会写 etcd
下面这张图可以当成“排查清单”:
关键结论:
- 只要你在 APISIX + Ingress Controller 环境里看到 revision 异常增长,
大概率是“定时全量同步”或“重试逻辑”在频繁写 etcd。
4. 如何确认真的是“异常写”?
给你几个实用的排查步骤:
4.1 看 revision 增长速度
# 看 revision 是否在短时间内暴涨
watch -n 1 'etcdctl endpoint status --write-out=table | grep -E "revision|raft"'
- 如果 revision 几秒就涨几十上百,那肯定有程序在频繁写。
4.2 实时观察谁在写 etcd
在 etcd 节点上执行:
# 简单粗暴:看最近有哪些操作
etcdctl watch / --prefix
# 或
etcdctl watch /apisix --prefix
观察:
- 是否有大量 PUT 同一个 key(比如某个
/apisix/routes/xxx); - 是否有周期性的写操作(比如每 1 分钟一次);
- 是否有来自 Ingress Controller Pod IP 的请求。
更精细一点,可以开启 etcd 的 debug 日志(根据版本调整):
ETCD_LOG_PACKAGE_LEVELS=etcdserver=debug etcd ...
5. 回到你的猜测,逐条对一下
你说:只有写操作才会产生大量版本,那问题基本可以认为是异常的写操作。
这个判断是对的。你对三个方向的猜测,对应到现实情况大概是这样:
- etcd 不稳定 + APISIX 客户端重复操作?
- 会放大写频率,但真正“写”的是客户端(脚本/Ingress Controller);
- etcd 本身不会“凭空产生 revision”。
- APISIX 有 bug,某些情况下异常写入 etcd?
- APISIX 核心本身不会后台写 etcd,Admin API 是唯一入口;
- 但 APISIX Ingress Controller 确实存在“持续写 etcd”的已知问题。
- K8s 数据源 etcd 不稳定导致连锁异常?
- 这会让 APISIX Ingress Controller 从 API Server 拉不到数据,内部重试加剧;
- 但最终“写 APISIX etcd”的还是 Ingress Controller。
更具体的排查方向:
- 如果你部署了 APISIX Ingress Controller,→ 重点排查它的版本 + 是否有“持续更新 etcd”的 issue;
- 如果只用 APISIX + 自己脚本,→ 检查脚本是否有死循环/重复调用 Admin API;
- 如果完全没有后台定时任务,
→ 那就要看是不是某个客户端在做“无变更的 PUT”(比如全量同步逻辑)。
6. 你接下来可以怎么验证?
建议你按这个顺序做:
- 用
etcdctl endpoint status确认 revision 是否在短时间内暴涨; - 用
etcdctl watch /apisix --prefix实时看写操作; - 在 APISIX Ingress Controller 日志里看是否有大量“同步 APISIX 配置”的日志;
- 对照 APISIX Ingress Controller 的 issue,看是否有你类似版本 +“etcd size increasing”的问题。
如果你愿意,可以把你当前用的 APISIX、APISIX Ingress Controller、etcd 版本,以及是否有自定义脚本/插件列出来,我可以帮你更具体地定位是哪一类写操作在“刷 revision”。
7. 全量刷新机制是导致 etcd 版本暴涨的“隐形杀手”。
下面我为你总结这三个组件的数据处理关系,并重点解释配置中“刷新机制”的影响。
1. 核心机制解读
首先回答你最关心的配置问题:
kubernetes.resync_interval: "6h":
这是 Kubernetes Informer 的核心机制。它的作用是:每隔 6 小时,Informer 会认为“内存里的 K8s 资源列表可能不准了”,于是它会重新发送所有对象(ApisixRoute, Service, Pod 等)的Update事件给控制器。- 后果:即使你一条配置都没改,每隔 6 小时,控制器都会收到成千上万个“虚假”的更新事件。
apisix_resource_sync_interval: "1h":
这是 APISIX Ingress Controller 的自定义全量同步机制。它会更激进地:每隔 1 小时,将控制器内存里的全量配置,强行同步到 APISIX(也就是写入 etcd)。
结论:这两个参数叠加,意味着你的 etcd 至少每小时会遭遇一次“全量写入风暴”,这就是你看到“版本异常增加”的根本原因。
2. 三组件数据处理全景图
为了让你一目了然,我画了一张数据流转图,标注了“写操作”发生的位置:
3. 详细数据处理过程解析
阶段一:监听与转换
- 监听 K8s 资源:
- Ingress Controller 启动时,会通过
Informer监听 K8s 的 CRD(如ApisixRoute)以及原生资源(Service,Pod,Endpoints)。
- Ingress Controller 启动时,会通过
- 事件触发:
- 当你执行
kubectl apply,K8s API Server 发出事件。 - Informer 接收到事件,调用控制器内部的
OnAdd或OnUpdate函数。
- 当你执行
阶段二:配置生成与写入(写操作发生点)
- 转换配置:
- 控制器将 K8s 的资源定义,翻译成 APISIX 能理解的格式(如
routes、upstreams、ssl)。
- 控制器将 K8s 的资源定义,翻译成 APISIX 能理解的格式(如
- 写入 etcd(关键点):
- 控制器调用 APISIX 的 Admin API(或直接操作 etcd client),将配置写入 etcd 的
/apisix/routes/xxx路径。 - 这里就是 etcd revision 增加的地方。
- 控制器调用 APISIX 的 Admin API(或直接操作 etcd client),将配置写入 etcd 的
阶段三:APISIX 生效
- Watch 变化:
- APISIX 网关进程实时 Watch etcd 的变化。
- 一旦 etcd 有写入,APISIX 立刻收到通知,更新内存中的路由树(radixtree)。
4. “全量刷新”是如何毁掉 etcd 的?
你的配置中隐藏着两个“定时炸弹”:
炸弹 1:kubernetes.resync_interval: "6h"
- 这是为了防止“控制器内存状态与 K8s 不一致”的容错机制。
- 每隔 6 小时,Informer 会把你所有的 ApisixRoute 重新发送一遍
Update事件。 - 假如你有 1000 条路由:
- 控制器收到 1000 个
Update。 - 它会尝试把这 1000 条路由重新“翻译”并“写入” etcd。
- 即使内容没变,etcd 也会为每次写入生成新的 revision。
- 结果:6 小时内瞬间增加 1000+ 个 revision。
- 控制器收到 1000 个
炸弹 2:apisix_resource_sync_interval: "1h"
- 这是为了防止“etcd 数据与控制器内存不一致”的容错机制。
- 每隔 1 小时,控制器可能会把内存里的全量配置,刷到 etcd。
- 这通常会导致更直接的写入压力。
为什么会导致“突然空间不足”?
- 初始状态:etcd 运行正常,DB 有一定大小。
- 时间推移:每小时(或每 6 小时)的定时任务,产生大量“无效写入”。
- 累积效应:etcd 的 DB 文件不断增长。虽然单次写入不大,但 revision 的累积导致 DB 文件内部碎片剧增。
- 爆发点:
- 当 DB 总大小接近
quota-backend-bytes(默认 2GB)时,etcd 触发NOSPACE告警。 - 告警一旦触发,集群进入只读模式,所有写入被拒绝。
- 表现为“某个时间点突然挂掉”。
- 当 DB 总大小接近
5. 建议解决方案
要解决这个问题,你需要打破“无效写入”的循环:
- 调大或关闭不必要的全量同步:
- 如果你的集群稳定,可以将
resync_interval设为0(禁用 K8s Informer 的定期 Resync)或设为很大的值(如24h)。 apisix_resource_sync_interval同理,视业务稳定性调整。
- 如果你的集群稳定,可以将
- 升级 APISIX Ingress Controller 版本:
- 新版本(如 1.5+)通常优化了比较逻辑,会对比“期望配置”和“实际配置”,只有在内容真的发生变化时才写入 etcd。
- 旧版本可能存在“无脑覆盖写入”的 Bug。
- 开启 etcd 自动压缩:
- 即使有写入,通过合理的压缩策略也能控制 DB 大小:
etcdctl put /config/auto-compaction-mode periodic etcdctl put /config/auto-compaction-retention 1h
- 即使有写入,通过合理的压缩策略也能控制 DB 大小:
- 监控 Revision 增长速率:
- 观察
etcdctl endpoint status中的revision字段。 - 调整配置后,看 revision 的增长速度是否平缓。
总结:你的问题核心在于Ingress Controller 的定时全量同步机制 + 旧版本可能存在的“无差别写入”逻辑。调整这两个配置参数,并确认控制器版本具备“差异化更新”能力,通常就能解决问题。
- 观察
这两个参数不是一回事,作用范围完全不同:
kubernetes.resync_interval:控制的是 Kubernetes Informer 的“周期性全量重推事件”(属于 client-go 的通用机制)。apisix_resource_sync_interval:
控制的是 APISIX Ingress Controller 自己的定时器:把 K8s 资源同步到 APISIX 的间隔。
虽然都带“sync”,但一个在 K8s 侧,一个在 APISIX 侧,行为不同,对 etcd 的影响也不同。
1. 官方配置里的直接解释
你贴的这段配置(来自一篇总结博客):
apisix-resource-sync-interval: "300s" # Default interval for synchronizing Kubernetes resources to APISIX
kubernetes:
kubeconfig: ""
resync_interval: "6h" # how long should apisix-ingress-controller re-synchronizes with Kubernetes
apisix-resource-sync-interval:Default interval for synchronizing Kubernetes resources to APISIX
把 K8s 资源同步到 APISIX 的默认间隔。resync_interval:how long should apisix-ingress-controller re-synchronizes with Kubernetes
apisix-ingress-controller 与 Kubernetes 重新同步的间隔。
所以从名字和注释就能看出区别:- 一个是:多久把 K8s 资源同步到 APISIX(写 APISIX/etcd)。
- 一个是:多久让 Informer 认为可能“有遗漏”,重新推一遍所有对象事件(触发控制器内部处理逻辑)。
2. kubernetes.resync_interval:K8s Informer 的“假更新”
2.1 它到底在干啥?
resync_interval 是传给 Kubernetes SharedInformer 的 resyncPeriod 参数。Informer 的行为(这是 client-go 的通用机制):
- 每隔
resync_interval:- Informer 会把所有正在 watch 的对象,重新当作
Update事件推给控制器; - 这些事件不是真的从 kube-apiserver 来的变更,而是 Informer 自己“重放”的。
官方文档里也说明:这是为了让控制器有机会“修补”自己的状态,防止因为某些事件丢失导致内存状态和 K8s 不一致。
- Informer 会把所有正在 watch 的对象,重新当作
2.2 对 APISIX Ingress Controller 的意义
- APISIX Ingress Controller 为每种资源(ApisixRoute、Service、Endpoints 等)都建了 Informer;
resync_interval: "6h"意味着:- 每 6 小时,所有这些 Informer 会把内存中所有对象重新“通知”一遍;
- 控制器的
OnUpdate等事件处理函数会被频繁调用。
关键点:
- 这一步不会立刻写 etcd,只是让控制器内部重新处理一遍 K8s 对象;
- 但如果控制器的实现是“每次事件都重新计算一遍期望配置,然后写 APISIX Admin API/etcd”,
那么每次 resync 就会变成一次全量写 APISIX。
3. apisix_resource_sync_interval:定时把 K8s → APISIX 的“真同步”
这个参数是 APISIX Ingress Controller 自己加的定时器机制:
- 每隔
apisix_resource_sync_interval:- 控制器会拿内存里所有的 K8s 资源(ApisixRoute、ApisixUpstream、Ingress 等),
- 翻译成 APISIX 的 routes / upstreams / ssl,
- 然后通过 Admin API 写入 APISIX(也就是写入 etcd)。
官方注释很明确:这是“同步 K8s 资源到 APISIX 的默认间隔”。
所以:
- 这个参数直接影响 etcd 写频率;
- 如果你的资源很多,而且这个间隔设得很短(比如你现在是
1h),
那么即使没有任何变更,每小时都会有一波“全量写 APISIX/etcd”。
4. 它们是如何“配合”又“互坑”的?
用一个简单的流程图帮你区分:
4.1 正常情况(无定时器)
- 只有 K8s 事件触发写 etcd;
- etcd revision 随业务变更缓慢增长;
- 不会“突然暴涨”。
4.2 有这两个定时器时
resync_interval: 6h- 每隔 6 小时,所有 K8s 对象触发一次
Update; - 控制器重新处理所有对象,生成期望配置;
- 如果这时刚好有
apisix_resource_sync_interval定时器触发,就会把这些配置写一次 etcd。
- 每隔 6 小时,所有 K8s 对象触发一次
apisix_resource_sync_interval: 1h- 每隔 1 小时,不管 K8s 有没有变更,都拿当前内存里的配置写一遍 APISIX/etcd;
- 写入频率更高,更容易让 revision 持续上涨。
所以你看到的“某个时间点突然空间不足”,很可能就是:
- 每小时一次的
apisix_resource_sync_interval; - 再叠加每 6 小时一次的
resync_interval; - 结果:在固定时间窗口内,etcd 被反复“全量刷一遍,revision 暴涨,DB 被撑满。
5. 为什么你容易误以为“它们是同一件事”?
因为从“宏观效果”上看,它们都会导致“控制器重新处理一遍 K8s 资源”:
resync_interval:K8s 侧:Informer 重推事件 → 控制器重新处理。apisix_resource_sync_interval:APISIX 侧:定时器触发 → 控制器重新生成并写 APISIX。
但差异在于:
- 触发源不同
- 一个在 K8s Informer 内部;
- 一个在 APISIX Ingress Controller 自己的定时器里。
- 是否必然写 etcd
resync_interval只保证“事件到控制器”,不保证写 APISIX;写不写取决于控制器实现(会不会每次事件都重新写)。apisix_resource_sync_interval是显式设计来写 APISIX/etcd 的。
- 对 etcd 的影响不同
- 如果你把
resync_interval设得很大(甚至禁用),etcd 写入压力只来自业务变更 +apisix_resource_sync_interval; - 如果你把
apisix_resource_sync_interval设成0(禁用),那么只有 K8s 变更才会触发写 etcd,压力会小很多。
- 如果你把
6. 给你的实际建议
结合你之前说的“etcd 空间暴涨”问题,可以这样调:
- 先看业务变更频率
- 如果 K8s/APISIX 配置变更很少,可以把:
apisix_resource_sync_interval调大,比如6h甚至更长;- 或者干脆设成
0,依赖 K8s 事件驱动即可。
- 如果 K8s/APISIX 配置变更很少,可以把:
resync_interval调整要保守- 官方默认
6h是为了容错; - 如果你事件系统稳定,可以适当放大(比如
12h),但不建议直接改成 0,否则一旦有事件丢失,状态很难自动修复。
- 官方默认
- 确认控制器版本是否做了“差异化写入”
- 新一点的版本会对比“期望配置”和“当前 APISIX 配置”,只在内容变化时才写;
- 旧版本可能是“无脑覆盖”,哪怕内容一样,也会对每个 key 做一次 PUT,导致 revision 暴涨。
一句话总结:
resync_interval:管的是 K8s Informer 多久“假更新”所有对象一次。apisix_resource_sync_interval:管的是 控制器多久把 K8s 资源全量刷到 APISIX(etcd)一次。
它们不是同一个参数,但叠加在一起时,确实会让 etcd 在固定时间承受“双倍写入风暴”,这就是你遇到问题的核心原因。