概述

在学习 Kubernetes 基础知识的时候,应该就很清楚,对于 Kubernetes 的操作,大多数情况下都是通过 YAML 描述文件来声明的,偶尔会通过 kubectl 命令行介入。而 YAML 描述文件,除了核心资源 Pod,Service,Volume 和 Namespace 之外,还有很多 Workload,例如常见的 Deployment / DaemonSet 之类的,那么不知道你是否有关心过这些 Workload 是如何工作的?

我曾经也很好奇,所以稍微看了一下 Kubernetes 的代码,下面节选一小段看看,因为有些 Workload 是比较复杂的,所以我挑选的是比较简单的其中一个 ReplicasSet 。

内置 Controller 一览

1. 构建 Controller

这个是构建 Replicaset Controller 的初始化代码,从这段代码中可以看到,Controller 持有 Informer 和 queueName,这两个都是我在上一篇中介绍过的。然后需要着重注意的就是这里分别对两个 Informer 添加了 EventHandlerFuncs,也就是说根据我在上一篇介绍 Informer 和 Event 的内容,当有 ReplicasSet 变更的时候,会根据资源的类型,分别调用:

2. 处理逻辑

然后,我再随便找一个 rsc.addRS 看一下处理流程,其实,PodInformer 的处理也是类似的,所以看一个就好了:

这里只是做了一个队列的入队,这里跳过出队的过程,直接查看处理的逻辑:

可以看到,Controller 的业务逻辑都在这里完成了,前面的哪些都是固定的套路,所谓真正属于一个 Controller 的内容还得数这一份。

整体的流程类似与这样:

自己编写 Controller

看了一个简单的内置 Controller 之后,我想是时候可以开始写一个自己的 Controller 了,不需要太复杂,简单点吧,在系统添加了 CRD 之后,我创建的 Controller 就在命令行中打印一条记录出来,记录的字段就是 CRD 中的 Username/Password 和 Email(这只是一个演示,其实可以很简单将它进行扩充,例如操作DB,通过 Client Go 操作 Kubernetes 的资源,都是很简单的)。

但是,前面这么多的框架代码要怎么写?看上去还比较复杂的,是否有像 ClientSet 一样的代码生成工具可以帮助我们完成?事实上,Kubernetes 官方没有提供这样的工具,但是在 SIG 还有第三方有提供好用的工具,但是,在这一篇中,我喜欢尝试点原生的东西,这样,在后面使用现成的工具的时候心中有底。

既然没有工具,那么一个比较通用的做法就是找一个简单的现成的 Controller(使用官方的 Sample-Controller)作为脚手架,然后修改一番,我这里就是这么做的,还是沿用第 3 篇中的代码,然后在这一篇中我添加一些关于 CRD 的处理,这里我用来修改的样例 Controller 就是来自于 Kubernetes 官方的 Sample Controller,那么下面开始。

1. 下载样例代码

这个就无需多言了,直接做:

  1. [[email protected].io]# git clone https://github.com/kubernetes/sample-controller.git
  2. [[email protected].io]# cd sample-controller

2. 添加 CRD Struct

第一件事情就是将我在第 3 篇中的 CRD 的 Struct 加进去,然后和第 3 篇中的一样操作,生成 clientset 和 informer:

  1. [[email protected].io]# rm -rf pkg/apis/samplecontroller
  2. [[email protected].io]# mkdir -p pkg/apis/admin/v1
  3. [[email protected].io]# cp -r $GOPATH/src/github.com/liuliqiang/blog-demos/kubernetes/crds/chap03/code-gen/apis/admin/v1/* pkg/apis/admin/v1
  4. [[email protected]]# go mod init
  5. [[email protected]]# go mod tidy
  6. [[email protected]]# hack/update-codegen.sh

这样就生成了。因为项目中还有一些包引用是 k8s.io/samplecontroller,这里根据需要修改成了 github.com/liuliqiang/admincontroller 了,同时,因为我的项目是在我的大 Repo 里面,所以为了方便操作,我就封装了 Docker,你可以直接用我的 Dockerfile 和 Makefile。

3. 修改 Controller 初始化代码

下一步就是修改 Controller 代码了,主要有两个地方,第一个就是初始化 Controller,然后第二个处理函数要修改一下:

main.go 我简单修改成这样:

然后修改下 NewController

然后就是对应的 Event 响应函数,下面这一段用不着,删掉,还有相应的引用代码:

然后就是 Admin 的 Event 响应代码了:

其中这里 enqueueAdmin 的代码不用修改,下面就可以开始写业务代码了。

4. 添加 Controller 业务逻辑

对于这个 Controller 框架来说,业务代码的逻辑都在 syncHandler 中实现,所以我的业务代码就写在这了:

很简单,首先,传过来的参数是一个 string,然后将它分割出 Namespace 和 Name,再通过 Lister 去获取 Resource,这样就可以使用 Resource 了,我这里是打印一句日志,记录一下确定是收到了,代码可以直接运行:

如何运行 Controller

刚接触 Controller 的时候,可能会存在一个误区,那就是 Controller 只能运行在 Kubernetes 集群内部。事实上,不是这样的,认识到这点很重要,尤其是在开发阶段。一般的 Controller 都是可以在本地运行,只要你本地可以连接上目标的 Kubernetes 集群,像我这个示例,我就是运行在我的开发环境上的(虽然 Kubernetes 也跑在本地),我的运行命令是:

  1. [[email protected].io]# go run main.go controller.go --kubeconfig /etc/rancher/k3s/k3s.yaml

通过指定 Kubeconfig 的位置,让 Controller 能够找到 Kubernetes 集群,并且与之交互即可。如果是在集群内部,那就无需如此折腾了,因为每个集群节点都可以轻松和 API Servcer 进行交互。

Ref