- 开发者工具
- 代码生成
- CLI
- 云原生
- 后端
【免费下载链接】kubebuilder
Kubebuilder - SDK for building Kubernetes APIs using CRDs
本文档(docs/migration_guide.md)是 kubebuilder 官方为v0 项目迁移到 v1 项目编写的手把手操作指南。它面向已经用 kubebuilder 0.x 创建过项目、希望升级到基于 controller-runtime/controller-tools 的 v1 架构的开发者,完整覆盖从初始化新项目、重建 API、移植 types 与控制器代码、更新依赖到最终集群验证的全部步骤。读完本文,你将掌握 v0 与 v1 项目在目录结构、控制器库、客户端库与装配机制上的关键差异,并能按官方推荐路径——新建 v1 项目并逐个移植代码——完成一次零风险的项目迁移。
迁移前的准备:先认清 v0 与 v1 的差异
在动手之前,建议先通读 kubebuilder v0 与 v1 差异清单,理解两个版本在命令体系、脚手架布局、依赖库与装配机制上的根本性不同,这决定了迁移中每一步改动背后的原因。
命令与工作流差异
- kubebuilder v0提供
init、create controller、create resource、create config、generate等命令。典型工作流为:
kubebuilder init --domain example.com kubebuilder create resource --group <group> --version <version> --kind <Kind> GOBIN=${PWD}/bin go install ${PWD#$GOPATH/src/}/cmd/controller-manager bin/controller-manager --kubeconfig ~/.kube/config kubectl apply -f hack/sample/<resource>.yaml docker build -f Dockerfile.controller . -t <image:tag> docker push <image:tag> kubebuilder create config --controller-image <image:tag> --name <project-name> kubectl apply -f hack/install.yaml每次修改 resource 或 controller 后,都必须手动运行kubebuilder generate重新生成项目代码。
- kubebuilder v1提供
init、create api命令,工作流大幅简化:
kubebuilder init --domain example.com --license apache2 --owner "The Kubernetes authors" kubebuilder create api --group ship --version v1beta1 --kind Frigate make install make runv1 项目不再有 generate 命令——resource 或 controller 更新后无需手动重新生成。这一差异也是本迁移指南的核心逻辑起点:迁移本质上是"重建"而非"升级"。
脚手架布局差异
- v0 项目包含
pkg/client目录,v1 项目没有; - v0 项目包含
inject目录,v1 项目没有; - v0 项目强制使用预定义目录布局
pkg/apis与pkg/controller,v1 项目允许用户指定路径; - v1 项目中每个 api 和 controller 都有对应的
init()函数。
依赖库与装配机制差异
- 控制器库:v0 项目从 kubebuilder 自身导入控制器库(
kubebuilder/pkg/controller),提供GenericController类型;v1 项目改从 controller-runtime 导入(如controller-runtime/pkg/controller、controller-runtime/pkg/reconcile)。 - 客户端库:v0 项目的 client 由
kubebuilder generate生成在pkg/client目录下;v1 项目直接使用 controller-runtime 的动态客户端库controller-runtime/pkg/client。 - 装配(Wiring):v0 项目通过
inject包把控制器加入 controller-manager 并注册 CRD;v1 项目没有inject包,控制器通过controller目录下add_<type>.go文件中的init函数加入 manager,类型通过apis目录下<type>_types.go文件中的init函数注册。
这些差异意味着 v0 代码几乎不能直接拷贝到 v1 项目,必须经过系统性改写。
迁移总体策略:新建 v1 项目,复制并修改 v0 代码
官方推荐的迁移方式非常明确:创建一个全新的 v1 项目,然后从 v0 项目中把代码复制过来并逐一修改适配,而不是试图原地把 v0 项目"升级"成 v1 结构。这种"重新脚手架 + 移植业务代码"的策略有两点好处:一是新项目天然具备 v1 的正确目录结构与生成管线;二是业务代码的移植可以分步骤进行、每一步都可编译验证。
第一步:初始化 v1 项目
从旧项目的pkg/apis/doc.go中找出项目的domain 名,用它初始化新项目:
kubebuilder init --project-version v1 --domain <domain>这里的关键标志是--project-version。在当前仓库的 CLI 实现中可以看到,该标志被定义在 pkg/cli/cli.go(projectVersionFlag = "project-version"),它接受v0与v1两个取值:使用v0时行为与 kubebuilder 0.* 完全一致;使用v1时生成架构不同的 v1 项目。从仓库当前的入口实现(internal/cli/cmd/cmd.go)看,现代 kubebuilder 已演进到以 go/v4 与 kustomize/v2 插件捆绑的默认脚手架体系,--project-version语义由插件系统解析并按项目版本过滤插件(见 pkg/cli/cli.go 中FilterPluginsByProjectVersion等逻辑)——但这不影响 v0→v1 迁移时的命令形态,init --project-version v1依然是创建 v1 布局项目的正确入口。
第二步:创建 API
从旧项目的pkg/apis目录中找出group / version / kind名称:
- group 和 version 名称即目录名;
- kind 名称需要从对应的
*_types.go文件中查找,注意 kind 名称必须首字母大写。
在新项目中创建 API:
kubebuilder create api --group <group> --version <version> --kind <kind>如果旧项目有多个资源,就重复执行多次kubebuilder create api,把全部资源逐一创建出来。v1 的create api同时取代了 v0 的create resource与create controller两个命令。
第三步:移植 types.go
把旧项目中<type>_types.go的内容复制到新项目同名文件<type>_types.go中。
特别提醒:v1 项目的 types.go 中含有一段包含<type>List与init函数的代码段,移植时必须保留:
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +genclient:nonNamespaced // HelloList contains a list of Hello type HelloList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []Hello `json:"items"` } func init() { SchemeBuilder.Register(&Hello{}, &HelloList{}) }这段代码正是 v1 "装配机制"的一部分:init()函数把类型注册进 scheme,+k8s:deepcopy-gen:interfaces与+genclient标记则控制代码生成器为HelloList生成 DeepCopy 方法。删除它会导致类型无法注册、DeepCopy 代码无法生成,项目无法编译运行。
第四步:移植并改造控制器代码
控制器代码的移植是迁移中工作量最大、最容易出错的部分,需要分三块处理。
4.1 复制并改写 Reconcile 函数
v0 与 v1 的Reconcile函数签名完全不同:
- v0 项目:
func (bc *<kind>Controller) Reconcile(k types.ReconcileKey) error - v1 项目:
func (r *Reconcile<kind>) Reconcile(request reconcile.Request) (reconcile.Result, error)
操作步骤:先删除 v1 项目中Reconcile函数的原有函数体,再把 v0 项目中Reconcile的函数体复制进来,然后做两处必要修改:
- 在每条
return语句的第一个返回值位置加上reconcile.Result{}; - 改写 client 函数的调用方式。v0 中 client 调用的格式是
bc.<kind>Lister.<kind>().Get()或bc.KubernetesClientSet.<group>.<version>.<Kind>.Get(),需要替换为r.Client的函数。以下是官方给出的完整改写对照示例:
# in v0 project mc, err := bc.memcachedLister.Memcacheds(k.Namespace).Get(k.Name) # in v1 project, change to mc := &myappsv1alpha1.Memcached{} err := r.Client.Get(context.TODO(), request.NamespacedName, mc) # in v0 project dp, err := bc.KubernetesInformers.Apps().V1().Deployments().Lister().Deployments(mc.Namespace).Get(mc.Name) # in v1 project, change to dp := &appsv1.Deployment{} err := r.Client.Get(context.TODO(), request.NamespacedName, dp) dep := &appsv1.Deployment{...} # in v0 project dp, err := bc.KubernetesClientSet.AppsV1().Deployments(mc.Namespace).Create(dep) # in v1 project, change to err := r.Client.Create(context.TODO(), dep) dep := &appsv1.Deployment{...} # in v0 project dp, err = bc.KubernetesClientSet.AppsV1().Deployments(mc.Namespace).Update(deploymentForMemcached(mc)) # in v1 project, change to err := r.Client.Update(context.TODO(), dep) labelSelector := labels.SelectorFrom{...} # in v0 project pods, err := bc.KubernetesInformers.Core().V1().Pods().Lister().Pods(mc.Namespace).List(labelSelector) # in v1 project, change to pods := &v1.PodList{} err = r.Client.List(context.TODO(), &client.ListOptions{LabelSelector: labelSelector}, pods)可以总结出几条稳定的改写规律:
- Get:先声明目标对象(
&<group><version>.<Kind>{}),再调用r.Client.Get(context.TODO(), request.NamespacedName, obj),request.NamespacedName同时提供了 namespace 与 name,取代 v0 里Lister...Get(k.Namespace, k.Name)的两段式传参; - Create / Update:直接
r.Client.Create(context.TODO(), obj)/r.Client.Update(context.TODO(), obj),不再需要经过ClientSet.<Group><Version>().<Kind>(namespace)链式构造; - List:声明一个
&v1.PodList{}接收结果,通过client.ListOptions{LabelSelector: labelSelector}传入标签选择器。
此外,v0 项目中用到的库导入(如log、fmt或 k8s 相关库)需要补进 v1 项目;但来自 kubebuilder 自身或旧项目 client 包的库一律不能添加——它们正是 v0 与 v1 架构差异的根源所在。
4.2 改写 add 函数(watcher 迁移)
v0 项目的控制器文件中有一个ProvideController函数,负责创建控制器并添加各种 watch;v1 项目中对应的函数是add。对于这一部分,不需要从 v0 拷贝任何代码,只需根据 v0 的ProvideController中调用了哪些watch函数,在 v1 的add函数中手工添加相应的 watcher。
官方给出的对照示例:
gc := &controller.GenericController{...} gc.Watch(&myappsv1alpha1.Memcached{}) gc.WatchControllerOf(&v1.Pod{}, eventhandlers.Path{bc.LookupRS, bc.LookupDeployment, bc.LookupMemcached})需要改写成:
c, err := controller.New{...} c.Watch(&source.Kind{Type: &myappsv1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{}) c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &myappsv1alpha1.Memcached{}, })改写要点:
- 直接 watch 自己的 CRD 类型时,用
&source.Kind{Type: &myappsv1alpha1.Memcached{}}配&handler.EnqueueRequestForObject{}; - watch 被管对象(如 Deployment、Pod)时,用
&source.Kind{Type: &appsv1.Deployment{}}配&handler.EnqueueRequestForOwner{IsController: true, OwnerType: &myappsv1alpha1.Memcached{}}。v0 中WatchControllerOf配合eventhandlers.Path做多级 owner 回溯的写法,在 v1 中被EnqueueRequestForOwner直接声明 owner 类型的方式取代。
4.3 移植其他函数
如果reconcile函数依赖一些用户自定义函数(helper、工具函数等),这些函数也要一并复制到 v1 项目中。逐一定位reconcile体内调用的每个自定义函数,确保没有遗漏,否则会出现编译错误或运行时 nil 引用。
移植用户自建库
如果旧项目中有用户自定义的库(公共包、内部工具包等),务必一并复制到新项目中。这些库通常位于pkg/等自定义目录下,属于业务逻辑的公共依赖,复制后记得检查其 import 路径是否需要随模块名调整。
更新依赖
旧项目使用 dep 管理依赖(Gopkg.toml)。打开旧项目的Gopkg.toml,找到用户自定义依赖所在的区块:
# Users add deps lines here [prune] go-tests = true #unused-packages = true # Note: Stanzas below are generated by Kubebuilder and may be rewritten when # upgrading kubebuilder versions. # DO NOT MODIFY BELOW THIS LINE.把这些用户自定义依赖复制到新项目的Gopkg.toml中,且必须放在下面这行之前:
# STANZAS BELOW ARE GENERATED AND MAY BE WRITTEN - DO NOT MODIFY BELOW THIS LINE.# Users add deps lines here注释段是 dep 为 Kubebuilder 用户预留的"安全区":此段之后的 stanza 是 kubebuilder 自动生成的,升级时会被重写覆盖;因此用户自定义依赖必须置于生成区之前,才能避免在后续 kubebuilder 版本升级时被清除。当前仓库各 testdata 项目(如 testdata/project-v4/go.mod)已演进到 Go modules 管理依赖,若你的新项目使用go.mod,则应把旧依赖等价迁移为go.mod中的require条目并运行go mod tidy。
移植其他用户文件
旧项目中若还有其他用户创建的文件——例如构建脚本、README.md等——也应一并复制进新项目。迁移的验收标准是"业务与配置两不丢":凡是 v0 项目中人工添加、非自动生成的文件,都应在迁移清单中逐项核对。
最终验证:确保构建与集群运行正常
迁移完成后,用以下命令验证新项目:
- 运行
make,确认新项目可以正常构建并通过全部测试; - 运行
make install与make run,确认 API 和控制器在集群上正常工作。
make install会把 CRD 安装到集群,make run会在本地启动 controller-manager 并连接到集群——这两步验证的是迁移后项目在真实 Kubernetes 环境中的可用性,而不是仅停留在编译通过层面。你可以在仓库的 testdata 项目中查看典型的 v1 项目结构与 Makefile 目标(如 testdata/project-v4/Makefile),作为迁移目标的参照模板。
总结
v0 到 v1 的迁移本质上是一次"脚手架重建 + 代码适配"的组合操作:用kubebuilder init --project-version v1和kubebuilder create api重建项目骨架,然后把 types.go、Reconcile 函数体、watcher 配置、用户自定义库与依赖逐项移植,并在移植过程中完成三处关键改写——保留<type>List/init注册段、为每个return补上reconcile.Result{}、把 Lister/ClientSet 链式调用改写为r.Client的Get/Create/Update/List。迁移完成后,v1 项目将摆脱kubebuilder generate手动再生的负担,直接受益于 controller-runtime 的动态客户端与基于init()的装配机制。
- 开发者工具
- 代码生成
- CLI
- 云原生
- 后端
【免费下载链接】kubebuilder
Kubebuilder - SDK for building Kubernetes APIs using CRDs
相关推荐
Kubebuilder项目从v0到v1版本的迁移指南
Kubebuilder项目从v0到v1版本的迁移指南 前言 Kubebuilder作为Kubernetes官方推荐的Operator开发框架,经历了从v0到v1
开发者工具代码生成CLI云原生后端Deepfake Offensive Toolkit开源项目年度财务报告:收支与预算
Deepfake Offensive Toolkit开源项目年度财务报告:收支与预算 Deepfake Offensive Toolkit(简称dot)作为一款
人工智能计算机视觉渗透测试应用安全NautilusTrader v1 到 v2 迁移完全指南:Cython 到 Rust 核心 + PyO3 的 Python 代码移植实战
NautilusTrader v1 到 v2 迁移完全指南:Cython 到 Rust 核心 + PyO3 的 Python 代码移植实战 导读 Nautilu
金融科技后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考