代码生成
生成控制器代码
client-go
要求 runtime.Object
类型必须有一个 DeepCopy()
方法。也就是说,Go语言实现的CR必须实现 runtime.Object
接口。
代码生成器 code-generator 主要提供如下功能:
代码生成器
功能
描述
client-gen
为 CR 的 API 组创建类型化的客户端集合
实现功能齐全、生产可用 controller 的基础,与 k8s 内置 controller 的实现机制及使用的标准库一致
conversion-gen
在创建聚合API服务器时,用于实现内外部类型之间的转换功能
deepcopy-gen
为每一种类型提供一个 DeepCopy()
方法,func(t* T) DeepCopy() *T
实现功能齐全、生产可用 controller 的基础
defaulter-gen
用于生成一些默认的字段
go-to-protobuf
import-boss
informer-gen
为 CR 创建 informer,用途是基于事件接口,对服务器上 CR 的变更作出响应
实现功能齐全、生产可用 controller 的基础
lister-gen
为 CR 创建 lister ,用途是对 GET 和 LIST 请求提供一个只读的缓存层
实现功能齐全、生产可用 controller 的基础
openapi-gen
prerelease-lifecycle-gen
register-gen
set-gen
官方提供了一个使用代码生成器生成 controller 的示例仓库:sample-controller。
使用
所有的 Kubernetes 代码生成器都是在 gengo 的基础上创建的,它们共享很多公用的命令行标识。通常,所有的生成器都是从 --input-dirs
中获取输入,然后一个类型一个类型的转换为生成的代码。
生成代码与输入代码在同一路径:像
deepcopy-gen
那样通过--output-file-base "zz_generated.deepcopy"
来定义输出的文件名。生成代码保存在一个或多个包中:使用
--output-package
来指定,输出类似client-
,informer-
,就像lister-gen
那样通常输出在pkg/client
。
代码生成器提供来一个 generate-groups.sh 脚本,这样就不需要熟悉那么多的命令行参数。
生成脚本保存在
hack/update-codegen.sh
,生成的所有 API 都在pkg/apis
,所有 clientsets、informers、listers 都在pkg/client
中。验证脚本保存在
hack/verify-codegen.sh
,如果生成的文件不是最新的,那么执行后会返回一个非零的输出,这在 CI/CD 中很有用。
标签
代码生成器的一些行为可以通过命令行参数来控制,但是更多的属性通过源文件中的标签来控制。有两种类型的标签:
全局标签,作用在
package
上,通常单独创建一个doc.go
文件来存放全局标签局部标签,作用在
type
上,在指定的类型定义的源代码上方,用空行隔开
通常标签看起来是这样的:// +tag-name
或者 // +tag-name=value
,都是写在注释中的,因此注释的位置就显得很重要了。大部分的标签都需要直接写在类型或者包的上面,而有一些标签则需要用空行分隔一下。
最佳实践就是根据一个模板来写,例如:
kubernets 官方的 sample-controller。
knative 官方的 sample-resource。
全局标签
全局的标签都是写在 doc.go
文件中,常见的路径是 pkg/apis/<apigroup>/<version>/doc.go
,内容如下:
第一行注释中标签含义是:指示 deepcopy-gen
通过默认方式为这个包中的每一个类型都创建 deepcopy()
方法。如果某个类型不需要 deepcopy()
方法,那么在该类型上增加局部标签 // +k8s:deepcopy-gen=false
。如果没有全局的 deepcopy
标签(包级别),那么需要为包中的每个类型都添加一个局部标签 // +k8s:deepcopy-gen=true
。
注意:第一行注释中的
register
值会将整个包中的deepcoyp()
方法都注册到 scheme 中。在1.9版本之后就没有这个关键字了,因为 scheme 不再负责runtime.Object
任何深拷贝。取而代之的是使用yourobject.DeepCopy()
或者yourobject.DeepCopyObject()
。
第二行注释中标签含义是:定义标准 API 的组名(即 k8s 中的 group),这里如果写错了,那么 client-gen
就会生成错误的代码。注意,这个标签必须在package上方的注释中。
局部标签
通常将 API 类型的源代码保存在 xxx_types.go
结尾的文件中。局部标签要么直接写在 API 类型上,要么写在 API 类型上的第二个注释块中。如下是一个 CR 的示例:
上面的注释中默认为所有的类型都启用了 deepcopy
,因为这些类型都是 API 类型,所以不需要在这个源文件中对 deepcopy
进行设置,只需要在包级别的 doc.go
中设置全局标签即可。
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
是一个比较特殊的deepcopy标签。在1.8版本中 runtime.Object
接口使用了此方法签名进行扩展,因此每个 runtime.Object
都要实现DeepCopyObject
。具体的实现如下所示:
不需要为每个类型都实现,只需要将 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
这个局部标签放在顶层 API 类型上就行。在上面的例子中,Database
和 DatabaseList
是顶层 API 类型,因为它们被当作 runtime.Object
使用。常见的顶层 API 类型就是那些嵌入 metav1.TypeMeta
的类型,这些也是客户端使用 client-gen
生成的类型。
注意:
// +k8s:deepcopy-gen:interfaces
标签应该在定义一些具有接口类型的 API 时使用,例如field SomeInterface
,然后// +k8s:deepcopy-gen:interfaces=example.com/pkg/apis/examples.SpneInterface
标签将会生成DeepCopySomeInterface() SomeInterface
方法。这允许它以类型正确的方式对这些字段进行深度拷贝。
client-gen 标签
有一些用于控制 client-gen
的标签,如下所示;
第一行注释中的标签:指示
client-gen
为这个 API 类型创建一个客户端,请注意, API 对象的 List 类型不需要这个标签。第二行注释中的标签:指示
client-gen
不需要为这个类型生成符合 spec-status 规范的通过/status
操作的子资源。那么该 API 类型的客户端中就没有UpdataStatus()
方法(client-gen
看到标准的值为status
就会直接生成这个方法)。
对应没有 Namespace 范围限定的资源,使用 // +genclient:nonNamespaced
。
对于有些客户端,可能还需要更细粒度的 HTTP 方法,通过使用如下的一组标签值来指示 client-gen
生成对应代码:
前三行注释中的标签是不言自明的。
第四行注释中的标签的含义是:这个 API 类型只有 create 方法,对它的 create 操作只会返回
metav1.Status
中的内容。这个对于 CRD 来说用处不大,对于自定义 API Server 就很有帮助。
生成 YAML 文件
使用 k8s-sigs 维护的生成工具 controller-tools。能够生成的 YAML 文件包括 CRD,RBAC等,通过命令行参数和标签来控制。
需要生成的YAML文件也是根据 xxx_types.go
源码和对应的注释标签生成的。常见的标签如下:
kubebuilder项目提供了2个make命令:
make manifests 用来生成 Kubernetes 对象的 YAML 文件,像
CustomResourceDefinitions
,WebhookConfigurations
和RBAC roles
。make generate 用来生成代码,像
runtime.Object/DeepCopy implementations
。(与上面代码生成部分原理相同)
通用的形式是这样的:
Empty:(
+kubebuilder:validation:Optional
):空标记,就像命令行中的布尔标记位,仅仅是指定来开启某些行为。Anonymous: (
+kubebuilder:validation:MaxItems=2
):匿名标记,使用单个值作为参数。Multi-option: (
+kubebuilder:printcolumn:JSONPath=".status.replicas",name=Replicas,type=string
):多选项标记,使用一个或多个命名参数。第一个参数与名称之间用冒号隔开,而后面的参数使用逗号隔开。参数的顺序没有关系。有些参数是可选的。
标记的参数可以是字符,整数,布尔,切片,或者 map 类型。 字符,整数,和布尔都应该符合 Go 语法:
切片可以用大括号和逗号分隔来指定:
Maps 是用字符类型的键和任意类型的值(有效地map[string]interface{}
)来指定的。一个 map 是由大括号({}
)包围起来的,每一个键和每一个值是用冒号(:
)隔开的,每一个键值对是由逗号隔开的。
CRD字段验证
CRD 支持使用 OpenAPI v3 schema 在 validation 段中进行声明式验证。通常验证标记会关联到字段或者类型上。
如果定义了复杂的验证,或者如果需要重复使用验证,或者需要验证切片元素,那么最好定义一个新的类型来描述验证,如下下面例子中的 Alias 和 Rank 类型。
kubectl get 命令中显示其他列信息
从 Kubernetes 1.11 开始,kubectl get
可以询问 Kubernetes 服务要展示哪些列。对于 CRD 来说,可以用 kubectl get
来提供展示有用的特定类型的信息,类似于为内置类型提供的信息。
CRD 的 additionalPrinterColumns
和 kube-additional-printer-columns
字段控制了要展示的信息,它是通过在 Go 类型上标注 +kubebuilder:printcolumn
标签来控制要展示的信息。
子资源
在 Kubernetes 1.13 中 CRD 可以选择实现 /status
和 /scale
这类子资源。推荐在所有资源上都实现 /status
子资源,那么对应的API 类型上要有一个状态(status)字段。
状态
通过 +kubebuilder:subresource:status
设置子资源的状态。启用状态时,更新主资源不会修改它的状态。类似的,更新子资源状态也只是修改了状态字段。
扩缩容
子资源的伸缩可以通过 +kubebuilder:subresource:scale
来启用。启用后,使用 kubectl scale
来对资源进行扩缩容。
如果 selectorpath
参数被指定为字符串形式的标签选择器,HorizontalPodAutoscaler 将可以自动扩容这些资源。
最后更新于
这有帮助吗?