前置:Go 基础语法 + K8s 基础使用(Deployment / Service / ConfigMap / kubectl apply)
01阶段 0:心智模型(1-2 天,不写代码)
核心链条:
CRD(YAML spec) → client-go(informer) → workqueue → reconciler → API server必懂概念清单:
| 概念 | 一句话 | 在哪看到 |
|---|---|---|
| GVK / GVR | Group/Version/Kind,API 路由的核心 | kubectl api-resources |
| Scheme | 把 Go struct 注册到 K8s API 体系 | k8s.io/apimachinery/pkg/runtime |
| Informer | watch + 本地缓存 + 事件分发 | client-go/informers |
| Workqueue | reconciler 的待办队列,带去重和限速 | client-go/util/workqueue |
| Reconcile | "期望 vs 实际"的对账函数,核心循环 | controller-runtime |
| CRD | 自定义资源定义,描述 spec/status schema | apiextensions.k8s.io/v1 |
| RBAC | Operator Pod 需要的权限 | rbac.authorization.k8s.io |
阶段产出: 能用嘴讲清 "Operator = CRD + Controller + 业务逻辑",能画出 informer → workqueue → reconcile 时序图。
验证: 问自己 "Reconcile 函数为什么必须幂等?"——能答出"因为同一 key 可能被多次入队、可能中途失败重试,所以只能看当前状态做补差,不能假设上次执行过"。
02阶段 1:client-go 裸写一个 Controller(3-5 天)
为什么不能跳过: 跳过这步直接上 kubebuilder 是新手最大坑——你会在 controller-runtime 的封装里迷路,因为它替你藏掉了 informer、workqueue、leader election 这些你根本没见过原貌的东西。
学习路径:
- 用
client-go的kubernetes.Clientset列出 Pod,打印名字(15 行 Go 代码,熟悉 in-cluster config vs kubeconfig) - 用
Informerwatch Pod 的 ADD/UPDATE/DELETE,打印事件 - 加一个
Workqueue,Informer 事件 → 入队 key → 启动 N 个 worker 出队处理 - 把目标改成 ConfigMap:reconcile 逻辑是"如果 namespace 里没有名为
my-config的 ConfigMap,就创建一个"
关键参考资料:
client-go/examples/workqueue(官方 sample,这一阶段的核心)- K8s sample-controller 仓库:
github.com/kubernetes/sample-controller - 《Programming Kubernetes》(O'Reilly)第 3-4 章,讲 informer/workqueue 最清楚
阶段产出: 一个 200-300 行的 main.go,能 watch ConfigMap 并保证"期望的 ConfigMap 存在"。
验证: kubectl delete cm my-config,几秒后它自动被重建。
03阶段 2:用 kubebuilder 脚手架起步(2-3 天)
现在你理解了底层,可以信任 controller-runtime 的抽象了。kubebuilder 是 K8s 官方推荐的脚手架(scaffold),会帮你生成 CRD schema、controller 骨架、RBAC、Makefile。
学习路径:
- 安装 kubebuilder(官方 book 第 1 章)
kubebuilder init --domain my.domain --repo my.domain/guestbookkubebuilder create api --group webapp --version v1 --kind Guestbook—— 生成api/v1/guestbook_types.go(CRD schema)和controllers/guestbook_controller.go(Reconcile 骨架)- 在
Spec里加Size int32和Image string字段,在Status里加Ready bool - 实现 Reconcile:读 Guestbook 实例 → 检查是否有对应 Deployment → 没有就 create,有就确保 replicas == spec.size → 写
status.Ready = true
关键参考资料:
- Kubebuilder Book:
book.kubebuilder.io(从 "Quick Start" 到 "Multi-version" 都要看) controller-runtimedocs:pkg/controller,pkg/manager,pkg/reconcile- K8s API conventions:
github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md(写 status 字段的规则)
阶段产出: 能 make install(CRD 装到本地集群)、make run(本地跑 controller)、kubectl apply -f config/samples/(创建实例,看到 Deployment 自动产生)。
验证: kubectl get guestbook -o yaml 看到 status 被回写。
04阶段 3:Operator 五大件实战(1-2 周)
真实 Operator 必须处理这五件事,每个都要亲手写过:
| 件 | 解决什么 | 关键 API |
|---|---|---|
| Finalizer | CR 被删时,先清理外部资源(数据库、云 LB)再允许删除 | controller-runtime finalizer pattern |
| Owner Reference | 子资源(Deployment)随父资源(Guestbook)级联删除 | metav1.OwnerReference |
| Status subresource | status 字段独立更新,不触发 spec 的 reconcile 抖动 | // +kubebuilder:subresource:status marker |
| Conditions | status 里用 []Condition{Type, Status, Reason, Message, LastTransitionTime} 表达"健康度",而不是裸 bool |
apimachinery metav1.Conditions |
| Events | 关键动作(创建 Deployment、扩容失败)发 Event,kubectl describe 能看到 |
k8s.io/client-go/tools/record |
学习路径:
- 把阶段 2 的 Guestbook 升级:加 finalizer,删 Guestbook 时先把对应 Deployment 缩到 0 再删
- 加 Conditions:status.conditions 里写
Ready/Progressing/Degraded - 加 Event recorder:创建 Deployment 时发 Normal 事件,失败时发 Warning
- 重要:每次 reconcile 用
fmt.Sprintf("%s/%s", namespace, name)做 log,接入ctrl.Log的 logr 接口
关键参考资料:
- Operator SDK examples:
github.com/operator-framework/operator-sdk/examples - K8s conditions 设计:
github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties - sample-controller 的 finalizer 实现
阶段产出: 一个完整的小 Operator,能管理一个有状态应用(比如:一个 Guestbook Operator,管理 Redis + 前端 Deployment + Service + ConfigMap,带 finalizer 和 status conditions)。
验证: kubectl describe guestbook myapp 看到 Events 和 Conditions;kubectl delete guestbook myapp 看到 finalizer 先清理再删除。
05阶段 4:Webhook 和多版本 CRD(3-5 天)
Webhook 分三种,Operator 通常用到两种:
- Mutating webhook(defaulter):CR 创建时填默认值,比如 spec.size 没写就填 3
- Validating webhook(validator):CR 创建/更新时校验,比如 spec.size 不能是负数
多版本 CRD: 当你的 API 从 v1 演进到 v2 时,集群里可能同时存在 v1 和 v2 的资源,需要 conversion webhook 或本地 conversion。
学习路径:
- kubebuilder 给 Guestbook 加 mutating + validating webhook
kubebuilder create webhook --group webapp --version v1 --kind Guestbook --defaulting --validation - 实现
Default()方法:spec.Image 为空时填redis:7 - 实现
ValidateCreate()/ValidateUpdate():spec.Size 必须 > 0 且 < 10 - 把 API 从 v1 升级到 v2:加字段、写 conversion(用 marker
+kubebuilder:conversion:version) - 部署 cert-manager 自动管理 webhook 证书(生产推荐)
关键参考资料:
- Kubebuilder Book "Webhooks" 章
controller-runtimepkg/webhook包文档- K8s admission controllers 官方文档
阶段产出: Guestbook Operator 带 webhook,能拒绝非法 spec、填默认值。
验证: kubectl apply 一个 spec.size=-1 的 Guestbook,被 webhook 拒绝;不写 spec.size,被默认填成 3。
06阶段 5:工程化、测试、发布(1-2 周集中投入)
这一步把"能跑的 demo"变成"可上生产的 Operator"。
5.1 测试
| 类型 | 工具 | 目的 |
|---|---|---|
| 单元测试 | testing + ginkgo(可选) |
reconcile 纯逻辑,用 fake client |
| 集成测试 | envtest(controller-runtime 自带) |
起真实 etcd + apiserver,跑完整流程 |
| E2E 测试 | kuttl 或 operator-sdk scorecard |
真集群上 YAML 声明式断言 |
envtest 是关键——它不需要 minikube,直接本地起 etcd + kube-apiserver 二进制,跑你的 controller,5 秒内启动,适合 CI。
5.2 可观测性
controller-runtime自带 Prometheus metrics(/metricsendpoint),关键指标:reconcile_errors_total、reconcile_time_seconds、workqueue_depth- 必接 Event recorder(阶段 3 已做)
- 日志用
logr(zerolog 或 slog 适配),不要直接用log.Printf
5.3 镜像 + 发布
make docker-build docker-push IMG=your.registry/guestbook-operator:v0.1- 用
kustomize管理不同环境 overlay - OLM(Operator Lifecycle Manager):如果要上 OpenShift 或通过 OperatorHub 发布,需要生成 ClusterServiceVersion(CSV),用
operator-sdk generate csv
5.4 CI/CD
GitHub Actions 模板:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- run: make test # 跑 envtest + ginkgo
- run: make docker-build阶段产出: GitHub repo 带 CI,PR 会自动跑 envtest;有 Dockerfile 和 kustomize 部署清单;README 写清"5 分钟跑起来"。
验证: make test 绿;make deploy 后集群里 Operator 跑起来,创建 CR 后 30 秒内 status.Ready=true。
07推荐学习资源(按优先级)
| 资源 | 价值 | 何时看 |
|---|---|---|
Kubebuilder Book (book.kubebuilder.io) |
官方权威,从 hello-world 到 webhook 全覆盖 | 阶段 2 开始,从头到尾过一遍 |
| 《Programming Kubernetes》(O'Reilly) | 把 informer/workqueue/scheme 讲得最透 | 阶段 0-1,挑第 3-4 章精读 |
| k8s.io/client-go/examples/workqueue | 官方 sample,直接抄 | 阶段 1 核心 |
| github.com/kubernetes/sample-controller | 官方完整 controller 示例 | 阶段 1-2 对照参考 |
Operator SDK Tutorial (docs.operatorhub.io) |
operator-sdk 与 kubebuilder 的差异 | 阶段 5 准备发布时 |
K8s API Conventions (github.com/kubernetes/community) |
写 status/conditions 的规则 | 阶段 3 写 status 时必查 |
controller-runtime godoc (pkg/controller, pkg/manager) |
抽象层的权威解释 | 阶段 2 起随时查 |
08时间预算
全职投入
| 阶段 | 预估 |
|---|---|
| 0 心智模型 | 1-2 天 |
| 1 client-go 裸写 | 3-5 天 |
| 2 kubebuilder 起步 | 2-3 天 |
| 3 五大件实战 | 1-2 周 |
| 4 Webhook + 多版本 | 3-5 天 |
| 5 工程化 | 1-2 周 |
| 合计 | 5-8 周 |
业余学习
翻倍到 10-16 周,但建议至少保证每周 10 小时连续投入,否则很容易在阶段 3 卡住——五大件不亲手写一遍,看再多文档也学不会。
09关键心法
- 先裸写再上框架 —— 阶段 1 的 client-go 裸 controller 不能跳,否则 kubebuilder 里的一切都是黑盒
- Reconcile 必须幂等 —— 同一 key 可能被多次入队、可能中途失败重试,只能看当前状态做补差
- Status 是输出不是输入 —— Spec 是用户想要的,Status 是 Operator 观测到的;status 字段永远由 controller 写,不由用户写
- Finalizer 是删除前的"安全锁" —— 没有外部资源的 CR 不需要 finalizer;有外部资源(数据库、云 LB)必须用 finalizer 保证先清理再删
- envtest 而非 minikube 做 CI —— envtest 5 秒启动,minikube 太重