本文发表已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已不正确。

每个人都可以使用的 Kubernetes 端到端测试

越来越多曾经是 Kubernetes 一部分的组件现在在 Kubernetes 之外开发。例如,存储驱动程序曾经被编译到 Kubernetes 二进制文件中,然后被移动到主机上的独立的 FlexVolume 二进制文件中,现在作为容器存储接口 (CSI) 驱动程序交付,这些驱动程序部署在 Kubernetes 集群本身的 pod 中。

这给开发这些组件的开发人员带来了挑战:如何对这些外部组件进行 Kubernetes 集群上的端到端 (E2E) 测试?用于测试 Kubernetes 本身的 E2E 框架具有所有必要的功能。但是,在 Kubernetes 之外尝试使用它很困难,只有仔细选择大量依赖项的正确版本才有可能。在 Kubernetes 1.13 中,E2E 测试变得简单得多。

这篇博客文章总结了 Kubernetes 1.13 中所做的更改。对于 CSI 驱动程序开发人员,它将介绍正在进行的努力,以使存储测试也可用于测试第三方 CSI 驱动程序。如何使用它们将基于两个英特尔 CSI 驱动程序进行展示

测试这些驱动程序是大多数这些增强功能背后的主要动机。

E2E 概述

E2E 测试包括几个阶段

  • 实现测试套件。这是这篇博客文章的重点。Kubernetes E2E 框架是用 Go 编写的。它依赖于 Ginkgo 来管理测试和 Gomega 来进行断言。这些工具支持“行为驱动开发”,它在“规范”中描述了预期的行为。在这篇博客文章中,“测试”用于引用一个单独的 Ginkgo.It 规范。测试使用 client-go 与 Kubernetes 集群交互。
  • 启动测试集群。像 kubetest 这样的工具可以在这里提供帮助。
  • 针对该集群运行 E2E 测试套件。Ginkgo 测试套件可以使用 ginkgo 工具或作为普通的 Go 测试使用 go test 运行。在没有任何参数的情况下,Kubernetes E2E 测试套件将像 kubectl 一样,基于 KUBECONFIG 等环境变量连接到默认集群。Kubetest 也知道如何运行 Kubernetes E2E 套件。

Kubernetes 1.13 中的 E2E 框架增强功能

以下所有增强功能都遵循相同的基本模式:它们使 E2E 框架在 Kubernetes 之外更加有用和易于使用,而不会更改原始 Kubernetes e2e.test 二进制文件的行为。

分离提供程序支持

在 Kubernetes <= 1.12 中使用 E2E 框架很困难的主要原因是依赖于特定于提供程序的 SDK,这些 SDK 引入了大量软件包。仅仅编译它就很不容易。

这些软件包中的许多仅在某些测试中需要。例如,测试预先配置的卷的挂载必须首先以管理员相同的方式配置这样一个卷,通过某些非 Kubernetes API 直接与特定的存储后端通信。

正在努力从核心 Kubernetes 中删除云提供商特定的测试PR #68483 中采用的方法可以看作是朝着该目标迈出的增量步骤:不是立即删除代码并破坏所有依赖于它的测试,而是将所有特定于云提供商的代码移动到 test/e2e/framework/providers 下的可选软件包中。然后,E2E 框架通过 一个接口 访问它,该接口由每个供应商软件包单独实现。

E2E 测试套件的作者决定将哪些软件包导入到测试套件中。然后通过 --provider 命令行标志激活供应商支持。Kubernetes e2e.test 二进制文件在 1.13 和 1.14 中仍然包含与 1.12 中相同的提供商支持。也可以不包含任何软件包,这意味着只有通用提供商可用

  • “skeleton”:集群通过 Kubernetes API 访问,没有其他操作
  • “local”:像“skeleton”一样,但此外,kubernetes/kubernetes/cluster 中的脚本可以在测试套件运行后通过 ssh 检索日志

外部文件

测试可能必须在运行时读取其他文件,例如 .yaml 清单。但是 Kubernetes e2e.test 二进制文件应该是可用且完全独立的,因为这简化了其交付和运行。Kubernetes 构建系统中的解决方案是将 test/e2e/testing-manifests 下的所有文件通过 go-bindata 链接到二进制文件中。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 包强制执行,该包在第二次注册标志时会发生 panic。新的 config 包简化了多个选项的定义,这些选项存储在一个结构中。

总结一下,这就是现在处理参数的方式

  • 测试包中的 init 代码定义了测试和参数。实际的参数尚不可用,因此测试定义不能使用它们。
  • 测试套件的 init 代码解析参数和(可选)配置文件。
  • 测试运行,现在可以使用参数值。

然而,最近有人指出,希望且有可能不将测试设置公开为命令行标志,而只通过配置文件设置它们。有一个开放错误和一个待定 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 文件,修补这些项目(例如,设置为当前测试创建的命名空间),并创建它们。目前用于从用于通过 kubectl 部署的完全相同的 .yaml 文件为每个测试重新部署 CSI 驱动程序。如果 CSI 驱动程序支持在不同的名称下运行,则测试是完全独立的,并且可以并行运行。

但是,重新部署驱动程序会减慢测试执行速度,并且它不涵盖对驱动程序的并发操作。更现实的测试场景是在启动测试集群时部署一次驱动程序,然后针对该部署运行所有测试。一旦清楚如何扩展测试集群启动,使其还包括安装 CSI 驱动程序等其他实体,最终 Kubernetes E2E 测试将转向该模型。

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 测试套件 HOWTO

测试套件初始化

第一步是设置定义测试套件所需的样板代码。在 Kubernetes E2E 中,这是在 e2e.goe2e_test.go 文件中完成的。它也可以在单个 e2e_test.go 文件中完成。Kubernetes 在 e2e_test.go 中导入所有各种提供程序、内部测试、Viper 配置支持和 bindata 文件查找。e2e.go 控制实际执行,包括一些集群准备和指标收集。

一个更简单的起点是来自 PMEM-CSIe2e_[test].go 文件。它不使用任何提供程序,没有 Viper,没有 bindata,只导入存储测试。

像 PMEM-CSI 一样,OIM 放弃了所有额外的功能,但由于它将自定义集群启动直接集成到测试套件中,所以稍微复杂一些。在这种情况下,这很有用,因为一些额外的组件必须在主机端运行。通过直接在 E2E 二进制文件中运行它们,使用 dlv 进行交互式调试变得更容易。

两个 CSI 驱动程序都遵循 Kubernetes 的示例,并使用 test/e2e 目录作为其测试套件,但任何其他目录和其他文件名也都可以工作。

添加 E2E 存储测试

测试由导入到测试套件中的包定义。E2E 测试唯一特有的事情是它们使用 framework.NewDefaultFramework 实例化一个 framework.Framework 指针(通常称为 f)。此变量在每个测试的 BeforeEach 中重新初始化,并在 AfterEach 中释放。它在运行时(并且仅在运行时!)具有 f.ClientSetf.Namespace,可供测试使用。

PMEM-CSI 存储测试导入 Kubernetes 存储测试套件,并为必须已安装在测试集群中的 PMEM-CSI 驱动程序设置一个配置测试实例。存储测试套件更改存储类以使用不同的文件系统类型运行测试。由于此要求,存储类是从 .yaml 文件创建的。

解释框架中提供的所有各种实用程序方法超出了本博客文章的范围。阅读现有测试和框架的源代码是入门的好方法。

供应商化

即使在消除了许多不必要的依赖项之后,供应商化 Kubernetes 代码仍然不容易。k8s.io/kubernetes 不打算包含在其他项目中,并且没有以 dep 等工具理解的方式定义其依赖项。其他 k8s.io 包旨在被包含,但是尚未遵循语义版本控制或未标记任何版本(k8s.io/kube-openapik8s.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 移动到暂存存储库并重组它,使其更加模块化 (#74352)。
  • 通过将更多代码移动到 test/e2e/framework 中来简化 e2e.go (#74353)。
  • 从 Kubernetes E2E 测试套件中删除特定于提供程序的代码 (#70194)。

特别感谢本文的审阅者