ETCD相关-apisix revision异常

作者:小橙子🍊 发布时间: 2026-02-13 阅读量:1 评论数:0

APISXI 造成 ETCD revision 异常

** 只有写操作才会让 revision 增加并撑大 DB,这个理解是对的。** 在 APISIX 场景下,“异常的写操作”基本都可以归到这几类:

  1. 通过 APISIX Admin API / Dashboard / 脚本反复写配置(包括 Ingress Controller)。
  2. APISIX Ingress Controller 定时全量同步,把内存里的 APISIX 配置“倒回” etcd,即使没有变更也会写。
  3. etcd 不稳定或超时,导致客户端重试逻辑频繁写
  4. 极少数情况下,自己写的插件/脚本在后台定时写 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,它主要做两件事:

  1. 监听 K8s 里的 CRD(ApisixRoute、ApisixUpstream 等);
  2. 把这些 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

下面这张图可以当成“排查清单”:

flowchart LR A[用户或 CI/CD] --> B[调用 APISIX Admin API] B --> C[etcd 写入 - PUT DELETE PATCH] D[APISIX Ingress Controller] --> E[监听 K8s CRD 变更] E --> F[调用 Admin API 写 APISIX 配置] F --> C D --> G[定时全量同步 - 可能存在异常] G --> F H[自定义脚本或插件] --> I[定时调用 Admin API 或直接写 etcd] I --> C J[etcd 不稳定或超时] --> K[客户端重试逻辑] K --> C

关键结论:

  • 只要你在 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. 回到你的猜测,逐条对一下

你说:只有写操作才会产生大量版本,那问题基本可以认为是异常的写操作。
这个判断是对的。你对三个方向的猜测,对应到现实情况大概是这样:

  1. etcd 不稳定 + APISIX 客户端重复操作?
    • 会放大写频率,但真正“写”的是客户端(脚本/Ingress Controller)
    • etcd 本身不会“凭空产生 revision”。
  2. APISIX 有 bug,某些情况下异常写入 etcd?
    • APISIX 核心本身不会后台写 etcd,Admin API 是唯一入口
    • 但 APISIX Ingress Controller 确实存在“持续写 etcd”的已知问题。
  3. K8s 数据源 etcd 不稳定导致连锁异常?
    • 这会让 APISIX Ingress Controller 从 API Server 拉不到数据,内部重试加剧;
    • 但最终“写 APISIX etcd”的还是 Ingress Controller。
      更具体的排查方向:
  • 如果你部署了 APISIX Ingress Controller,​→ 重点排查它的版本 + 是否有“持续更新 etcd”的 issue;
  • 如果只用 APISIX + 自己脚本,​→ 检查脚本是否有死循环/重复调用 Admin API;
  • 如果完全没有后台定时任务,
    → 那就要看是不是某个客户端在做“无变更的 PUT”(比如全量同步逻辑)。

6. 你接下来可以怎么验证?

建议你按这个顺序做:

  1. etcdctl endpoint status 确认 revision 是否在短时间内暴涨
  2. etcdctl watch /apisix --prefix 实时看写操作;
  3. 在 APISIX Ingress Controller 日志里看是否有大量“同步 APISIX 配置”的日志;
  4. 对照 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. 三组件数据处理全景图

为了让你一目了然,我画了一张数据流转图,标注了“写操作”发生的位置:

flowchart TD subgraph K8s集群 A[K8s API Server<br/>CRD/Service/Pod] end subgraph APISIX Ingress Controller B[Informer 监听机制] C[事件处理器<br/>Translate] D[定时全量同步器<br/>resync_interval: 6h<br/>sync_interval: 1h] E[APISIX 配置生成<br/>Routes/Upstreams] end subgraph Data Plane F[etcd 集群<br/>存储配置与版本] G[APISIX Gateway<br/>Watch etcd] end %% 正常变更流程 A -- 1.产生变更事件 --> B B -- 2.触发 OnUpdate --> C C -- 3.生成配置 --> E E -- 4.写入/更新 --> F F -- 5.监听到变化 --> G G -- 6.更新内存路由表 --> H[生效] %% 异常的全量刷新流程 (问题根源) A -.->|定时 6h 重推全量事件| B D -.->|定时 1h 强制全量对比| E E -.->|5. 每次对比都可能触发写入| F style F fill:#ffcccc,stroke:#333,stroke-width:2px style D fill:#ffcccc,stroke:#333,stroke-width:2px

3. 详细数据处理过程解析

阶段一:监听与转换

  1. 监听 K8s 资源
    • Ingress Controller 启动时,会通过 Informer 监听 K8s 的 CRD(如 ApisixRoute)以及原生资源(Service, Pod, Endpoints)。
  2. 事件触发
    • 当你执行 kubectl apply,K8s API Server 发出事件。
    • Informer 接收到事件,调用控制器内部的 OnAddOnUpdate 函数。

阶段二:配置生成与写入(写操作发生点)

  1. 转换配置
    • 控制器将 K8s 的资源定义,翻译成 APISIX 能理解的格式(如 routesupstreamsssl)。
  2. 写入 etcd(关键点)
    • 控制器调用 APISIX 的 Admin API(或直接操作 etcd client),将配置写入 etcd 的 /apisix/routes/xxx 路径。
    • 这里就是 etcd revision 增加的地方

阶段三:APISIX 生效

  1. 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

炸弹 2:apisix_resource_sync_interval: "1h"

  • 这是为了防止“etcd 数据与控制器内存不一致”的容错机制。
  • 每隔 1 小时,控制器可能会把内存里的全量配置,刷到 etcd。
  • 这通常会导致更直接的写入压力。

为什么会导致“突然空间不足”?

  • 初始状态:etcd 运行正常,DB 有一定大小。
  • 时间推移:每小时(或每 6 小时)的定时任务,产生大量“无效写入”。
  • 累积效应:etcd 的 DB 文件不断增长。虽然单次写入不大,但 revision 的累积导致 DB 文件内部碎片剧增。
  • 爆发点
    • 当 DB 总大小接近 quota-backend-bytes(默认 2GB)时,etcd 触发 NOSPACE 告警。
    • 告警一旦触发,集群进入只读模式,所有写入被拒绝。
    • 表现为“某个时间点突然挂掉”。

5. 建议解决方案

要解决这个问题,你需要打破“无效写入”的循环:

  1. 调大或关闭不必要的全量同步
    • 如果你的集群稳定,可以将 resync_interval 设为 0(禁用 K8s Informer 的定期 Resync)或设为很大的值(如 24h)。
    • apisix_resource_sync_interval 同理,视业务稳定性调整。
  2. 升级 APISIX Ingress Controller 版本
    • 新版本(如 1.5+)通常优化了比较逻辑,会对比“期望配置”和“实际配置”,只有在内容真的发生变化时才写入 etcd
    • 旧版本可能存在“无脑覆盖写入”的 Bug。
  3. 开启 etcd 自动压缩
    • 即使有写入,通过合理的压缩策略也能控制 DB 大小:
      etcdctl put /config/auto-compaction-mode periodic
      etcdctl put /config/auto-compaction-retention 1h
      
  4. 监控 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 SharedInformerresyncPeriod 参数。Informer 的行为(这是 client-go 的通用机制):

  • 每隔 resync_interval
    • Informer 会把所有正在 watch 的对象,重新当作 Update 事件推给控制器;
    • 这些事件不是真的从 kube-apiserver 来的变更,而是 Informer 自己“重放”的。
      官方文档里也说明:这是为了让控制器有机会“修补”自己的状态,防止因为某些事件丢失导致内存状态和 K8s 不一致。

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. 它们是如何“配合”又“互坑”的?

用一个简单的流程图帮你区分:

flowchart LR A[K8s API Server<br/>ApisixRoute/Service/Endpoints] --> B[Informer<br/>resync_interval 6h] B -- 正常事件 --> C[控制器事件处理<br/>OnAdd/OnUpdate] B -.->|每 6h 重推所有对象 Update| C C --> D[生成期望的 APISIX 配置<br/>routes/upstreams/ssl] D --> E[定时同步器<br/>apisix_resource_sync_interval 1h] E --> F[Admin API<br/>PUT /apisix/routes/xxx ...] F --> G[etcd<br/>写入 /apisix/*]

4.1 正常情况(无定时器)

  • 只有 K8s 事件触发写 etcd;
  • etcd revision 随业务变更缓慢增长;
  • 不会“突然暴涨”。

4.2 有这两个定时器时

  • resync_interval: 6h
    • 每隔 6 小时,所有 K8s 对象触发一次 Update
    • 控制器重新处理所有对象,生成期望配置;
    • 如果这时刚好有 apisix_resource_sync_interval 定时器触发,就会把这些配置写一次 etcd。
  • apisix_resource_sync_interval: 1h
    • 每隔 1 小时,不管 K8s 有没有变更,都拿当前内存里的配置写一遍 APISIX/etcd;
    • 写入频率更高,更容易让 revision 持续上涨。
      所以你看到的“某个时间点突然空间不足”,很可能就是:
  • 每小时一次的 apisix_resource_sync_interval
  • 再叠加每 6 小时一次的 resync_interval
  • 结果:在固定时间窗口内,etcd 被反复“全量刷一遍,revision 暴涨,DB 被撑满。

5. 为什么你容易误以为“它们是同一件事”?

因为从“宏观效果”上看,它们都会导致“控制器重新处理一遍 K8s 资源”:

  • resync_intervalK8s 侧:Informer 重推事件 → 控制器重新处理。
  • apisix_resource_sync_intervalAPISIX 侧:定时器触发 → 控制器重新生成并写 APISIX。
    但差异在于:
  1. 触发源不同
    • 一个在 K8s Informer 内部;
    • 一个在 APISIX Ingress Controller 自己的定时器里。
  2. 是否必然写 etcd
    • resync_interval 只保证“事件到控制器”,不保证写 APISIX;​写不写取决于控制器实现(会不会每次事件都重新写)。
    • apisix_resource_sync_interval显式设计来写 APISIX/etcd 的
  3. 对 etcd 的影响不同
    • 如果你把 resync_interval 设得很大(甚至禁用),etcd 写入压力只来自业务变更 + apisix_resource_sync_interval
    • 如果你把 apisix_resource_sync_interval 设成 0(禁用),那么只有 K8s 变更才会触发写 etcd,压力会小很多。

6. 给你的实际建议

结合你之前说的“etcd 空间暴涨”问题,可以这样调:

  1. 先看业务变更频率
    • 如果 K8s/APISIX 配置变更很少,可以把:
      • apisix_resource_sync_interval 调大,比如 6h 甚至更长;
      • 或者干脆设成 0,依赖 K8s 事件驱动即可。
  2. resync_interval 调整要保守
    • 官方默认 6h 是为了容错;
    • 如果你事件系统稳定,可以适当放大(比如 12h),但不建议直接改成 0,否则一旦有事件丢失,状态很难自动修复。
  3. 确认控制器版本是否做了“差异化写入”
    • 新一点的版本会对比“期望配置”和“当前 APISIX 配置”,只在内容变化时才写;
    • 旧版本可能是“无脑覆盖”,哪怕内容一样,也会对每个 key 做一次 PUT,导致 revision 暴涨。

一句话总结:

  • resync_interval:管的是 K8s Informer 多久“假更新”所有对象一次
  • apisix_resource_sync_interval:管的是 控制器多久把 K8s 资源全量刷到 APISIX(etcd)一次
    它们不是同一个参数,但叠加在一起时,确实会让 etcd 在固定时间承受“双倍写入风暴”,这就是你遇到问题的核心原因

评论