代码生成器#
deepcopy-gen 代码生成器#
deepcopy-gen 是一个自动生成 DeepCopy 相关函数的代码生成器。给定一个包的目录路径作为输入源,它可以为其生成 DeepCopy 相关函数,这些函数可以有效地执行每种类型的深复制操作。
为整个包生成 DeepCopy 相关函数时,其 Tags 形式如下。
// +k8s:deepcopy-gen=package为单个类型生成 DeepCopy 相关函数时,其 Tags 形式如下。
// +k8s:deepcopy-gen=true为整个包生成 DeepCopy 相关函数时,可以忽略单个类型,其 Tags 形式如下。
// +k8s:deepcopy-gen=false在 Kubernetes 源码中,会看到 deepcopy-gen 的 Tag 被定义为 runtime.Object,deepcopy-gen 会为该类型生成返回值为 runtime.Object 类型的 DeepCopyObject 函数,代码示例如下。
代码路径:pkg/apis/abac/types.go
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Policy contains a single ABAC policy rule
type Policy struct {
metav1.TypeMeta `json:",inline"`
Spec PolicySpec `json:"spec"`
}生成以下代码。
代码路径:pkg/apis/abac/zz_generated.deepcopy.go
func (in *Policy) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}作用:
满足
runtime.Object接口契约;Kubernetes 核心系统(API Server、各种 Controller 等)需要统一处理成百上千种不同的资源(Pod、Node、Deployment 以及你自定义的 CRD)。为了实现这种统一,K8s 将所有 API 资源抽象为
runtime.Object接口。提供底层的“泛型”克隆能力;
1. deepcopy-gen 使用示例#
构建 deepcopy-gen 二进制文件,并且执行 deepcopy-gen 代码生成器为 k8s.io/kubernetes/pkg/apis/abac/v1beta1 包生成 zz_generated.deepcopy.go 代码文件。
$ hack/make-rules/build.sh k8s.io/code-generator/cmd/deepcopy-gen
$ ./hack/run-in-gopath.sh _output/bin/deepcopy-gen \
--v 1 \
--logtostderr \
-i "k8s.io/kubernetes/pkg/apis/abac/v1beta1" \
--bounding-dirs k8s.io/kubernetes,"k8s.io/api" \
-O zz_generated.deepcopy2. deepcopy-gen 生成规则逻辑#
deepcopy-gen 会遍历包中的所有类型,如果类型为 types.Struct,则为该类型生成 DeepCopy 相关函数。
代码路径:vendor/k8s.io/gengo/examples/deepcopy-gen/generators/deepcopy.go
func copyableType(t *types.Type) bool {
if t.Kind != types.Struct {
return false
}
return true
}小结#
deepcopy-gen 为每个 Struct 类型的资源生成 DeepCopy() 方法和 DeepCopyObject() 方法。
DeepCopy()方法:返回同类型的深拷贝副本,方法签名如func (in *Policy) DeepCopy() *Policy。内部将每个字段逐个复制,切片、Map、指针等复杂类型会创建新副本而非共享引用。DeepCopyObject()方法:实现runtime.Object接口,返回runtime.Object类型,使资源对象可被 K8s 核心系统统一处理。
这两个函数使得任意资源对象可以被完整复制一份独立的副本,从而在并发操作中避免共享对象产生的竞态条件。例如在 Controller 收到同一个对象的多次更新时,可以复制一份再修改而不影响原始数据。
在 K8s 架构中,deepcopy-gen 是整个代码生成体系的基石。几乎所有其他代码生成器(client-gen、lister-gen、informer-gen 等)都依赖 deepcopy-gen 生成的深拷贝能力。没有它,K8s 无法安全地实现 Controller 模式下的并发处理,也无法在客户端和缓存层之间传递独立的对象副本。
defaulter-gen 代码生成器#
defaulter-gen 是一个自动生成 Defaulter 相关函数的代码生成器。给定一个包的目录路径作为输入源,它可以为其生成 Defaulter 相关函数,这些函数可以为资源对象生成默认的值。
为拥有 TypeMeta 属性的类型生成 Defaulter 相关函数时,其 Tags 形式如下:
// +k8s:defaulter-gen=TypeMeta为拥有 ListMeta 属性的类型生成 Defaulter 相关函数时,其 Tags 形式如下:
// +k8s:defaulter-gen=ListMeta为拥有 ObjectMeta 属性的类型生成 Defaulter 相关函数时,其 Tags 形式如下:
// +k8s:defaulter-gen=ObjectMetadefaulter-gen 的每一个 Tag 都是全局 Tag,没有局部 Tag。其值可以为 TypeMeta、ListMeta、ObjectMeta,其中最常用的是 TypeMeta,代码示例如下。
代码路径:pkg/apis/apps/v1/doc.go
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/apps/v1
package v1 // import "k8s.io/kubernetes/pkg/apis/apps/v1"1. defaulter-gen 使用示例#
构建 defaulter-gen 二进制文件,并且执行 defaulter-gen 代码生成器为 k8s.io/kubernetes/pkg/apis/rbac/v1 包生成 zz_generated.defaults.go 代码文件。
$ hack/make-rules/build.sh k8s.io/code-generator/cmd/defaulter-gen
$ ./hack/run-in-gopath.sh _output/bin/defaulter-gen \
--v 1 \
--logtostderr \
-i k8s.io/kubernetes/pkg/apis/rbac/v1 \
--extra-peer-dirs k8s.io/kubernetes/pkg/apis/rbac/v1 \
-O zz_generated.defaults2. defaulter-gen 生成规则逻辑#
代码路径:vendor/k8s.io/gengo/examples/defaulter-gen/generators/defaulter.go
if t.Kind == types.Struct && len(typesWith) > 0 {
for _, field := range t.Members {
for _, s := range typesWith {
if field.Name == s {
return true
}
}
}
}defaulter-gen 会遍历包中的所有类型,如果该类型拥有 +k8s:defaulter-gen=<Type> 指定的类型属性字段 (如 TypeMeta、ListMeta、ObjectMeta),则为该类型生成 Defaulter 函数和 RegisterDefaults 注册函数,代码示例如下。
代码路径:pkg/apis/rbac/v1/doc.go
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/rbac/v1
package v1 // import "k8s.io/kubernetes/pkg/apis/rbac/v1"代码路径:vendor/k8s.io/api/rbac/v1/types.go
type ClusterRoleBinding struct {
metav1.TypeMeta `json:",inline"`
// ...
}生成以下代码。
代码路径:pkg/apis/rbac/v1/zz_generated.defaults.go
func SetObjectDefaults_ClusterRoleBinding(in *v1.ClusterRoleBinding) {
// ...
}ClusterRoleBinding 类型拥有类型为 TypeMeta 的属性字段,defaulter-gen 自动为其生成 SetObjectDefaults_ClusterRoleBinding 函数。
RegisterDefaults 会将资源对象的 Defaulter 函数注册到 Scheme 注册表,可以通过 Scheme.Default 显式地为空资源对象设置默认值,代码示例如下。
代码路径:cmd/kube-controller-manager/app/options/options.go
versioned := kubectrlmgrconfigv1alpha1.KubeControllerManagerConfiguration{}
kubectrlmgrconfigscheme.Scheme.Default(&versioned)小结#
defaulter-gen 为包含特定字段类型(TypeMeta、ListMeta、ObjectMeta)的资源生成 SetDefaults_*() 和 RegisterDefaults() 函数。
SetDefaults_*()函数:如SetDefaults_ClusterRoleBinding(in *v1.ClusterRoleBinding),为对象的字段填充默认值(如 APIVersion、Kind 等)。RegisterDefaults()函数:将 Defaulter 注册到 Scheme 注册表,随后可通过Scheme.Default(&obj)统一调用。
这些函数使得空的资源对象在创建时就具备符合规范的默认值,无需人工逐字段设置。例如创建一个未指定任何字段的 Deployment 时,Scheme.Default 会自动为其填充 replicas、selector 等必要字段的默认值。
在 K8s 架构中,defaulter-gen 提供了声明式的默认值语义。用户只需关注自己设置的字段,其余字段由系统自动补全,降低了 API 使用成本,也保证了资源对象的规范性和一致性。
conversion-gen 代码生成器#
conversion-gen 是一个自动生成 Convert 相关函数的代码生成器。给定一个包的目录路径作为输入源,它可以为其生成 Convert 相关函数,这些函数可以为对象在内部类型和外部类型之间提供转换函数。
为整个包生成 Convert 相关函数时,其 Tags 形式如下:
// +k8s:conversion-gen=<peer-pkg><peer-pkg> 定义包的导入路径,例如 k8s.io/kubernetes/pkg/apis/abac。
为整个包生成 Convert 相关函数依赖额外的包时,其 Tags 形式如下:
// +k8s:conversion-gen-external-types=<type-pkg><type-pkg> 定义额外的包的路径,例如 k8s.io/api/autoscaling/v1。
排除某个属性为其生成 Convert 相关函数时,其 Tags 形式如下:
// +k8s:conversion-gen=false1. conversion-gen 使用示例#
构建 conversion-gen 二进制文件,并且执行 conversion-gen 代码生成器为 k8s.io/kubernetes/pkg/apis/abac/v1beta1 包生成 zz_generated.conversion.go 代码文件。
$ hack/make-rules/build.sh k8s.io/code-generator/cmd/conversion-gen
$ ./hack/run-in-gopath.sh _output/bin/conversion-gen \
--v 1 \
--logtostderr \
-i k8s.io/kubernetes/pkg/apis/abac/v1beta1 \
--extra-peer-dirs k8s.io/kubernetes/pkg/apis/core,k8s.io/kubernetes/pkg/apis/core/v1,k8s.io/api/core/v1 \
-O zz_generated.conversion2. conversion-gen 生成规则逻辑#
代码路径:vendor/k8s.io/code-generator/cmd/conversion-gen/generators/conversion.go
func (g genConversion) convertibleOnlyWithinPackage(inType, outType *types.Type) bool {
// ...
if t.Kind != types.Struct {
return false
}
if namer.IsPrivateGoName(other.Name.Name) {
return false
}
return true
}conversion-gen 会遍历包中的所有类型,如果类型为 types.Struct 且过滤了私有的 Struct 类型,则为该类型生成 Convert 相关函数,并且为该类型同时生成 RegisterConversions 注册函数,代码示例如下。
代码路径:pkg/apis/abac/v1beta1/types.go
type Policy struct {
metav1.TypeMeta `json:",inline"`
Spec PolicySpec `json:"spec"`
}生成 Convert 相关函数的代码示例如下。
代码路径:pkg/apis/abac/v1beta1/zz_generated.conversion.go
func Convert_v1beta1_Policy_To_abac_Policy(in *Policy, out *abac.Policy, s conversion.Scope) error {
return autoConvert_v1beta1_Policy_To_abac_Policy(in, out, s)
}
func Convert_abac_Policy_To_v1beta1_Policy(in *abac.Policy, out *Policy, s conversion.Scope) error {
return autoConvert_abac_Policy_To_v1beta1_Policy(in, out, s)
}在 types.go 中定义了 Policy 类型,conversion-gen 为该类型生成 Convert 函数。例如,从 v1beta1 版本转换到 internal 内部版本,从 internal 内部版本转换到 v1 版本。定义 old 变量为 v1beta1 资源版本,通过 Convert 函数将 v1beta1 版本转换为内部版本 (即 internal 变量),代码示例如下。
old := &v1beta1.Policy{Spec: v1beta1.PolicySpec{User: "bob"}}
internal := &abac.Policy{}
abac.Scheme.Convert(old, internal, nil)小结#
conversion-gen 为每个 Struct 类型生成 Convert_*_To_*() 和 RegisterConversions() 函数。
Convert_*_To_*()函数:如Convert_v1beta1_Policy_To_abac_Policy(in *Policy, out *abac.Policy, s conversion.Scope) error,负责将两个不同版本的结构体之间做字段映射和转换,去除或添加 JSON tag、版本元数据等。RegisterConversions()函数:将 Convert 函数注册到 Scheme,随后可通过Scheme.Convert(src, dst, nil)统一调用。
这些函数使得同一个资源对象可以在不同版本之间互相转换。例如 kube-apiserver 收到 v1beta1 版本的 Policy 后,转换为内部版本存储到 etcd;客户端查询时,再从内部版本转换回 v1beta1 返回。
在 K8s 架构中,conversion-gen 支撑了多版本 API 共存的设计。每个资源都有内部版本(用于存储)和外部版本(用于 API 传输),conversion-gen 负责在两条路径上做正确的字段转换,实现了 API 的版本解耦——用户始终使用稳定的外部版本,而存储层始终使用统一的内部版本。
openapi-gen 代码生成器#
openapi-gen 是一个自动生成 OpenAPI 定义文件的代码生成器。给定一个包的目录路径作为输入源,它可以为其生成 OpenAPI 定义文件,该定义文件用于 kube-apiserver 上 OpenAPI 规范的生成。
为特定类型或包生成 OpenAPI 定义时,其 Tags 形式如下:
// +k8s:openapi-gen=true排除为特定类型或包生成 OpenAPI 定义时,其 Tags 形式如下:
// +k8s:openapi-gen=false1. openapi-gen 使用示例#
构建 openapi-gen 二进制文件,并且执行 openapi-gen 代码生成器为 k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1 包生成 zz_generated.openapi.go 代码文件并将其存放在 k8s.io/kubernetes/pkg/generated/openapi 目录下。
$hack/make-rules/build.sh k8s.io/code-generator/cmd/openapi-gen$ ./hack/run-in-gopath.sh _output/bin/openapi-gen \
--v 1 \
--logtostderr \
-i k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1 \
-p k8s.io/kubernetes/pkg/generated/openapi \
-O zz_generated.openapi \
-h vendor/k8s.io/code-generator/hack/boilerplate.go.txt \
-r _output/KUBE_violations.report2. openapi-gen 生成规则逻辑#
代码路径:vendor/k8s.io/kube-openapi/pkg/generators/openapi.go
func (g openAPITypeWriter) generate(t *types.Type) error {
switch t.Kind {
case types.Struct:
if hasOpenAPIDefinitionMethod(t) {
return nil
}
}
return nil
}openapi-gen 会遍历包中的所有类型,如果类型为 types.Struct 且忽略其他类型,则为 types.Struct 类型生成 OpenAPI 定义文件,代码示例如下。
代码路径:vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go
type CustomResourceDefinitionSpec struct {
Group string `json:"group" protobuf:"bytes,1,opt,name=group"`
Version string `json:"version,omitempty" protobuf:"bytes,2,opt,name=version"`
}在 types.go 中定义了 CustomResourceDefinitionSpec 类型,openapi-gen 为该类型生成 OpenAPIDefinition。生成 OpenAPIDefinition 的代码示例如下。
代码路径:pkg/generated/openapi/zz_generated.openapi.go
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1.CustomResourceDefinitionSpec": schema_pkg_apis_apiextensions_v1beta1_CustomResourceDefinitionSpec(ref),
}
}小结#
openapi-gen 为每个 Struct 类型生成 GetOpenAPIDefinitions() 函数,返回该包中所有类型的 OpenAPI 规范定义。
GetOpenAPIDefinitions() 返回一个 map,key 为类型的完全限定名(如 CustomResourceDefinitionSpec),value 为该类型的 OpenAPI 定义,描述了每个字段的名称、类型、描述、是否必填、默认值等元信息。
这些定义使得 kube-apiserver 能够提供完整的 OpenAPI 接口。kubectl 可以通过 kubectl explain 查看资源字段说明,客户端工具可以自动生成对应语言的 SDK,API 文档站点也可以动态生成所有资源的 API Reference。
在 K8s 架构中,openapi-gen 提供了元编程级别的自描述能力。K8s 本身就是一个"用代码生成代码"的系统,OpenAPI 定义使得所有资源的 schema 信息对外可见,从而支撑了 kubectl、client-gen、自动生成 SDK 等一系列上游工具链的存在。
prerelease-lifecycle-gen 代码生成器#
prerelease-lifecycle-gen 是一个为 beta 资源自动生成 APILifecycle 相关函数的代码生成器。给定一个包的目录路径作为输入源,它可以为其生成 APILifecycle 相关函数,这些函数可以有效地管理每种资源的当前版本、弃用版本、删除版本及替换版本。
为整个包生成 APILifecycle 相关函数时,可以忽略单个类型,其 Tags 形式如下:
// +k8s:prerelease-lifecycle-gen=true为单个类型生成 APILifecycle 相关函数时,指定当前版本、弃用版本、删除版本,其 Tags 形式如下:
// +k8s:prerelease-lifecycle-gen:introduced=<version>
// +k8s:prerelease-lifecycle-gen:deprecated=<version>
// +k8s:prerelease-lifecycle-gen:removed=<version>为单个类型生成 APILifecycle 相关函数时,指定替换版本,其 Tags 形式如下:
// +k8s:prerelease-lifecycle-gen:replacement=<group>,<version>,<Kind> (例如: rbac.authorization.k8s.io,v1,Role)1. prerelease-lifecycle-gen 使用示例#
构建 prerelease-lifecycle-gen 二进制文件,并且执行 prerelease-lifecycle-gen 代码生成器为 k8s.io/kubernetes/vendor/k8s.io/api/rbac/v1beta1 包生成 zz_generated.prerelease-lifecycle.go 代码文件。
$hack/make-rules/build.sh k8s.io/code-generator/cmd/prerelease-lifecycle-gen$ ./hack/run-in-gopath.sh _output/bin/prerelease-lifecycle-gen \
--v 1 \
--logtostderr \
-i k8s.io/kubernetes/vendor/k8s.io/api/rbac/v1beta1 \
-O zz_generated.prerelease-lifecycle2. prerelease-lifecycle-gen 生成规则逻辑#
prerelease-lifecycle-gen 会遍历包中的所有类型,如果类型为 types.Struct,则为该类型生成 APILifecycle 相关函数。
代码路径:vendor/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go
func isAPIType(t *types.Type) bool {
if t.Kind != types.Struct {
return false
}
return true
}生成的 APILifecycle 相关函数的代码示例如下。
代码路径:vendor/k8s.io/api/rbac/v1beta1/zz_generated.prerelease-lifecycle.go
func (in RoleList) APILifecycleRemoved() (major, minor int) {
return 1, 22
}小结#
prerelease-lifecycle-gen 为每个 beta 资源生成 APILifecycleIntroduced()、APILifecycleDeprecated()、APILifecycleRemoved() 和 APILifecycleReplacement() 函数。
这些函数返回具体的版本号,例如 APILifecycleRemoved() (major, minor int) { return 1, 22 } 表示该 API 在 v1.22 被移除。kube-apiserver 在处理请求时会调用这些函数,当请求使用已弃用或已移除的 API 版本时,会返回警告或拒绝。
这些函数使得 API 版本生命周期可被程序化检测和处理,帮助集群管理员和开发者及时将应用迁移到新版本的 API,避免在 K8s 升级后突然失效。
在 K8s 架构中,prerelease-lifecycle-gen 将 API 演进的策略从运行时契约变成了编译时可见的代码。这使得 K8s 能够安全地淘汰旧版本 API——通过弃用周期告知用户即将移除的 API,同时在代码层面提供精确的版本判断逻辑。
client-gen 代码生成器#
client-gen 代码生成器是一种为资源生成 ClientSet 客户端的工具。ClientSet 对资源组、资源版本、资源进行了封装,可以针对资源执行生产资源操作方法 (Create、Update、Delete、Get 等),这些方法由 client-gen 代码生成器自动生成。
client-gen 代码生成器的代码生成策略与其他代码生成器的类似,都是通过 Tags 来识别一个包是否需要生成代码及确定生成代码的方式。
与其他代码生成器相比,client-gen 代码生成器支持的 Tags 更多,可扩展性非常高。除了生成基本的资源操作方法 (Verbs),例如 Create、Update、Delete、Get、List、Patch、Watch 等,如果存在 Status 字段,还能生成 UpdateStatus 函数。
生成基本的资源操作方法 (Verbs),其 Tags 形式如下:
// +genclient生成基本的资源操作方法,不生成 Namespaced 函数,其 Tags 形式如下:
// +genclient:nonNamespaced即便存在 Status 字段,也不生成 UpdateStatus 函数,其 Tags 形式如下:
// +genclient:noStatus不生成基本的资源操作方法 (Verbs),其 Tags 形式如下:
// +genclient:noVerbs仅生成指定的基本操作方法,如 Create、Get,其 Tags 形式如下:
// +genclient:onlyVerbs=create,get生成基本的资源操作方法 (Verbs),但排除 Get、Update 方法,其 Tags 形式如下:
// +genclient:skipVerbs=get,update生成相关扩展 (Extensions) 函数,如生成 UpdateScale 函数。在 UpdateScale 函数中会对 scale 子资源进行更新操作。input 和 result 是用于设置输入和输出的参数,其 Tags 形式如下:
// +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale1. client-gen 使用示例#
构建 client-gen 二进制文件,执行 client-gen 二进制文件并将给定资源的目录路径作为输入源,解析输入源中的 Tags,生成 ClientSet 代码。构建 client-gen 二进制文件的命令如下。
$ make all WHAT=vendor/k8s.io/code-generator/cmd/client-gen构建完成后,将 client-gen 二进制文件存放在 _output/bin/client-gen 下。将 k8s.io/kubernetes/vendor/k8s.io/api 下的资源目录作为输入源,使用 --input 指定需要生成 ClientSet 的资源组和资源版本列表,执行命令如下。
$ ./_output/bin/client-gen \
--input-base=k8s.io/kubernetes/vendor/k8s.io/api \
--input=core/v1,apps/v1 \
--output-base=$GOPATH/src/k8s.io/kubernetes/vendor \
--output-package=k8s.io/client-go \
--clientset-name=kubernetes \
--go-header-file=./hack/boilerplate/boilerplate.generatego.txt执行以上命令后,会在 $GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/client-go/kubernetes/typed 下生成 core/v1 和 apps/v1 两个资源组与资源版本的 ClientSet 代码。
除了手动构建 client-gen 代码生成器并生成代码,也可以执行 Kubernetes 提供的代码生成脚本,后者的生成代码的过程与上述过程完全相同。Kubernetes 代码生成脚本的路径为 hack/update-codegen.sh。
client-gen 二进制文件常用的输入参数如表所示。
| 参数 | 说明 |
|---|---|
--input-base | api 资源组的基本路径,默认为 k8s.io/kubernetes/pkg/apis |
--input | 用于生成客户端的资源组和资源版本列表,每个资源组可以指定最多一个资源版本,多个资源组和资源版本之间使用逗号分隔,例如 group1/version1,group2/version2… |
--output-base | 生成文件的基本路径(默认值为 $GOPATH/src,如果未设置 $GOPATH 环境变量,则默认值为 ./) |
--output-package | 输出的基本包路径,默认值为 k8s.io/kubernetes/pkg/client/clientset_generated/ |
--clientset-name | 生成的 ClientSet 包名称,默认值为 internalclientset |
--go-header-file | 指定许可证样式(License Boilerplate)文件。该文件用于存放开源软件作者及开源协议等信息(默认值为 vendor/k8s.io/code-generator/hack/boilerplate.go.txt) |
2. client-gen 生成规则#
以 Pod 资源对象为例,Pod 资源对象定义 //+genclient 标签,该标签负责生成 Pod 资源对象基本的资源操作方法 (Verbs),如 Create、Update、Delete、Get、List、Patch、Watch 等,代码示例如下。
代码路径:vendor/k8s.io/api/core/v1/types.go
// +genclient
// +genclient:method=UpdateEphemeralContainers,verb=update,subresource=ephemeralcontainers
type Pod struct {
metav1.TypeMeta
metav1.ObjectMeta
Spec PodSpec
Status PodStatus
}每一个代码生成器都提供了一个 GenerateType 函数,它实现了代码生成器的核心逻辑。在 client-gen 中用于生成资源客户端类 (如 vendor/k8s.io/client-go/kubernetes/typed/core/v1/pod.go) 的代码生成器 (genClientForType) 函数中,util.ParseClientGenTags 会解析并验证 Tags,当 Tags 中含有 get 代码标记时,为资源对象生成 Get 方法,代码示例如下。
代码路径:vendor/k8s.io/code-generators/cmd/client-gen/generators/generator_for_type.go
func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
sw := generator.NewSnippetWriter(w, c, "$", "$")
pkg := filepath.Base(t.Name.Package)
tags, err := util.ParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
// ...
if tags.HasVerb("get") {
sw.Do(getTemplate, m)
}
// ...
}当 tags.HasVerb("get") 含有 get 代码标记时 (示例中的 Pod 资源对象未使用 noVerbs、onlyVerbs、skipVerbs 指定任何操作方法,因此会包含 get 代码标记),为资源对象生成 Get 方法,通过 Go 语言标准库的 text/template 模板将资源信息渲染到 getTemplate 模板中,该模板代码进行变量替换生成 Get 方法的示例如图 13-1 所示。

小结#
client-gen 为每个资源生成对应的 ClientSet 客户端类,包含 Get()、List()、Create()、Update()、Delete()、Patch()、Watch() 等方法。
以 Pod 为例,生成的 pods.Get() 方法封装了 GET /api/v1/namespaces/{namespace}/pods/{name} 的 HTTP 请求,pods.List() 封装了 GET /api/v1/pods 的请求。这些方法返回强类型的资源对象(如 *v1.Pod),而非 runtime.Object 静态类型。
这些函数使得开发者可以通过强类型接口操作 K8s 资源,无需手动构造 HTTP 请求和解析响应。例如 client.CoreV1().Pods("default").Get("my-pod", metav1.GetOptions{}) 直接返回 *v1.Pod 对象。
在 K8s 架构中,client-gen 将 RESTful API 封装成了人类可用的强类型SDK。所有 K8s 的客户端工具(kubectl、controller-manager、operator 框架等)都构建在 client-gen 生成的客户端之上,使得与集群交互的开发工作从 HTTP 协议层面提升到了应用层。
lister-gen 代码生成器#
lister-gen 代码生成器是一种为资源生成 Lister 的工具。Lister 为每一个 Kubernetes 资源提供 Lister 功能 (即提供 Get 和 List 方法)。Get 和 List 方法为客户端提供只读的本地缓存数据。以上功能由 lister-gen 代码生成器提供。
lister-gen 代码生成器的代码生成策略与其他代码生成器的类似,都是通过 Tags 来识别一个包是否需要生成代码及确定生成代码的方式。
与其他代码生成器相比,lister-gen 代码生成器并没有独立的 Tags,它依赖于 client-gen 代码生成器的 //+genclient 标签。
1. lister-gen 使用示例#
构建 lister-gen 二进制文件,执行 lister-gen 二进制文件并将给定资源的目录路径作为输入源。构建 lister-gen 二进制文件的命令如下。
$ make all WHAT=vendor/k8s.io/code-generator/cmd/lister-gen构建完成后,将 lister-gen 二进制文件存放在 _output/bin/lister-gen 下。生成代码过程的执行命令如下。
$ ./_output/bin/lister-gen \
--output-base=$GOPATH/src/k8s.io/kubernetes/vendor \
--output-package=k8s.io/client-go/listers \
--input-dirs=k8s.io/api/core/v1,k8s.io/api/apps/v1执行以上命令后,会在 $GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/client-go/listers 下生成 core/v1 和 apps/v1 资源组和资源版本的 Listers 代码。
除了手动构建 lister-gen 代码生成器并生成代码,也可以执行 Kubernetes 提供的代码生成脚本,后者的生成代码的过程与上述过程完全相同。Kubernetes 代码生成脚本的路径为 hack/update-codegen.sh。
lister-gen 二进制文件常用的输入参数如表所示。
| 参数 | 说明 |
|---|---|
--output-base | 生成文件的基本路径(默认值为 $GOPATH/src,如果未设置 $GOPATH 环境变量,则默认值为 ./) |
--output-package | 输出的基本包路径,默认值为 k8s.io/kubernetes/pkg/client/clientset_generated/ |
--input-dirs | 输入源,以逗号分隔的导入路径 |
--go-header-file | 指定许可证样式(License Boilerplate)文件。该文件用于存放开源软件作者及开源协议等信息(默认值为 vendor/k8s.io/code-generator/hack/boilerplate.go.txt) |
2. lister-gen 生成规则#
lister-gen 代码生成器并没有独立的 Tags,它依赖于 client-gen 代码生成器的 //+genclient 标签。当判断是否需要生成某资源类型时,有 3 个过滤条件:资源类型拥有 //+genclient 标签,资源类型拥有 list 字段,资源类型拥有 get 字段。满足过滤条件以后,为该资源类型生成 List 和 Get 方法。代码示例如下。
代码路径:vendor/k8s.io/code-generators/cmd/lister-gen/generators/lister.go
func Packages(context generator.Context, arguments *args.GeneratorArgs) generator.Packages {
var typesToGenerate []*types.Type
for _, t := range p.Types {
tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
if !tags.GenerateClient || !tags.HasVerb("list") || !tags.HasVerb("get") {
continue
}
typesToGenerate = append(typesToGenerate, t)
}
// ...
}在生成资源的 Lister 时,lister-gen 会使用两个主要的代码生成器,分别为 expansionGenerator 和 listerGenerator。
expansionGenerator 用于为每一个资源生成 Lister 扩展接口,并且放在资源组和资源版本目录下的 expansion_generated.go 文件中。以为 Pod 生成资源扩展接口的 PodListerExpansion 为例,代码示例如下。
代码路径:$GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/client-go/listers/core/v1/expansion_generated.go
package v1
// PodListerExpansion allows custom methods to be added to
// PodLister.
type PodListerExpansion interface {}listerGenerator 用于为每个资源生成对应的 Lister 接口和实现类。以 Pod 资源对象为例,listerGenerator 会使用 Go 语言标准库的 text/template 模板描述对应的 List 方法,经过渲染并生成最终的 List 方法,如图 13-2 所示。

小结#
lister-gen 为每个资源生成 *Lister 接口和实现类,提供 Get(name string) 和 List() 两个只读方法。
以 PodLister 为例,podLister.Get("my-pod") 从本地缓存中查找指定名称的 Pod(而非每次请求 kube-apiserver),podLister.List() 返回缓存中所有的 Pod 列表。Lister 的数据来源于 Informer 的本地缓存(DeltaFIFO 队列),通过 Indexer 建立索引。
这些函数使得 Controller 可以在不发送网络请求的情况下读取资源数据,大幅降低了 kube-apiserver 的负载。例如在 Deployment Controller 的 reconcile 循环中,每次检查 ReplicaSet 数量时直接从 Lister 读取,而非发起一次 List 请求。
在 K8s 架构中,lister-gen 是 Controller 性能优化的关键一环。Informers 预先同步并缓存了集群状态,Lister 提供了只读的随机访问接口,共同构成了"旁路缓存"模式——Controller 始终读取本地副本而非直接查询 API Server,使得即使在数千个 Controller 同时运行的集群中,kube-apiserver 也不会过载。
informer-gen 代码生成器#
informer-gen 代码生成器为资源生成 Informer。Informer 为 client-go 提供了与 kube-apiserver 通信的机制,这些功能由 informer-gen 代码生成器自动生成。
informer-gen 代码生成器的代码生成策略与其他代码生成器的类似,都是通过 Tags 来识别一个包是否需要生成代码及确定生成代码的方式。
与其他代码生成器相比,informer-gen 代码生成器并没有可用的 Tags,它依赖于 client-gen 代码生成器的 //+genclient 标签,类似于 lister-gen 代码生成器。
1. informer-gen 使用示例#
构建 informer-gen 二进制文件,执行 informer-gen 二进制文件并将给定资源的目录路径作为输入源。构建 informer-gen 二进制文件的命令如下。
$ make all WHAT=vendor/k8s.io/code-generator/cmd/informer-gen构建完成后,informer-gen 二进制文件存放在 _output/bin/informer-gen 下。生成代码过程的执行命令如下。
$ ./_output/bin/informer-gen \
--output-base=$GOPATH/src/k8s.io/kubernetes/vendor \
--output-package=k8s.io/client-go/informers \
--single-directory \
--listers-package=k8s.io/client-go/listers \
--input-dirs=k8s.io/api/core/v1,k8s.io/api/apps/v1执行以上命令后,会在 $GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/client-go/informers 下生成 core/v1 和 apps/v1 资源组和资源版本的 Informer。
除了手动构建 informer-gen 代码生成器并生成代码,也可以执行 Kubernetes 提供的代码生成脚本,后者的生成代码的过程与上述过程完全相同。Kubernetes 代码生成脚本的路径为
hack/update-codegen.sh。
informer-gen 二进制文件常用的输入参数如表所示。
| 参数 | 说明 |
|---|---|
--output-base | 生成文件的基本路径(默认值为 $GOPATH/src,如果 $GOPATH 环境变量未设置,则默认值为 ./) |
--output-package | 输出的基本包路径,默认值为 k8s.io/kubernetes/pkg/client/informer_generated/ |
--single-directory | 如果为 true,则忽略 internalversion 和 externalversions 子目录 |
--input-dirs | 输入源,以逗号分隔的导入路径 |
--versioned-clientset-package | 依赖的 ClientSet 完整包路径(默认值为 k8s.io/kubernetes/pkg/client/clientset_generated/clientset) |
--listers-package | 依赖的 Listers 完整包路径(默认值为 k8s.io/kubernetes/pkg/client/listers) |
--go-header-file | 指定许可证样式(License Boilerplate)文件。该文件用于存放开源软件作者及开源协议等信息(默认值为 vendor/k8s.io/code-generator/hack/boilerplate.go.txt) |
2. informer-gen 生成规则#
informer-gen 代码生成器本身并没有可用的 Tags,它依赖于 client-gen 代码生成器的 //+genclient 标签。当判断是否需要生成某资源类型时,有 4 个过滤条件:资源类型拥有 //+genclient 标签,资源类型不能拥有 //+genclient:noVerbs 标签,资源类型拥有 list 字段,资源类型拥有 watch 字段。满足过滤条件以后,为该资源类型生成 Informer 相关函数。代码示例如下。
代码路径:vendor/k8s.io/code-generators/cmd/informer-gen/generators/packages.go
func Packages(...) generator.Packages {
var typesToGenerate []*types.Type
for _, t := range p.Types {
tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...))
if !tags.GenerateClient || tags.NoVerbs || !tags.HasVerb("list") || !tags.HasVerb("watch") {
continue
}
typesToGenerate = append(typesToGenerate, t)
}
// ...
}小结#
informer-gen 为每个资源生成完整的 Informer 组件,包括 *Informer(事件源)、SharedIndexInformer(带索引的事件监听器)、SharedInformerFactory(工厂类)以及 ResourceEventHandler(事件处理接口)。
Informer 通过 Watch API 实时监听 kube-apiserver 的资源变化,当创建/修改/删除资源时,将变化写入 DeltaFIFO 队列。开发者通过注册 ResourceEventHandler 的回调函数(OnAdd/OnUpdate/OnDelete)在资源变化时获得通知。Informer 还会驱动 Lister 和 Indexer 的数据更新。
这些函数使得 Controller 可以实时响应集群状态的变化,而无需自己实现一套 Watch + 重试 + 队列的逻辑。例如 Deployment Controller 注册了 Pod 的 Informer 后,当有任何 Pod 创建或删除时,Informer 会自动回调 Controller 的处理函数,触发一次 reconcile。
在 K8s 架构中,informer-gen 是 client-go 的核心支柱,也是整个 K8s Controller 模式的基础设施。所有 K8s 内置 Controller(Deployment、ReplicaSet、StatefulSet 等)和绝大多数 Operator 框架都构建在 Informer 之上。Informers 将"与 kube-apiserver 保持长连接 + 本地缓存 + 事件回调"这一套通用模式封装成了可复用的代码生成产物,大幅降低了 Controller 的开发门槛。
applyconfiguration-gen 代码生成器#
applyconfiguration-gen 代码生成器用于为资源的 Apply 和 ApplyStatus 函数生成专属的 ApplyConfiguration 资源配置应用类型。
client-go 中的 Apply 和 ApplyStatus 函数支持使用直接的、类型安全的方式对每一类资源执行服务端应用 (Server-side Apply) 操作,该操作使用 ApplyConfiguration 类型作为资源专属的强类型请求参数。以 HPA 为例,在 client-go 中执行 Apply 操作的使用示例如下。
import (
...
v1ac "k8s.io/client-go/applyconfigurations/autoscaling/v1"
)
hpaApplyConfig := v1ac.HorizontalPodAutoscaler(autoscalerName, ns).
WithSpec(v1ac.HorizontalPodAutoscalerSpec().
WithMinReplicas(0))
return hpav1client.Apply(ctx, hpaApplyConfig, metav1.ApplyOptions{FieldManager: "mycontroller", Force: true})HPA 专属的 ApplyConfiguration 资源配置应用类型为 v1ac.HorizontalPodAutoscaler,它提供了一系列的 WithXXX 工具方法以支持设置当前操作关注的资源对象属性值。在使用 ClientSet 中的 hpav1client 执行 Apply 函数进行服务端应用操作时,可以将 hpaApplyConfig 资源配置应用对象作为请求参数,并且将该对象提交到 kube-apiserver 实现资源属性的变更和应用。
applyconfiguration-gen 代码生成器的代码生成策略与其他代码生成器的类似,都是通过 Tags 来识别一个包是否需要生成代码及确定生成代码的方式。
与其他代码生成器相比,applyconfiguration-gen 代码生成器并没有可用的 Tags,它依赖于 client-gen 代码生成器的 //+genclient 标签,类似于 lister-gen 代码生成器和 informer-gen 代码生成器。
下面介绍 applyconfiguration-gen 的使用示例和生成规则。
1. applyconfiguration-gen 使用示例#
构建 applyconfiguration-gen 二进制文件,执行 applyconfiguration-gen 二进制文件并将给定资源的目录路径作为输入源。构建 applyconfiguration-gen 二进制文件的命令如下。
$ make all WHAT=vendor/k8s.io/code-generator/cmd/applyconfiguration-gen构建完成后,applyconfiguration-gen 二进制文件存放在 _output/bin/applyconfiguration-gen 下 (注:原书内容此处存在笔误,写成了 informer-gen)。生成代码过程的执行命令如下。
$ ./_output/bin/applyconfiguration-gen \
--output-base=$GOPATH/src/k8s.io/kubernetes/vendor \
--output-package=k8s.io/client-go/applyconfigurations \
--input-dirs=k8s.io/api/core/v1,k8s.io/api/apps/v1执行以上命令,会在 $GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/client-go/applyconfigurations 下生成 core/v1 和 apps/v1 资源组和资源版本的 ApplyConfigurations。
除了手动构建 applyconfiguration-gen 代码生成器并生成代码,也可以执行 Kubernetes 提供的代码生成脚本,后者的生成代码的过程与上述过程完全相同。Kubernetes 代码生成脚本的路径为
hack/update-codegen.sh。
applyconfiguration-gen 二进制文件常用的输入参数,如表所示。
| 参数 | 说明 |
|---|---|
--output-base | 生成文件的基本路径(默认值为 $GOPATH/src,如果 $GOPATH 环境变量未设置,则默认值为 ./) |
--output-package | 输出的基本包路径,默认值为 k8s.io/kubernetes/pkg/client/informer_generated/ |
--input-dirs | 输入源,以逗号分隔的导入路径 |
--openapi-schema | openapi 注册表的路径,在 openapi 注册表中存储了所有期望生成对应 ApplyConfiguration 的资源类型 |
--go-header-file | 指定许可证样式(License Boilerplate)文件。该文件用于存放开源软件作者及开源协议等信息(默认值为 vendor/k8s.io/code-generator/hack/boilerplate.go.txt) |
2. applyconfiguration-gen 生成规则#
applyconfiguration-gen 代码生成器并没有本身可用的 Tags,它依赖于 client-gen 代码生成器的 //+genclient 标签。当判断是否需要生成某资源类型时,Packages 函数调用的 refGraphForReachableTypes 函数会使用两个过滤条件:资源类型拥有 //+genclient 标签,资源类型拥有 apply 或 applyStatus 字段。
满足过滤条件后,递归查找到与该资源类型相关的所有类型,查找的资源类型范围包括:集合的元素类型,使用 type alias 方式定义关联的底层类型,Map 类型的主键类型,结构体类型的所有字段类型。
在这些类型中,会进一步筛选出包含 JSON 标签的 Struct 结构体类型,作为最终需要生成 ApplyConfiguration 代码的资源类型。
代码示例如下。
代码路径:vendor/k8s.io/code-generators/cmd/applyconfiguration-gen/generators/refgraph.go
func refGraphForReachableTypes(universe types.Universe, pkgTypes map[string]*types.Package, initialTypes map[types.Name]string) refGraph {
var refs refGraph = initialTypes
reachableTypes := map[types.Name]*types.Type{}
for _, p := range pkgTypes {
for _, t := range p.Types {
tags := genclientTags(t)
hasApply := tags.HasVerb("apply") || tags.HasVerb("applyStatus")
if tags.GenerateClient && hasApply {
findReachableTypes(t, reachableTypes)
}
...
}
}
for pkg, p := range pkgTypes {
for _, t := range p.Types {
if _, ok := reachableTypes[t.Name]; !ok {
continue
}
if requiresApplyConfiguration(t) {
refs[t.Name] = pkg
}
}
}
return refs
}如上述代码所示,findReachableTypes 函数用于找到所有可达的类型列表,requiresApplyConfiguration 函数用于从找到的类型列表中过滤出满足要求的类型。
小结#
applyconfiguration-gen 为每个资源生成链式构造器类型(ApplyConfiguration),包含 With*() 系列方法和 Apply() 方法。
以 HPA 为例,生成的 HorizontalPodAutoscalerApplyConfiguration 可以通过链式调用构建请求体:HorizontalPodAutoscaler(name, ns).WithSpec(HorizontalPodAutoscalerSpec().WithMinReplicas(0))。构造完成后传给 Apply() 方法,以服务端应用模式向 kube-apiserver 提交。
这些函数使得强类型的 Apply 操作成为可能。不同于 JSON Patch 的脆弱(字段名拼写错误无法被编译器检测),ApplyConfiguration 通过 WithMinReplicas() 等编译期安全的方法构造请求体,同时记录字段管理者,解决了多 Manager 同时管理同一资源时的冲突问题。
在 K8s 架构中,applyconfiguration-gen 补全了 K8s 1.16+ 引入的 Server-side Apply 所需的基础代码生成。与 client-gen 生成的 Update/Patch 方法相比,ApplyConfiguration 提供了声明式的、字段级别的更新语义,使得 Operator 开发者可以精确地声明自己管理的字段,而不会意外覆盖其他管理器的配置。这是 K8s 从命令式到声明式转变的最后一块拼图。

