Reconcile函数必须幂等且非阻塞:同一请求执行多次效果相同,禁止sleep或耗时操作;须用r.Client读取、Patch更新、Status().Update写状态,并确保CRD注册到Scheme、cache同步完成后再启动。

Reconcile 函数必须幂等且不阻塞
每次 Reconcile 被调用,都代表某个资源(比如 CR)发生了变化,但 Kubernetes 不保证调用顺序或次数——可能重复、乱序、并发触发。所以逻辑里不能依赖“只执行一次”,也不能做耗时操作(如 HTTP 请求、文件 IO、sleep),否则会卡住整个 controller 的 worker 队列。
常见错误现象:manager 日志里持续打印 Reconciling 但无后续,或大量 context deadline exceeded;实际资源状态却没更新。
- 所有对象读取必须用注入的
r.Client(不是client-go原生 client),它走 cache,快且线程安全 - 写操作优先用
r.Client.Patch(ctx, obj, client.MergeFrom(&original)),避免resourceVersion conflict - 更新
Status字段必须走子资源:用r.Status().Update(ctx, &obj),直接Update会报错the server does not allow this method on the requested resource - 如果需要延迟重试(比如依赖的 ConfigMap 还没 Ready),返回
ctrl.Result{RequeueAfter: 5 * time.Second},别用time.Sleep
For() 后必须接 Complete() 才能监听资源
controller-runtime 不会自动监听任何资源。调用 ctrl.NewControllerManagedBy(mgr).For(&myv1.MyApp{}) 只是构建配置,真正启动 Informer 并注册事件处理器,靠的是 .Complete(r)。
常见错误现象:控制器启动成功、日志显示 Starting workers,但创建 CR 后 Reconcile 完全不触发,manager.GetCache().Get() 也查不到该 CR。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 漏掉
.Complete(r)是本地调试最常踩的坑,没有报错,只有静默失效 -
For()监听的是集群全局该类型的所有实例;若只想管特定 namespace,得在Reconcile开头加if req.Namespace != "my-ns" { return ctrl.Result{}, nil } - 要同时响应多个资源(比如 MyApp 变了,也要看它引用的 Secret),不能在
Reconcile里反复r.Client.Get,而应提前用Watches()注册额外 Watch,并确保 Secret 上有正确的OwnerReference
CRD 类型必须提前注册到 Scheme
controller-runtime 用 Scheme 做序列化/反序列化和类型路由。如果你的控制器操作自定义资源(比如 MyApp),但没把它注册进 mgr.GetScheme(),r.Client.Get() 会 panic 报 no kind "MyApp" is registered for version "mygroup/v1"。
典型场景:用 kubebuilder create api 生成代码后,忘记在 main.go 的 setupScheme 或 init() 里调用 myv1.AddToScheme(scheme)。
- 注册位置通常在
mgr := ctrl.NewManager(...)之前,例如:myv1.AddToScheme(mgr.GetScheme()) - 如果用 Kubebuilder,默认已生成
api/v1/register.go和main.go中的注册调用,但手动项目容易遗漏 - 检查是否注册成功:启动前加一行
log.Println(mgr.GetScheme().KnownTypes(myv1.GroupVersion)),应输出非空 map
本地调试需等待 cache 同步完成
controller-runtime 启动时会先拉取全量资源到本地 cache,再开始 dispatch 事件。Reconcile 第一次被调用前,必须等 cache ready,否则 r.Client.List() 或 Get() 可能返回空或 IsNotFound 错误,即使资源真实存在。
常见错误现象:本地 make run 启动后立即创建 CR,控制器日志显示 not found,但 kubectl get 确认 CR 已存在;过几秒再创建就正常了。
- 启动 manager 前,显式加等待逻辑:
if err := mgr.GetCache().WaitForCacheSync(ctx); err != nil { ... } - Kubebuilder 默认已在
main.go的mgr.Start前做了同步等待,但手写项目常忽略 - 如果只是临时调试,也可加短 sleep(不推荐),但生产代码必须依赖
WaitForCacheSync
真正容易被忽略的是:cache 同步不是“一次性”动作,而是按 GroupVersion 分片加载;如果你监听了多个 CRD,但只注册了部分 Scheme,对应 cache 分片就不会 ready,导致整个 sync 卡住——这时候日志里既没报错也没提示,只能靠 mgr.GetCache().WaitForCacheSync 的返回值或 debug 日志定位。

















