本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
人人可用的 Kubernetes 端到端测试
越来越多的曾是 Kubernetes 一部分的组件,现在都在 Kubernetes 之外开发。例如,存储驱动程序曾被编译到 Kubernetes 二进制文件中,然后被移动到主机上的独立 FlexVolume 二进制文件中,现在则以容器存储接口 (CSI) 驱动程序的形式交付,部署在 Kubernetes 集群内部的 Pod 中。
这给开发此类组件的开发者带来了挑战:如何对这些外部组件进行 Kubernetes 集群上的端到端(E2E)测试?用于测试 Kubernetes 本身E2E框架具有所有必要的功能。然而,尝试在 Kubernetes 之外使用它很困难,只有仔细选择大量依赖项的正确版本才有可能。在 Kubernetes 1.13 中,E2E 测试已经变得简单得多。
这篇博文总结了 Kubernetes 1.13 中所做的更改。对于 CSI 驱动开发者,它将涵盖正在进行的努力,即如何使存储测试也可用于测试第三方 CSI 驱动。如何使用它们将以两个 Intel CSI 驱动为例进行演示。
测试这些驱动是这些改进的主要动机。
E2E 概述
端到端(E2E)测试包含几个阶段:
- 实现测试套件。这是本博文的重点。Kubernetes E2E 框架用 Go 编写。它依赖 Ginkgo 进行测试管理,依赖 Gomega 进行断言。这些工具支持“行为驱动开发”,它在“规格”中描述预期行为。在本博文中,“测试”用于指代单个
Ginkgo.It
规格。测试使用 client-go 与 Kubernetes 集群交互。 - 启动测试集群。诸如 kubetest 等工具可以在这方面提供帮助。
- 针对该集群运行 E2E 测试套件。Ginkgo 测试套件可以使用
ginkgo
工具运行,也可以作为常规 Go 测试使用go test
运行。不带任何参数时,Kubernetes E2E 测试套件将根据 KUBECONFIG 等环境变量连接到默认集群,就像 kubectl 一样。Kubetest 也知道如何运行 Kubernetes E2E 套件。
Kubernetes 1.13 中的 E2E 框架增强
以下所有增强都遵循相同的基本模式:它们使 E2E 框架在 Kubernetes 之外更有用且更易于使用,同时不改变原始 Kubernetes e2e.test 二进制文件的行为。
拆分提供商支持
在 Kubernetes <= 1.12 中,使用 E2E 框架困难的主要原因是它依赖于特定于提供商的 SDK,这引入了大量的包。仅仅编译它就非同小可。
其中许多包只在特定测试中需要。例如,测试预先配置的卷的挂载必须首先像管理员一样配置这样的卷,通过非 Kubernetes API 直接与特定的存储后端通信。
目前正在努力将云提供商特定测试从核心 Kubernetes 中移除。在PR #68483中采取的方法可以看作是实现这一目标的一个增量步骤:所有云提供商特定代码都被移到test/e2e/framework/providers下的可选包中,而不是立即删除代码并破坏所有依赖它的测试。E2E 框架然后通过由每个供应商包独立实现的接口访问它。
E2E 测试套件的作者决定将哪些包导入到测试套件中。然后通过 --provider
命令行标志激活供应商支持。Kubernetes 1.13 和 1.14 中的 e2e.test 二进制文件仍然包含与 1.12 中相同的供应商支持。也可以不包含任何包,这意味着只有通用提供商可用:
- “骨架”:集群通过 Kubernetes API 访问,仅此而已。
- “本地”:类似于“骨架”,但此外,kubernetes/kubernetes/cluster 中的脚本可以在测试套件运行后通过 ssh 检索日志。
外部文件
测试可能需要在运行时读取额外的文件,例如 .yaml 清单。但是 Kubernetes e2e.test 二进制文件应该能够独立使用和运行,因为这简化了分发和运行。Kubernetes 构建系统中的解决方案是使用 go-bindata 将 test/e2e/testing-manifests
下的所有文件链接到二进制文件中。E2E 框架曾经对 go-bindata
的输出有硬依赖,现在bindata 支持是可选的。通过 testfiles 包访问文件时,文件将从不同的源检索:
- 相对于用 `--repo-root` 参数指定的目录。
- 零个或多个 bindata 块
测试参数
e2e.test 二进制文件接受控制测试执行的额外参数。2016 年,曾开始努力用 Viper 配置文件替换所有 E2E 命令行参数。但该工作停滞不前,导致开发者在如何处理特定于测试的参数方面缺乏明确的指导。
v1.12 中的方法是将所有标志添加到中央 test/e2e/framework/test_context.go,但这不适用于独立于框架开发的测试。自 PR #69105 以来,建议使用普通的 flag
包在其自己的源代码中定义其参数。标志名称必须是分层的,用点分隔不同级别,例如 my.test.parameter
,并且必须是唯一的。唯一性由 flag
包强制执行,当第二次注册标志时会发生恐慌。新的 config 包简化了多个选项的定义,这些选项存储在单个结构体中。
总而言之,参数现在是这样处理的:
- 测试包中的初始化代码定义了测试和参数。实际的参数*值*尚未可用,因此测试定义无法使用它们。
- 测试套件的初始化代码解析参数和(可选地)配置文件。
- 测试运行,现在可以使用参数值。
然而,最近有人指出,不将测试设置作为命令行标志公开,而只通过配置文件设置它们是可取且可行的。关于这一点,有一个未解决的 bug 和一个待处理的 PR。
Viper 支持已得到增强。与提供者支持一样,它是完全可选的。通过导入 viperconfig
包并在解析常规命令行标志后调用它,它被拉入 e2e.test 二进制文件。这样实现,所有可以通过命令行标志设置的变量,当该标志出现在 Viper 配置文件中时,也会被设置。例如,Kubernetes v1.13 e2e.test
二进制文件接受 --viper-config=/tmp/my-config.yaml
,当该文件包含以下内容时,它将 my.test.parameter
设置为 value
: my: test: parameter: value
在较旧的 Kubernetes 版本中,该选项只能从当前目录加载文件,必须省略后缀,并且只有少数参数可以通过这种方式设置。请注意,Viper 的一个限制仍然存在:它通过将配置文件条目与已知标志匹配来工作,而不警告未知配置文件条目,从而导致拼写错误未被检测到。一个更好的 Kubernetes 配置文件解析器仍在开发中。
从 .yaml 清单创建项目
在 Kubernetes 1.12 中,虽然支持从 .yaml 文件加载单个项目,但创建该项目必须通过手写代码完成。现在,框架有了新方法,用于加载包含多个项目的 .yaml 文件,修补这些项目(例如,为当前测试设置创建的命名空间),并创建它们。目前,这用于为每个测试重新部署 CSI 驱动程序,使用的 .yaml 文件与通过 kubectl 部署时完全相同。如果 CSI 驱动程序支持以不同名称运行,那么测试将完全独立并可以并行运行。
然而,重新部署驱动程序会减慢测试执行速度,并且它不覆盖针对驱动程序的并发操作。更真实的测试场景是在启动测试集群时部署一次驱动程序,然后针对该部署运行所有测试。最终,Kubernetes E2E 测试将转向这种模式,一旦更清楚如何扩展测试集群的启动以包含安装 CSI 驱动程序等附加实体。
Kubernetes 1.14 中即将推出的增强功能
复用存储测试
能够在 Kubernetes 之外使用框架,可以构建自定义测试套件。但一个没有测试的测试套件仍然毫无用处。现有的一些测试,特别是针对存储的测试,也可以应用于树外组件。感谢 Masaki Kimura 所做的工作,Kubernetes 1.13 中的存储测试被定义为可以针对不同的驱动程序多次实例化。
但历史总是惊人的相似。和提供商一样,定义这些测试的包也引入了所有树内存储后端的驱动定义,这反过来又引入了比所需更多的额外包。这个问题已在即将发布的 Kubernetes 1.14 中修复。
跳过不支持的测试
一些存储测试依赖于集群的特性(例如在支持 XFS 的主机上运行)或驱动程序的特性(例如支持块卷)。这些条件在测试运行时进行检查,当它们不满足时会导致跳过测试。好处是它记录了测试未运行的原因。
启动测试很慢,特别是当它必须首先部署 CSI 驱动程序时,以及在其他场景中。在快速集群上,为一个测试创建命名空间需要 5 秒,并且会产生大量嘈杂的测试输出。本可以通过跳过不支持测试的定义来解决这个问题,但那样报告测试为什么甚至不是测试套件的一部分就变得棘手了。这种方法已被放弃,转而重新组织存储测试套件,使其在执行更昂贵的测试设置步骤之前首先检查条件。
更具可读性的测试定义
相同的 PR 也重写了测试,使其像传统的 Ginkgo 测试一样运行,测试用例及其局部变量位于单个函数中。
测试外部驱动
构建一个自定义的 E2E 测试套件仍然需要大量工作。在Kubernetes 1.14 测试存档中分发的 e2e.test 二进制文件将能够测试已安装的存储驱动程序,而无需重新构建测试套件。有关进一步说明,请参阅此README。
E2E 测试套件操作指南
测试套件初始化
第一步是设置定义测试套件所需的样板代码。在Kubernetes E2E中,这是在e2e.go
和e2e_test.go
文件中完成的。它也可以在一个单独的e2e_test.go
文件中完成。Kubernetes在e2e_test.go
中导入了所有各种提供者、树内测试、Viper配置支持和bindata文件查找。e2e.go
控制实际执行,包括一些集群准备和指标收集。
一个更简单的起点是 PMEM-CSI 中的 e2e_[test].go
文件。它不使用任何提供商、Viper 和 bindata,只导入存储测试。
与 PMEM-CSI 一样,OIM 也放弃了所有额外功能,但它稍微复杂一些,因为它将自定义集群启动直接集成到测试套件中,这在这种情况下很有用,因为一些附加组件必须在主机端运行。通过直接在 E2E 二进制文件中运行它们,使用 dlv
进行交互式调试变得更容易。
这两个 CSI 驱动都遵循 Kubernetes 的示例,使用 test/e2e
目录作为其测试套件,但任何其他目录和文件名也都适用。
添加 E2E 存储测试
测试由导入到测试套件中的包定义。E2E 测试的唯一特殊之处在于它们使用 framework.NewDefaultFramework
实例化一个 framework.Framework
指针(通常称为 f
)。此变量在每个测试的 BeforeEach
中重新初始化,并在 AfterEach
中释放。它在运行时(且仅在运行时!)具有 f.ClientSet
和 f.Namespace
,可供测试使用。
PMEM-CSI 存储测试导入 Kubernetes 存储测试套件,并为已安装在测试集群中的 PMEM-CSI 驱动程序设置一个 Provisioning 测试实例。存储测试套件更改存储类以使用不同的文件系统类型运行测试。由于此要求,存储类是从 .yaml 文件创建的。
解释框架中所有可用的实用方法超出了本博客文章的范围。阅读现有测试和框架的源代码是入门的好方法。
供应商化
即使在消除了许多不必要的依赖之后,将 Kubernetes 代码供应商化仍然并非易事。k8s.io/kubernetes
不应该包含在其他项目中,并且没有以 dep
等工具理解的方式定义其依赖项。其他 k8s.io
包应该包含在内,但尚未遵循语义版本控制,或者没有标记任何发布(k8s.io/kube-openapi
、k8s.io/utils
)。
PMEM-CSI 使用 dep。它的 Gopkg.toml 文件是一个很好的起点。它启用了修剪(dep 默认不启用)并将某些项目锁定到与所使用的 Kubernetes 版本兼容的版本。当 dep
未选择兼容版本时,检查 Kubernetes 的 Godeps.json 有助于确定哪个修订版可能是正确的。
编译并运行测试套件
go test ./test/e2e -args -help
是测试测试套件是否编译的最快方法。
一旦它编译完成并且集群已经设置好,命令 go test -timeout=0 -v ./test/e2e -ginkgo.v
将运行所有测试。为了并行运行测试,请改用 ginkgo -p ./test/e2e
命令。
参与进来
Kubernetes E2E 框架由 SIG-testing 中的 testing-commons 子项目拥有。有关联系信息,请参阅该页面。
有各种任务可以完成,包括但不限于:
- 将 test/e2e/framework 移入一个 staging 仓库,并对其进行重构,使其更具模块化(#74352)。
- 通过将更多代码移到
test/e2e/framework
中来简化e2e.go
(#74353)。 - 从 Kubernetes E2E 测试套件中移除特定于提供商的代码 (#70194)。
特别感谢本文的审阅者:
- Olev Kartau (https://github.com/okartau)
- Mary Camp (https://github.com/MCamp859)