K8s Operator开发入门:从CRD到控制器跑通第一个实例
K8s Operator开发入门的核心路径并不复杂:先用 CRD 声明一个自定义资源,再写一个控制器监听它,把期望状态和实际状态对齐。
本文用 Kubebuilder 脚手架带你从零跑通一个最小 Operator,环境为 Kubebuilder 3.x 加 Kubernetes 1.24 以上集群,命令和路径都可直接复制执行。
前置准备:确认工具链和集群可用
开始前先确认本地已经具备以下条件,缺一个后面都可能报错:
- Go 1.19 或更高版本,执行
go version能看到输出。 - 一个可用的 Kubernetes 集群,
kubectl cluster-info能返回 API Server 地址。 - 本地有
kubectl的 kubeconfig 权限,能创建 CRD 和 Deployment。 - 安装 Kubebuilder,下载对应系统二进制后放到
/usr/local/bin,执行kubebuilder version验证。
如果只是学习,不想影响正式集群,可以先用 kind 或 minikube 起一个本地集群,避免误操作生产资源。
用 Kubebuilder 生成项目骨架
在空目录下执行初始化,项目名建议全小写,避免生成的文件名带大写字符:
mkdir -p ~/demo-operator && cd ~/demo-operator
kubebuilder init --domain example.local --repo github.com/yourname/demo-operator
接着生成 API 和控制器代码。
这里定义一个叫 App 的资源,组为 apps,版本为 v1:
kubebuilder create api --group apps --version v1 --kind App
执行过程中会询问是否创建 Resource 和 Controller,两个都选 y。
生成后目录结构大致是:api/v1/app_types.go 放 CRD 结构体,controllers/app_controller.go 放控制器逻辑,config/ 下是部署用的 YAML。
编写 CRD 字段和控制器逻辑
打开 api/v1/app_types.go,在 AppSpec 里加一个副本数字段,在 AppStatus 里加一个就绪副本字段:
type AppSpec struct {
Replicas int32 `json:"replicas,omitempty"`
}
type AppStatus struct {
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
}
改完执行 make manifests 重新生成 CRD YAML。
控制器部分在 controllers/app_controller.go 的 Reconcile 方法里,核心是读取 App 对象,再创建或更新对应的 Deployment。
逻辑可以简化成三步:获取 App 实例、拼出期望的 Deployment、调用 CreateOrUpdate。
写完后用 make generate 和 make manifests 同步代码与配置。
本地运行与集群部署验证
调试阶段可以先把控制器跑在本地,直接连接集群:
make install
make run
另开一个终端提交一个最小实例:
apiVersion: apps.example.local/v1
kind: App
metadata:
name: demo-app
spec:
replicas: 2
保存为 demo.yaml 后执行 kubectl apply -f demo.yaml。
用 kubectl get app 能看到实例,用 kubectl get deploy 应该出现控制器创建的 Deployment。
如果本地调试没问题,再执行 make docker-build 和 make deploy 部署到集群。
常见报错和避坑要点
no matches for kind "App":CRD 还没 apply,先执行make install。- 控制器不触发:检查
Reconcile是否真的被调用,可以加日志打印req.NamespacedName。 - 权限不足:在
config/rbac/下确认 ClusterRole 是否包含apps资源的 get、list、watch、create、update 权限。 - 修改结构体后忘记
make manifests:CRD 字段不会同步,提交时会报 unknown field。 - 删除 App 时 Deployment 残留:需要在控制器里处理
DeletionTimestamp,或用 OwnerReference 做级联删除。
避坑重点是把 CRD 和控制器当成一对:CRD 决定能提交什么,控制器决定提交后发生什么,两边字段必须一致。
效果验证与下一步
验证是否成功,
可以看三个结果:kubectl get crd 里有 apps.example.local;kubectl get app demo-app 返回实例;kubectl get deploy 出现由控制器创建的 Deployment,
副本数与 spec 一致。
跑通这个最小闭环后,可以继续加 finalizer、事件记录和状态更新,把 Operator 从能跑推进到好用。
Operator 开发入门的门槛主要在理解控制器循环,而不是工具本身。
开发 Operator 必须用 Go 吗
不必须。
Kubebuilder 和 controller-runtime 生态以 Go 为主,但也可以用 Java 的 Java Operator SDK 或 Python 的 Kopf。
选 Go 的原因是社区示例多、调试链路短。
本地 make run 和集群里 make deploy 有什么区别
make run 是控制器进程跑在你本机,连的是 kubeconfig 指向的集群,适合调试;make deploy 会把控制器打包成镜像部署到集群,更接近生产形态。
CRD 字段改了为什么提交不生效
大概率是没重新生成 manifests。
改 _types.go 后要执行 make manifests,再 kubectl apply -f config/crd/bases/ 下的 CRD 文件。
Operator 和 Helm 应该选哪个
Helm 适合一次性渲染并提交一组静态资源,Operator 适合需要持续观察状态并自动纠偏的场景。
如果只是部署固定应用,Helm 更轻;
如果要处理备份、扩缩容、故障自愈这类动态逻辑,Operator 更合适。