容器存储接口--CSI
一、背景
K8s 原生支持一些存储类型的 PV,如 iSCSI
、NFS
、CephFS
等等,这些 in-tree
类型的存储代码放在 Kubernetes 代码仓库中。这里带来的问题是 K8s 代码与三方存储厂商的代码强耦合
- 更改
in-tree
类型的存储代码,用户必须更新 K8s 组件,成本较高 in-tree
存储代码中的 bug 会引发 K8s 组件不稳定- K8s 社区需要负责维护及测试 in-tree 类型的存储功能
in-tree
存储插件享有与 K8s 核心组件同等的特权,存在安全隐患- 三方存储开发者必须遵循 K8s 社区的规则开发 in-tree 类型存储代码
CSI 容器存储接口标准的出现解决了上述问题,将三方存储代码与 K8s 代码解耦,使得三方存储厂商研发人员只需实现 CSI 接口(无需关注容器平台是 K8s 还是 Swarm 等)。
二、CSI 是什么
CSI是Container Storage Interface
(容器存储接口)的简写。
CSI的目的是定义行业标准"容器存储接口",使存储供应商(SP,Storage Provider)能够开发一个符合CSI标准的插件并使其可以在多个容器编排(CO)系统中工作。CO包括Cloud Foundry, Kubernetes, Mesos,Swarm等。
kubernetes将通过CSI接口来跟第三方存储厂商进行通信,来操作存储,从而提供容器存储服务。
三、CSI 系统架构
CSI 主要包含两个部分:
-
Controller Server:主要控制端的功能,主要实现创建、删除、挂载、卸载等功能。通常以 StatefulSet/Deployment 形式部署
-
Node Server:主要实现节点上的 mount , unmount功能。通常以DaemonSet 形式部署在每一个节点
1、CSI 如何与 k8s 组件相互通信
CSI Controller Server 和 External CSI SideCar 之间以及 CSI Node Server 和 Kubelet 之间其实都是通过创建 Unix Domain Socket 文件使用 gRPC 协议进行互相通信的
2、CSI 由哪些组件组成
- Node Service: 运行在每个 Kubernetes 节点上,负责在节点上挂载和卸载存储卷,并处理节点级别的存储操作。
- Controller Service: 运行在 Kubernetes 控制平面中,负责管理存储卷的生命周期,包括创建、删除和扩容等操作。
- Identity Service:它在 CSI 驱动器注册时提供标识信息,并向 Kubernetes 集群公开驱动器的支持能力。它负责告知 Kubernetes 驱动器的存在,提供驱动器的基本信息和功能支持。
3、CSI 的工作原理
CSI 的工作流程实际上就是围绕着两方面的组件:
- 由 Kubernetes 官方维护的一组标准 external 组件,他们主要负责监听 K8s 里的资源对象,从而向 CSI Driver 发起 gRPC 调用。 Kubernetes CSI Sidecar Containers。它们是与 CSI 驱动器一起部署在同一个 Pod 中,用于辅助 CSI Driver 完成一些额外的任务和功能。
- 各存储厂商开发的组件(主要实现 Identity Service,Controller Service,Node Service)
4、k8s 存储中涉及的组件及其作用
4.1、Sidecar Containers
Kubernetes CSI Sidecar 容器
是一组标准容器,旨在简化 Kubernetes 上 CSI 驱动程序的开发和部署。
这些容器包含监视 Kubernetes API
、针对 "CSI 卷驱动程序" 容器触发适当操作以及根据需要更新 Kubernetes API 的通用逻辑。
这些容器旨在与第三方 CSI 驱动容器捆绑在一起,并作为 pod 一起部署。
这些 Sidecar 容器的优点:
-
减少 "样板" 代码
- CSI 驱动程序开发人员不必担心复杂的"Kubernetes 特定"代码。
-
关注点分离
- 与 Kubernetes API 交互的代码与实现 CSI 接口的代码是隔离的(并且位于不同的容器中)。
4.1.1、external-attacher
它监视 Kubernetes API 服务器
上的 VolumeAttachment
对象并Controller[Publish|Unpublish]Volume
针对 CSI 端点触发操作。
4.1.2、external-provisioner
CSI external-provisioner
是一个 sidecar
容器,用于监视 Kubernetes API 服务器中的 PersistentVolumeClaim
对象。
它针对指定的 CSI 端点调用 CreateVolume
以配置新卷。
如果 PVC 引用 Kubernetes StorageClass
,并且存储类的配置者字段中的名称与 GetPluginInfo
调用中指定的 CSI 端点返回的名称匹配,则通过创建新的 Kubernetes PersistentVolumeClaim
对象来触发卷配置。
成功配置新卷后,sidecar 容器会创建一个 Kubernetes PersistentVolume
对象来表示该卷。
使用删除回收策略删除绑定到与此驱动程序对应的 PersistentVolume
的 PersistentVolumeClaim
对象,会导致 sidecar 容器针对指定的 CSI 端点触发 DeleteVolume
操作以删除该卷。一旦卷被成功删除,sidecar 容器也会删除代表该卷的 PersistentVolume
对象。
4.1.3、external-resizer
CSI external-resizer
是一个 sidecar 容器,它监视 Kubernetes API 服务器
上的 PersistentVolumeClaim
对象编辑,并在用户请求 PersistentVolumeClaim
对象上的更多存储时触发针对 CSI 端点的 ControllerExpandVolume
操作。
4.1.4、external-snapshotter
CSI external-snapshotter sidecar
监视 Kubernetes API 服务器中的 VolumeSnapshotContent
CRD 对象。CSI external-snapshotter sidecar
还负责调用 CSI RPCs CreateSnapshot
、DeleteSnapshot
和 ListSnapshots
。
可以使用 --enable-volume-group-snapshots
选项启用卷组快照支持。启用后,CSI 外部快照 sidecar 会监视 API 服务器上的 VolumeGroupSnapshotContent
CRD 对象,并负责调用 CSI RPCs CreateVolumeGroupSnapshot
、DeleteVolumeGroupSnapshot
和 GetVolumeGroupSnapshot
。
4.1.5、livenessprobe
CSI livenessprobe
是一个 sidecar 容器,用于监控 CSI 驱动程序的运行状况,并通过 Liveness Probe
机制向 Kubernetes 报告。这使得 Kubernetes 能够自动检测驱动程序的问题并重新启动 pod 以尝试修复问题。
所有 CSI 驱动程序都应使用活性探针来提高部署在 Kubernetes 上的驱动程序的可用性。
4.1.6、node-driver-registrar
CSI node-driver-registrar
是一个 sidecar 容器,它从 CSI 端点获取驱动程序信息(使用 NodeGetInfo
),并使用 kubelet plugin registration mechanism (kubelet 插件注册机制)将其注册到该节点上的 kubelet。
Kubelet
直接针对 CSI driver
发出 CSI NodeGetInfo
、NodeStageVolume
和 NodePublishVolume
调用。它使用 kubelet
插件注册机制来发现 unix 域套接字以与 CSI 驱动程序通信。因此,所有 CSI 驱动程序都应该使用这个 sidecar 容器向 kubelet
注册自己。
4.1.7、cluster-driver-registrar(已弃用)
自 k8s 1.13以来,此 Sidecar Container 未更新。从 k8s 1.16开始,此 Sidecar Container 已正式弃用
CSI cluster-driver-registrar
是一个 sidecar 容器,它通过创建 CSIDriver
对象向 Kubernetes 集群注册 CSI 驱动程序,该对象使驱动程序能够自定义 Kubernetes 与其交互的方式。
使用以下 Kubernetes 功能之一的 CSI 驱动程序应使用此 sidecar 容器:
-
- 对于不支持
ControllerPublishVolume
的驱动程序,这指示 Kubernetes 跳过附加操作,并且无需部署外部附加器 sidecar。
- 对于不支持
-
- 这会导致 Kubernetes 将 Pod 名称和命名空间等元数据传递给
NodePublishVolume
调用。
- 这会导致 Kubernetes 将 Pod 名称和命名空间等元数据传递给
如果不使用这些功能之一,则不需要此 sidecar 容器(以及 CSIDriver
对象的创建)。
4.1.8、external-health-monitor-controller
CSI external-health-monitor-controller
是一个 sidecar 容器,与 CSI controller driver
一起部署,类似于 CSI external-provisioner sidecar
的部署方式。它调用 CSI 控制器 RPC ListVolumes
或 ControllerGetVolume
来检查 CSI 卷的运行状况,如果卷的状况异常,则在 PersistentVolumeClaim
上报告事件。
CSI 外部运行状况监视器控制器还监视节点故障事件。可以通过将 enable-node-watcher
标志设置为true来启用该组件。目前这只会对local PVs产生影响。当检测到节点故障事件时,将在 PVC 上报告一个事件,以指示使用此 PVC 的 pod 位于故障节点上。
支持 VOLUME_CONDITION 和 LIST_VOLUMES
或 VOLUME_CONDITION
和 GET_VOLUME
控制器功能的 CSI 驱动程序应使用此 sidecar 容器。
4.1.9、external-health-monitor-agent(已弃用)
此 sidecar 已被弃用,并替换为 Kubernetes 中的
CSIVolumeHealth
功能。
CSI external-health-monitor-agent
是一个 sidecar 容器,与 CSI 节点驱动程序一起部署,类似于 CSI node-driver-registrar sidecar
的部署方式。它调用 CSI 节点 RPC NodeGetVolumeStats
检查 CSI 卷的健康状况,如果卷状况异常,则报告 Pod 上的事件。
支持 VOLUME_CONDITION
和 NODE_GET_VOLUME_STATS
节点功能的 CSI 驱动程序应使用此 sidecar 容器。
4.2、PV Controller
是 kube-controller-manager
内部组件,是 k8s 控制面的一部分,负责 PV/PVC 绑定及周期管理,根据需求进行数据卷的 Provision/Delete 操作
-
in-tree:创建/删除底层存储、创建/删除pv对象的操作,由
PV controller
调用volume plugin(in-tree)
来完成。 -
out-tree CSI:创建/删除底层存储、创建/删除pv对象的操作由
external-provisioner
与csi plugin
共同来完成。
4.3、AD Controller
AD Cotroller
全称 Attachment/Detachment 控制器
,也是 kube-controller-manager
内部一个组件。主要负责创建、删除VolumeAttachment
对象,并调用 volume plugin
来做存储设备的 Attach/Detach
操作(将数据卷挂载到特定node节点上/从特定node节点上解除挂载),以及更新 node.Status.VolumesAttached
等。
不同的 volume plugin
的 Attach/Detach
操作逻辑有所不同,对于 csi plugin(out-tree volume plugin)
来说,AD controller
的 Attach/Detach
操作只是修改 VolumeAttachment
对象的状态,而不会真正的将数据卷挂载到节点/从节点上解除挂载,真正的节点存储挂载/解除挂载操作由kubelet中 volume manager
调用 csi plugin
来完成。
4.4、Volume Manager
是 kubelet
内部一个组件,它主要是用来做节点 Volume 的 Attach/Detach/Mount/Unmount
操作。
对于 csi
来说,volume manager
的 Attach/Detach
操作只创建/删除 VolumeAttachment
对象,而不会真正的将数据卷挂载到节点/从节点上解除挂载;
CSI-Attacher
组件也不会做挂载/解除挂载操作,只是更新 VolumeAttachment
对象状态,真正的节点存储挂载/解除挂载操作由kubelet
中 volume manager
调用调用 csi plugin
来完成。
四、CSI 存储流程
K8s 中的 Pod 在挂载存储卷时需经历三个的阶段:Provision/Delete(创盘/删盘)、Attach/Detach(挂接/摘除)和 Mount/Unmount(挂载/卸载),下面以图文的方式讲解 K8s 在这三个阶段使用 CSI 的流程。
1、Provisioning Volumes
1、集群管理员创建 StorageClass
资源,该 StorageClass
中包含 CSI 插件名称(provisioner: rancher.io/local-path )
2、用户创建 PersistentVolumeClaim 资源,PVC 指定存储大小及 StorageClass。
cat << EOF > pvc-test.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-test
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
storageClassName: local-path
EOF
3、卷控制器(PersistentVolumeController)观察到集群中新创建的 PVC 没有与之匹配的 PV,且其使用的存储类型为 out-of-tree,于是为 PVC 打 annotation:volume.beta.kubernetes.io/storage-provisioner=[out-of-tree CSI 插件名称](本例中为provisioner: rancher.io/local-path)。
4、External Provisioner 组件观察到 PVC 的 annotation 中包含 "volume.beta.kubernetes.io/storage-provisioner" 且其 value 是自己,于是开始创盘流程。
-
获取相关 StorageClass 资源并从中获取参数,用于后面 CSI 函数调用。
-
通过 unix domain socket 调用外部 CSI 插件的 CreateVolume 函数。
5、外部 CSI 插件返回成功后表示盘创建完成,此时 External Provisioner 组件会在集群创建一个 PersistentVolume 资源。
6、卷控制器会将 PV 与 PVC 进行绑定。
2、Attaching Volumes
-
AD 控制器(AttachDetachController)观察到使用 CSI 类型 PV 的 Pod 被调度到某一节点,此时 AD 控制器会调用内部 in-tree CSI 插件(csiAttacher)的 Attach 函数。
-
内部 in-tree CSI 插件(csiAttacher)会创建一个 VolumeAttachment 对象到集群中。
-
External Attacher 观察到该 VolumeAttachment 对象,并调用外部 CSI 插件的 ControllerPublish 函数以将卷挂接到对应节点上。外部 CSI 插件挂载成功后,External Attacher 会更新相关 VolumeAttachment 对象的 .Status.Attached 为 true。
-
AD 控制器内部 in-tree CSI 插件(csiAttacher)观察到 VolumeAttachment 对象的 .Status.Attached 设置为 true,于是更新 AD 控制器内部状态(ActualStateOfWorld),该状态会显示在 Node 资源的 .Status.VolumesAttached 上。
3、Mounting Volumes
-
Volume Manager(Kubelet 组件)观察到有新的使用 CSI 类型 PV 的 Pod 调度到本节点上,于是调用内部 in-tree CSI 插件(csiAttacher)的 WaitForAttach 函数。
-
内部 in-tree CSI 插件(csiAttacher)等待集群中 VolumeAttachment 对象状态 .Status.Attached 变为 true。
-
in-tree CSI 插件(csiAttacher)调用 MountDevice 函数,该函数内部通过 unix domain socket 调用外部 CSI 插件的 NodeStageVolume 函数;之后插件(csiAttacher)调用内部 in-tree CSI 插件(csiMountMgr)的 SetUp 函数,该函数内部会通过 unix domain socket 调用外部 CSI 插件的 NodePublishVolume 函数。
4、Unmounting Volumes
-
用户删除相关 Pod。
-
Volume Manager(Kubelet 组件)观察到包含 CSI 存储卷的 Pod 被删除,于是调用内部 in-tree CSI 插件(csiMountMgr)的 TearDown 函数,该函数内部会通过 unix domain socket 调用外部 CSI 插件的 NodeUnpublishVolume 函数。
-
Volume Manager(Kubelet 组件)调用内部 in-tree CSI 插件(csiAttacher)的 UnmountDevice 函数,该函数内部会通过 unix domain socket 调用外部 CSI 插件的 NodeUnpublishVolume 函数。
5、Detaching Volumes
-
AD 控制器观察到包含 CSI 存储卷的 Pod 被删除,此时该控制器会调用内部 in-tree CSI 插件(csiAttacher)的 Detach 函数。
-
csiAttacher 会删除集群中相关 VolumeAttachment 对象(但由于存在 finalizer,va 对象不会立即删除)。
-
External Attacher 观察到集群中 VolumeAttachment 对象的 DeletionTimestamp 非空,于是调用外部 CSI 插件的 ControllerUnpublish 函数以将卷从对应节点上摘除。外部 CSI 插件摘除成功后,External Attacher 会移除相关 VolumeAttachment 对象的 finalizer 字段,此时 VolumeAttachment 对象被彻底删除。
-
AD 控制器中内部 in-tree CSI 插件(csiAttacher)观察到 VolumeAttachment 对象已删除,于是更新 AD 控制器中的内部状态;同时 AD 控制器更新 Node 资源,此时 Node 资源的 .Status.VolumesAttached 上已没有相关挂接信息。
6、Deleting Volumes
-
用户删除相关 PVC。
-
External Provisioner 组件观察到 PVC 删除事件,根据 PVC 的回收策略(Reclaim)执行不同操作:
-
Delete:调用外部 CSI 插件的 DeleteVolume 函数以删除卷;一旦卷成功删除,Provisioner 会删除集群中对应 PV 对象。
-
Retain:Provisioner 不执行卷删除操作。
-