CSI系列二:如何用CSI为Kubernetes开发volume plugin

网友投稿 889 2022-11-03

本站部分文章、图片属于网络上可搜索到的公开信息,均用于学习和交流用途,不能代表睿象云的观点、立场或意见。我们接受网民的监督,如发现任何违法内容或侵犯了您的权益,请第一时间联系小编邮箱jiasou666@gmail.com 处理。

CSI系列二:如何用CSI为Kubernetes开发volume plugin

上一篇文章介绍了为什么要用CSI为Kubernetes开发volume plugin,这篇文章则是介绍如何用CSI来为Kubernetes开发volume plugin。

概述

对于开发者来说,用CSI为Kubernetes开发volume plugin,主要是要考虑两个问题。第一问题是要理解CSI的规范(spec),实现CSI spec中规定的接口。第二个问题是如何将自己的CSI plugin部署到Kubernetes cluster中,也就是如何与Kubernetes集成。

有童鞋可能会问,既然CSI是一个通用的标准,所有容器编排系统(包括Kubernetes)都支持。那么开发CSI plugin只需要关心CSI spec中定义的接口即可,为什么要关心Kubernetes呢?理想情况下确实只需要关心CSI spec,关键问题是CSI spec只是定义所有CO(Container Orchestration)都支持的接口,但是并没有定义CSI plugin与CO之间标准的集成解决方案。所以各个CO都有自己独特的集成解决方案,Kubernetes也不例外,作为开发者,必须要理解Kubernetes提供的集成方案。

CSI spec

在写作本文时,最新的CSI spec版本是1.1.0,在1.0.0的基础上增加了VolumeExpansion的功能。零君最近实现的一个CSI plugin就是基于1.0.0开发的,所以本文主要是基于1.0.0来说明。CSI spec 1.0.0的详细链接如下:

https://github.com/container-storage-interface/spec/blob/v1.0.0/spec.md

Kubernetes与CSI plugin之间通过gRPC通信。CSI spec主要定义了三组RPC接口:

Identity ServiceController ServiceNode Service

Identity Service定义了下面三个接口函数,如下所示。GetPluginInfo用于Kubernetes获取CSI plugin的信息,比如plugin的名称和版本。GetPluginCapabilities则是用于获取plugin所支持的能力,比如是否支持Controller service,是否支持topology。Probe则是用于health check,看plugin是否还在正常运行。

service Identity { rpc GetPluginInfo(GetPluginInfoRequest) returns (GetPluginInfoResponse) {} rpc GetPluginCapabilities(GetPluginCapabilitiesRequest) returns (GetPluginCapabilitiesResponse) {} rpc Probe (ProbeRequest) returns (ProbeResponse) {}}

Controller Service定义了下面的接口函数。CSI Controller Plugin要实现这组接口,但是并不是必须要实现这里定义的所有接口函数。那么Kubernetes如何知道plugin实现了哪些,没实现哪些接口呢?这就是接口ControllerGetCapabilities要实现的功能,Kubernetes调用这个接口就可以获取Controller Plugin实现了哪些接口函数。

这里主要对CreateVolume和DeleteVolume以及ControllerPublishVolume和ControllerUnpublishVolume这几个重要的接口函数简要说明。CreateVolume用于动态创建Volume,也就是用户事先无需手动创建Volume,这点后文还会详细说明,先不用着急。DeleteVolume则是CreateVolume的逆向操作,也就是动态删除Volume。ControllerPublishVolume通常用于将一个Block设备动态attach到一个node上,而ControllerUnpublishVolume则是动态地做Detach操作。

service Controller { rpc CreateVolume (CreateVolumeRequest) returns (CreateVolumeResponse) {} rpc DeleteVolume (DeleteVolumeRequest) returns (DeleteVolumeResponse) {} rpc ControllerPublishVolume (ControllerPublishVolumeRequest) returns (ControllerPublishVolumeResponse) {} rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest) returns (ControllerUnpublishVolumeResponse) {} rpc ValidateVolumeCapabilities (ValidateVolumeCapabilitiesRequest) returns (ValidateVolumeCapabilitiesResponse) {} rpc ListVolumes (ListVolumesRequest) returns (ListVolumesResponse) {} rpc GetCapacity (GetCapacityRequest) returns (GetCapacityResponse) {} rpc ControllerGetCapabilities (ControllerGetCapabilitiesRequest) returns (ControllerGetCapabilitiesResponse) {} rpc CreateSnapshot (CreateSnapshotRequest) returns (CreateSnapshotResponse) {} rpc DeleteSnapshot (DeleteSnapshotRequest) returns (DeleteSnapshotResponse) {} rpc ListSnapshots (ListSnapshotsRequest) returns (ListSnapshotsResponse) {}}

Node Service定义了下面的接口函数。同样的,CSI Node Plugin要实现这组接口,但是不是必须要实现这里定义的所有接口函数。Kubernetes根据NodeGetCapabilities的返回结果就可以知道CSI Node Plugin实现了哪些接口。NodeStageVolume和NodePublishVolume都用于对volume进行format和mount操作,但是区别在于前者是将volume挂载(mount)到一个全局的staging路径,而后者是直接将volume挂载(mount)到pod内。当同一个node上运行的多个pod需要共享访问同一个volume时,NodeStageVolume就派上用场了,不过这种场景并不常见。NodeUnstageVolume和NodeUnpublishVolume则是对应的逆向操作。

service Node { rpc NodeStageVolume (NodeStageVolumeRequest) returns (NodeStageVolumeResponse) {} rpc NodeUnstageVolume (NodeUnstageVolumeRequest) returns (NodeUnstageVolumeResponse) {} rpc NodePublishVolume (NodePublishVolumeRequest) returns (NodePublishVolumeResponse) {} rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest) returns (NodeUnpublishVolumeResponse) {} rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest) returns (NodeGetVolumeStatsResponse) {} rpc NodeGetCapabilities (NodeGetCapabilitiesRequest) returns (NodeGetCapabilitiesResponse) {} rpc NodeGetInfo (NodeGetInfoRequest) returns (NodeGetInfoResponse) {}}

从功能模块的角度来看,每个CSI plugin又可以分为两个Plugin:

Node PluginController Plugin

对于Node Plugin来说,必须要实现上面的Identify Service和Node Service两组接口。而Controller Plugin则必须要实现Identify Service和Controller Service两组接口。不难看出,Identify Service是两种Plugin都必须实现的接口。

可以为Node Plugin和Controller Plugin各生成一个docker image,也可以将它们的功能包含在一个docker image中。为了便于维护,通常是将它们包含一个docker image中。

有人可能会疑惑,如果包含在一个docker image中,那么实际运行时如何区分呢?不用着急,看到后面就会明白!

Kubernetes与CSI plugin的集成

Kubernetes提供了完善的解决方案用于集成CSI plugin。其实就是Kubernetes提供了一些sidecar containers。先给出一个典型的部署图:

Controller Plugin与external-provisioner和external-attacher这两个sidecar部署在一起,作为一个StatefulSets运行在master上。external-provisioner监控k8s cluster内PVC的变化,当有一个新的PVC产生,并满足下面两个条件时,external-provisioner就会通过gRPC调用CSI Controller Plugin的CreateVolume接口函数,动态创建一个Volume。

该PVC的storageClassName设置的就是我们开发的CSI Plugin;没有满足的PV存在。

当上面那个PVC被删除并且对应的StorageClass的reclaimPolicy是Delete时,external-provisioner就会通过gRPC调用Controller Plugin的DeleteVolume接口函数,动态删除之前创建的Volume。

接下来,当某个POD使用了上面的PVC,并且该POD被调度到某个node上时,external-attacher就会调用Controller Plugin的ControllerPublishVolune接口函数,动态将之间创建的Volulme attach到POD所在的node上。反之,当POD被删除时,external-attacher就会调用Controller Plugin的ControllerUnpublishVolume将之前attach的volume进行detach。

Node Plugin只与一个sidecar container部署在一起,也就是与node-driver-registrar部署在一起,作为Daemonset运行在每一个node上。node-driver-registrar的作用是将Node Plugin向Kubelet注册。在external-attacher将Volume attach到POD所在的node之后,Kubelet就会调用Node Plugin的NodePublishVolume将volume挂载到POD内。当该POD被删除时,Kubelet会调用Node Plugin的NodeUnpublishVolume来做unmount操作。

最终用户使用举例

这里的用户是指使用我们开发的CSI Plugin的用户。这里只举一个典型的动态create/attach/mount的例子。

首先,用户需要创建一个StorageClass(假设文件名为my-sc.yaml),其中的provisioner必须设置成我们开发的CSI plugin的名称。注意这里的名称必须与Identity Service中的GetPluginInfo返回的名称匹配。假设你的CSI Plugin是“csi-excellent-plugin”,那么就可以定义下面的StorageClass。当然我们也可以提供默认的StorageClass给用户使用。

apiVersion: storage.k8s.io/v1kind: StorageClassmetadata:  name: csi-my-scprovisioner: csi-excellent-pluginreclaimPolicy: DeletevolumeBindingMode: Immediate

使用如下命令创建上面的StorageClass.yaml:

kubectl create -f my-sc.yaml

接着,用户可以定义如下PVC(假设文件名为my-pvc.yaml)。其中的storageClassName就是上面的定义的StorageClass的名称。

apiVersion: v1kind: PersistentVolumeClaimmetadata: name: csi-pvcspec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi  storageClassName: csi-my-sc

当使用下面的命令创建上面的PVC时,就会自动创建一个Volume,

kubectl create -f my-pvc.yaml

最后在POD中就可以直接用上面的PVC,假设文件名为my-pod.yaml。

kind: PodapiVersion: v1metadata: name: my-csi-appspec: containers:    - name: my-fancy-app image: busybox volumeMounts: - mountPath: "/data" name: my-csi-volume command: [ "sleep", "1000000" ] volumes: - name: my-csi-volume persistentVolumeClaim: claimName: csi-pvc

当使用下面的命令创建上面的POD时,之前创建的volume就会自动被attach到POD所在的node上,并且被mount到POD内,

kubectl create -f my-pod.yaml

深入探讨

零君会在此系列的后续文章中单独详细介绍。

--END--

上一篇:软件测试培训之客户端测试注意点
下一篇:软件测试培训之怎样才能测试系统支持多少用户
相关文章

 发表评论

暂时没有评论,来抢沙发吧~