Operator 是什么
Kubernetes Operator 是一种特定于应用的控制器,可扩展 Kubernetes API 的功能,来代表 Kubernetes 用户创建、配置和管理复杂应用的实例。
它基于基本 Kubernetes 资源和控制器概念构建,但又涵盖了特定于域或应用的知识,用于实现其所管理软件的整个生命周期的自动化。
Kubernetes Operator 通过自定义资源定义 CRD 引入新的对象类型。Kubernetes API 可以像处理内置对象一样处理自定义资源定义,包括通过 kubectl 交互以及包含在基于角色的访问权限控制(RBAC)策略中。
kubebuilder 是什么
Kubebuilder 是一个使用 CRDs 构建 K8s API 的 SDK,底层使用了 controller-runtime,主要功能有:
- 提供脚手架工具初始化 CRDs 工程,自动生成 boilerplate 代码和配置
 - 提供代码库封装底层的 K8s client-go
 
可以看出,Kubebuilder 主要作用是方便用户从零开始开发 CRDs,Controllers 和 Admission Webhooks 来扩展 Kubernetes。
Kubebuilder 的工作流程
- 创建一个新的工程目录
 - 创建一个或多个资源 API CRD 然后将字段添加到资源
 - 在控制器中实现协调循环(reconcile loop),watch 额外的资源
 - 在集群中运行测试(自动安装 CRD 并自动启动控制器)
 - 更新引导集成测试测试新字段和业务逻辑
 - 使用用户提供的 Dockerfile 构建和发布容器
 
使用 kubebuilder 开发 Operator 示例
安装 Kubebuilder 命令行工具后,执行 kubebuilder init 命令,就可以生成项目。
$ kubebuilder init --project-name myk8soperator --domain c9g.io --repo github.com/caodailiang/myk8soperator
其中 domain 是自定义 CRD group 的 domain 后缀,repo 是对应的 go module 名。执行命令后,可以看到 Kubebuilder 生成了项目的相关目录,Manager 代码,以及用于部署的 Kubernetes 配置文件。
$ tree ./
./
├── cmd
│   └── main.go
├── config
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   └── rbac
│       ├── auth_proxy_client_clusterrole.yaml
│       ├── auth_proxy_role_binding.yaml
│       ├── auth_proxy_role.yaml
│       ├── auth_proxy_service.yaml
│       ├── kustomization.yaml
│       ├── leader_election_role_binding.yaml
│       ├── leader_election_role.yaml
│       ├── role_binding.yaml
│       ├── role.yaml
│       └── service_account.yaml
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
├── Makefile
├── PROJECT
├── README.md
└── test
    ├── e2e
    │   ├── e2e_suite_test.go
    │   └── e2e_test.go
    └── utils
        └── utils.go
11 directories, 28 files
在生成项目的目录和初始文件后,可以采用 kubebuilder create api 命令来创建自定义的 CRD 和其 Controller。
$ kubebuilder create api --group samplecontroller --version v1alpha1 --kind Foo
其中 group 与上一条命令中的 domain 一起构成自定义CRD的 APIGroup,加上 version 一起够成自定义CRD的GroupVersion: samplecontroller.c9g.io/v1alpha1。
执行该命令后,新增和变更的文件列表如下:
$ git status
...
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified:   PROJECT
modified:   cmd/main.go
modified:   config/default/kustomization.yaml
Untracked files:
(use "git add <file>..." to include in what will be committed)
api/
config/crd/
config/rbac/foo_editor_role.yaml
config/rbac/foo_viewer_role.yaml
config/samples/
internal/
自定义CRD类型 Foo 的定义在 api 目录中,我们需要修改生成的 api/v1alpha1/foo_types.go 文件,在其中加入 Foo 资源的相关属性。
// FooSpec defines the desired state of Foo
type FooSpec struct {
        // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
        // Important: Run "make" to regenerate code after modifying this file
        Image    string `json:"image"`
        Replicas *int32 `json:"replicas"`
}
// FooStatus defines the observed state of Foo
type FooStatus struct {
        // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
        // Important: Run "make" to regenerate code after modifying this file
        AvailableReplicas int32 `json:"availableReplicas"`
}
修改完成后再生成 CRD 的 Kubernetes yaml 定义文件。
$ make manifests
CRD 描述文件已经生成,现在可以将自定义 CRD Foo 安装到 Kubernetes 集群中。
$ make install
...
$ kubectl get crd
NAME                           CREATED AT
foos.samplecontroller.c9g.io   2023-10-06T04:45:41Z
现在来尝试创建一个 foo 资源。
$ kubectl apply -f config/samples/samplecontroller_v1alpha1_foo.yaml
$ kubectl get foos.samplecontroller.c9g.io
NAME         AGE
foo-sample   9s
自定义CRD Foo 已经创建成功,现在需要完成 Controller 逻辑。修改 internal/controller/foo_controller.go, 在其中加入调谐逻辑。本示例中我们只是简单地把 Foo 资源的名称打印出来:
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.2/pkg/reconcile
func (r *FooReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
        _ = log.FromContext(ctx)
        // TODO(user): your logic here
        fmt.Println("reconcile foo", req.Name)
        return ctrl.Result{}, nil
}
Controller 逻辑完成后,就可以构建镜像了:
$ make docker-build docker-push IMG=c9g.io/myk8soperator/sample-controller:v0.4.1
使用构建的镜像在集群中部署 Controller:
$ make deploy IMG=c9g.io/myk8soperator/sample-controller:v0.4.1
查看部署的 Controller 日志,可以看到对 Foo 资源的处理记录。
$ kubectl logs deployments/myk8soperator-controller-manager -n myk8soperator-system
2023-10-06T06:57:46Z	INFO	controller-runtime.metrics	Metrics server is starting to listen	{"addr": "127.0.0.1:8080"}
2023-10-06T06:57:46Z	INFO	setup	starting manager
2023-10-06T06:57:46Z	INFO	Starting server	{"path": "/metrics", "kind": "metrics", "addr": "127.0.0.1:8080"}
2023-10-06T06:57:46Z	INFO	Starting server	{"kind": "health probe", "addr": "[::]:8081"}
...
2023-10-06T06:58:10Z	INFO	Starting EventSource	{"controller": "foo", "controllerGroup": "samplecontroller.c9g.io", "controllerKind": "Foo", "source": "kind source: *v1alpha1.Foo"}
2023-10-06T06:58:10Z	INFO	Starting Controller	{"controller": "foo", "controllerGroup": "samplecontroller.c9g.io", "controllerKind": "Foo"}
2023-10-06T06:58:10Z	INFO	Starting workers	{"controller": "foo", "controllerGroup": "samplecontroller.c9g.io", "controllerKind": "Foo", "worker count": 1}
reconcile foo foo-sample
调试完成后,可以使用 make uninstall 卸载 CRD,使用 make undeploy 卸载控制器,进行环境清理。