跳转到内容

Kubebuilder — 写 K8s Operator 的官方脚手架

是什么

Kubebuilder 是 Kubernetes SIG(特别兴趣组)维护的脚手架工具——帮你几条命令生成一整个 Kubernetes Operator 项目骨架

日常类比:像 npm init 之于前端、rails new 之于 Web、cargo new 之于 Rust——你不用从空目录手抄一堆样板代码,跑一行命令就有完整目录结构、依赖配置、入口文件。

只不过 Kubebuilder 生成的不是普通项目,而是 Operator——一个跑在 kubernetes 集群里、替你”看着”自定义资源的程序。

Terminal window
kubebuilder init --domain example.com --repo example.com/foo
kubebuilder create api --group apps --version v1 --kind Memcached

两行命令,你就有了:CRD 定义、Controller 框架、main.go 入口、RBAC 权限模板、Dockerfile、Makefile。剩下的事——写 Reconcile 函数——才是你真正要思考的业务逻辑。

为什么重要

不理解 Kubebuilder,下面这些事都没法解释:

  • 为什么”K8s Operator”这个词 2018 年后突然铺天盖地——之前写 controller 要懂 client-go、informer、workqueue 一大堆细节,Kubebuilder 把它们封进生成代码
  • 为什么 Cert-Manager / Istio / Prometheus Operator / ArgoCD 这些”K8s 上的应用”长得都很像——它们底层基本都用 Kubebuilder 或它的兄弟 controller-runtime
  • 为什么 K8s 的”扩展性”不止 webhook:CRD + Controller 才是终极路径,Kubebuilder 是这条路径的官方入口

核心要点

Kubebuilder 把”写 Operator”拆成 三个核心动作

  1. 定义 CRD(Custom Resource Definition):用 Go struct 写”我想给 K8s 加一种新资源叫 Memcached”,Kubebuilder 用 controller-gen 把 struct 翻译成 K8s 能认的 YAML schema。
  2. 写 Reconcile 循环:每次 Memcached 资源变化,K8s 调你的 Reconcile(ctx, req)。你的工作是对比”用户期望”和”集群现状”,把差距补上。这个循环是 Operator 模式的灵魂。
  3. 由 controller-runtime 兜底:watch / cache / workqueue / leader election 这些底层细节,Kubebuilder 默认生成的 main.go 已经接好——你只管业务逻辑。

三步加起来,Operator = CRD 描述意图 + Reconcile 实现意图

实践案例

案例 1:Reconcile 函数长什么样

func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 1. 从 K8s 取出用户写的 Memcached 资源
var memcached cachev1.Memcached
if err := r.Get(ctx, req.NamespacedName, &memcached); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 2. 看集群里现在有几个 Memcached pod
var deployment appsv1.Deployment
err := r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, &deployment)
// 3. 如果不存在,造一个;如果存在但副本数不对,改它
if errors.IsNotFound(err) {
return ctrl.Result{}, r.Create(ctx, buildDeployment(&memcached))
}
if *deployment.Spec.Replicas != memcached.Spec.Size {
deployment.Spec.Replicas = &memcached.Spec.Size
return ctrl.Result{}, r.Update(ctx, &deployment)
}
return ctrl.Result{}, nil
}

注意 Reconcile 是幂等的——不管被调一次还是一百次,最终把集群状态拉到”用户期望”那一边就完事。

案例 2:CRD 是怎么从 Go 注解长出来的

// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=10
type MemcachedSpec struct {
Size int32 `json:"size"`
}

make manifests,Kubebuilder 调 controller-gen 读这些 +kubebuilder: 注解,生成一份 memcached-crd.yaml:里面有 OpenAPI v3 schema、字段限制(1-10)、字段说明。Go 代码是源真相,YAML 是衍生——和这个学习项目里”md 是源真相,html 是衍生”是同一个思路。

案例 3:Operator 在生产里跑起来是这样

部署 Cert-Manager 后,集群里多了一种资源:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-tls-cert
spec:
dnsNames: [example.com]
issuerRef: { name: letsencrypt }

kubectl apply 这个 YAML——它本身啥也不会做。但 Cert-Manager 那个用 Kubebuilder 写的控制器一直在 watch,看到新的 Certificate 资源,触发 Reconcile:去 Lets Encrypt 申请证书 → 把证书塞进 Secret。整个过程你只描述”要什么”,控制器替你”做什么”——这就是声明式 API。

踩过的坑

  1. Reconcile 必须幂等:新人常写”如果不存在就 Create”——但 Reconcile 可能被重复触发(K8s 没有”严格只调一次”承诺)。第二次进来 Create 会报 AlreadyExists,要用 IgnoreAlreadyExists 或 server-side apply 补救。

  2. 不要在 Reconcile 里 sleep / 循环等结果:要继续等就 return ctrl.Result{RequeueAfter: 30 * time.Second}, nil,让框架自己再调你。阻塞 worker 会让整个 controller 卡死。

  3. finalizer 容易写漏:删除带外部资源的 CRD(如挂着云厂商负载均衡器)必须先用 finalizer 拦下删除请求、清理外部资源、再放行。漏写就资源删了但云上钱还在烧

  4. CRD scope 一旦选错很难改Cluster 还是 Namespacedkubebuilder create api 时定,改要重新生成 + 数据迁移,影响所有已部署实例。

  5. v3 → v4 PROJECT 文件不兼容:升级 Kubebuilder 大版本时 PROJECT 文件格式有变更,旧项目要按官方 migration guide 一步步走,直接覆盖会丢配置

  6. owner reference 漏挂导致资源残留:你创建的 Deployment / Service 要用 controllerutil.SetControllerReference 挂到 CRD 上,否则删除 CRD 时它创建的子资源不会被级联回收——集群里慢慢堆出”幽灵资源”。

  7. status 子资源更新要走 r.Status().Update:直接 r.Update 写 status 在启用 subresource: status 后会被忽略——新人调试半天发现”我明明 set 了,咋 K8s 那边没看到”。

适用 vs 不适用场景

适用

  • 给 K8s 加一种”业务概念”——数据库实例、ML 训练任务、TLS 证书、CI 流水线
  • 把”运维 runbook”自动化——磁盘扩容、主从切换、版本升级
  • 多租户 / 平台化产品——你卖的 SaaS 让客户自己 kubectl apply 自定义资源

不适用

  • 一次性脚本 / 简单 CronJob → 用 K8s 原生 Job 或 ansible 就够,别为了”用 Operator”而 Operator
  • 需要写 admission webhook 但不需要 CRD → 直接用 controller-runtime 起 webhook server,不需要 Kubebuilder 全套脚手架
  • 不在 K8s 生态里 → Kubebuilder 完全绑死 K8s API model

学到什么

  1. 声明式 API + Reconcile 循环是 K8s 把”复杂运维”变成”写 YAML”的两根支柱——Kubebuilder 让你也能造这种 API

  2. 生成代码 vs 业务代码要分清——Kubebuilder 生成的部分(main.go / RBAC / Dockerfile)跑 make 时会被覆盖,业务逻辑只能写在 controllers/*.goapi/*_types.go

  3. Operator 模式不是 K8s 独有——任何”声明状态 + 自动收敛”的系统都用类似思路,terraform 的 plan/apply、git 的 merge 都是远亲

  4. 官方脚手架 = 社区共识——Kubebuilder 由 SIG 维护,意味着它生成的目录结构、命名约定、测试方式被整个生态默认接受。看到一个陌生 Operator 仓库,认得出 api/controllers/config/ 的人能 5 分钟上手

延伸阅读

关联

  • kubernetes —— Kubebuilder 生成的 Operator 跑在 K8s 上,扩展 K8s 自身
  • docker —— Operator 管理的 Pod 里跑的是容器
  • terraform —— 同样是”声明状态 + 自动收敛”思路,但作用在云资源
  • ansible —— 命令式运维自动化,和 Operator 的”声明式 + 控制循环”形成对比

反向链接

  • ansible —— Ansible — 无 agent 配置管理
  • calico —— Calico — 用 BGP 路由把 K8s pod 当成一个个小路由器
  • kubernetes —— Kubernetes — 容器编排平台
  • operator-sdk —— Operator SDK — 写 K8s Operator 的”豪华套餐”版脚手架
  • sealed-secrets —— Sealed Secrets — 把加密后的 Secret 安全提交到 Git