配置 envtest 以进行集成测试
controller-runtime/pkg/envtest
Go 库帮助您通过设置和启动 etcd 和 Kubernetes API 服务器的实例来编写控制器的集成测试,而无需 kubelet、controller-manager 或其他组件。
安装
安装二进制文件的过程非常简单,只需运行 make envtest
。默认情况下,envtest
会将 Kubernetes API 服务器的二进制文件下载到您项目中的 bin/
文件夹。make test
是一个一条龙的解决方案,用于下载二进制文件、设置测试环境和运行测试。
您可以参考 Kubebuilder 脚手架的 Makefile,观察到 envtest 的设置在所有 controller-runtime 版本中始终保持一致。从 release-0.19
开始,它被配置为自动从正确的位置下载工件,确保 kubebuilder 用户不受影响。
在隔离/断网环境中的安装
如果您想下载包含二进制文件的 tarball,以便在不联网的环境中使用,可以使用 setup-envtest
来本地下载所需的二进制文件。有很多方法可以配置 setup-envtest
以避免连接互联网,您可以在 这里 阅读相关内容。以下示例将展示如何使用 setup-envtest
设置的大多数默认值来安装 Kubernetes API 二进制文件。
下载二进制文件
make envtest
将会把 setup-envtest
二进制文件下载到 ./bin/
目录。
make envtest
使用 setup-envtest
安装二进制文件时,会将其存储在特定于操作系统的位置,您可以在 这里 阅读更多相关信息。
./bin/setup-envtest use 1.31.0
更新测试目标
一旦这些二进制文件安装完成,修改 test
make 目标,加入 -i
,如下所示。-i
只会检查本地安装的二进制文件,而不会访问远程资源。你也可以设置 ENVTEST_INSTALLED_ONLY
环境变量。
test: manifests generate fmt vet
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) 使用 $(ENVTEST_K8S_VERSION) -i --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out
注意:ENVTEST_K8S_VERSION
需要与您上面下载的 setup-envtest
匹配。否则,您将看到如下错误。
no such version (1.24.5) exists on disk for this architecture (darwin/amd64) -- try running `list -i` to see what"在磁盘上"
编写测试
在集成测试中使用 envtest
的一般流程如下:
import sigs.k8s.io/controller-runtime/pkg/envtest
//指定 testEnv 配置
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
}
//开始测试环境
cfg, err = testEnv.Start()
//编写测试逻辑
//停止测试环境
err = testEnv.Stop()
kubebuilder
在它生成的 /controllers
目录下的 ginkgo 测试套件中,为您处理测试环境的样板代码的设置和拆卸。
测试运行的日志以 test-env
为前缀。
配置您的测试控制平面
Controller-runtime 的 envtest 框架需要本地有 kubectl
、kube-apiserver
和 etcd
可执行文件,以模拟真实集群的 API 部分。
make test
命令将把这些二进制文件安装到 bin/
目录,并在运行使用 envtest
的测试时使用它们。即,
./bin/k8s/
└── 1.25.0-darwin-amd64
├── etcd
├── kube-apiserver
└── kubectl
您可以使用环境变量和/或标志来指定在集成测试中 kubectl
、api-server
和 etcd
的设置。
环境变量
变量名 | Type | 何时使用 |
---|---|---|
使用现有集群 | 布尔值 | 与其建立一个本地控制平面,不如指向现有集群的控制平面。 |
KUBEBUILDER_ASSETS | 目录路径 | 将集成测试指向一个包含所有二进制文件的目录(api-server、etcd 和 kubectl)。 |
TEST_ASSET_KUBE_APISERVER ,TEST_ASSET_ETCD ,TEST_ASSET_KUBECTL | 分别指向 api-server、etcd 和 kubectl 二进制文件的路径 | 类似于 KUBEBUILDER_ASSETS ,但更为细化。指向集成测试使用除了默认二进制文件以外的其他二进制文件。这些环境变量还可以用来确保特定测试使用期望版本的这些二进制文件运行。 |
KUBEBUILDER_CONTROLPLANE_START_TIMEOUT 和 KUBEBUILDER_CONTROLPLANE_STOP_TIMEOUT | 支持 time.ParseDuration 的格式的持续时间。 | 为测试控制平面指定不同于默认值的超时时间,以便(分别)启动和停止;任何超出这些时间的测试运行将失败。 |
KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT | 布尔值 | 将其设置为 true 以将控制平面的 stdout 和 stderr 附加到 os.Stdout 和 os.Stderr。当调试测试失败时,这可能很有用,因为输出将包括来自控制平面的输出。 |
请注意,test
这个 Makefile 目标将确保在使用它时所有设置都正确。不过,如果您想在不使用 Makefile 目标的情况下运行测试,例如通过 IDE,则可以直接在您的 suite_test.go
代码中设置环境变量:
var _ = BeforeSuite(func(done Done) {
Expect(os.Setenv("TEST_ASSET_KUBE_APISERVER", "../bin/k8s/1.25.0-darwin-amd64/kube-apiserver")).To(Succeed())
Expect(os.Setenv("TEST_ASSET_ETCD", "../bin/k8s/1.25.0-darwin-amd64/etcd")).To(Succeed())
Expect(os.Setenv("TEST_ASSET_KUBECTL", "../bin/k8s/1.25.0-darwin-amd64/kubectl")).To(Succeed())
// OR
Expect(os.Setenv("KUBEBUILDER_ASSETS", "../bin/k8s/1.25.0-darwin-amd64")).To(Succeed())
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
testenv = &envtest.Environment{}
_, err := testenv.Start()
Expect(err).NotTo(HaveOccurred())
close(done)
}, 60)
var _ = AfterSuite(func() {
Expect(testenv.Stop()).To(Succeed())
Expect(os.Unsetenv("TEST_ASSET_KUBE_APISERVER")).To(Succeed())
Expect(os.Unsetenv("TEST_ASSET_ETCD")).To(Succeed())
Expect(os.Unsetenv("TEST_ASSET_KUBECTL")).To(Succeed())
})
旗帜
这是一个示例,展示了在你的集成测试中修改启动 API 服务器的标志,与 envtest.DefaultKubeAPIServerFlags
中的默认值进行对比。
customApiServerFlags := []string{
"--secure-port=6884",
"--admission-control=MutatingAdmissionWebhook",
}
apiServerFlags := append([]string(nil), envtest.DefaultKubeAPIServerFlags...)
apiServerFlags = append(apiServerFlags, customApiServerFlags...)
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
KubeAPIServerFlags: apiServerFlags,
}
测试考虑事项
除非您正在使用现有的集群,否则请记住,在测试上下文中没有运行内置控制器。在某些方面,测试控制平面将与“真实“集群表现不同,这可能会影响您编写测试的方式。一个常见的例子是垃圾收集;因为没有控制器监视内置资源,尽管设置了 OwnerReference
,对象也不会被删除。
为了测试删除生命周期是否正常工作,请测试所有权,而不是仅仅断言存在性。例如:
expectedOwnerReference := v1.OwnerReference{
Kind: "MyCoolCustomResource",
APIVersion: "my.api.example.com/v1beta1",
UID: "d9607e19-f88f-11e6-a518-42010a800195",
Name: "userSpecifiedResourceName",
}
Expect(deployment.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference))
Cert-Manager 和 Prometheus 选项
使用 Kubebuilder 创建的项目可以启用 metrics
和 cert-manager
选项。请注意,当我们使用 ENV TEST 时,我们旨在测试控制器及其调整过程。这被视为集成测试,因为 ENV TEST API 会在集群中进行测试,因此会下载并使用二进制文件来配置其前置要求。然而,其主要目的是对控制器进行 单元
测试。
因此,在常见情况下,测试对账时您不需要关注这些选项。但是,如果您希望在安装了 Prometheus 和 Cert-manager 的情况下进行测试,可以在运行测试之前添加安装它们所需的步骤。以下是一个示例。
// 在测试之前添加安装 Prometheus operator 和 cert-manager 的操作。 BeforeEach(func() {
By("installing prometheus operator")
Expect(utils.InstallPrometheusOperator()).To(Succeed())
By("installing the cert-manager")
Expect(utils.InstallCertManager()).To(Succeed())
})
// 您也可以在测试后将它们移除::
AfterEach(func() {
By("uninstalling the Prometheus manager bundle")
utils.UninstallPrometheusOperManager()
By("uninstalling the cert-manager bundle")
utils.UninstallCertManager()
})
请检查以下示例,了解如何实现上述操作:
const (
prometheusOperatorVersion = "0.51"
prometheusOperatorURL = "https://raw.githubusercontent.com/prometheus-operator/" + "prometheus-operator/release-%s/bundle.yaml"
certmanagerVersion = "v1.5.3"
certmanagerURLTmpl = "https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml"
)
func warnError(err error) {
_, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err)
}
// InstallPrometheusOperator 安装 prometheus 操作器,用于导出启用的指标。func InstallPrometheusOperator() error {
url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
cmd := exec.Command("kubectl", "apply", "-f", url)
_, err := Run(cmd)
return err
}
// UninstallPrometheusOperator 卸载 Prometheusfunc UninstallPrometheusOperator() {
url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
cmd := exec.Command("kubectl", "delete", "-f", url)
if _, err := Run(cmd); err != nil {
warnError(err)
}
}
// UninstallCertManager 卸载证书管理器func UninstallCertManager() {
url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
cmd := exec.Command("kubectl", "delete", "-f", url)
if _, err := Run(cmd); err != nil {
warnError(err)
}
}
// InstallCertManager 安装证书管理器包。func InstallCertManager() error {
url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
cmd := exec.Command("kubectl", "apply", "-f", url)
if _, err := Run(cmd); err != nil {
return err
}
// 等待 cert-manager-webhook 准备就绪,如果在集群上卸载后重新安装 cert-manager,可能需要一些时间。 cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook",
"--for", "condition=Available",
"--namespace", "cert-manager",
"--timeout", "5m",
)
_, err := Run(cmd)
return err
}
// LoadImageToKindClusterWithName 将本地 Docker 镜像加载到 Kind 集群中func LoadImageToKindClusterWithName(name string) error {
cluster := "kind"
if v, ok := os.LookupEnv("KIND_CLUSTER"); ok {
cluster = v
}
kindOptions := []string{"load", "docker-image", name, "--name", cluster}
cmd := exec.Command("kind", kindOptions...)
_, err := Run(cmd)
return err
}
然而,请注意,对于指标和cert-manager的测试可能更适合作为端到端(e2e)测试,而不是在使用ENV TEST进行控制器测试时进行的测试。您可以查看在Operator-SDK 仓库中实施的示例示例,了解如何编写e2e测试以确保您项目的基本工作流程。此外,请注意,您可以在已配置集群上运行测试,使用现有集群进行测试的选项。
testEnv = &envtest.Environment{
UseExistingCluster: true,
}