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

CSI 驱动程序测试

在开发 容器存储接口 (CSI) 驱动程序 时,利用尽可能多的现有工作非常有用。这包括源代码(如 CSI hostpath 驱动程序示例)以及现有的测试。除了节省时间外,使用其他人编写的测试的优势在于,它可以指出规范中可能被忽略的方面。

一篇关于端到端测试的早期博客文章已经展示了如何使用 Kubernetes 存储测试来测试第三方 CSI 驱动程序。当目标是添加自定义 E2E 测试时,这种方法是合理的,但这依赖于为设置和维护测试套件所投入的大量精力。

如果目标仅仅是运行现有测试,则有更简单的方法。本文将介绍这些方法。

健全性测试

csi-test sanity 通过以各种方式调用 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 规范所需的暂存和目标目录,并通过 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。对测试外部 CSI 驱动程序的支持在 Kubernetes 1.14.0 中添加

安装

每个 Kubernetes 版本都会发布一个测试 tar 包。它未列在发布说明中(例如,1.16 的发布说明),因此必须知道完整的 URL 是 https://dl.k8s.io/<version>/kubernetes-test-linux-amd64.tar.gz(例如v1.16.0)。

这些文件包含一个适用于 x86-64 架构 Linux 的 e2e.test 二进制文件。其他平台的压缩包也可用,请参阅此 KEPe2e.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 规范但未能通过测试。如果发生这种情况,请提交 issue(链接如下)。

这些是开源项目,依赖于使用者的帮助,因此一旦问题被确认,将非常欢迎解决该问题的 Pull Request。

以下在 issue 跟踪器中的搜索会选出那些被专门标记为需要有人帮助的 issue:

测试愉快!愿它发现的问题少且易于解决。