自定义资源和控制器
创建一个Network的CRD(Custom Resource Definition)
创建CR实例配置文件
创建 example-network.yaml :
可以看到,描述“网络”的 API 资源类型是 Network;API 组是samplecrd.k8s.io
;API 版本是 v1。
这个 YAML 文件,就是一个具体的“自定义 API 资源实例,也叫 CR(Custom Resource)。
创建CRD配置文件
创建 network.yaml :
可以看到,在这个 CRD 中,我指定了“group: samplecrd.k8s.io
”“version: v1
”这样的 API 信息,也指定了这个 CR 的资源类型叫作 Network,复数(plural)是 networks。
然后,我还声明了它的 scope 是 Namespaced,即:我们定义的这个 Network 是一个属于 Namespace 的对象,类似于 Pod。
编码
上述两个 YAML 文件 是给出了 Network API 资源类型的 API 部分的宏观定义,这时候,Kubernetes 已经能够认识和处理所有声明了 API 类型是“samplecrd.k8s.io/v1/network
”的 YAML 文件了。
接下来,我还需要让 Kubernetes“认识”这种 YAML 文件里描述的“网络”部分,比如“cidr”(网段),“gateway”(网关)这些字段的含义。
创建如下结构的代码项目:
pkg/apis/samplecrd 目录下的 register.go 文件,用来放置后面要用到的全局变量。
pkg/apis/samplecrd/v1 目录下的 doc.go 文件(Golang 的文档源文件):
在这个文件中,有 +[=value] 格式的注释,这就是 Kubernetes 进行代码生成要用的 Annotation 风格的注释。
其中,+k8s:deepcopy-gen=package 意思是,请为整个 v1 包里的所有类型定义自动生成 DeepCopy 方法;而+groupName=samplecrd.k8s.io
,则定义了这个包对应的 API 组的名字。
源码如下的 types.go 文件作用就是定义一个 Network 类型到底有哪些字段(比如,spec 字段里的内容),这个文件也有Annotation 风格的注释。
可以看到 Network 类型定义方法跟标准的 Kubernetes 对象一样,都包括了 TypeMeta(API 元数据)和 ObjectMeta(对象元数据)字段。
而其中的 Spec 字段,就是需要我们自己定义的部分。所以,在 networkspec 里,我定义了 Cidr 和 Gateway 两个字段。其中,每个字段最后面的部分比如json:"cidr"
,指的就是这个字段被转换成 JSON 格式之后的名字,也就是 YAML 文件里的字段名字。
此外,除了定义 Network 类型,你还需要定义一个 NetworkList 类型,用来描述一组 Network 对象应该包括哪些字段。之所以需要这样一个类型,是因为在 Kubernetes 中,获取所有 X 对象的 List() 方法,返回值都是List 类型,而不是 X 类型的数组。这是不一样的。
+genclient 注释的意思是:请为下面这个 API 资源类型生成对应的 Client 代码。而 +genclient:noStatus 的意思是:这个 API 资源类型定义里,没有 Status 字段。否则,生成的 Client 就会自动带上 UpdateStatus 方法。
如果是需要 Status 字段的场景,删掉这句 +genclient:noStatus 注释,保留 +genclient 这句即可。
+genclient 只需要写在 Network 类型上,而不用写在 NetworkList 上。因为 NetworkList 只是一个返回值类型,Network 才是“主类型”。
有一句+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
的注释。它的意思是,请在生成 DeepCopy 的时候,实现 Kubernetes 提供的 runtime.Object 接口。否则,在某些版本的 Kubernetes 里,你的这个类型定义会出现编译错误。这是一个固定的操作,记住即可。
最后再看 pkg/apis/samplecrd/v1/register.go 文件,Network 资源类型在服务器端的注册的工作,APIServer 会自动帮我们完成。但与之对应的,我们还需要让客户端也能“知道” Network 资源类型的定义。这就是需要添加的 register.go 文件的作用,它最主要的功能,就是定义了如下所示的 addKnownTypes() 方法:
有了这个方法,Kubernetes 就能够在后面生成客户端的时候,“知道” Network 以及 NetworkList 类型的定义了。
像上面这种 register.go 文件里的内容其实是非常固定的,以后可以直接使用这部分代码做模板,然后把其中的资源类型、GroupName 和 Version 替换成其他定义即可。
代码生成
可以看到经过上面的代码编写,编译还不能通过,提示了 Network 结构体实例不满足AddDnowTypes
函数要接收的Object
接口,缺少了DeepCopyObject
方法的实现。
在 hack 目录下添加 tools.go 文件,写入如下内容:
然后修改 hack 目录下的 update-codegen.sh 文件:
接下来使用 Kubernetes 提供的代码生成工具,为定义的 Network 资源类型自动生成 clientset、informer 和 lister,和 DeepCopyObject 方法:
代码生成工作完成之后,我们再查看一下这个项目的目录结构:
其中,pkg/apis/samplecrd/v1 下面的 zz_generated.deepcopy.go 文件,就是自动生成的 DeepCopy 代码文件。
而整个 client 目录,以及下面的三个包(clientset、informers、 listers),都是 Kubernetes 为 Network 类型生成的客户端库,这些库会在后面编写自定义控制器的时候用到。
注册CRD并创建CR
查看创建的自定义 Network 对象:
编写自定义控制器(Custom Controller)
“声明式 API”并不像“命令式 API”那样有着明显的执行逻辑。这就使得基于声明式 API 的业务功能实现,往往需要通过控制器模式来“监视”API 对象的变化(比如,创建或者删除 Network),然后以此来决定实际要执行的具体工作。
涉及到的概念和执行机制
所谓的 Informer 通知器,是自定义控制器跟 APIServer 进行数据同步的重要组件,是一个自带缓存和索引机制,可以触发 Handler 的客户端库。这个本地缓存在 Kubernetes 中一般被称为 Store,索引一般被称为 Index。
Informer 使用了 Reflector 包,它是一个可以通过 ListAndWatch 机制获取并监视 API 对象变化的客户端封装。
Reflector 和 Informer 之间,用到了一个“增量先进先出队列”进行协同。而 Informer 与要编写的控制循环之间,则使用了一个工作队列来进行协同。
在实际应用中,除了控制循环之外的所有代码,实际上都是 Kubernetes 为你自动生成的,即:pkg/client/{informers, listers, clientset}里的内容。
而这些自动生成的代码,就为我们提供了一个可靠而高效地获取 API 对象“期望状态”的编程库。
所以,接下来,作为开发者,就只需要关注如何拿到“实际状态”,然后如何拿它去跟“期望状态”做对比,从而决定接下来要做的业务逻辑即可。
Last updated
Was this helpful?