Operator开发不必须但强烈建议用controller-runtime,因其封装client-go底层细节,自动处理informer同步、reconcile排队、leader选举等;自行基于client-go易出错导致状态不一致。

Operator开发必须用controller-runtime吗
不是必须,但强烈建议。Kubernetes官方推荐的controller-runtime封装了client-go底层细节,自动处理informer同步、reconcile排队、leader选举和健康检查等重复逻辑。自己基于client-go从零写,容易漏掉ResourceVersion校验、watch断连重试或finalizer清理,导致状态不一致。
实操建议:
- 初始化项目时直接用
operator-sdk init --plugins=go-sdk(v1.28+已默认基于controller-runtime) - 避免手动引入
k8s.io/client-go裸包做list/watch——controller-runtime的Client和Manager已足够覆盖95%场景 - 若需调用非CRD资源(如
Secret),用mgr.GetClient()而非自己new clientset,否则可能绕过scheme注册导致解码失败
Reconcile函数里不能直接return err
controller-runtime的Reconcile函数返回ctrl.Result, error,但error ≠ 立即重试。如果返回非nil error,controller会按指数退避重试;而ctrl.Result.RequeueAfter才是控制下次调度时间的正确方式。
常见错误现象:
- 日志反复打印
"Reconciler error" "error"="failed to get pod: not found",实际是pod还没被调度出来,不该报错 - 误把业务逻辑“暂不可用”当成严重错误,导致控制器疯狂重试压垮API Server
正确做法:
- 资源不存在(如
IsNotFound(err))→ 返回ctrl.Result{}, nil,静默退出 - 需要等待条件满足(如等待job完成)→ 返回
ctrl.Result{RequeueAfter: 10 * time.Second}, nil - 真正不可恢复错误(如RBAC缺失、证书过期)→ 返回
err,触发告警而非轮询
Finalizer添加和移除必须成对出现
Finalizer是防止CR被误删的关键机制,但很多Operator在Reconcile中只加不删,或删之前没确保外部资源已清理完毕,导致CR卡在Terminating状态无法删除。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
使用场景:
- CR关联了云厂商资源(如AWS RDS实例),删除CR前必须先调API销毁RDS
- CR创建了命名空间下的一组ConfigMap,需在finalizer里逐个清理
关键检查点:
- 加finalizer前,先用
client.Patch()更新CR,且patch必须包含metadata.finalizers字段,不能只改struct再Update(会覆盖其他字段) - 移除finalizer前,确认所有依赖资源已不存在(例如
client.List()查不到对应Pod) - finalizer名称必须全局唯一,建议带域名前缀,如
"example.com/cleanup-rds",避免和其他Operator冲突
本地调试时为什么List/Get总是返回空
最常见原因是Scheme未注册自定义资源类型。controller-runtime的Client依赖scheme做序列化/反序列化,如果CRD结构体没通过 AddToScheme(scheme)注册,client会静默忽略该类型,list结果为空且无报错。
实操排查步骤:
- 确认
api/v1alpha1/register.go中AddToScheme已调用SchemeBuilder.Register - 检查
main.go中mgr := ctrl.NewManager(...)是否传入了注册好CRD的scheme(通常是apis.AddToScheme(mgr.GetScheme())) - 用
kubectl get确认集群中CRD已存在且-o yaml status.conditions为Accepted - 调试时加一行
log.Info("scheme info", "known types", mgr.GetScheme().AllKnownTypes()),看输出是否包含你的CRD类型
别忽略scheme注册顺序——如果在mgr.Start()之后才注册类型,client已经初始化完毕,后续注册无效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










