Kubernetes Operator 原理与实践:从入门到实战

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)                                              │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │  定义:持续检查"上的菜"是否等于"点的菜"                              │  │
│   │  逻辑:订单变了 → 重新做菜 → 确保一致                                 │  │
│   │  作用:保证"实际状态 = 期望状态"                                      │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

核心要点

  1. CRD 先定义:没有菜单(CRD),顾客不知道能点什么
  2. CR 来下单:顾客按菜单(CRD)点菜,生成订单(CR)
  3. 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>
相关推荐
匀泪2 小时前
云原生(nginx实验(2))
运维·nginx·云原生
DeeplyMind2 小时前
第6章 Docker镜像基础操作
运维·docker·容器
马丁的代码日记3 小时前
Docker 无法拉取镜像的解决方案
运维·docker·容器
是小王吖!3 小时前
容器技术 - docker
运维·docker·容器
小义_4 小时前
【RH134知识点问答题】第13章 运行容器
linux·云原生
Cyber4K4 小时前
【Kubernetes专项】Ingress、Ingress-Controller
云原生·容器·kubernetes
礼拜天没时间.8 小时前
Docker与Harbor迁移实战:从入门到生产级完整指南
linux·运维·docker·容器·架构·centos
匀泪11 小时前
云原生(nginx实验(1))
nginx·云原生
猫头虎12 小时前
OpenClaw相关的开源AI项目汇总大全:本文涵盖近期所有OpenClaw相关的GitHub高星star热门项目
运维·人工智能·macos·docker·容器·开源·github