用controller-runtime的reconcile函数监听crd状态变化是唯一稳定、生产可用的方式;直接调用watch或轮询易丢事件、绕过informer缓存与去重,导致状态不一致。

用 controller-runtime 的 Reconcile 函数监听 CRD 状态变化是唯一稳定、生产可用的方式;直接调用 watch 或轮询不仅易丢事件,还会绕过 Informer 缓存和事件去重机制,导致状态不一致。
为什么不能直接用 client-go 的 Watch?
CRD 资源一旦注册进集群,其变更必须经由 controller-runtime 的 Informer 体系分发,否则会遇到三类典型问题:
-
Watch连接断开后无法自动恢复,且缺失 resourceVersion 断点续传逻辑 - 未经过
SharedInformer的对象没有本地缓存,Get操作全走 API Server,压垮控制平面 - 多个副本的 Operator 实例可能重复处理同一事件,因为没走 WorkQueue 的去重与限速
Reconcile 函数怎么被触发?
触发不是靠“监听某个字段”,而是靠 controller-runtime 对整个 CR 对象的生命周期事件(Create/Update/Delete)+ Informer 缓存同步完成(Sync)+ OwnerReference 关联资源变更这三类信号驱动。实际开发中需注意:
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 每次
Reconcile都接收一个reconcile.Request,含NamespacedName,不是原始事件对象 - CR 更新时,哪怕只改了
spec.replicas,也会触发完整调谐;但若只更新metadata.annotations且没配置WithEventFilter,默认也会触发 - 若 CR 的
Status字段被其他组件(如 webhook 或另一个 controller)更新,Reconcile不会自动触发——除非你显式在Watches中监听该 CR 的Status子资源
如何让 Reconcile 只响应特定字段变更?
controller-runtime 不提供“监听 spec.xxx 字段”的原生能力,但可通过 EnqueueRequestsFromMapFunc + 自定义比对逻辑实现轻量过滤:
- 在
Builder中用.Watches(&source.Kind{Type: &myappv1.MyApp{}}, handler.EnqueueRequestsFromMapFunc(...)) - 在 MapFunc 中,先
Get上一版对象(从 cache),再对比old.Spec.Replicas != new.Spec.Replicas,仅在此时返回非空reconcile.Request - 注意:此逻辑不能放在
Reconcile内部做,否则仍会进队列、占 worker、消耗锁
容易忽略的两个硬性前提
很多团队卡在“写了 Reconcile 却收不到事件”,根本原因常是以下两点之一没做:
- CRD YAML 中
spec.preserveUnknownFields: false必须设为false,否则 controller-runtime 无法校验结构,Informer 启动失败且无明确错误日志 - RBAC 中
rules必须同时包含get/list/watch对 CR 的权限,缺watch就等于没注册 Informer,Reconcile永远不会被调用
真正难的不是写 Reconcile,而是理解它被谁触发、何时触发、以及触发时你能拿到什么上下文——这些决定了你是否要在里面查 Deployment、是否要加 finalizer、是否要拆成 status 和 spec 两次 Update。别把“监听变化”想成一个动作,它是一整套缓存、队列、校验、重试组成的闭环。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










