K8s Operator 开发之 kubebuilder 实战

举报
kaliarch 发表于 2021/11/20 20:37:19 2021/11/20
【摘要】 一 背景对于业务应用,需要对其进行先k8s内置资源进行一系列运维操作,因此编写业务的operator必不可少,在此了解到kubebuilder 是社区认可度很高的一种官方、标准化 Operator 框架,可以利用其非常方便的编写业务operator,以此来扩展 Kubernetes API 1.1 kubebuilder是什么Kubebuilder 是一个使用 CRDs 构建 K8s AP...

一 背景

对于业务应用,需要对其进行先k8s内置资源进行一系列运维操作,因此编写业务的operator必不可少,在此了解到kubebuilder 是社区认可度很高的一种官方、标准化 Operator 框架,可以利用其非常方便的编写业务operator,以此来扩展 Kubernetes API

1.1 kubebuilder是什么

Kubebuilder 是一个使用 CRDs 构建 K8s API 的 SDK,主要是:

  • 提供脚手架工具初始化 CRDs 工程,自动生成 boilerplate 代码和配置;
  • 提供代码库封装底层的 K8s go-client;

方便用户从零开始开发 CRDs,Controllers 和 Admission Webhooks 来扩展 K8s。

1.2 功能

自定义资源 CRD(Custom Resource Definition)可以扩展 Kubernetes API,掌握 CRD 是成为 Kubernetes 高级玩家的必备技能,本文将介绍 CRD 和 Controller 的概念,并对 CRD 编写框架 Kubebuilder 进行深入分析,让您真正理解并能快速开发 CRD。

1.3 基本概念

  • CRD (Custom Resource Definition): 允许用户自定义 Kubernetes 资源,是一个类型;
  • CR (Custom Resourse): CRD 的一个具体实例;
  • webhook: 它本质上是一种 HTTP 回调,会注册到 apiserver 上。在 apiserver 特定事件发生时,会查询已注册的 webhook,并把相应的消息转发过去。

按照处理类型的不同,一般可以将其分为两类:一类可能会修改传入对象,称为 mutating webhook;一类则会只读传入对象,称为 validating webhook。

  • 工作队列: controller 的核心组件。它会监控集群内的资源变化,并把相关的对象,包括它的动作与 key,例如 Pod 的一个 Create 动作,作为一个事件存储于该队列中;
  • controller :它会循环地处理上述工作队列,按照各自的逻辑把集群状态向预期状态推动。不同的 controller 处理的类型不同,比如 replicaset controller 关注的是副本数,会处理一些 Pod 相关的事件;
  • operator:operator 是描述、部署和管理 kubernetes 应用的一套机制,从实现上来说,可以将其理解为 CRD 配合可选的 webhook 与 controller 来实现用户业务逻辑,即 operator = CRD + webhook + controller。

二 架构即基本概念

2.1 架构图

2.2 基础概念

2.2.1 GVKs&GVRs

GVK = GroupVersionKind,GVR = GroupVersionResource。

  • API Group & Versions(GV)
    API Group 是相关 API 功能的集合,每个 Group 拥有一或多个 Versions,用于接口的演进。

  • Kinds & Resources(GVR)
    每个 GV 都包含多个 API 类型,称为 Kinds,在不同的 Versions 之间同一个 Kind 定义可能不同, Resource 是 Kind 的对象标识(resource type),一般来说 Kinds 和 Resources 是 1:1 的,比如 pods Resource 对应 Pod Kind,但是有时候相同的 Kind 可能对应多个 Resources,比如 Scale Kind 可能对应很多 Resources:deployments/scale,replicasets/scale,对于 CRD 来说,只会是 1:1 的关系。

每一个 GVK 都关联着一个 package 中给定的 root Go type,比如 apps/v1/Deployment 就关联着 K8s 源码里面 k8s.io/api/apps/v1 package 中的 Deployment struct,我们提交的各类资源定义 YAML 文件都需要写:

apiVersion:这个就是 GV 。
kind:这个就是 K。

根据 GVK K8s 就能找到你到底要创建什么类型的资源,根据你定义的 Spec 创建好资源之后就成为了 Resource,也就是 GVR。GVK/GVR 就是 K8s 资源的坐标,是我们创建/删除/修改/读取资源的基础。

2.2.3 Scheme

每一组 Controllers 都需要一个 Scheme,提供了 Kinds 与对应 Go types 的映射,也就是说给定 Go type 就知道他的 GVK,给定 GVK 就知道他的 Go type,比如说我们给定一个 Scheme: “tutotial.kubebuilder.io/api/v1”.CronJob{} 这个 Go type 映射到 batch.tutotial.kubebuilder.io/v1 的 CronJob GVK,那么从 Api Server 获取到下面的 JSON:

{
    "kind": "CronJob",
    "apiVersion": "batch.tutorial.kubebuilder.io/v1",
    ...
}

就能构造出对应的 Go type了,通过这个 Go type 也能正确地获取 GVR 的一些信息,控制器可以通过该 Go type 获取到期望状态以及其他辅助信息进行调谐逻辑。

2.2.4 Manager

Kubebuilder 的核心组件,具有 3 个职责:

  • 负责运行所有的 Controllers;
  • 初始化共享 caches,包含 listAndWatch 功能;
  • 初始化 clients 用于与 Api Server 通信。

2.2.5 Cache

Kubebuilder 的核心组件,负责在 Controller 进程里面根据 Scheme 同步 Api Server 中所有该 Controller 关心 GVKs 的 GVRs,其核心是 GVK -> Informer 的映射,Informer 会负责监听对应 GVK 的 GVRs 的创建/删除/更新操作,以触发 Controller 的 Reconcile 逻辑。

2.2.6 Controller

Kubebuidler 为我们生成的脚手架文件,我们只需要实现 Reconcile 方法即可。

2.2.7 Client

在实现 Controller 的时候不可避免地需要对某些资源类型进行创建/删除/更新,就是通过该 Clients 实现的,其中查询功能实际查询是本地的 Cache,写操作直接访问 Api Server。

2.2.8 Index

由于 Controller 经常要对 Cache 进行查询,Kubebuilder 提供 Index utility 给 Cache 加索引提升查询效率。

2.2.9 Finalizer

在一般情况下,如果资源被删除之后,我们虽然能够被触发删除事件,但是这个时候从 Cache 里面无法读取任何被删除对象的信息,这样一来,导致很多垃圾清理工作因为信息不足无法进行,K8s 的 Finalizer 字段用于处理这种情况。在 K8s 中,只要对象 ObjectMeta 里面的 Finalizers 不为空,对该对象的 delete 操作就会转变为 update 操作,具体说就是 update deletionTimestamp 字段,其意义就是告诉 K8s 的 GC“在deletionTimestamp 这个时刻之后,只要 Finalizers 为空,就立马删除掉该对象”。

所以一般的使用姿势就是在创建对象时把 Finalizers 设置好(任意 string),然后处理 DeletionTimestamp 不为空的 update 操作(实际是 delete),根据 Finalizers 的值执行完所有的 pre-delete hook(此时可以在 Cache 里面读取到被删除对象的任何信息)之后将 Finalizers 置为空即可。

2.2.10 OwnerReference

K8s GC 在删除一个对象时,任何 ownerReference 是该对象的对象都会被清除,与此同时,Kubebuidler 支持所有对象的变更都会触发 Owner 对象 controller 的 Reconcile 方法。

三 实战kubebuilder

3.1 需求环境

  • go version v1.15+.
  • docker version 17.03+.
  • kubectl version v1.11.3+.
  • kustomize v3.1.0+
  • Kind 本地开发可安装kind

能够访问 Kubernetes v1.11.3+ 集群

  • 环境
    创建:k8s.io和sigs.k8s.io两个目录
    • k8s.io直接拉去k8s项目源码,并将其中kubernetes/staging/src/k8s.io拷贝出来即可
    • sigs.k8s.io 需要创建目录,clone k8s-sigs/controller-runtime项目
# 在gopath的src目录下拉取k8s源码
$ pwd
/Users/xuel/workspace/goworkspace/src
$ ls
github.com        golang.org        google.golang.org gopkg.in
# 拷贝k8s源码中的k8s.io到上层目录
$ cp -R kubernetes/staging/src/k8s.io k8s.io

# 在gopath 中创建sigs.k8s.io,并clone controller-runtime
$ mkdir /Users/xuel/workspace/goworkspace/src/sigs.k8s.io && cd sigs.k8s.io && git clone https://github.com/kubernetes-sigs/controller-runtime.git

$ ls
drwxr-xr-x  24 xuel  staff   768B Jan  3 18:46 github.com
drwxr-xr-x   3 xuel  staff    96B Mar 22  2020 golang.org
drwxr-xr-x   3 xuel  staff    96B May 21  2020 google.golang.org
drwxr-xr-x   3 xuel  staff    96B May 21  2020 gopkg.in
drwxr-xr-x  28 xuel  staff   896B Jan 28 19:53 k8s.io
drwxr-xr-x  41 xuel  staff   1.3K Jan 28 19:52 kubernetes
drwxr-xr-x   3 xuel  staff    96B Jan 28 19:57 sigs.k8s.io

3.2 创建项目

  • 创建目录,初始化系统
$ kubebuilder init --domain imoc-operator

目录结构如下:

3.3 创建API

运行下面的命令,创建一个新的 API(组/版本)为 “webapp/v1”,并在上面创建新的 Kind(CRD) “Guestbook”。

kubebuilder create api --group webapp --version v1 --kind Guestbook

如果你在 Create Resource [y/n] 和 Create Controller [y/n] 中按y,那么这将创建文件 api/v1/guestbook_types.go ,该文件中定义相关 API ,而针对于这一类型 (CRD) 的对账业务逻辑生成在 controller/guestbook_controller.go 文件中。

3.4 编译运行controller

kubebuilder自动生成的controller源码地址是:$GOPATH/src/helloworld/controllers/guestbook_controller.go , 内容如下:

3.5 安装crd到集群

  • 将 CRD 安装到集群中
make install

  • 运行控制器(这将在前台运行,如果你想让它一直运行,请切换到新的终端)。
make run

此时控制台输出以下内容,这里要注意,controller是在kubebuilder电脑上运行的,一旦使用Ctrl+c中断控制台,就会导致controller停止:

3.6 创建Guestbook资源的实例

  • 现在kubernetes已经部署了Guestbook类型的CRD,而且对应的controller也已正在运行中,可以尝试创建Guestbook类型的实例了(相当于有了pod的定义后,才可以创建pod);
  • kubebuilder已经自动创建了一个类型的部署文件:$GOPATH/src/helloworld/config/samples/webapp_v1_guestbook.yaml ,内容如下,很简单,接下来咱们就用这个文件来创建Guestbook实例:
apiVersion: webapp.com.bolingcavalry/v1
kind: Guestbook
metadata:
  name: guestbook-sample
spec:
  # Add fields here
  foo: bar

3.6.2 安装pod

$ kubectl apply -f config/samples/
$ kubectl get Guestbook


3.7 将controller制作为docker镜像

  1. 至此,咱们已经体验过了kubebuilder的基本功能,不过实际生产环境中controller一般都会运行在kubernetes环境内,像上面这种运行在kubernetes之外的方式就不合适了,咱们来试试将其做成docker镜像然后在kubernetes环境运行;
  2. 这里有个要求,就是您要有个kubernetes可以访问的镜像仓库,例如局域网内的Harbor,或者公共的hub.docker.com我这为了操作方便选择了hub.docker.com,使用它的前提是拥有hub.docker.com的注册帐号;
  3. 在kubebuilder电脑上,打开一个控制台,执行docker login命令登录,根据提示输入hub.docker.com的帐号和密码,这样就可以在当前控制台上执行docker push命令将镜像推送到hub.docker.com上了(这个网站的网络很差,可能要登录好几次才能成功);
  4. 执行以下命令构建docker镜像并推送到hub.docker.com,镜像名为bolingcavalry/guestbook:002:
make docker-build docker-push IMG=bolingcavalry/guestbook:002
  1. hub.docker.com的网络状况不是一般的差,kubebuilder电脑上的docker一定要设置镜像加速,上述命令如果遭遇超时失败,请重试几次,此外,构建过程中还会下载诸多go模块的依赖,也需要您耐心等待,也很容易遇到网络问题,需要多次重试,所以,最好是使用局域网内搭建的Habor服务;
  2. 最终,命令执行成功后输出如下:
[root@kubebuilder helloworld]# make docker-build docker-push IMG=bolingcavalry/guestbook:002

build会链接国外网站,需要注意翻墙
镜像推送上去后,可以查看镜像信息

$  make docker-build docker-push IMG=127.0.0.1:5000/guesstbook:v1
/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/bin/controller-gen "crd:trivialVersions=true,preserveUnknownFields=false" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
mkdir -p /Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/testbin
test -f /Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/testbin/setup-envtest.sh || curl -sSLo /Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/testbin/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh
source /Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/testbin/setup-envtest.sh; fetch_envtest_tools /Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/testbin; setup_envtest_env /Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/testbin; go test ./... -coverprofile cover.out
fetching envtest tools@1.19.2 (into '/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/testbin')
x bin/
x bin/etcd
x bin/kubectl
x bin/kube-apiserver
setting up env vars
?       github.com/kaliarch/imoc-operator       [no test files]
?       github.com/kaliarch/imoc-operator/api/v1        [no test files]
ok      github.com/kaliarch/imoc-operator/controllers   12.959s coverage: 0.0% of statements
docker build -t 127.0.0.1:5000/guesstbook:v1 .
Sending build context to Docker daemon  335.5MB
Step 1/14 : FROM golang:1.15 as builder
1.15: Pulling from library/golang
b9a857cbf04d: Pull complete 
d557ee20540b: Pull complete 
3b9ca4f00c2e: Pull complete 
667fd949ed93: Pull complete 
547cc43be03d: Pull complete 
0977886e8147: Pull complete 
cceccf7c7738: Pull complete 
Digest: sha256:de97bab9325c4c3904f8f7fec8eb469169a1d247bdc97dcab38c2c75cf4b4c5d
Status: Downloaded newer image for golang:1.15
 ---> 5f46b413e8f5
Step 2/14 : WORKDIR /workspace
 ---> Running in 597efa584096
Removing intermediate container 597efa584096
 ---> a21979056316
Step 3/14 : COPY go.mod go.mod
 ---> b6c4b03d5126
Step 4/14 : COPY go.sum go.sum
 ---> f1af7c95cdc8
Step 5/14 : RUN go mod download
 ---> Running in baf57375b805
Removing intermediate container baf57375b805
 ---> 62e488ee06f5
Step 6/14 : COPY main.go main.go
 ---> 72c3d023e770
Step 7/14 : COPY api/ api/
 ---> b164eb864a85
Step 8/14 : COPY controllers/ controllers/
 ---> 843af6a782ec
Step 9/14 : RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go
 ---> Running in af2881daee7c
Removing intermediate container af2881daee7c
 ---> cf6ef1542da6
Step 10/14 : FROM gcr.io/distroless/static:nonroot
nonroot: Pulling from distroless/static
9e4425256ce4: Pull complete 
Digest: sha256:b89b98ea1f5bc6e0b48c8be6803a155b2a3532ac6f1e9508a8bcbf99885a9152
Status: Downloaded newer image for gcr.io/distroless/static:nonroot
 ---> 88055b6758df
Step 11/14 : WORKDIR /
 ---> Running in 35900ca6d19f
Removing intermediate container 35900ca6d19f
 ---> 902a3991fa3b
Step 12/14 : COPY --from=builder /workspace/manager .
 ---> 5af066bf1214
Step 13/14 : USER 65532:65532
 ---> Running in b44fbfb3c52b
Removing intermediate container b44fbfb3c52b
 ---> 6ca11554d8fa
Step 14/14 : ENTRYPOINT ["/manager"]
 ---> Running in 716538bf799a
Removing intermediate container 716538bf799a
 ---> a98e090c1e68
Successfully built a98e090c1e68
Successfully tagged 127.0.0.1:5000/guesstbook:v1
docker push 127.0.0.1:5000/guesstbook:v1
The push refers to repository [127.0.0.1:5000/guesstbook]
babc932481e7: Pushed 
8651333b21e7: Pushed 
v1: digest: sha256:5dbb4e549c0dff1a4edba19ea5a35f9e21deeabe2fcefbc6b6358fb849dd61e2 size: 739
$ curl -XGET http://127.0.0.1:5000/v2/_catalog                   
{"repositories":["guesstbook"]}

  1. 部署controller 镜像到集群内
$ make deploy IMG=127.0.0.1:5000/guesstbook:v1
/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/bin/controller-gen "crd:trivialVersions=true,preserveUnknownFields=false" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
cd config/manager && /Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/bin/kustomize edit set image controller=127.0.0.1:5000/guesstbook:v1
/Users/xuel/workspace/goworkspace/src/github.com/kaliarch/imoc-operator/bin/kustomize build config/default | kubectl apply -f -
namespace/imoc-operator-system created
customresourcedefinition.apiextensions.k8s.io/guestbooks.webapp.com.bolingcavalry configured
role.rbac.authorization.k8s.io/imoc-operator-leader-election-role created
clusterrole.rbac.authorization.k8s.io/imoc-operator-manager-role created
clusterrole.rbac.authorization.k8s.io/imoc-operator-metrics-reader created
clusterrole.rbac.authorization.k8s.io/imoc-operator-proxy-role created
rolebinding.rbac.authorization.k8s.io/imoc-operator-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/imoc-operator-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/imoc-operator-proxy-rolebinding created
configmap/imoc-operator-manager-config created
service/imoc-operator-controller-manager-metrics-service created
deployment.apps/imoc-operator-controller-manager created


  • 查看部署进集群的控制器
    查看镜像拉取异常,

  • 在此使用阿里云镜像仓库

$ sudo docker login --username=1123845260@qq.com registry.cn-shanghai.aliyuncs.com
$ sudo docker pull registry.cn-shanghai.aliyuncs.com/kaliarch/slate:[镜像版本号]

$ sudo docker login --username=1123845260@qq.com registry.cn-shanghai.aliyuncs.com
$ sudo docker tag [ImageId] registry.cn-shanghai.aliyuncs.com/kaliarch/slate:[镜像版本号]
$ sudo docker push registry.cn-shanghai.aliyuncs.com/kaliarch/slate:[镜像版本号]

1. 登陆阿里云镜像仓库
密码:阿里控制台账号

2. 修改tag
docker tag 127.0.0.1:5000/guesstbook:v1 registry.cn-shanghai.aliyuncs.com/kaliarch/guesstbook:v1
3. 推送镜像
docker push registry.cn-shanghai.aliyuncs.com/kaliarch/guesstbook:v1

重新部署

make deploy IMG=registry.cn-shanghai.aliyuncs.com/kaliarch/guesstbook:v1

查看已经成功运行

  1. 查看详细信息

    显示这个pod实际上有两个容器,用kubectl describe命令细看,分别是kube-rbac-proxy和manager

  2. 查看日志

kubectl logs -f imoc-operator-controller-manager-648b4877c6-4bpp9 -n imoc-operator-system  -c manager

四 清理

4.1 清理kubebuilder

make uninstall

4.2 清理kind

kind delete cluster

五 其他

通过了解,我们可以看到 Kubebuilder 提供的功能对于快速编写 CRD 和 Controller 是十分有帮助的,无论是云原生中知名的开源项目例如 Istio、Knative 等知名项目还是各种自定义 Operators,都大量使用了 CRD,将各种组件抽象为 CRD,据此可以将为自己业务编写对应的CRD,使得业务更加生于云长于云。

参考链接

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。