在此讲中, 我们介绍go的自动代码生成功能, 事实上kubernetes在很多地方都在用code generators这个功能, 我接下来会说如何使用code generators来编写custom resources
在kubernetes开发早期 , 随着越来越多的资源被加进系统, 越来越多的代码不得不被重写。 这个时候代码生成使得代码的维护更加容易 , 在早起是用Gengo library 之后在gengo的基础上, kubernetes发展出了k8s.io/code-generator 它是一个代码生成器的集合
使用代码生成器
通常在代码生成器的调用都是一样的方法, 只是group , version等不同而已, 使用k8s.io/code-generator/generate-groups.sh或者hack/update-codegen.sh能更加简单的向CR中添加代码生成
举个例子, 使用如下方法调用
1 | $ vendor/k8s.io/code-generator/generate-groups.sh all \ |
其中有4个标准的代码生成器
deepcopy-gen
生成
func
(t *T)
DeepCopy()
*T
和func
(t *T)
DeepCopyInto(*T)
方法client-gen
创建类型化客户端集合(typed client sets)
informer-gen
为CR创建一个informer , 当CR有变化的时候, 这个informer可以基于事件接口获取到信息变更
lister-gen
为CR创建一个listers , 就是为
GET
andLIST
请求提供read-only caching layer
后面2个是构建controller的基础
让我们先看看上述脚本后面跟的几个参数
- 第二个参数是要生成的clients, listers, and informers的包名
- 第三个参数是API group的包
- 第四个参数是API 组及其版本
- 将 –output-base 作为标志传递给所有生成器,以定义在其中找到给定包的基目录
- – go-header-file 使我们能够将版权标头放入生成的代码中
有些生成器(如 deepcopy-gen)直接在 API group包中创建文件,这些文件遵循标准的命名方案,并生成一个 zz_generated.前缀,以便很容易从版本控制系统中排除它们
如果这个项目遵循了 k8s.io/sample-controllerーthe 的样本控制器模式,那么这个项目就是一个蓝图项目,它复制了内置在 Kubernetes 的许多控制器所建立的模式ーー那么代码生成就是这样开始的
1 | $ hack/update-codegen.sh |
用Tag控制生成器
虽然一些代码生成器行为是通过前面描述的命令行标志(特别是要处理的包)来控制的,但是更多的属性是通过 Go 文件中的标记来控制的
1 | // +some-tag |
这里有2种形式的tag:
- Global tags , 在doc.go上方进行标记
- Local tags 声明在struct结构体上方
有许多标记必须直接位于类型(或全局标记的包行)之上的注释中,而其他标记必须与类型(或包行)分开,两者之间必须至少有一个空行。 例如
1
2
3
4
5
6 > // +second-comment-block-tag
>
> // +first-comment-block-tag
> type Foo struct {
> }
>
出现这种情况是历史原因, Kubernetes 的 API 文档生成器过去不知道代码生成标签,而只导出第一个注释块。 因此,该块中的标记将显示在 apihtml 文档中
global tags
global tags被写在doc.go中 , 典型的pkg/apis/group/version/doc.go文件长这样:
1 | // +k8s:deepcopy-gen=package |
该文件的第一行告诉 deepcopy-gen 默认情况下为该包中的每个类型创建深层复制方法。 如果您的类型不需要深度拷贝,那么您可以使用本地标签// +k8s:deepcopy-gen=false
,如果不启用包范围的深度拷贝,则必须通过 // +k8s:deepcopy-gen=true
每个所需类型选择深度拷贝
第二个标记,// +groupName=example.com
定义了完全限定的 API 组名。 如果 Go 父包名称与组名称不匹配,则此标记是必需的。
使用// +groupName
tag , client generator将使用正确的 HTTP 路径/apis/foo.project.example.com来生成客户机
除了+groupName 还有一个+groupGoName,它定义了一个定制的 Go 标识符(用于变量和类型名称)来代替父包名称 , 例如,在默认情况下,生成器将使用大写的父包名作为标识 , 这个时候你可以自定义标识
Local Tags
本地标记可以直接写在 API 类型之上,也可以写在 API 类型之上的第二个注释块中 , 比如下面的例子
1 | // AtSpec defines the desired state of At |
下面我们就将一一介绍这些标签
deepcopy-gen Tags
深度复制方法生成通常默认通过 Global// +k8s:deepcopy-gen=package
tag 生成, 如果我们在 API 类型包中有一个 helper 结构,那么我们就必须禁用深度复制生成 , 比如
1 | // +k8s:deepcopy-gen=false |
runtime.Object and DeepCopyObject
有一个特殊的深层复制标签需要多加说明下
1 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object |
之前我们就说 runtime.Object
必须实现DeepCopyObject() runtime.Object方法,原因在于,Kubernetes 内部的通用代码必须能够创建对象的深层副本
Deepcopyobject ()方法只是调用生成的 DeepCopy 方法。 后者的签名因类型而异(DeepCopy () * t 取决于 t)。 前者的签名总是 DeepCopyObject ()运行时
1 | func (in *T) DeepCopyObject() runtime.Object { |
将本地标签//
+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
放在顶级 API 类型之上使用 deepcopy-gen 生成此方法,这告诉 deepcopy-gen 为运行时创建这样一个方法,叫DeepCopyObject()
顶级类型都会被用作
runtime.Object
,顶级类型是那些内嵌metav1.TypeMeta 的类型
比如:如果 API 类型有一个接口类型 Foo 的字段 , 比如
1 | type SomeAPIType struct { |
API 类型必须是深复制的,因此字段 Foo 也必须是深复制的。 如果不在 Foo 接口中添加 DeepCopyFoo () Foo,怎么能够以通用方式(不使用类型转换)实现这一点呢?
1 | type Foo interface { |
在这种情况下,可以使用相同的标签
1 | // +k8s:deepcopy-gen:interfaces=<package>.Foo |
client-gen Tags
最后,还有一些控制 client-gen 的标签
1 | // +genclient |
它告诉 client-gen 为此类型创建一个客户机 , 但是他不能置于list类型之上 比如 自定义类型foo 就可以放在foo类型上 , 但是不能放在foolist之上 , 不清楚foo和foolist类型的, 请看官方的simple-controller项目的type.go文件
当然还有其他例子, 用法
比如
1 | // +genclient:noStatus |
如果没有这个标记,client-gen 将盲目地生成 UpdateStatus ()方法。 但是,只有在 resource custom definition 清单中实际启用了 / status 子资源才能不加
再比如
客户机生成器必须选择正确的 HTTP 路径,无论是否带有名称空间。 对于集群范围的资源,必须使用以下标记
1 | // +genclient:nonNamespaced |
默认是生成一个名称空间的客户机。 同样,这必须与 CRD 清单中的范围设置相匹配。 对于特殊用途的客户机,您可能还希望详细控制提供哪些 HTTP 方法。 你可以通过使用几个标签来做到这一点,例如
1 | // +genclient:noVerbs |
对于// +genclient:method=
标记,一个常见的情况是添加一个方法来缩放资源 , 如果CR启用 / Scale 子资源,下面的标签创建了相应的客户端方法
1 | // +genclient:method=GetScale,verb=get,subresource=scale,\ |
第一个标签创建getter GetScale
, 第二个创建setter UpdateScale
informer-gen and lister-gen
代码自动生成会生成对应类型的专门informer和lister的