做了一段时间Kubernetes 容器存储,根据文档重新整理一下Kubernetes CSI接口逻辑,并以几大云厂商的CSI Driver 和 kubernetes-csi/csi-driver-nfs 为例,描述一下CSI插件实现的逻辑。
CSI接口逻辑
容器存储接口(CSI)是在 Kubernetes 等容器编排系统(CO)上向容器化工作负载公开任意块和文件存储系统的标准。 使用 CSI,第三方存储提供商可以编写和部署插件,在 Kubernetes 中公开新的存储系统,而无需接触 Kubernetes 的核心代码。
早在 Kubernetes 1.23 CSI接口就已经GA了。
CSI Driver通信方式
Kubelet 与 CSI 驱动程序的通信
- Kubelet 通过 Unix 域套接字直接向 CSI 驱动程序发出 CSI 调用(如 NodeStageVolume、NodePublishVolume 等),以挂载和卸载卷。
- Kubelet 通过 kubelet 插件注册机制发现 CSI 驱动程序(以及与之交互所需的 Unix 域套接字)。
- 因此,所有在 Kubernetes 上部署的 CSI 驱动程序必须在每个支持的节点上使用 kubelet 插件注册机制进行注册。
控制平面与 CSI 驱动程序的通信
- Kubernetes 控制平面组件不直接(通过 Unix 域套接字或其他方式)与 CSI 驱动程序进行通信。
- Kubernetes 控制平面组件仅与 Kubernetes API 进行交互。
- 因此,要求依赖 Kubernetes API 的操作的 CSI 驱动程序(如卷创建、卷附加、卷快照等)必须监视 Kubernetes API,并针对其触发相应的 CSI 操作。
CSI Driver接口描述
每个存储供应商提供Kubernetes存储插件时都需要根据 CSI接口实现各自的 CSI Driver。
需要按照接口定义API(RPCs),以支持:
- 动态创建和删除卷。
- 将卷附加或分离到节点。
- 将卷挂载/卸载到节点。
- 使用块存储和可挂载卷。
- 本地存储提供者(例如,设备映射器、LVM)。
- 创建和删除快照(快照的来源是一个卷)。
- 从快照中配置新卷(恢复快照,其中原卷中的数据被快照中的数据替换,超出范围)。
接口功能
CSI插件通常包含三种主要的API接口:Identity Service、Controller Service 和 Node Service:
- Identity Service 负责提供插件的身份信息。它主要用于以下功能:
- 获取插件信息 :Identity Service 提供方法以获取插件的名称、版本、类型等信息。这有助于容器编排系统了解所使用的存储插件的基本信息。
- 支持的功能 :它还允许查询插件支持的功能,例如是否支持快照、克隆等。
- Controller Service 主要用于管理存储卷的生命周期,主要在集群级别操作,通常由控制平面组件(如Kubernetes控制器)调用。它提供了许多与存储相关的操作,例如:
- 卷的创建和删除 :允许创建新的存储卷和删除不再需要的存储卷。
- 卷的扩展 :支持扩展现有卷的大小。
- 快照管理 :提供创建、删除和恢复快照的功能。
- 卷的绑定和解绑 :允许将卷绑定到特定的容器或解绑。
- Node Service 主要处理与具体节点相关的操作,在工作节点上运行,确保应用程序能够访问所需的存储资源。它通常包括以下功能:
- 卷挂载和卸载 :处理在特定节点上挂载和卸载存储卷的请求。
- 卷的检查和状态管理 :提供方法检查卷的状态,确保卷在节点上的可用性。
- 数据传输 :在需要时,可以支持在节点之间的数据传输和复制。
Identity Service
- GetPluginInfo:
- 功能 :该接口用于获取 CSI 插件的基本信息,包括插件的名称、版本和供应商等信息。
- 返回内容 :通常返回一个包含插件名称、版本以及供应商信息的结构体。这有助于 Kubernetes 确认使用的插件是什么,以及其是否符合预期的版本要求。
- GetPluginCapabilities:
- 功能 :该接口用于查询 CSI 插件所支持的功能和能力。
- 返回内容 :返回一个能力列表,描述插件支持的操作(例如,卷的创建、删除、扩展等,见 特性)。这些能力信息使 Kubernetes 能够了解插件的功能范围,从而在调度和管理卷时做出更好的决策。
- Probe:
- 功能 :该接口用于检查 CSI 插件的可用性和健康状态。
- 返回内容 :返回一个状态指示,表明插件是否正常运行。Kubernetes 可以通过此接口定期检查插件的状态,以确保其在集群中可用,并能够处理存储请求。
Controller Service
Controller Service和Node Service的接口较多,主要列出几个关键接口
- CreateVolume:
- 功能 :创建一个新的存储卷。
- 参数 :通常包括卷的名称、大小、存储类型等信息。
- 返回内容 :返回创建的卷的唯一标识符(Volume ID),以便后续操作使用。
- DeleteVolume:
- 功能 :删除指定的存储卷。
- 参数 :需要提供要删除的卷的唯一标识符(Volume ID)。
- 返回内容 :通常是一个空的响应,表示删除操作的结果。
- ControllerPublishVolume:
- 功能 :将存储卷绑定到指定的节点上,以使该节点能够访问该卷。
- 参数 :包括卷的唯一标识符、节点的标识符等信息。
- 返回内容 :通常是一个空的响应,表示卷已成功绑定到节点。
- ControllerUnpublishVolume:
- 功能 :将存储卷从指定的节点上解绑。
- 参数 :需要提供卷的唯一标识符和节点的标识符。
- 返回内容 :通常是一个空的响应,表示解绑操作的结果。
- ValidateVolumeCapabilities:
- 功能 :验证存储卷是否支持特定的能力(如读写模式)。
- 参数 :包括卷的唯一标识符和要验证的能力列表。
- 返回内容 :返回一个结果,指示哪些能力是支持的。
- ListVolumes:
- 功能 :列出所有已创建的存储卷。
- 返回内容 :返回一个卷的列表,通常包括每个卷的唯一标识符和相关信息。
- GetCapacity:
- 功能 :查询存储系统的可用容量。
- 返回内容 :返回可用的存储容量信息,帮助调度器做出决策。
其他还存在一些快照及扩容接口,将在相关功能中描述。
Node Service
- NodeStageVolume:
- 功能 :准备存储卷以便于挂载,通常涉及到对存储卷的预处理。
- 参数 :包括卷的唯一标识符、目标路径、存储参数等。
- 返回内容 :通常是一个空的响应,表示卷已成功准备就绪。
- NodeUnstageVolume:
- 功能 :清理存储卷的预处理状态,通常在卷卸载之前调用。
- 参数 :需要提供卷的唯一标识符和目标路径。
- 返回内容 :通常是一个空的响应,表示卷已成功清理。
- NodePublishVolume:
- 功能 :将指定的存储卷挂载到节点上的特定路径,以便容器可以访问该卷。
- 参数 :包括卷的唯一标识符(Volume ID)、目标挂载路径、挂载选项等。
- 返回内容 :通常是一个空的响应,表示卷已成功挂载。
- NodeUnpublishVolume:
- 功能 :将存储卷从节点的特定路径上卸载。
- 参数 :需要提供卷的唯一标识符和目标挂载路径。
- 返回内容 :通常是一个空的响应,表示卷已成功卸载。
- NodeGetVolumeStats:
- 功能 :获取指定存储卷的统计信息,如使用情况、容量等。
- 参数 :需要提供卷的唯一标识符和目标挂载路径。
- 返回内容 :返回卷的统计信息,包括使用的空间、总空间等。
- NodeGetCapabilities:
- 功能 :获取节点的能力信息。
- 返回内容 :返回节点支持的能力列表,例如是否支持挂载、卸载等操作。
其他还存在扩容等接口,将在相关功能中描述。
CSI Driver卷生命周期
包含STAGE 的完整生命周期
CreateVolume +------------+ DeleteVolume
+------------->| CREATED +--------------+
| +---+----^---+ |
| Controller | | Controller v
+++ Publish | | Unpublish +++
|X| Volume | | Volume | |
+-+ +---v----+---+ +-+
| NODE_READY |
+---+----^---+
Node | | Node
Stage | | Unstage
Volume | | Volume
+---v----+---+
| VOL_READY |
+---+----^---+
Node | | Node
Publish | | Unpublish
Volume | | Volume
+---v----+---+
| PUBLISHED |
+------------+
Figure 6: The lifecycle of a dynamically provisioned volume, from
creation to destruction, when the Node Plugin advertises the
STAGE_UNSTAGE_VOLUME capability.
动态卷最全的生命周期:
- CreateVolume:
- 在这个阶段,控制器接收创建卷的请求。
- CSI驱动会处理该请求,通常涉及到在存储后端创建一个新的卷。
- 一旦卷创建成功,驱动将返回卷的标识信息。
- ControllerPublishVolume:
- 该阶段是将创建的卷与某个特定的节点进行绑定。
- 控制器会处理请求并执行必要的步骤,以使卷在指定节点上可用。
- 例如,可能需要在存储后端进行一些配置或设置。
- NodeStageVolume:
- 在这个阶段,节点会准备卷以便于挂载。
- 这通常涉及到在节点上挂载存储后端的卷,使其可以被访问。
- 该步骤可能包括创建目录、挂载文件系统等。
- NodePublishVolume:
- 该阶段将卷挂载到容器中,使其可以被应用程序使用。
- 具体来说,这一步骤会将已经准备好的卷挂载到容器的指定路径。
- NodeUnpublishVolume:
- 在这个阶段,节点会卸载已挂载的卷。
- 这通常是为了在容器停止运行或需要释放资源时进行的操作。
- NodeUnstageVolume:
- 该步骤是将卷从节点的文件系统中卸载。
- 这意味着将卷从节点的挂载点解除,使其不再可用。
- ControllerUnpublishVolume:
- 该阶段是解除卷与节点之间的绑定。
- 控制器会处理请求,确保卷不再与特定节点关联。
- DeleteVolume:
- 最后,删除卷的阶段。
- 控制器会接收删除请求,并在存储后端删除相应的卷。
- 这通常是清理资源的最后一步。
云硬盘生命周期示例
- aws-ebs-csi-driver/pkg/driver at master · kubernetes-sigs/aws-ebs-csi-driver · GitHub
- azuredisk-csi-driver/pkg/azuredisk at master · kubernetes-sigs/azuredisk-csi-driver · GitHub
- [huaweicloud-csi-driver/pkg/evs at master · huaweicloud/huaweicloud-csi-driver · GitHub](https://github.com/huaweicloud/huaweicloud-csi-driver/tree/master/pkg/evs
- alibaba-cloud-csi-driver/pkg/disk at master · kubernetes-sigs/alibaba-cloud-csi-driver · GitHub
上面列举了四个云厂商云硬盘的driver地址,可以从各个云厂商的代码中看到driver每个接口的实现,从而了解各个生命周期实际做了什么事情。各个厂商在实现云硬盘生命周期时都大同小异。
ControllerServer的 CreateVolume 和 DeleteVolume 显而易见是调用云硬盘接口进行创删卷的实现。
ControllerPublishVolume 和 ControllerUnpublishVolume 通过调用云服务器进行了云硬盘的挂卷和卸卷。例如AWS调用了EC2的挂卷接口进行挂卷,华为云使用了ECS的接口进行挂卷。
NodeStageVolume 是将已经挂卷到云服务器的 device 路径挂载(mount)到 StagingTargetPath
。一般会在 /dev/disk/by-id/
或 /dev/disk/by-path/
等路径查找到包含相应volume id的挂载盘,具体路径需要看各个云厂商 IaaS的实现。如果不存在文件系统,一般还会进行格式化。NodeUnstageVolume 正好相反。
NodePublishVolume 比较简单,将 StagingTargetPath
挂载到 Pod 相应挂载路径即可。NodeUnpublishVolume 进行 umount。
极简挂卸卷及示例
+-+ +-+
|X| | |
+++ +^+
| |
Node | | Node
Publish | | Unpublish
Volume | | Volume
+---v----+---+
| PUBLISHED |
+------------+
Figure 8: Plugins MAY forego other lifecycle steps by contraindicating
them via the capabilities API. Interactions with the volumes of such
plugins is reduced to `NodePublishVolume` and `NodeUnpublishVolume`
calls.
NFS / S3 等卷的挂载极其简单,仅有 NodePublishVolume 和 NodeUnpublishVolume。
对于 S3 来说,参考 awslabs/mountpoint-s3-csi-driver 。在 NodePublishVolume 中使用mount-s3 直接将对象桶挂载到 Pod 挂载路径。
NFS更为简单,参考 kubernetes-csi/csi-driver-nfs 直接进行 mount 挂载即可。
对于这些极简挂卸卷的driver来说,只要不注册相应能力即可。对于Controller(Un)PublishVolume,不注册 ControllerServiceCapability的 PUBLISH_UNPUBLISH_VOLUME;对于 Node(Un)StageVolume不注册 STAGE_UNSTAGE_VOLUME。
另外发布的CSIDriver 中的 attachRequired
也可设置为 false,不再进行挂载动作。
attachRequired
是一个高层的标志,用于指示 Kubernetes 是否需要在 Pod 调度之前执行附加操作,而 PUBLISH_UNPUBLISH_VOLUME
则是在 CSI 驱动程序的能力层面上定义的,表示驱动程序能够处理的具体操作。
CSI Driver 资源及注册
资源
CSIDriver
要安装 CSI 驱动,部署清单必须包含一个 CSIDriver
对象。
CSIDriver
Kubernetes API 对象有两个主要用途:
- 简化驱动发现 :如果一个 CSI 驱动创建了
CSIDriver
对象,Kubernetes 用户可以轻松发现集群中安装的 CSI 驱动(通过运行kubectl get CSIDriver
)。 - 自定义 Kubernetes 行为 :Kubernetes 在处理 CSI 驱动时有一套默认行为(例如,默认调用
Attach
/Detach
操作)。这个对象允许 CSI 驱动指定 Kubernetes 应如何与之交互。
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: mycsidriver.example.com
spec:
attachRequired: true
podInfoOnMount: true
fsGroupPolicy: File # Kubernetes 1.19 添加,此字段在 Kubernetes 1.23 为 GA
volumeLifecycleModes: # Kubernetes 1.16 添加,此字段为 beta
- Persistent
- Ephemeral
tokenRequests: # Kubernetes 1.20 添加
- audience: "gcp"
- audience: "" # 空字符串表示默认使用 kube-apiserver 的 --api-audiences
expirationSeconds: 3600
requiresRepublish: true # Kubernetes 1.20 添加
seLinuxMount: true # Kubernetes 1.25 添加
- name: 对应 CSI 驱动的全名。
- attachRequired: 指示 CSI 卷驱动是否需要 attach 操作。
- podInfoOnMount: 指示该 CSI 卷驱动是否需要在挂载操作中获取额外的 pod 信息。
- fsGroupPolicy: 控制 CSI 卷驱动是否支持在挂载时更改卷的所有权和权限。
- volumeLifecycleModes: 指示驱动支持的卷模式,支持持久卷和临时卷。
- tokenRequests: 指示 Kubelet 是否在
NodePublishVolume
中传递绑定的服务账户令牌。 - requiresRepublish: 指示 Kubelet 是否定期调用
NodePublishVolume
。 - seLinuxMount: 指示 CSI 驱动是否可以为不同的卷使用不同的 SELinux 标签。
CSINode
CSINode 对象用于存储与 Kubernetes 节点相关的 CSI 驱动程序生成的特定信息。CSI 驱动程序不需要直接创建 CSINode
对象。Kubelet 在通过 kubelet 插件注册机制注册 CSI 驱动程序时管理该对象。node-driver-registrar
辅助容器帮助完成此注册。
它的主要用途包括:
- 节点名称映射
- CSI
GetNodeInfo
调用返回存储系统引用节点的名称。Kubernetes 必须在后续的ControllerPublishVolume
调用中使用该名称。因此,当新的 CSI 驱动程序注册时,Kubernetes 会在CSINode
对象中存储存储系统节点 ID 以供将来参考。
- CSI
- 驱动程序可用性
- kubelet 可以与 kube-controller-manager 和 Kubernetes 调度程序通信,指示驱动程序在节点上是否可用(已注册)。
- 卷拓扑
- CSI
GetNodeInfo
调用返回一组标识节点拓扑的键/值标签。Kubernetes 使用这些信息进行拓扑感知的配置。它将键/值存储为 Kubernetes 节点对象的标签。为了回忆哪些Node
标签键属于特定的 CSI 驱动程序,kubelet 将这些键存储在CSINode
对象中以供将来参考。
- CSI
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
name: node1
spec:
drivers:
- name: mycsidriver.example.com
nodeID: storageNodeID1
topologyKeys: ['mycsidriver.example.com/regions', 'mycsidriver.example.com/zones']
drivers
- 运行在节点上的 CSI 驱动程序列表及其属性。name
- 此对象所指的 CSI 驱动程序。nodeID
- 驱动程序确定的节点分配标识符。topologyKeys
- 驱动程序支持的分配给节点的拓扑键列表。
node-driver-registrar
node-driver-registrar
是一个Sidecar容器,它通过 Kubelet 插件注册机制 注册 CSI 驱动程序。Kubelet 负责发出 CSI 的 NodeGetInfo
、NodeStageVolume
和 NodePublishVolume
调用。node-driver-registrar
使 Kubelet 知道哪个 Unix 域套接字用于发出这些调用。
node-driver-registrar
使用两个 UNIX 域套接字:
- 注册套接字 :用于将驱动程序注册到 Kubelet,通常位于
/var/lib/kubelet/plugins_registry/<drivername.example.com>-reg.sock
。 - CSI 驱动程序套接字 :Kubelet 用于与 CSI 驱动程序交互,通常位于
/var/lib/kubelet/plugins/<drivername.example.com>/csi.sock
。
必需参数
--csi-address
:CSI 驱动程序套接字在 pod 内的路径。--kubelet-registration-path
:Kubelet 用于发出 CSI 操作的套接字路径
CSI Controller Sidecar
Kubernetes CSI Sidecar 容器是一组标准容器,旨在简化 Kubernetes 上 CSI 驱动程序的开发和部署。
以下均是一些controller容器,需要创建服务账户,并授予其足够的权限。都可以和ControllerServer放在同一Pod中运行。见部署方式
external-provisioner
external-Provisioner 是一个 sidecar 容器,它监视 Kubernetes API 服务器中的 PersistentVolumeClaim
对象,并调用指定的 CSI Driver 的 CreateVolume
方法来配置新的卷。卷的配置是通过创建新的 PersistentVolumeClaim
对象触发的,前提是 PVC 引用一个 Kubernetes StorageClass
,并且 StorageClass 中的 provisioner
字段的名称与指定的 CSI 端点在 GetPluginInfo
调用中返回的名称匹配。
成功配置新卷后,外部 provisioner 会创建一个 Kubernetes PersistentVolume
对象来表示该卷。
当绑定到该驱动的 PersistentVolume
的 PersistentVolumeClaim
对象被删除,并且其回收策略为 delete
时,外部 provisioner 会触发 DeleteVolume
操作以删除指定 CSI 端点中的卷。
external-attacher
external-attacher 是一个sidecar 容器,负责监视由控制器管理器创建的 VolumeAttachment
对象,并通过调用 CSI 驱动的 ControllerPublish
和 ControllerUnpublish
函数,将卷附加到节点。由于 Kubernetes 控制器管理器中的内部附加/分离控制器没有直接与 CSI 驱动的接口,因此需要使用 external-attacher。
external-resizer
CSI external-resizer
是一个sidecar容器,监视 Kubernetes API 服务器中的 PersistentVolumeClaim
更新,并在用户请求扩展存储时触发 ControllerExpandVolume
操作。
external-snapshotter
CSI Snapshotter 是 Kubernetes 中实现的容器存储接口(CSI)的一部分,实现了卷快照和卷组快照功能。
external-snapshotter 主要由以下组件构成:
- Volume Snapshot 和 Volume Group Snapshot CRDs:自定义资源定义,用于定义快照对象。
- 快照控制器 :负责处理快照的创建和管理逻辑。
- 验证 webhook:用于验证快照对象的合法性。
部署方式
CSI驱动程序通常在Kubernetes中作为两个组件部署:控制器组件和每个节点组件。
控制器插件
- 部署方式 :控制器组件可以作为Deployment或StatefulSet部署在集群中的任何节点上。它包括实现CSI控制器服务的CSI驱动程序和一个或多个侧车容器(sidecar containers)。
- 功能 :控制器侧车容器通常与Kubernetes对象交互,并调用驱动程序的CSI控制器服务。它通常不需要直接访问主机,并且可以通过Kubernetes API和外部控制平面服务执行所有操作。
- 高可用性 :可以部署多个控制器组件以实现高可用性(HA),但建议使用领导选举来确保同时只有一个活动控制器。
- 侧车容器 :控制器侧车包括external-provisioner、external-attacher、external-snapshotter和external-resizer等。包括侧车的部署可能是可选的。
节点插件
- 部署方式 :节点组件应通过DaemonSet在集群中的每个节点上部署。它包括实现CSI节点服务的CSI驱动程序和node-driver-registrar侧车容器。
- Kubelet通信 :Kubernetes的kubelet在每个节点上运行,负责调用CSI节点服务。这些调用挂载和卸载存储卷,使其可供Pod使用。
- 卷挂载 :节点插件需要直接访问主机,以使块设备和/或文件系统挂载可用于Kubernetes kubelet。
部署步骤
- 允许特权Pod:Kubernetes集群必须允许特权Pod(即API服务器和kubelet的
--allow-privileged
标志必须设置为true
)。 - 启用挂载传播 :CSI依赖于挂载传播功能,以允许一个容器挂载的卷与同一Pod中的其他容器或同一节点上的其他Pod共享。
举个例子
还是以aws ebs插件举例。可以看 aws-ebs-csi-driver/deploy 中的Kubernetes资源配置。
clusterrole-attacher.yaml
clusterrolebinding-attacher.yaml
clusterrolebinding-csi-node.yaml
clusterrolebinding-provisioner.yaml
clusterrolebinding-resizer.yaml
clusterrolebinding-snapshotter.yaml
clusterrole-csi-node.yaml
clusterrole-provisioner.yaml
clusterrole-resizer.yaml
clusterrole-snapshotter.yaml
controller.yaml
csidriver.yaml
kustomization.yaml
node.yaml
poddisruptionbudget-controller.yaml
rolebinding-leases.yaml
role-leases.yaml
serviceaccount-csi-controller.yaml
serviceaccount-csi-node.yaml
其中大部分都是RBAC权限控制的配置。其他进行一下简要说明:
- CSIDriver.yaml: 用于创建 CSIDriver 资源;
- node.yaml: 是DaemonSet,将会在每个节点上都部署该Pod。里面包含了 node-driver-registrar 注册容器,livenessprobe 健康检查容器,还有 aws-ebs-csi-driver 本身。
- aws-ebs-csi-driver 传入参数为 node,可以结合 driver.go 代码中的
NewDriver
可以看到传入 node 时,仅开启了 NodeService,因此 node.yaml 不存在任何 ControllerService的接口。
- aws-ebs-csi-driver 传入参数为 node,可以结合 driver.go 代码中的
- controller.yaml: 是Deployment,默认两个副本数。其中包含了 external-provisioner / external-attacher / external-resizer / external-snapshotter 四个管理容器,livenessprobe 健康检查容器,还有 aws-ebs-csi-driver 本身。
- aws-ebs-csi-driver 在此没传入模式,代码中默认使用all模式,同时启用 ControllerService 和 NodeService,但由于没有注册容器,此处的NodeService应该是不生效的。
特性
Kubernetes CSI 包含很多子特性,可以从 Features 找到说明。