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

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 始终从主分支构建最新版本。要构建特定版本,获取源代码 并运行 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)。

这些包括一个用于 Linux x86-64 架构的 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 二进制文件。

用法

并非可以通过 Kubernetes API 发现 CSI 驱动程序的所有功能。因此,需要一个 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 规范。如果发生这种情况,请提交问题(链接如下)。

这些是依赖于使用者的帮助的开源项目,因此一旦问题得到承认,我们非常欢迎解决该问题的拉取请求。

编写新测试也是如此。以下问题跟踪器中的搜索会选择已被明确标记为需要某人帮助的问题

祝您测试愉快!愿它发现的问题很少且容易修复。