K8s CSI(Container Storage Interface)

名词解释

名词解释
in-tree存在于核心 Kubernetes 存储库中的代码。
out-of-tree存在于 Kubernetes 核心代码库之外的代码。
Volume PluginPV Controller、ADController 以及 Volume Manager 对某一 volume 进行 Provision/Delete、Attach/Detach、Mount/Umount 等操作,其实都是通过调用 volume 所对应的 Volume Plugin 实现的 k8s 定义的 VolumePlugin 接口来完成的。Volume Plugin 有多种,包括 NFS Volume PluginCSI Volume Plugin 等(Volume Plugin 都是 in-tree 的)
Container Storage Interface (CSI)一个行业标准的接口规范,使容器编排系统(COs)能够将任意存储系统暴露给其容器化工作负载。
CSI Volume Driver实现了 CSI 的驱动程序。CSI Volume Plugin 将会调用 CSI Volume Driver 实现的 CSI,完成对 volume 的 Provision/Delete、Attach/Detach、Mount/Umount 等操作(CSI Volume Driver 都是 out-of-tree 的)

背景和动机

由于,Kubernetes所有的Volume Plugin是in-tree的,意味着存储供应商(Storage Provider,SP)向Kubernetes添加新的存储系统需要实现新的Volume Plugin,并将代码检入核心Kubernetes代码仓库中。这样做有很多不好的地方。同时SP也一直在应对如何使他们的存储系统在不同的容器编排系统(COs)中可用。SP必须为同一个存储系统编写和支持多个Volume Plugin以适用于不同的CO或选择不支持某些CO。

容器存储接口(CSI)是一个规范,由来自各种CO(包括Kubernetes、Mesos、Cloud Foundry和Docker)的社区成员合作而产生。 该接口旨在建立标准化机制,这样SP只需要各自维护其符合CSI规范的out-of-tree的CSI Volume Driver,并由各CO来维护各自的in-tree的CSI Volume Plugin,就可以在各种CO中使用其存储系统。

Kubernetes 的存储架构

在Kubernetes上CSI Volume Driver的推荐部署模式

第三方SP需要实现并创建一个CSI Volume Driver(以下简称CVD)容器,要求其中的bin(例如alibaba-cloud-csi-driver)实现CSI规范中所定义的包括ControllerNode,和Identity服务,并通过Unix域套接字公开gRPC接口。

https://img-blog.csdnimg.cn/a8df24246a6a417388a704c2072aad99.png 部署主要包含两个部分:CSI Controller Server 与 CSI Node Server。

  • Controller Server(StatefulSet或Deployment)是控制端的功能,主要实现create/delete、attach/detach等功能;
  • Node Server(DaemonSet)主要实现的是节点上的 mount/umount 功能。

Controller Server可以部署为Deployment或StatefulSet在集群中的任何节点上。其中的Pod由SP创建的CVD容器(例如alibaba CVD),和一个或多个Kubernetes团队提供的辅助容器作为sidecar容器,并与CVD容器共享EmptyDir。CVD容器应在此目录中创建其UDS,暴露其实现的Controller和Identity服务,以供辅助容器调用。 https://img-blog.csdnimg.cn/432853e630cf4880b192b72a64a1f69b.png

这些sidecar容器会watch API Server中特定的资源(PVC,VolumeAttachment)对象,然后通过EmptyDir中的UDS调用CVD实现的Controller或Identity服务中的方法。这些调用主要负责从存储供应商的所提供的后端存储系统中create/delete volume或attach/detach volume到host上。

可以部署多个replica以实现高可用性,但需要对辅助容器使用leader选举确保一次只有一个active controller。

辅助容器包括external-provisioner、external-attacher、external-snapshotter 和 external-resizer。具体在pod中部署哪些辅助容器是可选的,由存储供应商自己决定:

  • external-provisioner是一个sidecar容器,它监视Kubernetes API Server以获取PVC对象。如果PVC引用了StorageClass,并且StorageClass中provisioner字段的名称与通过UDS的调用的CVD的GetPluginInfo方法返回的名称匹配,则会通过UDS调用的CVD的CreateVolume方法来创建新volume。一旦创建了新volume,external-provisioner将创建一个PV对象来表示该volume;将绑定到使用delete ReclaimPolicy的PV的PVC对象删除会导致external-provisioner通过UDS调用的CVD的DeleteVolume方法。一旦成功删除了该volume,external-provisioner将删除表示该volume的PV对象。
  • external-attacher是一个sidecar容器,它监视Kubernetes API Server以获取VolumeAttachment对象(下面Attach/Detach流程细讲),通过UDS调用的CVD的Controller[Publish|Unpublish]Volume操作来将存储设备从指定node上attach/detach。
  • external-resizer是一个sidecar容器,它监视Kubernetes API Server以发现PVC对象的编辑,并在用户请求PVC对象上的更多存储时通过UDS调用的CVD的ControllerExpandVolume方法。
  • external-snapshotter
  • livenessprobe

Node Server通过DaemonSet部署在集群中的每个节点上。其中的Pod由存储供应商创建的CVD容器(例如alibaba CVD),和Kubernetes团队提供的node-driver-registrar作为sidecar容器。

kubelet在每个节点上运行,并与CVD共享hostPath,通过UDS调用CVD实现的Node或Identity服务中的方法。这些调用主要负责从存储供应商的所提供的后端存储系统中mount/umount volume到CVD并share到host上,使其可供被调度到该host的Pod使用。 https://img-blog.csdnimg.cn/7c260f251d8743f5b62b06563b2981d3.png

综上,Pod中具有以下volume:

  • hostPath
    • 暴露host的/var/lib/kubelet/plugins/[SanitizedCSIDriverName]/目录(hostPath.type = “DirectoryOrCreate”),挂载到CVD容器的任意目录中,CVD将在该目录中创建UDS。这是Kubelet和CVD容器之间通信的主要方式(通过 UDS 上的 gRPC)。(其实也会被挂载到node-driver-registrar中,因为node-driver-registrar需要调用CVD的GetPluginInfo方法)
  • hostPath
    • 暴露host的/var/lib/kubelet/plugins_registry目录,挂载到node-driver-registrar容器中/registration目录。node-driver-registrar将在/registration目录创建UDS,然后通过该UDS向kubelet注册CVD容器的UDS路径。
  • hostPath
    • 暴露host的/var/lib/kubelet/目录,挂载到CVD容器中/var/lib/kubelet/目录。确保启用bi-directional mount propagation,以便在此CVD容器内的任何挂载都会传播回host,并供其他真正需要使用该挂载的业务pod来consume。

服务注册

CSI是通过CRD的形式实现的,所以CSI引入了这么几个对象类型:CSIDriverCSINodeVolumeAttachment

CSIDriver描述了集群中所部署的CVD,需要由管理员创建(例如alibaba CSI Driver)。一个CSIDriver对象的metadata.Name字段表示该对象所指的CVD的名称;它必须与CVD的GetPluginInfo方法返回的名称相同。 ADController在调用CSI Volume Plugin时,会根据CSIDriver对象的AttachRequired字段判断目标volume是否需要进行attach(主要是为了对块存储和文件存储做区分。比如文件存储不需要attach操作)

一个CSINode对象包含了其所对应node上安装的所有CSI Node Server的信息。CSINode与其所对应的node具有相同的名称。如果CSINode对象不存在,则表示node上没有可用的CSI Node Server 当一个新的CSI Node Server通过node-driver-registrar进行服务注册时,kubelet将自动为该node对应的CSINode对象添加一个CSINodeDriver以表示该CSI Node Server。

VolumeAttachment描述一个volume在一个Pod使用中attach、detach的相关信息。例如,对一个volume在某个node上的attach,我们通过VolumeAttachment对该attach操作进行跟踪。ADController创建一个VolumeAttachment对象,该对象包含了CSI Driver信息、node名称、待挂接PV信息;而external-attacher则通过watch VolumeAttachment资源,根据对象的状态来进行attach、detach操作。

工作流程

https://img-blog.csdnimg.cn/e710380105b54214ad70504b8a2435f6.png

  1. 集群管理员创建了一个StorageClass对象,其中包含了指定的SP名称(Provisioner)和其CSI Driver所需的任何参数。
  2. 用户创建了一个PVC和使用该PVC的Pod。其中,PVC指定使用上一步创建的StorageClass对象来创建动态PV。
  3. PVController watch到该Pod使用的PVC处于Pending状态,并根据其所使用的StorageClass的Provisioner来选择对应的Volume Plugin
  4. Scheduler根据Pod配置、Node状态、PV配置等信息,把Pod调度到一个合适的Worker Node上。
  5. ADController发现Pod被调度到某一Node上,会根据Pod所使用的PVC所绑定的PV来选择对应的Volume Plugin(若是CSI类型的volume,则会选择 CSI Volume Plugin)。
    • 若Volume Plugin实现了AttachableVolumePlugin接口,ADController会调用Volume Plugin实现的接口(其中,CSI Volume Plugin会创建一个VolumeAttachement对象,external-attacher会watch集群中的VolumeAttachement资源,发现有需要挂接的数据卷时,调用CVD的ControllerPublishVolume方法)来attach volume到Node。
  6. Kubelet在Node上真正创建出Pod前会做如下工作:
    1. Kubelet中的Volume Manager等待volume attach到Node完成。
    2. 若Volume Plugin实现了DeviceMountableVolumePlugin接口,Volume Manager会调用Volume Plugin实现的接口(其中,CSI Volume Plugin会调用CVD的NodeStageVolume方法)来mount volume到一个公共目录。
    3. Volume Manager会调用Volume Plugin实现的NewMounter(其中,CSI Volume Plugin会调用CVD的NodePublishVolume方法)来mount volume到一个每个pod专属的目录。
  7. Kubelet通过Docker启动Pod的containers,用bind mount方式将pod专属的目录卷映射到container中。

CSI Node服务中定义了NodeStageVolume和NodePublishVolume两个方法。前者是可选的,而后者则是必须实现的。如果两者都实现的情况下,调用顺序是先调用NodeStageVolume,然后再调用NodePublishVolume。比如云厂商的云硬盘,如果希望以文件系统的方式被容器使用的话,在NodeStageVolume阶段,就会将云硬盘格式化成对应文件系统,并且mount到一个全局目录上(例如:/var/lib/kubelet/plugins/kubernetes.io/csi/pv/csi-blockvolume-pv/globalmount),然后在NodePublishVolume阶段,会直接从该全局目录bind mount到最终pod使用的目录上(例如:/var/lib/kubelet/pods/835516e8-49e7-11e9-ab71-00001700f4ea/volumes/kubernetes.io~csi/csi-blockvolume-pv/mount)。因为考虑到会有同一个volume被多个pod使用的情况,这样mkfs、mount等操作只需要做一遍,后续多个pod使用这个volume,就只需要bind这个全局目录到各自pod使用的目录上就行了。

参考

https://github.com/kubernetes/design-proposals-archive/blob/main/storage/container-storage-interface.md#recommended-mechanism-for-deploying-csi-drivers-on-kubernetes https://kubernetes-csi.github.io/docs/deploying.html https://kubernetes-csi.github.io/docs/sidecar-containers.html https://github.com/container-storage-interface/spec/blob/master/spec.md#container-storage-interface https://www.infoq.cn/article/afju539zmbpp45yy9txj https://mp.weixin.qq.com/s?__biz=MzUzNzYxNjAzMg==&mid=2247490043&idx=1&sn=c09ad4a9bc790f4b742abd8ca1301ffb&scene=21 https://blog.hdls.me/16255765577465.html