子模块布局

这部分描述了如何修改一个脚手架项目,以便与多个 go.mod 文件一起使用,用于 API 和控制器。

子模块布局(可以称之为 Monorepo 的一种特殊形式)是一个特殊的用例,可以在涉及重用 API 的场景中提供帮助,而不会引入不应在外部调用 API 的项目中出现的间接依赖。

概述

为 APIs 和 Controllers 使用分开的 go.mod 模块可以帮助解决以下情况:

  • 有一个企业版的 Operator 可用,它希望重用社区版的 API。
  • 有许多(可能是外部的)模块依赖于该 API,您希望对传递依赖关系进行更严格的分离。
  • 如果您想减少传递依赖对您的 API 被其他项目包含的影响
  • 如果您希望将 API 发布过程的生命周期与控制器发布过程分开管理。
  • 如果您希望在不将代码分割到多个代码库的情况下对代码库进行模块化。

然而,他们在典型项目中引入了多个警告,这是使它们在通用用例或插件中难以推荐的主要因素之一:

  • 多个 go.mod 模块并不被推荐作为 Go 的最佳实践,并且 多个模块大多是不鼓励的
  • 你总是可以将你的API提取到一个新的代码库中,并且在一个跨多个代码库、依赖于相同API类型的项目中,可能会对发布过程有更多的控制。
  • 它至少需要一个 替换指令,要么通过 go.work(这至少需要两个以上的文件以及一个用于没有 GO_WORK 的构建环境的环境变量),要么通过 go.mod 替换,这需要在每次发布时手动添加和移除。

调整您的项目

为了实现正确的子模块布局,我们将以生成的 API 作为起点。

对于以下步骤,我们将假设您在您的 GOPATH 中创建了您的项目。

kubebuilder init

并创建了一个 API 和控制器,带有

kubebuilder create api --group operator --version v1alpha1 --kind Sample --resource --controller --make

为您的 API 创建第二个模块

现在我们有了基本布局,我们将为您启用多个模块。

  1. 导航到 api/v1alpha1
  2. 运行 go mod init 来创建一个新的子模块。
  3. 运行 go mod tidy 以解决依赖关系。

您的 API go.mod 文件现在可能如下所示:

如您所见,它仅包括 apimachinery 和 controller-runtime 作为依赖项,而您在控制器中声明的任何依赖项都不会被间接导入。

在开发中使用替换指令

当尝试在 Operator 的根文件夹中解析主模块时,如果使用 VCS 路径,您会注意到一个错误:

原因是您可能尚未将您的模块推送到版本控制系统,因此解析主模块将失败,因为它不再能以包的形式直接访问 API 类型,而只能作为模块访问。

为了解决这个问题,我们需要告诉 Go 工具正确地将 API 模块替换为对您路径的本地引用。

您可以通过两种不同的方法来实现这一点:Go 模块和 Go 工作区。

使用 Go 模块

对于 Go 模块,您需要编辑项目的主 go.mod 文件并发出一个替换指令。

您可以通过编辑 go.mod 来完成此操作,使用 ``

go mod edit -require YOUR_GO_PATH/test-operator/api/v1alpha1@v0.0.0 # Only if you didn't already resolve the module
go mod edit -replace YOUR_GO_PATH/test-operator/api/v1alpha1@v0.0.0=./api/v1alpha1
go mod tidy

请注意,我们使用了 API 模块的占位符版本 v0.0.0。如果您已经发布过一次您的 API 模块,您也可以使用真实版本。然而,这仅在 API 模块已经在版本控制系统中可用的情况下有效。

使用 Go 工作区

对于 Go 工作区,您将不会自己编辑 go.mod 文件,而是依赖 Go 中的工作区支持。

要为您的项目初始化工作空间,请在项目根目录中运行 go work init

现在让我们将这两个模块包含到我们的工作区中:

go work use . # This includes the main module with the controller
go work use api/v1alpha1 # This is the API submodule
go work sync

这将导致像 go rungo build 这样的命令尊重工作区,并确保使用本地解析。

您可以在本地进行此操作,而无需构建您的模块。

在使用 go.work 文件时,建议不要将它们提交到版本库,并将其添加到 .gitignore 中。

go.work
go.work.sum

在发布时,请确保将环境变量 GOWORK=off 设置为关闭(可通过 go env GOWORK 验证),以确保发布过程不受可能已提交的 go.work 文件的影响。

调整 Dockerfile

在构建您的控制器镜像时,kubebuilder 默认无法处理多个模块。您需要手动将新的 API 模块添加到依赖项的下载中:

创建一个新的 API 和控制器版本

因为你调整了默认布局,所以在发布你的操作员的第一个版本之前,请确保熟悉 mono-repo/multi-module 发布,涉及多个不同子目录中的 go.mod 文件。

假设创建了一个单一的API,发布流程可能如下所示:

git commit
git tag v1.0.0 # 这是你的主要模块发布git tag api/v1.0.0 # 这是您的 API 发布版本
go mod edit -require YOUR_GO_PATH/test-operator/api@v1.0.0 # 现在我们在主模块中依赖于 API 模块。go mod edit -dropreplace YOUR_GO_PATH/test-operator/api/v1alpha1 # 这将移除本地开发中针对 Go 模块的替换指令,这意味着将使用来自版本控制系统的源代码,而不是您本地检出的单一代码库中的源代码。git push origin main v1.0.0 api/v1.0.0

在此之后,您的模块将在 VCS 中可用,您不再需要本地替换。然而,如果您进行本地更改,请确保根据 replace 指令调整您的行为。

重用您提取的 API 模块

每当您想要在一个独立的 kubebuilder 中重用您的 API 模块时,我们会假设您遵循了 使用外部类型 的指南。当您到达“编辑 API 文件“这一步时,只需使用以下方式导入依赖项:

go get YOUR_GO_PATH/test-operator/api@v1.0.0

然后按照指南中的说明使用它。