Reconcile方法必须返回ctrl.Result和error,其签名固定为func(ctx context.Context, req ctrl.Request) (ctrl.Result, error),返回值分别控制重试行为与错误处理,nil,nil表示成功且无需重入,任何业务异常都应通过error显式返回而非panic。

Reconcile方法必须返回ctrl.Result和error
Operator的调谐逻辑全部封装在Reconcile方法中,它的签名固定为:func(ctx context.Context, req ctrl.Request) (ctrl.Result, error)。返回ctrl.Result控制是否重试或延迟下次调谐,返回error会触发失败重试(带指数退避)。如果两个都返回nil,表示本次调谐成功且无需再次触发。
常见错误是只返回nil, nil却不检查资源状态变化,导致状态漂移;或者误将业务错误当作可忽略日志,不返回error,使控制器静默失败。
- 需要立即重试(如API临时不可用):返回
ctrl.Result{}, err - 希望10秒后重试(如等待依赖就绪):返回
ctrl.Result{RequeueAfter: 10 * time.Second}, nil - 需要立刻再次调谐(如发现Spec变更但未生效):返回
ctrl.Result{Requeue: true}, nil - 不要用
panic代替error返回——这会导致整个Manager崩溃
从req.NamespacedName获取对象并做“读-改-写”三步操作
每次Reconcile只处理一个对象,其名字由req.NamespacedName给出。不能跳过读取直接构造对象,否则无法感知集群真实状态。
典型流程是:Get当前资源 → 检查是否被删除(IsNotFound)→ 按Spec计算期望状态 → Get或List下游资源 → 对比实际与期望 → 调用Create/Update/Delete同步。
- 使用
client.Get(ctx, req.NamespacedName, &myCR)加载自定义资源,而非new()或硬编码 - 更新对象时必须先
Get再Update,不能仅靠DeepCopy构造新对象后Create——否则会丢失ResourceVersion导致冲突 - 对下游资源(如
Deployment)执行Patch比Update更安全,避免覆盖其他控制器的字段 - 记得在
Get后检查errors.IsNotFound(err),而不是err != nil——否则删除事件会被当成错误重试
Watch关联资源需显式配置Owns()或Watches()
默认情况下,Reconcile只响应自身CR的创建/更新/删除事件。要让一次CR变更触发对Service、Secret等下游资源的调谐,必须在Setup阶段注册监听关系。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
最常用的是Owns(&appsv1.Deployment{}),它自动建立OwnerReference,并确保当Deployment被外部删除时,触发对应CR的Reconcile。但注意:Owns只对设置了controllerutil.SetControllerReference()的对象生效。
- 若下游资源无法加OwnerRef(如跨命名空间或第三方资源),改用
Watches(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForOwner{...}) - 用
EnqueueRequestForOwner时,isController: true必须设为true,否则不会反向触发 - 不要在Reconcile里手动
client.List所有Deployment去轮询——这既低效又错过事件驱动时机
Context超时和取消必须贯穿整个Reconcile链路
Reconcile函数接收的ctx来自Manager,已带超时(默认10秒)和取消信号。所有下游调用(client.Get、client.Update、HTTP请求、exec命令)都必须传入该ctx,否则可能阻塞整个Worker线程。
尤其要注意子goroutine和defer清理逻辑:一旦ctx取消,应尽快释放资源,比如关闭HTTP连接、停止轮询goroutine、清理临时文件。
- 禁止在Reconcile中用
context.Background()或context.TODO()替代入参ctx - 调用
time.Sleep前先select判断ctx.Done(),避免死等 - 使用
client.SubResource("scale").Get等子资源操作时,同样要传ctx - 日志打点也建议用
log := log.FromContext(ctx),便于追踪调谐生命周期
真正容易被忽略的是:Reconcile不是“运行一次就完事”的函数,而是一个持续收敛的过程。哪怕所有步骤都返回nil, nil,只要集群状态和Spec不一致,下一次事件(或定时Resync)仍会进来。所以重点不在“做完”,而在“每次进来都能正确识别差异并修复”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










