本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
CSI 驱动的测试
在开发 容器存储接口 (CSI) 驱动程序时,利用尽可能多的前期工作非常有用。这包括源代码(如示例 CSI hostpath 驱动程序)以及现有测试。除了节省时间,使用别人编写的测试还有一个优点,那就是它可以指出规范中可能被忽略的方面。
一篇关于端到端测试的早期博文已经展示了如何使用 Kubernetes 存储测试来测试第三方 CSI 驱动程序。这种方法在目标是添加自定义 E2E 测试时很有意义,但它需要花费相当大的精力来设置和维护一个测试套件。
如果目标仅仅是运行现有测试,那么有更简单的方法。这篇博文将介绍这些方法。
健全性测试
csi-test 健全性通过以各种方式调用 gRPC 方法并检查结果是否符合要求来确保 CSI 驱动程序符合 CSI 规范。尽管它目前托管在 Kubernetes-CSI 组织下,但它完全独立于 Kubernetes。测试通过其 Unix 域套接字连接到正在运行的 CSI 驱动程序,因此尽管测试是用 Go 编写的,但驱动程序本身可以用任何语言实现。
主 README 解释了如何将这些测试包含到现有 Go 测试套件中。更简单的替代方法是直接调用 csi-sanity 命令。
安装
从 csi-test v3.0.0 开始,您可以使用 go get github.com/kubernetes-csi/csi-test/cmd/csi-sanity
构建 csi-sanity
命令,您将在 $GOPATH/bin/csi-sanity
中找到编译后的二进制文件。
go get
总是从 master 分支构建最新版本。要构建特定版本,请获取源代码并运行 make -C cmd/csi-sanity
。这将生成 cmd/csi-sanity/csi-sanity
。
用法
csi-sanity
二进制文件是一个完整的 Ginkgo 测试套件,因此具有常规的 -gingko
命令行标志。特别是,-ginkgo.focus
和 -ginkgo.skip
可用于选择要运行或不运行的测试。
在测试运行期间,csi-sanity
通过创建 CSI 规范要求的 staging 和 target 目录并通过 gRPC 调用 CSI 驱动程序来模拟容器编排器 (CO) 的行为。驱动程序必须在调用 csi-sanity
之前启动。尽管测试目前只检查 gRPC 返回代码,但这可能会改变,因此驱动程序确实应该进行调用请求的更改,例如挂载文件系统。这可能意味着它必须以 root 身份运行。
调用 csi-sanity
时,必须通过 -csi.endpoint
参数指定至少一个 gRPC 端点,可以是 Unix 域套接字的绝对路径(unix:/tmp/csi.sock
),也可以是 TCP 的主机名加端口(dns:///my-machine:9000
)。然后 csi-sanity
将该端点用于节点和控制器操作。可以通过 -csi.controllerendpoint
指定控制器操作的单独端点。默认情况下,目录在 /tmp
中创建。这可以通过 -csi.mountdir
和 -csi.stagingdir
更改。
有些驱动程序无法部署,以保证所有内容都在同一主机上运行。在这种情况下,必须使用自定义脚本来处理目录:它们登录到 CSI 节点控制器运行的主机并在那里创建或删除目录。
例如,在 CI 测试期间,CSI hostpath 示例驱动程序在调用 csi-sanity
之前部署在真实的 Kubernetes 集群上,然后 csi-sanity
通过 socat
提供的端口转发连接到它。脚本用于创建和删除目录。
下面是使用 CSI hostpath 驱动程序 v1.2.0 版本如何重现此操作的示例
$ cd csi-driver-host-path
$ git describe --tags HEAD
v1.2.0
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
127.0.0.1 Ready <none> 42m v1.16.0
$ deploy/kubernetes-1.16/deploy-hostpath.sh
applying RBAC rules
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-provisioner/v1.4.0/deploy/kubernetes/rbac.yaml
...
deploying hostpath components
deploy/kubernetes-1.16/hostpath/csi-hostpath-attacher.yaml
using image: quay.io/k8scsi/csi-attacher:v2.0.0
service/csi-hostpath-attacher created
statefulset.apps/csi-hostpath-attacher created
deploy/kubernetes-1.16/hostpath/csi-hostpath-driverinfo.yaml
csidriver.storage.k8s.io/hostpath.csi.k8s.io created
deploy/kubernetes-1.16/hostpath/csi-hostpath-plugin.yaml
using image: quay.io/k8scsi/csi-node-driver-registrar:v1.2.0
using image: quay.io/k8scsi/hostpathplugin:v1.2.0
using image: quay.io/k8scsi/livenessprobe:v1.1.0
...
service/hostpath-service created
statefulset.apps/csi-hostpath-socat created
07:38:46 waiting for hostpath deployment to complete, attempt #0
deploying snapshotclass
volumesnapshotclass.snapshot.storage.k8s.io/csi-hostpath-snapclass created
$ cat >mkdir_in_pod.sh <<EOF
#!/bin/sh
kubectl exec csi-hostpathplugin-0 -c hostpath -- mktemp -d /tmp/csi-sanity.XXXXXX
EOF
$ cat >rmdir_in_pod.sh <<EOF
#!/bin/sh
kubectl exec csi-hostpathplugin-0 -c hostpath -- rmdir "\$@"
EOF
$ chmod u+x *_in_pod.sh
$ csi-sanity -ginkgo.v \
-csi.endpoint dns:///127.0.0.1:$(kubectl get "services/hostpath-service" -o "jsonpath={..nodePort}") \
-csi.createstagingpathcmd ./mkdir_in_pod.sh \
-csi.createmountpathcmd ./mkdir_in_pod.sh \
-csi.removestagingpathcmd ./rmdir_in_pod.sh \
-csi.removemountpathcmd ./rmdir_in_pod.sh
Running Suite: CSI Driver Test Suite
====================================
Random Seed: 1570540138
Will run 72 of 72 specs
...
Controller Service [Controller Server] ControllerGetCapabilities
should return appropriate capabilities
/nvme/gopath/src/github.com/kubernetes-csi/csi-test/pkg/sanity/controller.go:111
STEP: connecting to CSI driver
STEP: creating mount and staging directories
STEP: checking successful response
•
------------------------------
Controller Service [Controller Server] GetCapacity
should return capacity (no optional values added)
/nvme/gopath/src/github.com/kubernetes-csi/csi-test/pkg/sanity/controller.go:149
STEP: reusing connection to CSI driver at dns:///127.0.0.1:30056
STEP: creating mount and staging directories
...
Ran 53 of 72 Specs in 148.206 seconds
SUCCESS! -- 53 Passed | 0 Failed | 0 Pending | 19 Skipped
PASS
一些评论
- 这些测试的源代码位于
pkg/sanity
包中。 - 如何确定节点的外部 IP 地址取决于集群。在此示例中,集群是使用
hack/local-up-cluster.sh
启动的,因此它在本地主机 (127.0.0.1
) 上运行。它使用 Kubernetes 分配的端口,如上文通过kubectl get "services/hostpath-service"
获得。Kubernetes-CSI CI 使用 kind,并且 Docker 命令可以在那里使用。 - 创建脚本必须打印最终目录。为每个测试用例使用唯一的目录的优点是,如果在一个测试用例中出现问题,其他测试用例仍然可以从头开始。
- “暂存目录”,即 CSI 规范中的
NodePublishVolumeRequest.target_path
,必须由 CSI 驱动程序创建和删除,而 CO 负责父目录。csi-sanity
通过创建一个目录,然后将该目录路径附加/target
交给 CSI 驱动程序来处理。Kubernetes 在这方面出了错,并创建了实际的target_path
目录,因此希望与 Kubernetes 配合使用的 CSI 驱动程序目前必须宽容,并且在该目录已存在时不得失败。 - “挂载目录”对应于
NodeStageVolumeRequest.staging_target_path
,并且确实由 CO(即csi-sanity
)创建。
端到端测试
与 csi-sanity
不同,端到端测试通过 Kubernetes API 与 CSI 驱动程序交互,即它模拟普通用户的操作,例如创建 PersistentVolumeClaim。在 Kubernetes 1.14.0 中添加了对测试外部 CSI 驱动程序的支持。
安装
对于每个 Kubernetes 版本,都会发布一个测试 tar 归档文件。它没有在发布说明中列出(例如,1.16 的发布说明),所以必须知道完整的 URL 是 https://dl.k8s.io/<version>/kubernetes-test-linux-amd64.tar.gz
(例如 v1.16.0)。
这些包括一个用于 x86-64 Linux 的 e2e.test
二进制文件。其他平台的归档文件也可用,请参阅此 KEP。e2e.test
二进制文件是完全自包含的,因此可以“安装”它和 ginkgo
测试运行程序,如下所示
curl --location https://dl.k8s.io/v1.16.0/kubernetes-test-linux-amd64.tar.gz | \
tar --strip-components=3 -zxf - kubernetes/test/bin/e2e.test kubernetes/test/bin/ginkgo
每个 e2e.test
二进制文件都包含与相应版本中可用功能匹配的测试。特别是,[Feature: xyz]
标签在不同版本之间发生变化:它们将 Alpha 功能的测试与非 Alpha 功能的测试分开。此外,旧版本中的测试可能依赖于在最新 Kubernetes 版本中删除的 API。为了避免问题,最好只使用与用于测试的 Kubernetes 版本匹配的 e2e.test
二进制文件。
用法
并非所有 CSI 驱动程序的功能都可以通过 Kubernetes API 发现。因此,需要一个 YAML 或 JSON 格式的配置文件来描述要测试的驱动程序。该文件用于填充 driverDefinition 结构和嵌入在其中的 DriverInfo 结构。有关单个字段的详细使用说明,请参阅这些结构。
警告:测试通常只在设置某些字段时运行,并且文件解析器不会警告未知字段,因此请务必检查文件是否确实与这些结构匹配。
这是一个测试 csi-driver-host-path
的示例
$ cat >test-driver.yaml <<EOF
StorageClass:
FromName: true
SnapshotClass:
FromName: true
DriverInfo:
Name: hostpath.csi.k8s.io
Capabilities:
block: true
controllerExpansion: true
exec: true
multipods: true
persistence: true
pvcDataSource: true
snapshotDataSource: true
InlineVolumes:
- Attributes: {}
EOF
至少,您需要定义要在测试中使用的存储类、驱动程序的名称以及要测试的功能。与 csi-sanity
一样,驱动程序必须在测试之前在集群中运行。实际的 e2e.test
调用然后使用 -storage.testdriver
启用此驱动程序的测试,并使用 -ginkgo.focus
选择其存储测试
$ ./e2e.test -ginkgo.v \
-ginkgo.focus='External.Storage' \
-storage.testdriver=test-driver.yaml
Oct 8 17:17:42.230: INFO: The --provider flag is not set. Continuing as if --provider=skeleton had been used.
I1008 17:17:42.230210 648569 e2e.go:92] Starting e2e run "90b9adb0-a3a2-435f-80e0-640742d56104" on Ginkgo node 1
Running Suite: Kubernetes e2e suite
===================================
Random Seed: 1570547861 - Will randomize all specs
Will run 163 of 5060 specs
Oct 8 17:17:42.237: INFO: >>> kubeConfig: /var/run/kubernetes/admin.kubeconfig
Oct 8 17:17:42.241: INFO: Waiting up to 30m0s for all (but 0) nodes to be schedulable
...
------------------------------
SSSSSSSSSSSSSSSSSSSS
------------------------------
External Storage [Driver: hostpath.csi.k8s.io] [Testpattern: Dynamic PV (filesystem volmode)] multiVolume [Slow]
should access to two volumes with different volume mode and retain data across pod recreation on the same node
/workspace/anago-v1.16.0-rc.2.1+2bd9643cee5b3b/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/test/e2e/storage/testsuites/multivolume.go:191
[BeforeEach] [Testpattern: Dynamic PV (filesystem volmode)] multiVolume [Slow]
...
您可以使用 ginkgo
并行运行某些类型的测试。Alpha 功能测试或那些设计上必须按顺序运行的测试则需要单独运行
$ ./ginkgo -p -v \
-focus='External.Storage' \
-skip='\[Feature:|\[Disruptive\]|\[Serial\]' \
./e2e.test \
-- \
-storage.testdriver=test-driver.yaml
$ ./ginkgo -v \
-focus='External.Storage.*(\[Feature:|\[Disruptive\]|\[Serial\])' \
./e2e.test \
-- \
-storage.testdriver=test-driver.yaml
参与进来
Kubernetes 存储测试和健全性测试都旨在适用于任意 CSI 驱动程序。但是,测试可能基于额外的假设,并且您的驱动程序虽然符合 CSI 规范,但未能通过测试。如果发生这种情况,请提交问题(下方链接)。
这些是开源项目,依赖于使用者的帮助,因此一旦问题得到确认,我们将非常欢迎解决该问题的拉取请求。
编写新测试也适用同样的情况。以下问题跟踪器中的搜索专门选择标记为需要某人帮助的问题
测试愉快!愿它发现的问题少且易于修复。