使用 Golang 构建你的第一个 k8s Operator

使用 Golang 构建你的第一个 k8s Operator

本文将展示如何使用 Operator SDK 搭建一个基本的 k8s Operator。在本文中,您将了解如何创建一个新项目,并通过创建自定义资源定义 (CRD) 和基本控制器来添加 API。

我们将在 CRD 中添加字段,以包含一些有关期望状态和实际状态的信息,修改控制器以调和新资源的实例,然后将 operator 部署到 Kubernetes 集群。

Prerequisites

  • 安装 Operator-sdk v1.5.0+
  • 安装 Kubectl v1.17.0+
  • 一个 Kubernetes 集群以及其管理访问权限
  • 安装 Docker v3.2.2+ 版本
  • 安装 Golang v1.16.0+ 版本

Step 1: Create a project

建一个目录,并初始化一个 operator 项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ mkdir memcached-operator
$ cd memcached-operator
$ operator-sdk init --domain=example.com --repo=github.com/example/memcached-operator
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.7.0
Update go.mod:
$ go mod tidy

Running make:
$ make
go: creating new go.mod: module tmp
Downloading sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1
go: found sigs.k8s.io/controller-tools/cmd/controller-gen in sigs.k8s.io/controller-tools v0.4.1
/Users/username/workspace/projects/memcached-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go
1
operator-sdk init — domain=example.com — repo=github.com/example/memcached-operator
  • domin 用于 operator 创建的任何 API Group,因此这里的 API Group 是 *.example.com。

大家可能熟悉的一个 API Group 是 rbac.authorization.k8s.io,创建 RBAC 资源(如 ClusterRoles 和 ClusterBindings)的功能通常就设置在 Kubernetes 集群上。operator-sdk 允许您指定一个自定义域,将其附加到您定义的任何 API 组,以帮助避免名称冲突。

这里使用的 –repo 值只是一个示例。如果你想提交项目并保留它,可以将其设置为你可以访问的 Git 仓库。

项目初始化后会生生一个 Operator 项目的壳子,我们剩下的工作就是在这个框架之上,实现 operator 的功能。

Step 2: Create an API

使用 create 命令生成 CRD 和控制器:

注意:–version 标志针对的是操作符的 Kubernetes API 版本,而不是语义版本。因此,请勿在 –version 中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
$ operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource=true --controller=true


Writing scaffold for you to edit...
api/v1alpha1/memcached_types.go
controllers/memcached_controller.go
Running make:

$ make
/Users/username/workspace/projects/memcached-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go

--group 是自定义资源所在的组,因此它最终会出现在 API Group “cache.example.com “中。

--version 决定 API Group 的版本。可以使用不同的版本连续升级自定义资源。

-resource--controller 标志设置为 “true”,以便为这两样东西生成脚手架。

现在我们已经有了组件的轮廓,让我们开始用实际功能来填充它们。
首先是 CRD。在 api/v1alpha1/memcached_types.go 中,您应该能看到一些定义自定义资源类型规格和状态的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// MemcachedSpec defines the desired state of Memcached
type MemcachedSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of Memcached. Edit Memcached_types.go to remove/update
Foo string `json:"foo,omitempty"`
}
// MemcachedStatus defines the observed state of Memcached
type MemcachedStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}

请注意文件顶部的信息。该文件是由 Operator-SDK 搭建的脚手架,我们可以根据自己的项目需求去修改。
Spec 包含指定资源所需状态的信息,而 Status 则包含系统的可观测状态,尤其是其他资源可能想要使用的信息。这些 Golang 结构与 Kubernetes 用户为创建自定义资源类型实例而编写的 YAML 直接对应。

让我们为类型添加一些基本字段。

1
2
3
4
5
6
7
8
9
10
11
// MemcachedSpec defines the desired state of Memcached
type MemcachedSpec struct {
// +kubebuilder:validation:Minimum=0
// Size is the size of the memcached deployment
Size int32 `json:"size"`
}
// MemcachedStatus defines the observed state of Memcached
type MemcachedStatus struct {
// Nodes are the names of the memcached pods
Nodes []string `json:"nodes"`
}

Size 是一个整数,用于确定 Memcached 集群中的节点数量。我们在 Status 中添加了一个字符串数组,用于存储集群中包含的节点的 IP 地址。需要注意的是,这个特定的实现方式只是一个示例。
注意父 Memcached 结构 Status 字段的 Kubebuilder 子资源标记。这将在生成的 CRD 清单中添加 Kubernetes 状态子资源。这样,控制器就可以只更新状态字段,而无需更新整个对象,从而提高性能。

1
2
3
4
5
6
7
8
// Memcached is the Schema for the memcacheds API
// +kubebuilder:subresource:status
type Memcached struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MemcachedSpec `json:"spec,omitempty"`
Status MemcachedStatus `json:"status,omitempty"`
}

更改 types.go 文件后,应始终在项目根目录下运行以下命令:

1
make generate

此 make target 会调用 controller-gen 更新 api/v1alpha1/zz_generated.deepcopy.go,使其包含您刚刚添加的字段的必要实现。完成更新后,我们应运行以下命令为 CRD 生成 YAML 清单:

1
$ make manifests

会生成以下文件:

1
2
3
New:
config/crd/bases/cache.example.com_memcacheds.yaml
config/rbac/role.yaml

config/crd/bases/cache.example.com_memcacheds.yaml 是 memcached CRD 的清单。config/rbac/role.yaml 是 RBAC 清单,其中包含控制器所需的管理 memcached 类型的权限。

Step 3: Create a controller

让我们看看 controllers/memcached_controller.go 中目前包含的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Memcached object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = r.Log.WithValues("memcached", req.NamespacedName)
// your logic here
return ctrl.Result{}, nil
}

Reconcile 方法负责将自定义资源状态中包含的期望状态与系统上运行的实际状态进行核对,也是实现控制器逻辑的主要部分。实现调和循环的具体细节超出了本教程的范围,将在进阶的文章中介绍。现在,请用此参考实现替换 controllers/memcached_controller.go。注意,如果你在初始化项目时指定了不同的 repo,可能需要更改 github.com/example/memcached-operator/api/v1alpha1 的导入路径,以便正确指向你定义 memcached_types.goin 的目录。粘贴后,确保重新生成清单:

1
make manifests

Step 4: Build and deploy your operator

现在您已经填写了所有需要的组件,是时候部署 operator 了!一般来说,有三种不同的方法来部署:

  • 作为在 Kubernetes 集群外执行的程序。这样做可能是出于开发目的,也可能是出于集群中数据的安全考虑。Makefile 包含目标 make install run,用于在本地运行运算符,但这种方法不是这里的重点。
  • 作为 Kubernetes 集群内的部署运行。这就是我现在要向你展示的内容。

  • 由 operator 生命周期管理器部署和管理。建议在生产中使用,因为 OLM 具有管理和升级运行中的 operator 的附加功能。

现在,先构建并推送控制器的 Docker image。本示例使用了基础 Dockerfile,但你也可以根据自己的需要进行修改。我使用 Docker Hub 作为镜像仓库,但你能推/拉访问的任何仓库都可以。

1
2
$ export USERNAME=<docker-username>
$ make docker-build docker-push IMG=docker.io/$USERNAME/memcached-operator:v1.0.0

如果出现如下错误,则可能需要获取其他依赖项。运行建议的命令下载依赖项。

1
2
/controllers: package k8s.io/api/apps/v1 imported from implicitly required module; to add missing requirements, run:
go get k8s.io/api/apps/v1@v0.19.2

我们还可以在 Makefile 中设置镜像的默认名称和标签。镜像构建完成后,就可以部署 operator 了:

1
$ make deploy IMG=docker.io/$USERNAME/memcached-operator:v1.0.0

它使用 config/ 中的清单创建 CRD,在 Pod 中部署控制器,创建控制器管理 CRD 所需的 RBAC 角色,并将其分配给控制器。让我们来看看。
我们的 CRD 类型为 memcacheds.cache.example.com:

1
2
3
4
5
6
7
8
9
$ kubectl get crds
NAME CREATED AT
catalogsources.operators.coreos.com 2021-01-22T00:13:22Z
clusterserviceversions.operators.coreos.com 2021-01-22T00:13:22Z
installplans.operators.coreos.com 2021-01-22T00:13:22Z
memcacheds.cache.example.com 2021-02-06T00:52:38Z
operatorgroups.operators.coreos.com 2021-01-22T00:13:22Z
rbacsyncs.ibm.com 2021-01-22T00:08:59Z
subscriptions.operators.coreos.com 2021-01-22T00:13:22Z

运行控制器的部署和 Pod:

1
2
3
4
5
6
7
$ kubectl --namespace memcached-operator-system get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
memcached-operator-controller-manager 1/1 1 1 2m18s

$ kubectl --namespace memcached-operator-system get pods
NAME READY STATUS RESTARTS AGE
memcached-operator-controller-manager-76b588bbb5-wvl7b 2/2 Running 0 2m44s

当 pod 开始运行,我们的 Operator 就可以使用了。编辑 config/samples/cache_v1alpha1_memcached.yaml 中的示例 YAML,加入一个大小整数,就像在自定义资源规范中定义的那样:

1
2
3
4
5
6
apiVersion: cache.example.com/v1alpha1
kind: Memcached
metadata:
name: memcached-sample
spec:
size: 1

然后创建一个自定义资源的新实例:

1
2
$ kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml 
memcached.cache.example.com/memcached-sample created

再看看新的自定义资源和控制器在后台创建的对象:

1
2
3
4
5
6
7
8
9
10
11
$ kubectl get memcached
NAME AGE
memcached-sample 18s

$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
memcached-sample 1/1 1 1 33s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
memcached-sample-9b765dfc8-2hvf8 1/1 Running 0 44s

如果查看一下 Memcached 对象,就会发现状态已用运行节点的名称更新:

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
$ kubectl get memcached memcached-sample -o yaml
apiVersion: cache.example.com/v1alpha1
kind: Memcached
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"cache.example.com/v1alpha1","kind":"Memcached","metadata":{"annotations":{},"name":"memcached-sample","namespace":"default"},"spec":{"size":1}}
creationTimestamp: "2021-03-29T19:22:53Z"
generation: 1
managedFields:
- apiVersion: cache.example.com/v1alpha1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
f:spec:
.: {}
f:size: {}
manager: kubectl
operation: Update
time: "2021-03-29T19:22:53Z"
- apiVersion: cache.example.com/v1alpha1
fieldsType: FieldsV1
fieldsV1:
f:status:
.: {}
f:nodes: {}
manager: manager
operation: Update
time: "2021-03-29T19:22:58Z"
name: memcached-sample
namespace: default
resourceVersion: "1374"
uid: 63c7b1b1-1a75-49e6-8132-2164807a1b78
spec:
size: 1
status:
nodes:
- memcached-sample-9b765dfc8-2hvf8

要查看控制器的运行情况,可以在集群中添加另一个节点。将 config/samples/cache_v1alpha1_memcached.yaml 中的大小改为 2,然后运行:

1
2
$ kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
memcached.cache.example.com/memcached-sample configured

查看创建的新 pod:

1
2
3
4
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
memcached-sample-9b765dfc8-2hvf8 1/1 Running 0 50s
memcached-sample-9b765dfc8-jdhlq 1/1 Running 0 3s

然后看到 Memcached 对象再次更新为新 pod 的名称:

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
$ kubectl get memcached memcached-sample -o yaml
apiVersion: cache.example.com/v1alpha1
kind: Memcached
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"cache.example.com/v1alpha1","kind":"Memcached","metadata":{"annotations":{},"name":"memcached-sample","namespace":"default"},"spec":{"size":1}}
creationTimestamp: "2021-03-29T19:22:53Z"
generation: 2
managedFields:
- apiVersion: cache.example.com/v1alpha1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
f:spec:
.: {}
f:size: {}
manager: kubectl
operation: Update
time: "2021-03-29T19:22:53Z"
- apiVersion: cache.example.com/v1alpha1
fieldsType: FieldsV1
fieldsV1:
f:status:
.: {}
f:nodes: {}
manager: manager
operation: Update
time: "2021-03-29T19:22:58Z"
name: memcached-sample
namespace: default
resourceVersion: "1712"
uid: 63c7b1b1-1a75-49e6-8132-2164807a1b78
spec:
size: 2
status:
nodes:
- memcached-sample-9b765dfc8-2hvf8
- memcached-sample-9b765dfc8-jdhlq

Step 5: Cleanup

完成后,可以通过运行这些命令来清理已部署的 operator:

1
2
3
$ kubectl delete memcached memcached-sample

$ make undeploy

Debugging

查看操作员管理器日志:

1
$ kubectl logs deployment.apps/memcached-operator-controller-manager -n memcached-operator-system -c manager

-------------The End-------------

本文标题:使用 Golang 构建你的第一个 k8s Operator

文章作者:cloud sjhan

发布时间:2024年06月03日 - 22:06

最后更新:2024年06月03日 - 22:06

原始链接:https://cloudsjhan.github.io/2024/06/03/使用-Golang-构建你的第一个-k8s-Operator/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

cloud sjhan wechat
subscribe to my blog by scanning my public wechat account
坚持原创技术分享,您的支持将鼓励我继续创作!
0%
;