Kubernetes编程—— 编写 Operator 的方案 —— 2、基于 sample-controller
编写 Operator 的方案 —— 2、基于 sample-controller
https://github.com/kubernetes/sample-controller
首先我们将基于 k8s.io/sample-controller 来实现 cnat,通过直接使用 client-go 完成。
sample-controller 使用 k8s.io/code-generator 来生成强类型的客户端、Informer、Lister 和深拷贝函数。如果你的自定义控制器中的 API 发生了变化,比如在自定义资源中添加了一个新的字段,你需要运行 update-codegen.sh 脚本来重新生成先前提到过的源代码文件。
我们开始基于 sample-controller 使用 client-go 来实现我们的 cnat Operator 吧。
一、定义 CRD 资源
1、首先,运行 go get k8s.io/sample-controller 获取相关的源代码及依赖并安装到系统中。
[root@JumperServer:zuoyang] # git clone https://github.com/kubernetes/sample-controller Cloning into 'sample-controller'... remote: Enumerating objects: 26102, done. remote: Counting objects: 100% (1850/1850), done. remote: Compressing objects: 100% (363/363), done. remote: Total 26102 (delta 1564), reused 1751 (delta 1478), pack-reused 24252 Receiving objects: 100% (26102/26102), 11.64 MiB | 13.98 MiB/s, done. Resolving deltas: 100% (18087/18087), done. [root@JumperServer:zuoyang] # cd sample-controller/ [root@JumperServer:sample-controller] # ls -l total 108 drwxr-xr-x 3 root root 4096 Aug 21 17:43 artifacts -rw-r--r-- 1 root root 148 Aug 21 17:43 code-of-conduct.md -rw-r--r-- 1 root root 756 Aug 21 17:43 CONTRIBUTING.md -rw-r--r-- 1 root root 16506 Aug 21 17:43 controller.go -rw-r--r-- 1 root root 9746 Aug 21 17:43 controller_test.go drwxr-xr-x 3 root root 4096 Aug 21 17:43 docs -rw-r--r-- 1 root root 2735 Aug 21 17:43 go.mod -rw-r--r-- 1 root root 15455 Aug 21 17:43 go.sum drwxr-xr-x 2 root root 4096 Aug 21 17:43 hack -rw-r--r-- 1 root root 11358 Aug 21 17:43 LICENSE -rw-r--r-- 1 root root 2946 Aug 21 17:43 main.go -rw-r--r-- 1 root root 183 Aug 21 17:43 OWNERS drwxr-xr-x 5 root root 4096 Aug 21 17:43 pkg -rw-r--r-- 1 root root 7543 Aug 21 17:43 README.md -rw-r--r-- 1 root root 550 Aug 21 17:43 SECURITY_CONTACTS [root@JumperServer:sample-controller] # go get k8s.io/sample-controller go: downloading k8s.io/api v0.0.0-20230810042731-2f6eec10c476 go: downloading k8s.io/apimachinery v0.0.0-20230815235016-14436eb53afd go: downloading k8s.io/client-go v0.0.0-20230816000758-856e847bb7cb [root@JumperServer:sample-controller] #
2、这个命令会启动自定义控制器,等待你注册 CRD 并创建一个自定义资源。
[root@JumperServer:sample-controller] # kubectl apply -f artifacts/examples/ crd-status-subresource.yaml crd.yaml example-foo.yaml [root@JumperServer:sample-controller] # kubectl apply -f artifacts/examples/crd.yaml customresourcedefinition.apiextensions.k8s.io/foos.samplecontroller.k8s.io created [root@JumperServer:sample-controller] # cat artifacts/examples/crd.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: foos.samplecontroller.k8s.io # for more information on the below annotation, please see # https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/2337-k8s.io-group-protection/README.md annotations: "api-approved.kubernetes.io": "unapproved, experimental-only; please get an approval from Kubernetes API reviewers if you're trying to develop a CRD in the *.k8s.io or *.kubernetes.io groups" spec: group: samplecontroller.k8s.io versions: - name: v1alpha1 served: true storage: true schema: # schema used for validation openAPIV3Schema: type: object properties: spec: type: object properties: deploymentName: type: string replicas: type: integer minimum: 1 maximum: 10 status: type: object properties: availableReplicas: type: integer names: kind: Foo plural: foos scope: Namespaced
3、如果成功注册了 CRD,运行下面的命令应该会看到相应的输出:
[root@JumperServer:sample-controller] # kubectl get crds NAME CREATED AT applicationdrafts.orchestration.caicloud.io 2023-06-12T06:34:12Z applications.orchestration.caicloud.io 2023-06-12T06:34:12Z clusteroperations.operation.cce.io 2023-07-24T04:02:19Z clusterquotas.tenant.caicloud.io 2023-06-12T06:35:06Z clusters.resource.caicloud.io 2023-06-12T06:34:14Z configclaims.config.caicloud.io 2023-06-12T06:34:14Z configreferences.config.caicloud.io 2023-06-12T06:34:14Z configs.resource.caicloud.io 2023-06-12T06:34:14Z crontabs.zuoyang.tech 2023-07-10T10:49:55Z drainages.node.cce.io 2023-07-24T04:58:53Z extendedresources.resource.caicloud.io 2023-06-12T06:34:14Z foos.samplecontroller.k8s.io 2023-08-21T09:49:51Z geips.geip.cce.io 2023-06-12T06:27:48Z infranetworks.resource.caicloud.io 2023-06-12T06:34:14Z loadbalancers.loadbalance.caicloud.io 2023-06-12T06:34:25Z machineautoscalinggroups.resource.caicloud.io 2023-06-12T06:34:14Z machines.resource.caicloud.io 2023-06-12T06:34:14Z network-attachment-definitions.k8s.cni.cncf.io 2023-06-12T06:28:04Z networkpolicies.networking.caicloud.io 2023-06-12T06:35:07Z networks.resource.caicloud.io 2023-06-12T06:32:47Z nodeclaims.resource.caicloud.io 2023-06-12T06:34:14Z nodeconfigs.config.k8s.io 2023-06-12T06:30:52Z nodelocalstorages.resource.caicloud.io 2023-06-12T06:34:39Z nodelocalvolumes.localvolume.everest.io 2023-06-12T06:28:24Z nodeoperations.operation.cce.io 2023-07-24T04:02:20Z packageversions.version.cce.io 2023-06-12T06:28:15Z partitions.tenant.caicloud.io 2023-06-12T06:35:06Z permissions.rbac.cce.io 2023-06-12T06:28:13Z podnetworkinterfaceclaims.crd.yangtse.cni 2023-06-12T06:28:03Z podnetworkinterfacepools.crd.yangtse.cni 2023-06-12T06:28:03Z podnetworkinterfaceqosconfigs.crd.yangtse.cni 2023-06-12T06:28:03Z podnetworkinterfaces.crd.yangtse.cni 2023-06-12T06:28:03Z releasehistories.release.caicloud.io 2023-06-12T06:33:15Z releases.release.caicloud.io 2023-06-12T06:32:59Z requirementgaps.resource.caicloud.io 2023-06-12T06:34:14Z resourceclasses.resource.caicloud.io 2023-06-12T06:34:14Z snapshots.resource.caicloud.io 2023-06-12T06:35:01Z springclouds.microservice.caicloud.io 2023-06-12T06:34:52Z tags.resource.caicloud.io 2023-06-12T06:34:14Z tenants.tenant.caicloud.io 2023-06-12T06:35:05Z volumesnapshotclasses.snapshot.storage.k8s.io 2023-06-12T06:32:14Z volumesnapshotcontents.snapshot.storage.k8s.io 2023-06-12T06:32:14Z volumesnapshots.snapshot.storage.k8s.io 2023-06-12T06:32:14Z workloads.workload.caicloud.io 2023-06-12T06:34:53Z
4、下一步,创建一个名为 foo.samplecontroller.k8s.io/example-foo 的自定义资源,并确认控制器有没有正常工作:
[root@JumperServer:sample-controller] # kubectl apply -f artifacts/examples/example-foo.yaml foo.samplecontroller.k8s.io/example-foo created [root@JumperServer:sample-controller] # cat artifacts/examples/example-foo.yaml apiVersion: samplecontroller.k8s.io/v1alpha1 kind: Foo metadata: name: example-foo spec: deploymentName: example-foo replicas: 1 [root@JumperServer:sample-controller] # kubectl get po,rs,deploy,foo NAME AGE foo.samplecontroller.k8s.io/example-foo 46s
二、编写 CRD 业务逻辑
在开始业务逻辑之前,让我们先把现有的 sample-controller 复制一个新的 cnat:
[root@JumperServer:sample-controller] # cp sample-controller cnat [root@JumperServer:sample-controller] # ll total 8 drwxr-xr-x 8 root root 4096 Aug 21 17:57 cnat drwxr-xr-x 8 root root 4096 Aug 21 17:49 sample-controller [root@JumperServer:sample-controller] # cd cnat/artifacts/examples/ [root@JumperServer:sample-controller] # ll total 12 -rw-r--r-- 1 root root 1315 Aug 21 17:49 crd-status-subresource.yaml -rw-r--r-- 1 root root 1190 Aug 21 17:49 crd.yaml -rw-r--r-- 1 root root 135 Aug 21 17:49 example-foo.yaml [root@JumperServer:sample-controller] # cp crd.yaml cnat-crd.yaml [root@JumperServer:sample-controller] # cp example-foo.yaml cnat-example.yaml [root@JumperServer:sample-controller] # cat cnat-crd.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: ats.cnat.zuoyang.tech # for more information on the below annotation, please see # https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/2337-k8s.io-group-protection/README.md annotations: "api-approved.kubernetes.io": "unapproved, experimental-only; please get an approval from Kubernetes API reviewers if you're trying to develop a CRD in the *.k8s.io or *.kubernetes.io groups" spec: group: cnat.zuoyang.tech versions: - name: v1alpha1 kind: Foo plural: foos scope: Namespaced [root@JumperServer:sample-controller] # cat cnat-example.yaml apiVersion: cnat.zuoyang.tech/v1alpha1 kind: At metadata: labels: controller-tools.k8s.io: "1.0" name: example-at spec: schedule: "2019-04-12T10:12:00Z" command: "echo YAY"
注意:只要 API 类型的定义发生了变化,比如你向 CRD 中添加了一个 At 字段,你就需要重新运行 update-codegen.sh 脚本:
[root@JumperServer:sample-controller] # cat hack/update-codegen.sh #!/usr/bin/env bash # Copyright 2017 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)} source "${CODEGEN_PKG}/kube_codegen.sh" # generate the code with: # --output-base because this script should also be able to run inside the vendor dir of # k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir # instead of the $GOPATH directly. For normal projects this can be dropped. kube::codegen::gen_helpers \ --input-pkg-root k8s.io/sample-controller/pkg/apis \ --output-base "$(dirname "${BASH_SOURCE[0]}")/../../.." \ --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" kube::codegen::gen_client \ --with-watch \ --input-pkg-root k8s.io/sample-controller/pkg/apis \ --output-pkg-root k8s.io/sample-controller/pkg/generated \ --output-base "$(dirname "${BASH_SOURCE[0]}")/../../.." \ --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt"
这里有个问题,我执行这个脚本报错:
./hack/verify-codegen.sh /zuoyang/cnat/hack/update-codegen.sh: line 24: ../code-generator/kube_codegen.sh: No such file or directory
故障排查后,找到解决办法:
[root@JumperServer:sample-controller] # ll total 8 drwxr-xr-x 8 root root 4096 Aug 21 17:57 cnat drwxr-xr-x 8 root root 4096 Aug 21 17:49 sample-controller [root@JumperServer:sample-controller] # git clone https://github.com/kubernetes/code-generator.git Cloning into 'code-generator'... remote: Enumerating objects: 13112, done. remote: Counting objects: 100% (3548/3548), done. remote: Compressing objects: 100% (627/627), done. remote: Total 13112 (delta 2955), reused 3405 (delta 2865), pack-reused 9564 Receiving objects: 100% (13112/13112), 9.78 MiB | 6.19 MiB/s, done. Resolving deltas: 100% (7683/7683), done. [root@cce-poc-daxs1 zuoyang]# ll total 12 drwxr-xr-x 8 root root 4096 Aug 21 17:57 cnat drwxr-xr-x 8 root root 4096 Aug 21 18:21 code-generator drwxr-xr-x 8 root root 4096 Aug 21 17:49 sample-controller [root@cce-poc-daxs1 zuoyang]# cd cnat/ [root@JumperServer:sample-controller] # ls -l total 108 drwxr-xr-x 3 root root 4096 Aug 21 17:57 artifacts -rw-r--r-- 1 root root 148 Aug 21 17:57 code-of-conduct.md -rw-r--r-- 1 root root 756 Aug 21 17:57 CONTRIBUTING.md -rw-r--r-- 1 root root 16506 Aug 21 17:57 controller.go -rw-r--r-- 1 root root 9746 Aug 21 17:57 controller_test.go drwxr-xr-x 3 root root 4096 Aug 21 17:57 docs -rw-r--r-- 1 root root 2735 Aug 21 17:57 go.mod -rw-r--r-- 1 root root 15455 Aug 21 17:57 go.sum drwxr-xr-x 2 root root 4096 Aug 21 18:16 hack -rw-r--r-- 1 root root 11358 Aug 21 17:57 LICENSE -rw-r--r-- 1 root root 2946 Aug 21 17:57 main.go -rw-r--r-- 1 root root 183 Aug 21 17:57 OWNERS drwxr-xr-x 5 root root 4096 Aug 21 17:57 pkg -rw-r--r-- 1 root root 7543 Aug 21 17:57 README.md -rw-r--r-- 1 root root 550 Aug 21 17:57 SECURITY_CONTACTS [root@JumperServer:sample-controller] # ./hack/ update-codegen.sh verify-codegen.sh [root@JumperServer:sample-controller] # ./hack/update-codegen.sh go: downloading k8s.io/gengo v0.0.0-20220902162205-c0856e24416d go: downloading k8s.io/klog/v2 v2.100.1 go: downloading golang.org/x/tools v0.8.0 go: downloading github.com/go-logr/logr v1.2.4 go: downloading golang.org/x/mod v0.10.0 go: downloading golang.org/x/sys v0.10.0 fatal: ambiguous argument ':(glob)/k8s.io/sample-controller/pkg/apis/**/*.go': unknown revision or path not in the working tree. Use '--' to separate paths from revisions, like this: 'git <command> [<revision>...] -- [<file>...]' fatal: ambiguous argument ':(glob)/k8s.io/sample-controller/pkg/apis/**/*.go': unknown revision or path not in the working tree. Use '--' to separate paths from revisions, like this: 'git <command> [<revision>...] -- [<file>...]' fatal: ambiguous argument ':(glob)/k8s.io/sample-controller/pkg/apis/**/*.go': unknown revision or path not in the working tree. Use '--' to separate paths from revisions, like this: 'git <command> [<revision>...] -- [<file>...]' go: downloading github.com/google/gnostic-models v0.6.8 go: downloading k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 go: downloading google.golang.org/protobuf v1.30.0 go: downloading github.com/go-openapi/jsonreference v0.20.2 go: downloading github.com/google/gofuzz v1.2.0 fatal: ambiguous argument ':(glob)/k8s.io/sample-controller/pkg/apis/**/types.go': unknown revision or path not in the working tree. Use '--' to separate paths from revisions, like this: 'git <command> [<revision>...] -- [<file>...]'