用kubebuilder初始化Operator项目,聚焦Reconcile幂等逻辑与终态处理:生成CRD、设置OwnerReference、分离Status更新、本地kind调试,再完善Finalizer清理边界。

直接上手写一个能跑通的 Go Kubernetes Operator,核心不是“从零造轮子”,而是用 controller-runtime 搭骨架、用 kubebuilder 生成脚手架、再聚焦在你自己的业务逻辑上——否则光是处理 Informer、Reconcile 循环、OwnerReference、Finalizer 这些就足以卡死两周。
用 kubebuilder 初始化项目结构(别手写 Makefile 和 CRD YAML)
kubebuilder 是目前最稳的 Operator 开发起点,它生成的目录结构和代码模板已适配 controller-runtime v0.16+ 和 Kubernetes 1.25+。手写 API 定义或 controller 入口容易漏掉 SetupWebhookWithManager 或 Watch 调用,导致 CR 创建后压根不触发 Reconcile。
- 确保安装
kubebuilderv3.12+(对应 Kubernetes 1.25+)和controller-genv0.14+ - 运行
kubebuilder init --domain example.com --repo example.com/myop,它会自动初始化 Go module 并写好Makefile - CRD 定义统一放在
api/v1alpha1/下,用kubebuilder create api生成,不要手动改spec字段后再补// +kubebuilder:validation注释——生成器会帮你补全 OpenAPI v3 schema - 生成后立刻执行
make manifests,检查输出的config/crd/bases/下 YAML 是否含x-kubernetes-preserve-unknown-fields: true(对嵌套 map/slice 必需,否则 apply 时字段被静默丢弃)
Reconcile 函数里只做“当前状态 → 期望状态”的单次对齐
Reconcile 不是“执行部署流程”,而是“检查当前有没有 Pod、Service、ConfigMap,没有就创建,有但不对就更新,有多余就删”。它会被反复调用,必须幂等;任何阻塞操作(如 HTTP 轮询外部服务、sleep)都会卡住整个 controller 队列。
- 用
r.Get(ctx, req.NamespacedName, &myObj)加载 CR 实例,再用client.List查它依赖的资源(比如查同 namespace 下 label 为app=myobj-name的 Pod) - 判断是否需要创建/更新:优先比对
ObjectMeta.Generation和Status.ObservedGeneration,避免因 spec 未变却反复重建 - 创建资源时务必设置
OwnerReferences:controllerutil.SetControllerReference(&myObj, &pod, r.Scheme),否则手动删 CR 后残留 Pod - 更新 Status 用
r.Status().Update(ctx, &myObj)单独提交,不要和 spec 更新合在一个Update里——Status 子资源更新不触发新 Reconcile,而 spec 更新会
本地调试用 kind + make install && make run,别急着 build image
Operator 本质是运行在集群内的 client,本地调试时让它直连 kube-apiserver 最快。用 make run 启动后,所有日志、断点、panic 堆栈都在终端可见,比先打包镜像、推 registry、改 Deployment YAML、kubectl logs 查半天高效得多。
立即学习“go语言免费学习笔记(深入)”;
- 先用
kind create cluster起一个单节点集群(默认加载最新 stable 版 Kubernetes) -
make install把 CRD 和 RBAC 安装进集群(检查输出是否有customresourcedefinition.apiextensions.k8s.io/xxx created) -
make run ENABLE_WEBHOOKS=false(除非你真要测 webhook,否则关掉省去证书配置) - 另起 terminal 执行
kubectl apply -f config/samples/下的示例 CR,看终端日志是否打出Reconciling MyObj及后续资源创建 log - 如果报错
no matches for kind "MyObj" in version "example.com/v1alpha1",说明 CRD 没装成功或 Group/Version 写错,回看api/v1alpha1/groupversion_info.go的GroupVersion常量
真正难的不是写完第一个 Reconcile,而是处理终态不一致的边界:比如 Pod 已 Running 但 readiness probe 还没通过,此时该等还是该上报 Failure?又比如用户删了 CR 但 Finalizer 死锁导致卡在 Terminating——这些得靠 FinalizeMyObj 函数里显式清理,并在 Reconcile 开头判断 myObj.DeletionTimestamp != nil 分支。没覆盖这些,Operator 在生产环境就会变成“半吊子控制器”。


















