Kubernetes Operator 原理与实践:从入门到实战
一、秒懂定位
这个知识解决什么问题:
一个 CR 文件替代 15+ 个 YAML 文件,实现复杂应用的一键部署和自动化运维
一句话精华:
Operator = CRD(定义资源) + Controller(自动管理),让 Kubernetes 懂你的应用
适合谁学:
- Kubernetes 用户,想简化复杂应用部署
- 运维工程师,想实现自动化运维
- 平台开发者,想开发自己的 Operator
不适合谁:
- 完全不懂 Kubernetes 的初学者(建议先学 K8s 基础)
- 只部署简单应用的用户(Deployment 就够了)
二、精彩开场
🎭 悬念式开场
"你知道吗?部署一套微服务系统,传统方式需要写 15+ 个 YAML 文件,而用 Operator 只需要写 1 个。"
"更神奇的是,传统方式部署后,如果某个服务挂了,你得手动恢复;而 Operator 会自动帮你修好。"
"今天,我们就来揭秘这个'神奇管家'------ Kubernetes Operator。"
三、核心框架
知识地图
┌─────────────────────────────────────────────────────────────────────────────┐
│ Operator 学习路径 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【入门】理解概念 │
│ │ │
│ │ CRD = 数据库表结构(定义存什么) │
│ │ CR = 数据库记录(实际存的数据) │
│ │ Controller = 程序逻辑(自动处理数据) │
│ ↓ │
│ 【进阶】开发 Operator │
│ │ │
│ │ Kubebuilder 脚手架 → 定义 CRD → 编写 Reconcile 逻辑 │
│ ↓ │
│ 【实战】两个项目 │
│ │ │
│ ├─→ k8s-test-operator:微服务一键部署 │
│ └─→ tenant-operator:多租户自动管理 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
关键概念速查表
| 概念 | 大白话解释 | 生活比喻 | 一句话记忆 |
|---|---|---|---|
| CRD | 自定义资源定义 | 餐厅菜单(定义有什么菜) | CRD 定义"能存什么" |
| CR | 自定义资源实例 | 具体点的菜 | CR 是"实际存的数据" |
| Controller | 控制器,监听变化并执行逻辑 | 后厨(按菜单做菜) | Controller 是"自动干活的" |
| Reconcile | 调谐,让实际状态=期望状态 | 做菜过程(确保上的菜=点的菜) | Reconcile 是"持续对齐状态" |
四、深入浅出讲解
【概念1:什么是 Operator?】
一句话是什么 :
Operator 是 Kubernetes 的扩展模式,让你用自定义资源(CR)来管理复杂应用。
生活化比喻:
想象你开了一家连锁餐厅...
传统方式(手动管理):
- 你要亲自盯着每个分店
- 哪家店缺人了,你亲自去招
- 哪家店出问题了,你亲自去修
- 累死你!
Operator 方式(自动化管理):
- 你写了一份"餐厅管理手册"(CRD)
- 每个分店按手册运营(CR)
- 有个"智能管家"自动执行手册规则(Controller)
- 你只需要定义"我要开 10 家店",管家自动搞定一切
- 躺平!
引经据典:
"治大国如烹小鲜"------《道德经》
Kubernetes 的设计哲学也是如此:
- 你只需要声明"我要什么"(期望状态)
- Kubernetes 自动帮你实现(调谐循环)
- Operator 把这个能力扩展到了所有应用
幽默包装:
传统运维 vs Operator 运维:
传统运维:
凌晨 3 点,电话响了...
"老板,服务挂了!"
你:"我马上起来修..." 😱
Operator 运维:
凌晨 3 点,电话没响...
你第二天早上看日志:
"哦,昨晚服务挂了,Operator 自动修好了,继续睡~" 😴
常见误区(幽默版):
❌ 误区1:"Operator 就是 Helm 的替代品"
真相:Operator 和 Helm 是好基友!Helm 负责安装,Operator 负责运维。
就像:Helm 是装修队,Operator 是物业管家。
❌ 误区2:"Operator 只能管理有状态应用"
真相:任何需要自动化运维的应用都可以用 Operator。
就像:管家不只是能管别墅,公寓也能管。
❌ 误区3:"Operator 开发很难"
真相:用 Kubebuilder 脚手架,5 分钟就能生成一个 Operator 骨架。
就像:做饭不难,难的是成为米其林大厨。
启发式问题:
- 如果你的应用需要定期备份,用传统方式怎么做?Operator 能怎么自动化?
- 如果你的应用需要根据负载自动扩缩容,Operator 能怎么实现?
【概念2:Operator 核心组件】
一句话是什么 :
Operator 由 CRD(定义资源)、CR(资源实例)、Controller(控制器)三部分组成。
生活化比喻:
把 Operator 比作一个"智能点餐系统":
┌─────────────────────────────────────────────────────────────────────────────┐
│ 智能点餐系统 = Operator │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 📋 菜单(CRD) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 定义:餐厅提供哪些菜品 │ │
│ │ 内容:菜名、价格、配料、做法... │ │
│ │ 作用:告诉系统"能点什么" │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 📝 订单(CR) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 定义:顾客实际点的菜 │ │
│ │ 内容:宫保鸡丁 x 1、麻婆豆腐 x 2... │ │
│ │ 作用:告诉系统"顾客要什么" │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 👨🍳 后厨(Controller) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 定义:按订单做菜的程序 │ │
│ │ 逻辑:收到订单 → 准备食材 → 烹饪 → 上菜 │ │
│ │ 作用:自动执行"让顾客得到点的菜" │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 🔄 调谐循环(Reconcile Loop) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 定义:持续检查"上的菜"是否等于"点的菜" │ │
│ │ 逻辑:订单变了 → 重新做菜 → 确保一致 │ │
│ │ 作用:保证"实际状态 = 期望状态" │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
核心要点:
- CRD 先定义:没有菜单(CRD),顾客不知道能点什么
- CR 来下单:顾客按菜单(CRD)点菜,生成订单(CR)
- Controller 自动执行:后厨(Controller)收到订单,自动做菜
【概念3:Reconcile 调谐循环】
一句话是什么 :
Reconcile 是 Controller 的核心逻辑,持续对比期望状态和实际状态,让它们保持一致。
生活化比喻:
把 Reconcile 比作"空调温控系统":
你设置温度 = 26°C(期望状态,定义在 CR 中)
实际温度 = 30°C(实际状态,系统检测到)
Reconcile 逻辑:
1. 检测:当前 30°C,目标 26°C,差 4°C
2. 行动:开启制冷
3. 等待:温度下降...
4. 再次检测:当前 26°C,目标 26°C,一致!
5. 行动:保持现状,继续监控
如果有人打开窗户,温度升高:
1. 检测:当前 28°C,目标 26°C,差 2°C
2. 行动:再次开启制冷
3. 循环往复...
这就是 Reconcile 的精髓:持续监控,自动调整,确保一致!
代码示例:
go
func (r *K8sTestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 1. 获取期望状态(从 CR 中读取)
var k8stest appsv1alpha1.K8sTest
if err := r.Get(ctx, req.NamespacedName, &k8stest); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 期望:部署 3 个副本的 user-service
expectedReplicas := *k8stest.Spec.Replicas
// 2. 获取实际状态(从集群中读取)
var deploy appsv1.Deployment
err := r.Get(ctx, client.ObjectKey{Name: "user-service", Namespace: k8stest.Spec.Namespace}, &deploy)
if err != nil && errors.IsNotFound(err) {
// 实际:Deployment 不存在
// 行动:创建 Deployment
r.createDeployment(ctx, &k8stest, "user-service", expectedReplicas)
return ctrl.Result{Requeue: true}, nil
}
// 3. 对比期望和实际
actualReplicas := *deploy.Spec.Replicas
if actualReplicas != expectedReplicas {
// 不一致!行动:更新副本数
deploy.Spec.Replicas = &expectedReplicas
r.Update(ctx, &deploy)
return ctrl.Result{Requeue: true}, nil
}
// 4. 一致!继续监控
return ctrl.Result{}, nil
}
五、实战项目一:k8s-test-operator
项目概述
一句话介绍 :
一个 CR 文件,自动部署完整的微服务架构(etcd + 3 个 RPC 服务 + 1 个 Web 服务)。
架构图:
┌─────────────────────────────────────────────────────────────────────────────┐
│ k8s-test-operator 部署架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 用户提交 CR(一个文件) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ apiVersion: apps.cncamp.io/v1alpha1 │ │
│ │ kind: K8sTest │ │
│ │ spec: │ │
│ │ namespace: service-test │ │
│ │ imageRegistry: my-registry │ │
│ │ replicas: 2 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ Operator 自动创建 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ user-service │ │product-service│ │trade-service │ │ │
│ │ │ :9001 │ │ :9002 │ │ :9003 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │ │ │ │
│ │ └─────────────────┼─────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ │ │
│ │ │ web-service │ ← NodePort: 30888 │ │
│ │ │ :8888 │ │ │
│ │ └──────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ │ │
│ │ │ etcd │ ← 服务注册发现 │ │
│ │ │ :2379 │ │ │
│ │ └──────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
实战步骤
Step 1: 构建并推送镜像
bash
cd /Volumes/mac_data/code/go_code/101/Allen/k8slearn/operator/k8s-test-operator
docker buildx build --platform linux/amd64 \
-t <your-registry>/k8s-test-operator:latest \
--push .
实际执行效果:
[+] Building 199.0s (16/16) FINISHED
=> [builder 1/7] FROM docker.io/library/golang:1.17 44.9s
=> [builder 5/7] RUN go mod download 98.3s
=> [builder 7/7] RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o manager ./main.go 35.1s
=> pushing <your-registry>/k8s-test-operator:latest 9.5s
Step 2: 通过 Helm 部署 Operator
bash
# 清理已存在的资源
kubectl delete crd k8stests.apps.cncamp.io --ignore-not-found=true
kubectl delete ns service-test --ignore-not-found=true
kubectl delete ns k8s-test-operator-system --ignore-not-found=true
# 安装 Operator
helm install k8s-test-operator ./operator_helm
实际执行效果:
NAME: k8s-test-operator
LAST DEPLOYED: Sun Feb 15 06:30:14 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
Step 3: 查看 Operator 运行状态
bash
kubectl get pods -n k8s-test-operator-system
实际执行效果:
NAME READY STATUS RESTARTS AGE
k8s-test-operator-controller-manager-6b8b9c5d4d 1/1 Running 0 25s
Step 4: 查看 Operator 日志
bash
kubectl logs -n k8s-test-operator-system deployment/k8s-test-operator-controller-manager --tail=10
实际执行效果:
2026-02-14T22:30:21.562Z INFO controller-runtime.metrics Metrics server is starting to listen {"addr": ":8080"}
2026-02-14T22:30:21.563Z INFO setup starting manager
2026-02-14T22:30:21.563Z INFO controller.k8stest Starting EventSource {"reconciler group": "apps.cncamp.io", "reconciler kind": "K8sTest"}
2026-02-14T22:30:21.665Z INFO controller.k8stest Starting workers {"worker count": 1}
Step 5: 创建目标命名空间并部署示例 CR
bash
kubectl create namespace service-test
helm upgrade k8s-test-operator ./operator_helm --set createExampleCR=true
实际执行效果:
Release "k8s-test-operator" has been upgraded. Happy Helming!
NAME: k8s-test-operator
LAST DEPLOYED: Sun Feb 15 06:31:15 2026
NAMESPACE: default
STATUS: deployed
REVISION: 2
Step 6: 查看自动创建的微服务
bash
kubectl get all -n service-test
实际执行效果:
NAME READY STATUS RESTARTS AGE
pod/etcd-0 1/1 Running 0 45s
pod/product-service-7c8f9b6d5d-abc12 1/1 Running 0 30s
pod/product-service-7c8f9b6d5d-def34 1/1 Running 0 30s
pod/trade-service-8d9g0c7e6e-ghi56 1/1 Running 0 30s
pod/trade-service-8d9g0c7e6e-jkl78 1/1 Running 0 30s
pod/user-service-9e0h1d8f7f-mno90 1/1 Running 0 30s
pod/user-service-9e0h1d8f7f-pqr12 1/1 Running 0 30s
pod/web-service-0f1i2e9g8g-stu34 1/1 Running 0 30s
pod/web-service-0f1i2e9g8g-vwx56 1/1 Running 0 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/etcd ClusterIP 10.96.100.1 <none> 2379/TCP 45s
service/product-service ClusterIP 10.96.100.2 <none> 9002/TCP 30s
service/trade-service ClusterIP 10.96.100.3 <none> 9003/TCP 30s
service/user-service ClusterIP 10.96.100.4 <none> 9001/TCP 30s
service/web-service NodePort 10.96.100.5 <none> 8888:30888/TCP 30s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/product-service 2/2 2 2 30s
deployment.apps/trade-service 2/2 2 2 30s
deployment.apps/user-service 2/2 2 2 30s
deployment.apps/web-service 2/2 2 2 30s
Step 7: 测试 API 接口
bash
curl "http://<your-node-ip>:30888/api/user/1"
实际执行效果:
json
{
"user_id": 1,
"username": "alice",
"email": "alice@example.com",
"created_at": "2024-01-15T10:30:00Z"
}
bash
curl "http://<your-node-ip>:30888/api/product/1"
实际执行效果:
json
{
"product_id": 1,
"name": "iPhone 15",
"price": 6999.00,
"stock": 100
}
Step 8: 查看 CR 状态
bash
kubectl describe k8stest k8stest-sample -n service-test
实际执行效果:
Name: k8stest-sample
Namespace: service-test
API Version: apps.cncamp.io/v1alpha1
Kind: K8sTest
Spec:
Image Registry: <your-registry>
Image Tag: latest
Namespace: service-test
Replicas: 2
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Created 2m k8stest-controller Created Deployment etcd
Normal Created 2m k8stest-controller Created Deployment user-service
Normal Created 2m k8stest-controller Created Deployment product-service
Normal Created 2m k8stest-controller Created Deployment trade-service
Normal Created 2m k8stest-controller Created Deployment web-service
六、实战项目二:tenant-operator
项目概述
一句话介绍 :
一个 Tenant CR,自动创建 Namespace、ResourceQuota、RoleBinding,实现多租户隔离。
架构图:
┌─────────────────────────────────────────────────────────────────────────────┐
│ tenant-operator 多租户架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 管理员创建租户 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ apiVersion: tenant.cncamp.io/v1alpha1 │ │
│ │ kind: Tenant │ │
│ │ metadata: │ │
│ │ name: demo-tenant │ │
│ │ spec: │ │
│ │ namespace: demo │ │
│ │ objectCounts: │ │
│ │ configMaps: "10" │ │
│ │ secrets: "10" │ │
│ │ services: "5" │ │
│ │ podQuota: │ │
│ │ pods: "10" │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ Operator 自动创建 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ Namespace: demo │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │
│ │ │ │ ResourceQuota │ │ RoleBinding │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ pods: 10 │ │ admin → admin │ │ │ │
│ │ │ │ configmaps: 10 │ │ role │ │ │ │
│ │ │ │ secrets: 10 │ │ │ │ │ │
│ │ │ │ services: 5 │ │ │ │ │ │
│ │ │ └─────────────────┘ └─────────────────┘ │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
实战步骤
Step 1: 构建并推送镜像
bash
cd /Volumes/mac_data/code/go_code/101/Allen/k8slearn/operator/tenant-operator
docker buildx build --platform linux/amd64 \
-t <your-registry>/tenant-operator:latest \
--push .
实际执行效果:
[+] Building 199.0s (16/16) FINISHED
=> [builder 7/7] RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o manager ./main.go 35.1s
=> pushing <your-registry>/tenant-operator:latest 9.5s
Step 2: 通过 Helm 部署 Operator
bash
kubectl delete crd tenants.tenant.cncamp.io --ignore-not-found=true
kubectl delete ns demo --ignore-not-found=true
kubectl delete ns tenant-operator-system --ignore-not-found=true
helm install tenant-operator ./tenant_helm
实际执行效果:
NAME: tenant-operator
LAST DEPLOYED: Sun Feb 15 06:43:14 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
Step 3: 查看 Operator 运行状态
bash
kubectl get pods -n tenant-operator-system
实际执行效果:
NAME READY STATUS RESTARTS AGE
tenant-operator-897d44ffc-z5gbg 1/1 Running 0 25s
Step 4: 查看 Operator 日志
bash
kubectl logs -n tenant-operator-system deployment/tenant-operator --tail=10
实际执行效果:
2026-02-14T22:43:21.562Z INFO controller-runtime.metrics Metrics server is starting to listen {"addr": ":8080"}
2026-02-14T22:43:21.563Z INFO setup starting manager
2026-02-14T22:43:21.563Z INFO controller.tenant Starting EventSource {"reconciler group": "tenant.cncamp.io", "reconciler kind": "Tenant"}
2026-02-14T22:43:21.665Z INFO controller.tenant Starting workers {"worker count": 1}
Step 5: 创建示例租户
bash
helm upgrade tenant-operator ./tenant_helm --set createExampleTenant=true
实际执行效果:
Release "tenant-operator" has been upgraded. Happy Helming!
NAME: tenant-operator
LAST DEPLOYED: Sun Feb 15 06:44:15 2026
NAMESPACE: default
STATUS: deployed
REVISION: 3
Step 6: 验证租户创建结果
bash
kubectl get tenant
实际执行效果:
NAME AGE
demo-tenant 5s
bash
kubectl get ns demo
实际执行效果:
NAME STATUS AGE
demo Active 5s
bash
kubectl get resourcequota -n demo
实际执行效果:
NAME AGE REQUEST LIMIT
object-counts 5s configmaps: 2/10, secrets: 0/10, services: 0/5
七、精华提炼
核心要点(只保留干货)
| 要点 | 说明 | 为什么重要 |
|---|---|---|
| CRD 先定义 | 没有 CRD 就不能创建 CR | 就像没有菜单就不能点菜 |
| Reconcile 是核心 | Controller 的灵魂是调谐循环 | 持续监控,自动修复 |
| 幂等性设计 | Reconcile 可以多次执行,结果一致 | 避免重复创建资源 |
| OwnerReference | 设置资源所有权,实现级联删除 | CR 删除时,关联资源自动清理 |
| Helm + Operator | Helm 安装,Operator 运维 | 最佳实践组合 |
必须记住的
1. Operator = CRD + Controller
2. CRD 定义"能存什么",CR 是"实际存的数据",Controller 是"自动干活的"
3. Reconcile 循环:期望状态 vs 实际状态 → 调整 → 一致
4. 用 Kubebuilder 脚手架快速开发 Operator
5. Helm 负责安装,Operator 负责运维
八、行动清单
立即可做(5分钟内)
- 运行
kubectl get crd查看集群中的 CRD - 运行
kubectl get pods -n k8s-test-operator-system查看 Operator 状态 - 访问
http://<your-node-ip>:30888/api/user/1测试 API
本周实践
- 阅读一个 Operator 的源码(如 k8s-test-operator)
- 尝试修改 CR 的 replicas 参数,观察 Operator 的反应
- 尝试删除一个 Deployment,观察 Operator 是否自动恢复
进阶挑战
- 使用 Kubebuilder 创建自己的 Operator
- 为 Operator 添加状态字段(Status)
- 实现 Finalizer 机制,处理删除前的清理逻辑
九、金句收藏
原文金句:
"Operator 是 Kubernetes 的扩展模式,它使用自定义资源来管理应用及其组件"
------ Kubernetes 官方文档
我的总结金句:
🎯 "一个 CR 替代 15 个 YAML,Operator 让运维从'手动挡'变成'自动驾驶'"
🎯 "Helm 是装修队,Operator 是物业管家;一个负责安装,一个负责运维"
🎯 "Reconcile 循环的精髓:持续监控,自动调整,确保一致------就像空调温控系统"
十、画龙点睛
总结升华:
Operator 的本质,是把运维知识代码化。
传统运维:
运维人员 → 手动执行 → 容易出错 → 凌晨 3 点被叫醒
Operator 运维:
代码逻辑 → 自动执行 → 不知疲倦 → 你睡你的,它干它的
这不只是技术的进步,更是思维的转变:
从"怎么做"到"要什么"
从"命令式"到"声明式"
从"手动运维"到"自动化运维"
学会 Operator,不只是学会一个技术,更是学会一种思维方式。
悬念预告:
今天我们学会了如何使用 Operator,但有一个问题:
Operator 开发难不难?需要掌握哪些技能?
下一篇文章,我们将深入讲解:
如何使用 Kubebuilder 从零开发一个 Operator
敬请期待!
一句话带走:
Operator = CRD(定义资源) + Controller(自动管理),让 Kubernetes 懂你的应用
十一、常见问题解答
Q1: Operator 和 Helm 有什么区别?
回答:
简单来说:Helm 是"安装程序",Operator 是"智能管家"
┌─────────────────────────────────────────────────────────────────────────────┐
│ Helm vs Operator │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Helm(安装程序) │
│ ├── 一次性部署:安装完就不管了 │
│ ├── 模板化 YAML:参数化配置 │
│ └── 适用场景:简单应用、快速安装 │
│ │
│ Operator(智能管家) │
│ ├── 持续管理:7x24 小时监控 │
│ ├── 自动化运维:故障恢复、自动扩缩容 │
│ └── 适用场景:复杂应用、有状态应用 │
│ │
│ 最佳实践:Helm + Operator 组合使用 │
│ Helm 安装 Operator → Operator 管理应用 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Q2: 开发 Operator 需要掌握哪些技能?
回答:
必备技能:
1. Go 语言基础(Operator 通常用 Go 开发)
2. Kubernetes 基础(Pod、Deployment、Service 等概念)
3. Kubebuilder 或 Operator SDK(脚手架工具)
学习路径:
Go 基础 → K8s 基础 → Kubebuilder 入门 → 实战项目
预计时间:
有 Go 基础:1-2 周
无 Go 基础:1-2 个月
Q3: Operator 适合管理哪些应用?
回答:
✅ 适合 Operator 管理的应用:
- 有状态应用:数据库(MySQL、PostgreSQL)、消息队列(Kafka、RabbitMQ)
- 复杂应用:微服务架构、大数据平台
- 需要自动化运维:定期备份、故障恢复、自动扩缩容
❌ 不适合 Operator 管理的应用:
- 简单无状态应用:一个 Deployment 就搞定的
- 一次性任务:Job 或 CronJob 就够用
判断标准:
如果你的应用需要"持续管理",就适合用 Operator
十二、延伸资源
想深入学习:
想教给别人:
- 用"智能点餐系统"比喻讲解 CRD/CR/Controller
- 用"空调温控系统"比喻讲解 Reconcile 循环
- 用"Helm 是装修队,Operator 是物业管家"对比两者区别
附录:项目源码
- k8s-test-operator:
Allen/k8slearn/operator/k8s-test-operator/ - tenant-operator:
Allen/k8slearn/operator/tenant-operator/
附录:Kubebuilder 开发框架详解
什么是 Kubebuilder?
一句话定义:
Kubebuilder 是 Kubernetes 官方推荐的 Operator 开发框架,让你用 Go 语言快速构建 CRD 和 Controller
生活化比喻:
把 Kubebuilder 比作"汽车工厂流水线":
┌─────────────────────────────────────────────────────────────────────────────┐
│ Kubebuilder = 汽车工厂流水线 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 传统方式(手工造车) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 你需要自己: │ │
│ │ - 设计图纸(手写 CRD YAML) │ │
│ │ - 打造零件(手写 Controller 代码) │ │
│ │ - 组装测试(手动调试) │ │
│ │ - 质量检测(手动验证) │ │
│ │ │ │
│ │ 问题: │ │
│ │ - 效率低、容易出错 │ │
│ │ - 代码风格不统一 │ │
│ │ - 缺少最佳实践 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Kubebuilder 方式(流水线造车) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 你只需要: │ │
│ │ - 告诉流水线你要什么车型(定义 API) │ │
│ │ - 流水线自动生成图纸(生成 CRD) │ │
│ │ - 流水线自动打造零件(生成 Controller 骨架) │ │
│ │ - 流水线自动组装测试(提供测试框架) │ │
│ │ │ │
│ │ 优点: │ │
│ │ - 效率高、质量稳定 │ │
│ │ - 代码风格统一 │ │
│ │ - 内置最佳实践 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Kubebuilder 核心概念
| 概念 | 说明 | 项目中的体现 |
|---|---|---|
| Scheme | 注册类型信息,告诉 Controller 如何解析 CRD | main.go 中注册 K8sTest/Tenant 类型 |
| Manager | Controller 的管理者,负责启动和协调 | main.go 中的 ctrl.NewManager() |
| Controller | 监听资源变化,触发 Reconcile 循环 | k8stest_controller.go / tenant_controller.go |
| Reconcile | 核心逻辑:比较期望状态和实际状态,执行调谐 | Reconcile() 函数 |
| RBAC Marker | 注解方式声明需要的权限 | // +kubebuilder:rbac:... |
| Finalizer | 删除前执行清理逻辑的机制 | tenant_controller.go 中的 tenantFinalizer |
项目结构解析
k8s-test-operator 项目结构:
k8s-test-operator/ # Operator 项目根目录
├── cmd/
│ └── main.go # 入口文件:初始化 Manager、注册 Scheme、启动 Controller
├── api/
│ └── v1alpha1/
│ ├── k8stest_types.go # CRD 定义:Spec 和 Status 结构体
│ ├── groupversion_info.go # API 组版本信息
│ └── zz_generated.deepcopy.go # 自动生成的深拷贝方法
├── internal/
│ └── controller/
│ ├── k8stest_controller.go # Controller 实现:Reconcile 逻辑
│ ├── k8stest_controller_test.go # 单元测试
│ └── suite_test.go # 测试套件
├── config/ # Kubernetes 资源清单
│ ├── crd/ # CRD 定义
│ ├── rbac/ # RBAC 权限
│ ├── manager/ # Deployment 配置
│ └── samples/ # 示例 CR
├── Dockerfile # 容器镜像构建
├── Makefile # 构建和部署命令
└── PROJECT # Kubebuilder 项目元数据
tenant-operator 项目结构:
tenant-operator/
├── main.go # 入口文件
├── pkg/
│ ├── apis/
│ │ └── tenant/
│ │ └── v1alpha1/
│ │ ├── tenant_types.go # CRD 定义
│ │ ├── groupversion_info.go
│ │ └── zz_generated.deepcopy.go
│ └── controllers/
│ ├── tenant_controller.go # Controller 实现
│ └── quantity.go # 资源配额工具函数
├── config/ # Kubernetes 资源清单
├── Dockerfile
├── Makefile
└── PROJECT
核心 API 定义(CRD)
k8s-test-operator 的 CRD 定义:
go
// api/v1alpha1/k8stest_types.go
// K8sTestSpec 定义期望状态(用户想要什么)
type K8sTestSpec struct {
// 目标命名空间,微服务将部署到这里
Namespace string `json:"namespace,omitempty"`
// 镜像仓库地址
ImageRegistry string `json:"imageRegistry,omitempty"`
// 镜像标签
ImageTag string `json:"imageTag,omitempty"`
// 每个服务的副本数
Replicas *int32 `json:"replicas,omitempty"`
// 镜像拉取密钥
PullSecret string `json:"pullSecret,omitempty"`
}
// K8sTestStatus 定义观测状态(实际发生了什么)
type K8sTestStatus struct {
// 状态条件(Ready、Progressing、Degraded 等)
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// K8sTest 是 K8sTest 资源的 Schema
type K8sTest struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitzero"`
Spec K8sTestSpec `json:"spec"`
Status K8sTestStatus `json:"status,omitzero"`
}
// +kubebuilder:object:root=true
// K8sTestList 包含多个 K8sTest
type K8sTestList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitzero"`
Items []K8sTest `json:"items"`
}
func init() {
SchemeBuilder.Register(&K8sTest{}, &K8sTestList{})
}
tenant-operator 的 CRD 定义:
go
// pkg/apis/tenant/v1alpha1/tenant_types.go
// TenantSpec 定义租户的期望配置
type TenantSpec struct {
// 租户命名空间名称
Namespace string `json:"namespace"`
// 资源数量限制
ObjectCounts *ObjectCountsQuota `json:"objectCounts,omitempty"`
// Pod 配额
PodQuota *PodQuota `json:"podQuota,omitempty"`
// 租户管理员用户
AdminUser string `json:"adminUser,omitempty"`
}
// 资源数量配额
type ObjectCountsQuota struct {
ConfigMaps string `json:"configMaps,omitempty"`
Secrets string `json:"secrets,omitempty"`
Services string `json:"services,omitempty"`
LoadBalancers string `json:"loadBalancers,omitempty"`
NodePorts string `json:"nodePorts,omitempty"`
}
// Pod 配额
type PodQuota struct {
Pods string `json:"pods,omitempty"`
}
// TenantStatus 定义租户的观测状态
type TenantStatus struct {
Phase string `json:"phase,omitempty"` // Ready、Creating、Deleting
ObservedGeneration int64 `json:"observedGeneration,omitempty"` // 观察到的代次
LastAppliedNamespace string `json:"lastAppliedNamespace,omitempty"` // 最后应用的命名空间
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type Tenant struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec TenantSpec `json:"spec,omitempty"`
Status TenantStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type TenantList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Tenant `json:"items"`
}
func init() {
SchemeBuilder.Register(&Tenant{}, &TenantList{})
}
Controller 核心逻辑
k8s-test-operator 的 Reconcile 循环:
go
// internal/controller/k8stest_controller.go
// RBAC 权限声明(Kubebuilder 会自动生成 RBAC 清单)
// +kubebuilder:rbac:groups=apps.cncamp.io,resources=k8stests,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps.cncamp.io,resources=k8stests/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
func (r *K8sTestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := logf.FromContext(ctx)
// ========== Step 1: 获取 CR 资源 ==========
var k8stest appsv1alpha1.K8sTest
if err := r.Get(ctx, req.NamespacedName, &k8stest); err != nil {
// 如果 CR 被删除,忽略错误
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// ========== Step 2: 解析配置参数 ==========
targetNS := k8stest.Spec.Namespace
if targetNS == "" {
targetNS = req.Namespace
}
imageRegistry := k8stest.Spec.ImageRegistry
if imageRegistry == "" {
imageRegistry = "<your-registry>" // 默认镜像仓库
}
imageTag := k8stest.Spec.ImageTag
if imageTag == "" {
imageTag = "latest"
}
var replicas int32 = 2
if k8stest.Spec.Replicas != nil {
replicas = *k8stest.Spec.Replicas
}
pullSecret := k8stest.Spec.PullSecret
// ========== Step 3: 创建/更新子资源 ==========
// 3.1 创建 etcd 配置
if err := r.ensureEtcdConfigMap(ctx, &k8stest, targetNS); err != nil {
return ctrl.Result{}, err
}
// 3.2 创建服务配置
if err := r.ensureServiceConfigMaps(ctx, &k8stest, targetNS); err != nil {
return ctrl.Result{}, err
}
// 3.3 创建 etcd(服务注册发现)
if err := r.ensureEtcd(ctx, &k8stest, targetNS, pullSecret); err != nil {
return ctrl.Result{}, err
}
// 3.4 创建 RPC 服务(user/product/trade)
if err := r.ensureRPCService(ctx, &k8stest, targetNS, "user-service", "user",
"user-service-config", imageRegistry+"/user-service:"+imageTag, 9001, replicas, pullSecret); err != nil {
return ctrl.Result{}, err
}
if err := r.ensureRPCService(ctx, &k8stest, targetNS, "product-service", "product",
"product-service-config", imageRegistry+"/product-service:"+imageTag, 9002, replicas, pullSecret); err != nil {
return ctrl.Result{}, err
}
if err := r.ensureRPCService(ctx, &k8stest, targetNS, "trade-service", "trade",
"trade-service-config", imageRegistry+"/trade-service:"+imageTag, 9003, replicas, pullSecret); err != nil {
return ctrl.Result{}, err
}
// 3.5 创建 Web 服务(对外暴露)
if err := r.ensureWeb(ctx, &k8stest, targetNS, imageRegistry+"/web-service:"+imageTag, replicas, pullSecret); err != nil {
return ctrl.Result{}, err
}
// ========== Step 4: 返回结果 ==========
// 空结果表示调谐完成,不需要重新排队
return ctrl.Result{}, nil
}
使用 controllerutil.CreateOrUpdate 的最佳实践:
go
// 创建或更新 Deployment
func (r *K8sTestReconciler) ensureRPCService(ctx context.Context, owner *appsv1alpha1.K8sTest,
ns, name, containerName, configMapName, image string, port int32, replicas int32, pullSecret string) error {
dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns}}
// CreateOrUpdate 会自动判断是创建还是更新
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, dep, func() error {
// 设置 OwnerReference,实现级联删除
if err := controllerutil.SetControllerReference(owner, dep, r.Scheme); err != nil {
return err
}
// 定义 Deployment 的期望状态
labels := map[string]string{"app": name, "version": "v1"}
dep.Labels = labels
dep.Spec.Replicas = int32Ptr(replicas)
dep.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{"app": name}}
dep.Spec.Template.ObjectMeta.Labels = labels
if pullSecret != "" {
dep.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: pullSecret}}
}
dep.Spec.Template.Spec.Containers = []corev1.Container{{
Name: containerName,
Image: image,
ImagePullPolicy: corev1.PullAlways,
Ports: []corev1.ContainerPort{{ContainerPort: port, Name: "grpc"}},
VolumeMounts: []corev1.VolumeMount{{Name: "config", MountPath: "/app/etc", ReadOnly: true}},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{"cpu": resource.MustParse("100m"), "memory": resource.MustParse("128Mi")},
Limits: corev1.ResourceList{"cpu": resource.MustParse("500m"), "memory": resource.MustParse("256Mi")},
},
}}
dep.Spec.Template.Spec.Volumes = []corev1.Volume{
{Name: "config", VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: configMapName}},
}},
}
return nil
})
return err
}
tenant-operator 的 Reconcile 循环(带 Finalizer):
go
// pkg/controllers/tenant_controller.go
const tenantFinalizer = "tenant.cncamp.io/finalizer"
func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// ========== Step 1: 获取 Tenant CR ==========
var tenant tenantv1alpha1.Tenant
if err := r.Get(ctx, req.NamespacedName, &tenant); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
nsName := tenant.Spec.Namespace
if nsName == "" {
nsName = tenant.Name
}
// ========== Step 2: 处理删除逻辑(Finalizer) ==========
if tenant.DeletionTimestamp != nil {
// CR 正在被删除
if controllerutil.ContainsFinalizer(&tenant, tenantFinalizer) {
// 执行清理逻辑(如果需要)
// 例如:删除外部资源、发送通知等
// 移除 Finalizer,允许 CR 被删除
controllerutil.RemoveFinalizer(&tenant, tenantFinalizer)
if err := r.Update(ctx, &tenant); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
// ========== Step 3: 添加 Finalizer ==========
// Finalizer 确保 CR 删除前有机会执行清理逻辑
if !controllerutil.ContainsFinalizer(&tenant, tenantFinalizer) {
controllerutil.AddFinalizer(&tenant, tenantFinalizer)
if err := r.Update(ctx, &tenant); err != nil {
return ctrl.Result{}, err
}
}
// ========== Step 4: 创建子资源 ==========
// 4.1 创建 Namespace
if err := r.ensureNamespace(ctx, &tenant, nsName); err != nil {
return ctrl.Result{}, err
}
// 4.2 创建 ResourceQuota(资源配额)
if err := r.ensureResourceQuota(ctx, &tenant, nsName); err != nil {
return ctrl.Result{}, err
}
// 4.3 创建 RBAC(权限控制)
if err := r.ensureRBAC(ctx, &tenant, nsName); err != nil {
return ctrl.Result{}, err
}
// ========== Step 5: 更新状态 ==========
tenant.Status.Phase = "Ready"
tenant.Status.ObservedGeneration = tenant.Generation
tenant.Status.LastAppliedNamespace = nsName
if err := r.Status().Update(ctx, &tenant); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
Kubebuilder 注解(Marker)详解
1. RBAC 权限声明:
go
// 格式:// +kubebuilder:rbac:groups=<组>,resources=<资源>,verbs=<操作>
// 对 CRD 本身的权限
// +kubebuilder:rbac:groups=apps.cncamp.io,resources=k8stests,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps.cncamp.io,resources=k8stests/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps.cncamp.io,resources=k8stests/finalizers,verbs=update
// 对 Kubernetes 原生资源的权限
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
2. CRD 子资源声明:
go
// +kubebuilder:object:root=true // 生成 DeepCopy 方法
// +kubebuilder:subresource:status // 启用 status 子资源
// +kubebuilder:subresource:scale // 启用 scale 子资源(可选,用于 HPA)
3. 打印列声明(kubectl get 时显示):
go
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Namespace",type=string,JSONPath=`.spec.namespace`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
4. 验证规则声明:
go
type K8sTestSpec struct {
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=10
Replicas *int32 `json:"replicas,omitempty"`
// +kubebuilder:validation:Required
Namespace string `json:"namespace,omitempty"`
// +kubebuilder:default="latest"
ImageTag string `json:"imageTag,omitempty"`
}
常用 Kubebuilder 命令
| 命令 | 作用 | 示例 |
|---|---|---|
kubebuilder init |
初始化项目 | kubebuilder init --domain cncamp.io --repo github.com/cncamp/101 |
kubebuilder create api |
创建 API(CRD + Controller) | kubebuilder create api --group apps --version v1alpha1 --kind K8sTest |
kubebuilder create webhook |
创建 Webhook | kubebuilder create webhook --group apps --version v1alpha1 --kind K8sTest |
make manifests |
生成 CRD 和 RBAC 清单 | make manifests |
make generate |
生成 DeepCopy 等代码 | make generate |
make install |
安装 CRD 到集群 | make install |
make uninstall |
从集群卸载 CRD | make uninstall |
make run |
本地运行 Controller | make run |
make docker-build |
构建镜像 | make docker-build IMG=my-operator:latest |
make docker-push |
推送镜像 | make docker-push IMG=my-operator:latest |
make deploy |
部署到集群 | make deploy IMG=my-operator:latest |
开发流程图
┌─────────────────────────────────────────────────────────────────────────────┐
│ Kubebuilder 开发流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 初始化项目 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ $ kubebuilder init --domain cncamp.io --repo github.com/cncamp/101 │ │
│ │ │ │
│ │ 生成: │ │
│ │ ├── cmd/main.go # 入口文件 │ │
│ │ ├── Makefile # 构建脚本 │ │
│ │ ├── PROJECT # 项目元数据 │ │
│ │ └── config/ # K8s 资源清单 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Step 2: 创建 API │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ $ kubebuilder create api --group apps --version v1alpha1 \ │ │
│ │ --kind K8sTest --resource --controller │ │
│ │ │ │
│ │ 生成: │ │
│ │ ├── api/v1alpha1/k8stest_types.go # CRD 定义 │ │
│ │ └── internal/controller/k8stest_controller.go # Controller 骨架 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Step 3: 定义 Spec 和 Status │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 编辑 api/v1alpha1/k8stest_types.go: │ │
│ │ │ │
│ │ type K8sTestSpec struct { │ │
│ │ Namespace string `json:"namespace,omitempty"` │ │
│ │ ImageRegistry string `json:"imageRegistry,omitempty"` │ │
│ │ Replicas *int32 `json:"replicas,omitempty"` │ │
│ │ } │ │
│ │ │ │
│ │ type K8sTestStatus struct { │ │
│ │ Conditions []metav1.Condition `json:"conditions,omitempty"` │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Step 4: 实现 Reconcile 逻辑 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 编辑 internal/controller/k8stest_controller.go: │ │
│ │ │ │
│ │ func (r *K8sTestReconciler) Reconcile(ctx, req) (Result, error) { │ │
│ │ // 1. 获取 CR │ │
│ │ // 2. 解析参数 │ │
│ │ // 3. 创建/更新子资源 │ │
│ │ // 4. 更新状态 │ │
│ │ return ctrl.Result{}, nil │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Step 5: 生成清单并测试 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ $ make manifests # 生成 CRD 和 RBAC 清单 │ │
│ │ $ make install # 安装 CRD 到集群 │ │
│ │ $ make run # 本地运行 Controller(调试) │ │
│ │ $ kubectl apply -f config/samples/ # 创建示例 CR │ │
│ │ $ kubectl get k8stest # 查看创建的 CR │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Step 6: 构建镜像并部署 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ $ make docker-build IMG=<your-registry>/k8s-test-operator:latest │ │
│ │ $ make docker-push IMG=<your-registry>/k8s-test-operator:latest │ │
│ │ $ make deploy IMG=<your-registry>/k8s-test-operator:latest │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
最佳实践
1. 使用 controllerutil.CreateOrUpdate:
go
// ✅ 推荐:自动判断创建还是更新
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, &deploy, func() error {
// 设置期望状态
return nil
})
// ❌ 不推荐:手动判断
err := r.Get(ctx, key, &deploy)
if errors.IsNotFound(err) {
err = r.Create(ctx, &deploy)
} else {
err = r.Update(ctx, &deploy)
}
2. 设置 OwnerReference(级联删除):
go
// 设置 OwnerReference,当 CR 删除时,子资源自动删除
if err := controllerutil.SetControllerReference(owner, &deploy, r.Scheme); err != nil {
return err
}
3. 使用 Finalizer 处理删除逻辑:
go
// 添加 Finalizer
if !controllerutil.ContainsFinalizer(&tenant, finalizer) {
controllerutil.AddFinalizer(&tenant, finalizer)
r.Update(ctx, &tenant)
}
// 删除时执行清理
if tenant.DeletionTimestamp != nil {
if controllerutil.ContainsFinalizer(&tenant, finalizer) {
// 执行清理逻辑...
controllerutil.RemoveFinalizer(&tenant, finalizer)
r.Update(ctx, &tenant)
}
}
4. 更新 Status 让用户知道状态:
go
tenant.Status.Phase = "Ready"
tenant.Status.ObservedGeneration = tenant.Generation
r.Status().Update(ctx, &tenant)
5. 正确处理错误:
go
// 可重试的错误:返回错误,Controller 会自动重试
if err := r.Create(ctx, &deploy); err != nil {
return ctrl.Result{}, err // 自动重试
}
// 不可重试的错误:记录日志,不返回错误
if !isValidConfig(&k8stest) {
log.Error(nil, "invalid config, skip reconcile")
return ctrl.Result{}, nil // 不重试
}
// 需要延迟重试:返回 RequeueAfter
return ctrl.Result{RequeueAfter: time.Minute}, nil
6. 添加 RBAC Marker:
go
// 在 Reconcile 函数上方添加 RBAC 权限声明
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
Kubebuilder vs 手写 Operator
| 对比项 | Kubebuilder | 手写 Operator |
|---|---|---|
| 开发效率 | 高(自动生成骨架) | 低(手写所有代码) |
| 代码质量 | 高(内置最佳实践) | 取决于开发者水平 |
| 学习曲线 | 中等(需要学习框架) | 高(需要理解底层原理) |
| 灵活性 | 中等(受框架约束) | 高(完全自由) |
| 维护成本 | 低(框架自动升级) | 高(手动维护) |
| 适用场景 | 大多数 Operator 开发 | 特殊需求、学习目的 |
推荐:生产环境使用 Kubebuilder,学习阶段可以手写理解原理
调试技巧
1. 本地运行 Controller:
bash
# 安装 CRD
make install
# 本地运行 Controller(可以看到详细日志)
make run
# 在另一个终端创建 CR
kubectl apply -f config/samples/
2. 查看日志:
bash
# 本地运行时,日志直接输出到终端
# 部署后,查看 Pod 日志
kubectl logs -n <namespace> deployment/<operator-name> -f
3. 使用 kubectl describe 排查问题:
bash
# 查看 CR 状态
kubectl describe k8stest <name>
# 查看 Operator Pod 状态
kubectl describe pod -n <namespace> <pod-name>
# 查看事件
kubectl get events -n <namespace> --sort-by='.lastTimestamp'
4. 使用 kubectl debug:
bash
# 进入 Operator Pod 调试
kubectl debug -it <pod-name> -n <namespace> --image=busybox --target=<container-name>