- 云原生
- 后端
- 运维
- 可观测性
- 微服务
【免费下载链接】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
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" \ $@逐项拆解这条命令的五个位置参数与两个选项:
- 生成组列表
defaulter,client,lister,informer:按顺序执行四个生成器。defaulter生成默认值填充函数(zz_generated.defaults.go);client生成 Typed Clientset;lister生成 Lister;informer生成 Informer。需要说明的是,Rainbond 当前提交的pkg/generated产物中主要包含 client、lister、informer 三类,defaulter 对应的生成逻辑由脚本统一驱动。 - 输出包路径
github.com/goodrain/rainbond/pkg/generated:生成的 clientset / listers / informers 将以该包为根。 - API 源码包路径
github.com/goodrain/rainbond/pkg/apis:生成器从该目录扫描全部 API group。 - group 与版本
rainbond:v1alpha1:只针对rainbond.iogroup 的v1alpha1版本生成。 --go-header-file:指定 boilerplate.go.txt 作为每个生成文件的版权头模板。该模板声明了 Rainbond(Goodrain Co., Ltd.)的 GPL 许可信息。$@:透传额外参数,例如--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/其执行流程值得注意:
- 先确保
generate-groups.sh具备执行权限(说明仓库依赖vendor/目录下的 code-generator); - 运行
update-generated.sh生成 clientset / listers / informers / defaulter; - 再调用 controller-gen(仓库注明版本 v0.9.2,若本机缺失则自动
go get下载)以同一 boilerplate 头生成zz_generated.deepcopy.go等 object 代码; - 由于
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
相关推荐
k8s.io/code-generator 开发指南:Kubernetes 代码生成器的原理、十大生成器与 kube_codegen.sh 实战
k8s.io/code generator 开发指南:Kubernetes 代码生成器的原理、十大生成器与 kube_codegen.sh 实战 本文基于 Ku
云原生容器编排集群管理微服务JHipster `java:openapi-generator` 子生成器:基于 OpenAPI 定义的 API-First 后端代码生成实战
JHipster java:openapi generator 子生成器:基于 OpenAPI 定义的 API First 后端代码生成实战 导读 java:o
代码生成开发工具后端前端OpenDisplay GPL-3.0许可详解:免费开源第二显示器的 Fork、商用与分发完整指南
OpenDisplay GPL 3.0许可详解:免费开源第二显示器的 Fork、商用与分发完整指南 OpenDisplay 是一款免费、开源的 Mac 副屏工具
开发工具云原生微服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考