介绍

什么是 CRD?

首先我们需要知道第一个概念就是 CRD,CRD 全称 Custom Resource Definition,是 Kubernetes 提供的一种 API 扩展机制,允许用户定义新的资源类型。
通过 CRD,你可以像使用原生资源(如 Pod、Service)一样操作自定义资源。

什么是 Kubernetes Operator?

Kubernetes Operator 是一种特定于应用的控制器,用于将人工运维操作自动化。它基于 CRD 定义的自定义资源,并根据资源的状态进行相应的操作,例如部署、升级、备份等。
我们可以简单的理解为 Kubernetes Operator = CRD + Controller 也就是说自定义资源加自定义控制器就是 Kubernetes Operator,使用它我们不仅可以自定义我们想要的资源,还可以通过我们想要的逻辑和方式对它进行操作。

核心原理:利用 CRD 定义领域特定资源(Domain Specific Resources),控制器监听这些资源的变化并作出反应(Reconciliation Loop),实现“期望状态”与“实际状态”的自动同步

Kubernetes Operator 架构

Kubernetes Operator

自定义Operator开发

1. 准备开发环境

确认主机上已经安装 Docker 和 kubectl 并配置好 kubeconfig

2. 安装 Go

浏览器访问 https://go.dev/dl/ 获取最新版 Go 链接。使用 wget 下载

1
wget https://go.dev/dl/go1.24.4.linux-amd64.tar.gz
解压并安装到 /usr/local
1
sudo tar -C /usr/local -xzf go1.24.4.linux-amd64.tar.gz
编辑 ~/.bashrc 文件,添加 Go 到环境变量
1
2
3
export PATH=$PATH:/usr/local/go/bin
# 设置 Go 模块代理
export GOPROXY=https://goproxy.cn,direct
激活环境变量使生效
1
source ~/.bashrc
验证 Go 版本
1
go version

3. 安装 kubebuilder

浏览器访问 https://github.com/kubernetes-sigs/kubebuilder/releases 获取最新版 kubebuilder 链接。使用 wget 下载

1
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v4.6.0/kubebuilder_linux_amd64
移动到 /usr/local/bin
1
sudo mv kubebuilder_linux_amd64 /usr/local/bin/kubebuilder
验证 kubebuilder 版本
1
kubebuilder version

4. 初始化项目

1
2
3
mkdir my-operator
cd my-operator
kubebuilder init --repo github.com/my-org/my-operator --domain mydomain.com

参数解释:

  1. --repo:指定 Go module 的根路径(一般为你的仓库路径,如 github.com/my-org/my-operator)。Kubebuilder 用它生成 go.mod 和导入路径。
  2. --domain:指定 CRD API Group 后缀(如 apps.mydomain.com)。

5. 定义一个新的 API(CRD + Controller)

1
kubebuilder create api --group my-operator --version v1alpha1 --kind MyKind --resource --controller

参数解释:

  1. --group:指定 CRD 的 API Group。
  2. --version:指定 CRD 的 API Version。
  3. --kind:指定 CRD 的 Kind(资源类型)。

这会:

  1. 在 api/ 目录中创建 CRD 的 Go 类型。
  2. 在 controllers/ 中创建控制器逻辑。
  3. 更新 config/ 目录以包含部署文件模板。

6. 修改 CRD 的定义

修改 api/v1/examplea_types.go 文件。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type MyKindSpec struct {
GroupName string `json:"groupName,omitempty"`
}

type MyKindStatus struct {
UnderControl bool `json:"underControl,omitempty"`
}

type MyKind struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec MyKindSpec `json:"spec,omitempty"`
Status MyKindStatus `json:"status,omitempty"`
}

type MyKindList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []MyKind `json:"items"`
}

func init() {
SchemeBuilder.Register(&MyKind{}, &MyKindList{})
}

7. 修改 Controller 的定义

修改 internal/controller/mykind_controller.go 文件。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package controller

import (
"context"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

myoperatorv1alpha1 "github.com/my-org/my-operator/api/v1alpha1"
)

type MyKindReconciler struct {
client.Client
Scheme *runtime.Scheme
}

func (r *MyKindReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
logger.Info("开始调用Reconcile方法")

var exp myoperatorv1alpha1.MyKind
if err := r.Get(ctx, req.NamespacedName, &exp); err != nil {
logger.Error(err, "未找到对应的CRD资源")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

exp.Status.UnderControl = false

var podList corev1.PodList
if err := r.List(ctx, &podList); err != nil {
logger.Error(err, "无法获取pod列表")
} else {
for _, item := range podList.Items {
if item.GetLabels()["group"] == exp.Spec.GroupName {
logger.Info("找到对应的pod资源", "name", item.GetName())
exp.Status.UnderControl = true
}
}
}

if err := r.Status().Update(ctx, &exp); err != nil {
logger.Error(err, "无法更新CRD资源状态")
return ctrl.Result{}, err
}
logger.Info("已更新CRD资源状态", "status", exp.Status.UnderControl)

return ctrl.Result{}, nil
}

func (r *MyKindReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&myoperatorv1alpha1.MyKind{}).
Watches(
&corev1.Pod{},
handler.EnqueueRequestsFromMapFunc(r.podChangeHandler),
).
Complete(r)
}

func (r *MyKindReconciler) podChangeHandler(ctx context.Context, obj client.Object) []reconcile.Request {
logger := log.FromContext(ctx)

var req []reconcile.Request
var list myoperatorv1alpha1.MyKindList
if err := r.Client.List(ctx, &list); err != nil {
logger.Error(err, "无法获取到资源")
} else {
for _, item := range list.Items {
if item.Spec.GroupName == obj.GetLabels()["group"] {
req = append(req, reconcile.Request{
NamespacedName: types.NamespacedName{Name: item.Name, Namespace: item.Namespace},
})
}
}
}
return req
}

8. 运行 Operator

执行下面的命令生成并将 CRD 安装到 k8s 集群中

1
2
make manifests
make install

安装成功后,查看一下

1
kubectl get crd | grep mykind

新开一个终端窗口启动

1
make run

创建一个 MyKind 资源

1
2
3
4
5
6
7
8
9
apiVersion: my-operator.mydomain.com/v1alpha1
kind: MyKind
metadata:
labels:
app.kubernetes.io/name: my-operator
app.kubernetes.io/managed-by: kustomize
name: mykind-sample
spec:
groupName: business

创建成功后,我们查看一下当前的 CRD 状态,可以看到现在状态应该是空的

1
kubectl describe MyKind mykind-sample

然后新建一个文件 example_v1_examplea 1.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
name: busybox
labels:
group: "business"
spec:
containers:
- name: busybox
image: busybox:latest
command:
- sleep
- "3600"

然后再次查看 CRD 状态,可以看到控制状态已经变成了 true 了,同时你也可以在控制台的日志中看到资源状态变更的日志。
之后你就可以摸索更加高级的各种操作了,根据具体的实际业务场景需求来满足不同的需要。
如果需要回收删除对应的资源先使用 kubectl delete -f 删除所有创建的测试。然后直接执行 make uninstall 就可以了。