本文发布已超过一年。较旧的文章可能包含过时内容。请检查页面信息自发布以来是否已失效。
面向所有人的 Kubernetes 端到端测试
越来越多曾经作为 Kubernetes 一部分的组件现在正在 Kubernetes 外部开发。例如,存储驱动过去编译到 Kubernetes 二进制文件中,后来转移到主机上的独立的 FlexVolume 二进制文件中,现在则作为容器存储接口 (CSI) 驱动交付,部署在 Kubernetes 集群本身的 Pod 中。
这给开发此类组件的开发者带来了挑战:如何对这些外部组件在 Kubernetes 集群上进行端到端 (E2E) 测试?用于测试 Kubernetes 本身的 E2E 框架具备所有必要功能。然而,尝试在 Kubernetes 外部使用它很困难,只有通过仔细选择大量依赖项的正确版本才可能实现。E2E 测试在 Kubernetes 1.13 中变得更加简单。
这篇博文总结了 Kubernetes 1.13 中所做的更改。对于 CSI 驱动开发者,它将涵盖一项正在进行中的工作,即使存储测试也适用于测试第三方 CSI 驱动。如何使用它们将基于两个 Intel CSI 驱动进行展示:
测试这些驱动是推动大多数这些增强功能的主要动力。
E2E 概述
E2E 测试包含几个阶段:
- 实现测试套件。这是本博文的主要焦点。Kubernetes E2E 框架是用 Go 语言编写的。它依赖 Ginkgo 来管理测试,依赖 Gomega 来进行断言。这些工具支持“行为驱动开发 (behavior driven development)”,它在“spec”中描述预期行为。在本博文中,“测试”用来指代单个
Ginkgo.It
spec。测试使用 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 二进制文件的行为。
拆分 Provider 支持
在 Kubernetes <= 1.12 版本中使用 E2E 框架困难的主要原因是依赖于 provider 特定 SDK,这引入了大量软件包。仅仅是编译它就很不容易。
其中许多软件包仅用于某些测试。例如,测试预置卷的挂载必须首先像管理员那样预置此类卷,通过某种非 Kubernetes API 直接与特定的存储后端通信。
有一项工作致力于从核心 Kubernetes 中移除云 provider 特定测试。PR #68483 中采取的方法可以看作是实现该目标的一个渐进步骤:而不是立即移除代码并破坏所有依赖它的测试,所有云 provider 特定代码都被移到了 test/e2e/framework/providers 下的可选软件包中。E2E 框架然后通过一个接口访问它,该接口由每个 vendor 软件包独立实现。
E2E 测试套件的作者决定将哪些软件包导入到测试套件中。vendor 支持随后通过 --provider
命令行标志激活。1.13 和 1.14 中的 Kubernetes e2e.test 二进制文件仍然包含与 1.12 相同的 provider 支持。也可以不包含任何软件包,这意味着只有通用 provider 可用:
- “skeleton”:集群通过 Kubernetes API 访问,仅此而已
- “local”:类似于“skeleton”,但此外 kubernetes/kubernetes/cluster 中的脚本可以在测试套件运行后通过 ssh 检索日志
外部文件
测试可能需要在运行时读取额外文件,例如 .yaml manifest。但是 Kubernetes e2e.test 二进制文件应该可直接使用且完全独立,因为这样可以简化分发和运行。Kubernetes 构建系统中的解决方案是使用 go-bindata 将 test/e2e/testing-manifests
下的所有文件链接到二进制文件中。E2E 框架过去对 go-bindata
的输出有硬依赖,现在bindata 支持是可选的。通过 testfiles 包访问文件时,文件将从不同源检索:
- 相对于通过
--repo-root
参数指定的目录 - 零个或多个 bindata chunk
测试参数
e2e.test 二进制文件接受控制测试执行的附加参数。2016 年,一项工作开始尝试用 Viper 配置文件替换所有 E2E 命令行参数。但该工作停滞了,这使得开发者对于如何处理测试特定参数没有明确指导。
v1.12 中的方法是将所有标志添加到中央 test/e2e/framework/test_context.go 文件中,这对于独立于框架开发的测试不起作用。自 PR #69105 以来,建议是使用标准的 flag
包来定义其参数,在其自己的源代码中。标志名称必须是分层的,使用点分隔不同级别,例如 my.test.parameter
,并且必须是唯一的。flag 包强制执行唯一性,当第二次注册同一标志时会 panic。新的 config 包简化了多个选项的定义,这些选项存储在一个单一的 struct 中。
总结一下,现在参数是这样处理的:
- 测试包中的 init 代码定义了测试和参数。实际参数值尚不可用,因此测试定义无法使用它们。
- 测试套件的 init 代码解析参数和(可选)配置文件。
- 测试运行,现在可以使用参数值。
然而,最近有人指出,理想的做法是将测试设置不暴露为命令行标志,而只通过配置文件设置,这是可能且期望的。关于这一点有一个未解决的 bug 和一个待处理的 PR。
Viper 支持已得到增强。就像 provider 支持一样,它是完全可选的。通过导入 viperconfig
包并在解析普通命令行标志后调用它,它被引入到 e2e.test 二进制文件中。这样实现是为了确保所有可以通过命令行标志设置的变量,在标志出现在 Viper 配置文件中时也能被设置。例如,Kubernetes v1.13 的 e2e.test
二进制文件接受 --viper-config=/tmp/my-config.yaml
参数,当该文件内容如下时,会设置 my.test.parameter
的值为 value
:
在旧版本的 Kubernetes 中,该选项只能从当前目录加载文件,必须省略后缀,并且只有少数参数能通过这种方式设置。注意,Viper 仍然存在一个限制:它通过将配置文件条目与已知标志匹配来工作,不会警告未知配置文件条目,因此会遗漏拼写错误。Kubernetes 的一个更好的配置文件解析器仍在进行中。
从 .yaml manifest 创建条目
在 Kubernetes 1.12 中,对从 .yaml 文件加载单个条目有一些支持,但创建该条目需要手动编写代码完成。现在框架有了新方法,可以加载包含多个条目的 .yaml 文件,修补这些条目(例如,设置当前测试创建的命名空间),并创建它们。这目前被用于针对每个测试从与通过 kubectl 部署所用的完全相同的 .yaml 文件重新部署 CSI 驱动。如果 CSI 驱动支持在不同名称下运行,那么测试是完全独立的,可以并行运行。
然而,重新部署驱动会减慢测试执行速度,并且它不涵盖针对驱动的并发操作。更真实的测试场景是在启动测试集群时部署一次驱动,然后针对该部署运行所有测试。最终 Kubernetes E2E 测试将转向这种模式,一旦明确了如何扩展测试集群的启动过程使其也能包含安装 CSI 驱动等附加实体。
Kubernetes 1.14 中即将推出的增强功能
重用存储测试
能够在 Kubernetes 外部使用该框架使得构建自定义测试套件成为可能。但是一个没有测试的测试套件仍然无用。现有的一些测试,特别是存储相关的测试,也可以应用于 out-of-tree 组件。感谢 Masaki Kimura 所做的工作,Kubernetes 1.13 中的存储测试定义为可以针对不同的驱动多次实例化。
但历史往往会重演。与 providers 一样,定义这些测试的软件包也引入了所有 in-tree 存储后端的驱动定义,这反过来引入了比所需更多的额外软件包。这个问题已在即将发布的 Kubernetes 1.14 中得到解决。
跳过不受支持的测试
一些存储测试依赖于集群的特定功能(例如在支持 XFS 的主机上运行)或驱动的功能(例如支持块卷)。这些条件在测试运行时进行检查,当条件不满足时,导致测试被跳过。好处是它记录了测试未运行的原因解释。
启动测试很慢,特别是当它必须首先部署 CSI 驱动程序时,但在其他场景下也是如此。在快速集群上,为测试创建命名空间测量需要 5 秒,并且会产生大量嘈杂的测试输出。本来可以通过跳过不支持的测试定义来解决这个问题,但这使得报告为什么某个测试甚至不是测试套件的一部分变得棘手。这种方法已被放弃,取而代之的是重新组织存储测试套件,使其首先检查条件,然后再执行更昂贵的测试设置步骤。
更易读的测试定义
同一个 PR 还重写了测试,使其像传统的 Ginkgo 测试一样运行,测试用例及其局部变量都在一个函数中。
测试外部驱动程序
构建自定义的 E2E 测试套件仍然需要相当多的工作。将在Kubernetes 1.14 测试存档中分发的 e2e.test
二进制文件将具备测试已安装的存储驱动程序的能力,而无需重建测试套件。有关更多说明,请参阅这份README 文件。
E2E 测试套件 HOWTO
测试套件初始化
第一步是设置定义测试套件所需的样板代码。在 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 测试实例。存储测试套件会更改 StorageClass 以运行具有不同文件系统类型的测试。因此,StorageClass 是从 .yaml 文件创建的。
解释框架中可用的各种实用方法超出了本文的范围。阅读现有测试和框架的源代码是入门的好方法。
Vendoring
Vendoring Kubernetes 代码仍然不简单,即使消除了许多不必要的依赖项。k8s.io/kubernetes
不打算包含在其他项目中,并且其依赖项的定义方式不被 dep
等工具理解。其他 k8s.io
包旨在包含,但尚未遵循语义版本控制或未标记任何发布版本(k8s.io/kube-openapi
, k8s.io/utils
)。
PMEM-CSI 使用dep。其Gopkg.toml 文件是一个很好的起点。它启用了裁剪(dep 默认不启用)并将某些项目锁定到与所使用的 Kubernetes 版本兼容的版本。当 一旦编译成功并且集群已经设置好,命令 Kubernetes E2E 框架由 SIG-testing 中的 testing-commons 子项目负责。请参阅该页面获取联系信息。 有各种任务可以开展工作,包括但不限于: 特别感谢本文的审阅者:dep
没有选择兼容版本时,检查 Kubernetes 的编译并运行测试套件go test ./test/e2e -args -help
是测试测试套件是否编译的最快方法。go test -timeout=0 -v ./test/e2e -ginkgo.v
将运行所有测试。为了并行运行测试,请改用 ginkgo -p ./test/e2e
命令。参与其中
test/e2e/framework
来简化 e2e.go
(#74353)。