K8s CSI(Container Storage Interface)
名词解释
名词 | 解释 |
---|---|
in-tree | 存在于核心 Kubernetes 存储库中的代码。 |
out-of-tree | 存在于 Kubernetes 核心代码库之外的代码。 |
Volume Plugin | PV Controller、ADController 以及 Volume Manager 对某一 volume 进行 Provision/Delete、Attach/Detach、Mount/Umount 等操作,其实都是通过调用 volume 所对应的 Volume Plugin 实现的 k8s 定义的 VolumePlugin 接口来完成的。Volume Plugin 有多种,包括 NFS Volume Plugin,CSI 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的推荐部署模式
CVD
第三方SP需要实现并创建一个CSI Volume Driver(以下简称CVD)容器,要求其中的bin(例如alibaba-cloud-csi-driver)实现CSI规范中所定义的包括Controller,Node,和Identity服务,并通过Unix域套接字公开gRPC接口。
部署
部署主要包含两个部分:CSI Controller Server 与 CSI Node Server。
- Controller Server(StatefulSet或Deployment)是控制端的功能,主要实现create/delete、attach/detach等功能;
- Node Server(DaemonSet)主要实现的是节点上的 mount/umount 功能。
Controller Server
Controller Server可以部署为Deployment或StatefulSet在集群中的任何节点上。其中的Pod由SP创建的CVD容器(例如alibaba CVD),和一个或多个Kubernetes团队提供的辅助容器作为sidecar容器,并与CVD容器共享EmptyDir。CVD容器应在此目录中创建其UDS,暴露其实现的Controller和Identity服务,以供辅助容器调用。
这些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
Node Server通过DaemonSet部署在集群中的每个节点上。其中的Pod由存储供应商创建的CVD容器(例如alibaba CVD),和Kubernetes团队提供的node-driver-registrar作为sidecar容器。
- node-driver-registrar是一个sidecar容器,它通过UDS调用的CVD的GetPluginInfo方法,并使用kubelet插件注册机制在该节点上向kubelet进行注册(服务发现)。
kubelet在每个节点上运行,并与CVD共享hostPath,通过UDS调用CVD实现的Node或Identity服务中的方法。这些调用主要负责从存储供应商的所提供的后端存储系统中mount/umount volume到CVD并share到host上,使其可供被调度到该host的Pod使用。
综上,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引入了这么几个对象类型:CSIDriver、CSINode、VolumeAttachment
CSIDriver
CSIDriver描述了集群中所部署的CVD,需要由管理员创建(例如alibaba CSI Driver)。一个CSIDriver对象的metadata.Name字段表示该对象所指的CVD的名称;它必须与CVD的GetPluginInfo方法返回的名称相同。 ADController在调用CSI Volume Plugin时,会根据CSIDriver对象的AttachRequired字段来判断目标volume是否需要进行attach(主要是为了对块存储和文件存储做区分。比如文件存储不需要attach操作)
CSINode
一个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
VolumeAttachment描述一个volume在一个Pod使用中attach、detach的相关信息。例如,对一个volume在某个node上的attach,我们通过VolumeAttachment对该attach操作进行跟踪。ADController创建一个VolumeAttachment对象,该对象包含了CSI Driver信息、node名称、待挂接PV信息;而external-attacher则通过watch VolumeAttachment资源,根据对象的状态来进行attach、detach操作。
node-driver-registrar服务注册
工作流程
- 集群管理员创建了一个StorageClass对象,其中包含了指定的SP名称(Provisioner)和其CSI Driver所需的任何参数。
- 用户创建了一个PVC和使用该PVC的Pod。其中,PVC指定使用上一步创建的StorageClass对象来创建动态PV。
- PVController watch到该Pod使用的PVC处于Pending状态,并根据其所使用的StorageClass的Provisioner来选择对应的Volume Plugin。
- 若Volume Plugin实现了ProvisionableVolumePlugin接口,PVController会调用Volume Plugin实现的接口来create volume,然后创建PV对象,并与PVC绑定。
- 若Volume Plugin未实现ProvisionableVolumePlugin,或没有找到StorageClass的Provisioner对应的 Volume Plugin(是CSI类型的volume),PVController会为PVC打上annotation(volume.beta.kubernetes.io/storage-provisioner={StorageClass的Provisioner}),然后被CSI Controller Server中watch PVC的external-provisioner发现。若PVC的annotation与external-provisioner的CSI Driver名字一致时调用CVD的CreateVolume方法来create volume。一旦创建了新volume,external-provisioner将创建一个PV对象并设置CSI字段来表示该volume属于哪个CSI Driver,并与PVC绑定。
- Scheduler根据Pod配置、Node状态、PV配置等信息,把Pod调度到一个合适的Worker Node上。
- 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。
- Kubelet在Node上真正创建出Pod前会做如下工作:
- Kubelet中的Volume Manager等待volume attach到Node完成。
- 若Volume Plugin实现了DeviceMountableVolumePlugin接口,Volume Manager会调用Volume Plugin实现的接口(其中,CSI Volume Plugin会调用CVD的NodeStageVolume方法)来mount volume到一个公共目录。
- Volume Manager会调用Volume Plugin实现的NewMounter(其中,CSI Volume Plugin会调用CVD的NodePublishVolume方法)来mount volume到一个每个pod专属的目录。
- 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