☰
Rainbond 自定义 API 代码生成指南:基于 k8s.io/code-generator 生成 Clientset、Listers 与 Informers
2026/10/15 3:39:55 网站建设 项目流程
  • 云原生
  • 后端
  • 运维
  • 可观测性
  • 微服务

【免费下载链接】rainbond

Rainbond is an open-source container platform that requires no Kubernetes expertise. Its core capabilities are 100% open source. It abstracts away infrastructure complexity and provides a unified way to deploy and manage business applications, AI-generated projects, open-source AI software, and large language model services. AI helps teams deploy

项目地址:https://gitcode.com/gh_mirrors/ra/rainbond
点击查看免费下载

Rainbond(github.com/goodrain/rainbond)在其pkg/apis目录中定义了rainbond.io/v1alpha1这一组自定义 Kubernetes API 资源(如ComponentDefinition、HelmApp、RBDPlugin、RBDAbility、ThirdComponent)。为了让这些 API 类型具备标准的 deepcopy 深拷贝能力,并能像内置资源一样通过 Typed Clientset、Lister 与 Informer 被控制器和 API 服务层访问,仓库在hack/k8s/codegen目录下提供了一套完整的代码生成工具链。本文以仓库中的 hack/k8s/codegen/README.md 为核心骨架,结合脚本源码与生成产物,讲解如何一键生成、验证并最终使用这批客户端代码,帮助你在 Rainbond 二次开发中正确维护自定义 CRD 的 Go 代码。

一、codegen 在 Rainbond 中的作用

Kubernetes 生态对自定义资源(CRD)的 Go 类型有一套约定俗成的要求:每个类型必须实现DeepCopyInto/DeepCopy方法,以便被runtime.Object机制序列化、缓存和深拷贝;同时,控制器通常需要 Typed Clientset(强类型访问 CRD)、Lister(本地缓存读取)和 Informer(事件监听与增量同步)。手写这些代码工作量巨大且极易出错,因此 Rainbond 直接复用 Kubernetes 官方维护的k8s.io/code-generator工具链,通过一个脚本批量产出全部样板代码。

从仓库文件结构可以清晰看到这一分工:

  • API 类型定义(手写):pkg/apis/rainbond/v1alpha1 下的*_types.go;
  • 生成代码(自动产出):pkg/generated 下的clientset、informers、listers;
  • deepcopy 代码(自动产出):pkg/apis/rainbond/v1alpha1/zz_generated.deepcopy.go,文件头明确标注 “Code generated by controller-gen. DO NOT EDIT.”。

因此,每次修改 API 类型定义后,都必须重新运行代码生成,这正是本文档与脚本存在的意义。

二、codegen 目录结构一览

hack/k8s/codegen目录共包含四个文件,各自职责如下:

文件作用
README.md使用说明:如何运行生成脚本以及预期输出
update-generated.sh生成脚本:调用code-generator的generate-groups.sh生成 clientset / listers / informers / defaulter
verify-generated.sh验证脚本:以--verify-only模式检查生成代码是否与当前内容一致
tools.go构建辅助文件:通过 build tagtools引入k8s.io/code-generator依赖
boilerplate.go.txt许可头模板:注入到每一个生成文件顶部的 GPL 版权注释

其中tools.go采用了 Kubernetes 社区常见的 “tools 依赖” 技巧:

// +build tools package tools import _ "k8s.io/code-generator"

通过 build tag 将k8s.io/code-generator显式声明为项目依赖,使其能够被go mod正常拉取,同时不会进入最终编译产物。仓库 go.mod 中对应依赖为k8s.io/code-generator v0.28.1(并经由 replace 指令指向 v0.26.4,与项目整体使用的 Kubernetes 版本对齐)。

三、一键生成:update-generated.sh 深度解析

原文档给出的使用方式非常简洁——在仓库根目录执行:

./hack/k8s/codegen/update-generated.sh

脚本内容同样简洁,核心只有一条命令调用:

#!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail vendor/k8s.io/code-generator/generate-groups.sh \ "defaulter,client,lister,informer" \ "github.com/goodrain/rainbond/pkg/generated" \ "github.com/goodrain/rainbond/pkg/apis" \ "rainbond:v1alpha1" \ --go-header-file "./hack/k8s/codegen/boilerplate.go.txt" \ $@

逐项拆解这条命令的五个位置参数与两个选项:

  1. 生成组列表defaulter,client,lister,informer:按顺序执行四个生成器。defaulter生成默认值填充函数(zz_generated.defaults.go);client生成 Typed Clientset;lister生成 Lister;informer生成 Informer。需要说明的是,Rainbond 当前提交的pkg/generated产物中主要包含 client、lister、informer 三类,defaulter 对应的生成逻辑由脚本统一驱动。
  2. 输出包路径github.com/goodrain/rainbond/pkg/generated:生成的 clientset / listers / informers 将以该包为根。
  3. API 源码包路径github.com/goodrain/rainbond/pkg/apis:生成器从该目录扫描全部 API group。
  4. group 与版本rainbond:v1alpha1:只针对rainbond.iogroup 的v1alpha1版本生成。
  5. --go-header-file:指定 boilerplate.go.txt 作为每个生成文件的版权头模板。该模板声明了 Rainbond(Goodrain Co., Ltd.)的 GPL 许可信息。
  6. $@:透传额外参数,例如--verify-only(仅校验不写入)、--output-base(修改输出根目录)等,这为 verify 脚本复用同一入口提供了机制。

原文档展示了执行后应打印的日志:

Generating deepcopy funcs Generating clientset for etcd:v1beta2 at github.com/coreos/etcd-operator/pkg/generated/clientset Generating listers for etcd:v1beta2 at github.com/coreos/etcd-operator/pkg/generated/listers Generating informers for etcd:v1beta2 at github.com/coreos/etcd-operator/pkg/generated/informers

需要说明:这三行是generate-groups.sh上游脚本的通用输出模板(示例文本取自 etcd-operator 项目)。在 Rainbond 仓库中实际执行时,对应输出的第二至第四行会替换为:

Generating clientset for rainbond:v1alpha1 at github.com/goodrain/rainbond/pkg/generated/clientset Generating listers for rainbond:v1alpha1 at github.com/goodrain/rainbond/pkg/generated/listers Generating informers for rainbond:v1alpha1 at github.com/goodrain/rainbond/pkg/generated/informers

如果生成前后无报错(脚本开启了set -o errexit,任何失败都会立即中断并以非零码退出),即表示一次成功的生成。

四、生成校验:verify-generated.sh 与 --verify-only

在 CI 或提交前,通常需要确认生成代码与 API 定义是否一致,避免有人手工改动生成文件或漏跑生成脚本。仓库提供了配套的 verify-generated.sh:

#!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail if ! output=$(./hack/k8s/codegen/update-generated.sh --verify-only 2>&1); then echo "FAILURE: verification of codegen failed:" echo "${output}" exit 1 fi echo "Verified generated code ==="

其原理非常简单:将--verify-only透传给update-generated.sh(即$@参数),generate-groups.sh在--verify-only模式下只比对生成结果而不写入磁盘。若输出与现有文件不一致则返回非零退出码,脚本随即打印FAILURE: verification of codegen failed:及 diff 输出并退出;一致则打印Verified generated code ===。

这意味着你可以在自己的 CI 流水线中直接执行:

./hack/k8s/codegen/verify-generated.sh

来保证 “提交的生成代码永远与 API 定义同步”。

五、生成产物全貌:pkg/generated 的三层结构

运行生成脚本后,产物落在 pkg/generated 目录下,其结构与 Kubernetes 官方 client-go 完全一致:

pkg/generated/ ├── clientset/ │ └── versioned/ │ ├── clientset.go # Clientset 聚合入口,含 Discovery() 与 RainbondV1alpha1() │ ├── fake/ # 测试用 Fake Clientset │ ├── scheme/ # Scheme 注册 │ └── typed/ │ └── rainbond/v1alpha1/ # 每个资源一个强类型 Client ├── informers/ │ └── externalversions/ │ ├── factory.go # SharedInformerFactory │ └── rainbond/v1alpha1/ # 每个资源一个 Informer └── listers/ └── rainbond/v1alpha1/ # 每个资源一个 Lister

生成器为rainbond:v1alpha1下的每个资源(ComponentDefinition、HelmApp、RBDPlugin、RBDAbility、ThirdComponent)都产出了对应的 typed client、fake client、informer 与 lister。例如:

  • pkg/generated/clientset/versioned/typed/rainbond/v1alpha1/helmapp.go:HelmApps的增删改查方法;
  • pkg/generated/informers/externalversions/rainbond/v1alpha1/helmapp.go:HelmAppInformer;
  • pkg/generated/listers/rainbond/v1alpha1/helmapp.go:HelmAppLister。

Clientset 入口 clientset.go 中,Interface接口暴露了两个方法:

type Interface interface { Discovery() discovery.DiscoveryInterface RainbondV1alpha1() rainbondv1alpha1.RainbondV1alpha1Interface }

并通过NewForConfig(c *rest.Config)从 kubeconfig 构造实例,同时自动注入默认 UserAgent 与限流器(QPS/Burst),与官方 client-go 行为一致。

此外,API 类型自身的 deepcopy 方法由 controller-gen 生成于 pkg/apis/rainbond/v1alpha1/zz_generated.deepcopy.go,其中为Author、ComponentDefinition、HelmApp、RBDPlugin等类型逐一生成了DeepCopyInto/DeepCopy/DeepCopyObject,例如:

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Author) DeepCopyInto(out *Author) { *out = *in }

六、生成器如何识别 API 类型:标记与注册

生成器并非凭空猜测,而是依据pkg/apis/rainbond/v1alpha1中的 Go 注释标记(markers)与注册代码来识别资源。以 rbdplugin_types.go 为例:

// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:resource:scope=Cluster // RBDPlugin is the Schema for the rbdplugins API type RBDPlugin struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec RBDPluginSpec `json:"spec,omitempty"` Status RBDPluginStatus `json:"status,omitempty"` }
  • // +genclient告诉 client-gen 为这个类型生成 Typed Client;
  • //+kubebuilder:object:root=true声明这是一个根对象(非列表),controller-gen 据此生成 deepcopy 与 CRD;
  • //+kubebuilder:resource:scope=Cluster声明该资源的 CRD 作用域(RBDPlugin是集群级,而ComponentDefinition则是scope=Namespaced,见 componentdefinition.go)。

同时,每个类型在init()中注册到 SchemeBuilder:

func init() { SchemeBuilder.Register(&RBDPlugin{}, &RBDPluginList{}) }

SchemeBuilder 定义于 register.go,其中声明了SchemeGroupVersion = schema.GroupVersion{Group: "rainbond.io", Version: "v1alpha1"}。doc.go顶部的// +k8s:deepcopy-gen=package,register与// +groupName=rainbond.io则分别指示 deepcopy 生成器按包生成、并明确 API group 名称。

因此,新增一个 CRD 类型的标准流程是:在v1alpha1包内新建*_types.go(带上述标记与init()注册),然后重新运行生成脚本。

七、生成代码在 Rainbond 中的实际使用

生成的代码并非摆设,Rainbond 的 API 服务层与 worker 控制器都在直接消费它们。

1. API 服务层使用 Typed Clientset 操作 CRD

在 api/handler/cluster.go 中,通过注入的rainbondClient列出集群级自定义资源:

list, err := c.rainbondClient.RainbondV1alpha1().RBDAbilities(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{}) // ... list, err := c.rainbondClient.RainbondV1alpha1().RBDPlugins(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{})

而 api/handler/application_handler.go 则对HelmApp做完整的生命周期操作:

_, err = a.rainbondClient.RainbondV1alpha1().HelmApps(helmApp.Namespace).Create(ctx2, helmApp, metav1.CreateOptions{})

以及Get、Update、Delete(见同文件 L378、L392、L472)。正是因为有了 codegen 生成的强类型接口,这些调用才能写出RainbondV1alpha1().HelmApps(ns).Create(...)这样与内置资源无异的链式 API。

2. worker 运行时使用 Informer 与 Lister 维护本地缓存

worker/appm/store/store.go 引入:

"github.com/goodrain/rainbond/pkg/generated/informers/externalversions" "github.com/goodrain/rainbond/pkg/generated/listers/rainbond/v1alpha1"

构建SharedInformerFactory并对自定义资源建立 Informer 监听,配合 worker/appm/store/lister.go 中的 Lister 从本地缓存读取对象,从而支撑应用运行时(appm)的实时状态维护,避免每次查询都直连 API Server。

3. 测试场景使用 Fake Clientset

pkg/generated/clientset/versioned/fake 目录下为每个资源生成了 Fake 实现(如 fake_helmapp.go),可配合k8s.io/client-go/testing的 Action 断言机制编写单元测试,无需真实集群。

八、与 Makefile 的集成:generate 目标

为了将代码生成纳入日常开发闭环,仓库 Makefile 提供了generate目标:

# Generate code, controller-gen version: v0.9.2 generate: controller-gen chmod +x vendor/k8s.io/code-generator/generate-groups.sh ./hack/k8s/codegen/update-generated.sh $(CONTROLLER_GEN) object:headerFile="hack/k8s/codegen/boilerplate.go.txt" paths="./pkg/apis/..." cp -r github.com/goodrain/rainbond/pkg/ ./pkg/ rm -rf github.com/

其执行流程值得注意:

  1. 先确保generate-groups.sh具备执行权限(说明仓库依赖vendor/目录下的 code-generator);
  2. 运行update-generated.sh生成 clientset / listers / informers / defaulter;
  3. 再调用 controller-gen(仓库注明版本 v0.9.2,若本机缺失则自动go get下载)以同一 boilerplate 头生成zz_generated.deepcopy.go等 object 代码;
  4. 由于generate-groups.sh默认会按 go import 路径在临时目录输出(形如github.com/goodrain/rainbond/pkg/),Makefile 通过cp -r将产物拷回./pkg/并清理临时目录。

因此,在 Rainbond 仓库中推荐的一条龙操作是:

make generate

其效果等价于分别执行 deepcopy 生成与 client/lister/informer 生成。若只做快速校验,则运行:

./hack/k8s/codegen/verify-generated.sh

九、使用前提与注意事项

  • vendor 依赖:update-generated.sh直接引用vendor/k8s.io/code-generator/generate-groups.sh,因此执行前需确保vendor/目录已正确填充(仓库通过 go.mod 中k8s.io/code-generator的 replace 指令固定版本),这也是Makefile中先chmod +x再执行的背景。
  • 版本对齐:当前仓库go.mod中 code-generator 主版本为 v0.28.1,经 replace 实际解析为 v0.26.4;controller-gen 版本为 v0.9.2。升级 Kubernetes 依赖版本时,应同步验证生成器版本兼容性,并重新生成全部代码。
  • 生成文件勿手改:所有Code generated by ... DO NOT EDIT.标注的文件(如 zz_generated.deepcopy.go)都应在修改 API 类型后由脚本重新生成,手工改动会在下次生成时被覆盖。
  • 新增资源必须注册:生成器只处理带有+genclient标记并在init()中注册到SchemeBuilder的类型,缺失任一条件都会导致对应 client / informer / lister 缺失。

十、小结

Rainbond 的 codegen 工具链遵循 Kubernetes 官方实践,用一条脚本解决了自定义 CRD 客户端代码的重复劳动:update-generated.sh负责生成,verify-generated.sh负责在 CI 中守护一致性,Makefile的generate目标将其与 controller-gen 的 deepcopy 生成整合为一步。理解这套流程后,你在为 Rainbond 新增或调整rainbond.io/v1alpha1自定义资源时,只需修改类型定义、执行make generate并提交生成产物,即可让 API 服务层与 worker 运行时无缝获得类型安全的自定义资源访问能力。

  • 云原生
  • 后端
  • 运维
  • 可观测性
  • 微服务

【免费下载链接】rainbond

Rainbond is an open-source container platform that requires no Kubernetes expertise. Its core capabilities are 100% open source. It abstracts away infrastructure complexity and provides a unified way to deploy and manage business applications, AI-generated projects, open-source AI software, and large language model services. AI helps teams deploy

项目地址:https://gitcode.com/gh_mirrors/ra/rainbond
点击查看免费下载

相关推荐

上一篇:KServe 部署 BLOOM 大语言模型:基于 Triton FasterTransformer 后端的完整实战指南
下一篇:Apache Pulsar 命令行工具完全指南:pulsar / pulsar-client / pulsar-perf / bookkeeper 等六类 CLI 实战详解

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询