CSI架构和原理
CSI
CSI简介
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 等)。
Pod挂载volume的过程
-
用户创建一个包含PVC的Pod(使用动态存储卷);
-
PV Controller发现这个PVC处于待绑定状态,调用Volume Plugin(in-tree或者out-of-tree)创建存储卷,并创建PV对象,然后将创建的PV与PVC绑定;
-
Scheduler根据Pod的配置、节点状态、PV配置等信息,把Pod调度刀worker节点Node上;
-
AD Controller发现Pod和PVC处于待挂载状态,调用Volume Plugin(in-tree或者out-of-tree)实现设备挂载到目标节点(/dev/vdb);
-
在worker节点上,kubelet(Volume Manager)等待设备挂载完成,通过Volume Plugin将设备挂载到指定目录:/var/lib/kubelet/pods/646154cf-dc72-11e9-b200-00163e007d53/volumes/alicloud~disk/pv-disk;
-
kubelet在被告知挂载目录准备好后,启动Pod中的containers,用Docker -v方式(bind)将已经挂载到本地的卷映射到容器中;
CSI工作原理
CSI的部署模式
CSI 主要包含两个部分:CSI Controller Server 与 CSI Node Server,分别对应Controller Server Pod和Node Server Pod
Controller Server
Controller Server 主要是通过多个外部插件来实现的,一个 Pod 中可以定义多个 sideCar形式的External Container 和一个包含 CSI Controller Server 的 Container,这时候不同的 External 组件会和 Controller Server 组成不同的功能。
交互 | 过程 |
---|---|
External Provisioner + Controller Server | 创建、删除数据卷 |
External Attacher + Controller Server | 执行数据卷的挂载、卸载操作 |
Volume Manager + Volume Plugin + Node Server | 执行数据卷的Mount、Umount操作 |
AD Controller + VolumePlugin | 创建、删除VolumeAttachment对象 |
External Resizer + Controller Server | 执行数据卷的扩容操作 |
ExternalSnapshotter+ControllerServer | 执行数据卷的备份操作 |
Driver Registrar + VolumeManager + Node Server | 注册CSI插件,创建CSINode对象 |
Node Server
Node Server Pod 是个 DaemonSet,它会在每个节点上进行注册。
Kubelet 会直接通过 Socket 的方式直接和 CSI Node Server 进行通信、调用 Attach/Detach/Mount/Unmount 等。
Driver Registrar
Driver Registrar 只是做一个注册的功能,会在每个节点上进行部署。
CSI的工作原理
CSI Controller
CSI Controller,它是以 deployment 的形式运行在集群里面,主要负责 provision 和 attach 工作。
attach并不是不是每一个存储都会用到的,而 provision 就是在使用 StorageClass 的时候会动态创建 PV 的过程, CSI Controller 在实现 provision 这个功能的时候,是 external-provisioner 这个 SideCar 去配合实现的,在实现 attach 功能的时候是external-attacher 这个SideCar配合它一起完成的。
注意:动态创建pv才会走到provision流程,静态并不会
CSI Node & CSI Identity
CSI Node 和 CSI Identity 通常是部署在一个容器里面的,它们是以 daemonset 的形式运行在集群里面,保证每一个节点会有一个 Pod 部署出来,这两个组件会和 CSI Controller 一起完成 volume 的 mount 操作。
CSI Identity 是用来告诉 Controller,我现在是哪一个 CSI 插件,它实现的接口会被 node-driver-registrar 调用给 Controller 去注册自己。
CSI Node 会实现一些 publish volume 和 unpublished volume 的接口,Controller 会去调用来完成 volume 的 mount 的操作,我们只需要实现这几个插件的接口就可以了。
CSI对象
volumeAttachment
volumeAttachment描述了一个volume卷挂载、卸载相关的信息,包含:卷的名字、挂载的节点、使用的CSIDriver插件、当前状态等信息,代表一个挂载操作的期望;
-
AD Controller在执行挂载一个CSI PV的时候,会调用csi-attacher(in-tree)创建一个volume Attachment
-
External-attacher通过watch volume attachment,发现有需要挂载的数据卷,调用csi-plugin的controllerPublishVolume方法,执行attach操作
-
volume Attachment是由AD Controller调用csi-attacher删除
? kubectl get volumeattachments -o wide
NAME ATTACHER PV NODE ATTACHED AGE
csi-1ac8 udisk.csi.ucloud.cn pvc-fc6b4beb-3766-488e-a261-792a25 10.13.34.201 true 103m
csi-87fe udisk.csi.ucloud.cn pvc-65bb1aa1-b15e-45eb-82ce-29b2f7 10.13.136.103 true 103m
CSIDriver(驱动程序)
CSIDriver 用于定义和配置 CSI 驱动程序的属性和行为,是集群范围的资源对象。它描述了集群中所部署的 CSI Plugin 列表,需要管理员根据插件类型进行创建。CSIDriver 对象是集群范围的,即在整个集群中共享和使用;
可以通过 kuberctl get csidriver
可以看到集群里面创建的各种类型的 CSI Driver
? kubectl get csidrivers.csi.storage.k8s.io
NAME AGE
udisk.csi.ucloud.cn 4h9m
CSINode(节点)
CSINode 用于将 CSI 驱动程序绑定到节点上,表示节点上的 CSI 驱动程序插件,是节点级别的资源对象。它是集群中的节点信息,在 Node Driver Registrar 组件向 Kubelet 注册完毕后,Kubelet 会创建该资源,故不需要显式创建 CSINode 资源。它的作用是每一个新的 CSI Plugin 注册后,都会在 CSINode 列表里添加一个 CSINode 信息。
-
CSINode 对象用于告知 Kubernetes 集群该节点上可用的 CSI 驱动程序,以便在调度 Pod 时进行选择和匹配;
-
CSINode 对象是节点级别的,每个节点上都需要创建一个对应的 CSINode 对象;
将 Kubernetes 中 Node 资源名称与三方存储系统中节点名称(nodeID)一一对应。此处Kubelet会调用外部 CSI 插件NodeServer 的 GetNodeInfo 函数获取 nodeID。
CSINode 中 topologyKeys 用来表示存储节点的拓扑信息,卷拓扑信息会使得Scheduler在 Pod 调度时选择合适的存储节点。
? kubectl get csinodes.storage.k8s.io -o wide # 集群一共有5个节点,对应5个csinode
NAME DRIVERS AGE
10.13.109.116 1 4h13m
10.13.116.114 1 4h13m
10.13.136.103 1 4h12m
10.13.170.186 1 4h13m
10.13.34.201 1 4h12m
CSI核心流程
K8s 中的 Pod 在挂载存储卷时需经历三个的阶段
-
Provision/Delete(创盘/删盘)
-
Attach/Detach(挂接/摘除)
-
Mount/Unmount(挂载/卸载)
Provisioning Volumes
-
创盘由External Provisioner来完成
-
集群管理员创建 StorageClass 资源,该 StorageClass 中包含 CSI 插件名称;
-
用户创建 PVC 资源,PVC 指定存储大小及 StorageClass;
-
卷控制器(PV Controller)观察到集群中新创建的 PVC 没有与之匹配的 PV,且其使用的存储类型为 out-of-tree,于是为 PVC 打 annotation:
volume.beta.kubernetes.io/storage-provisioner=[out-of-tree CSI 插件名称]
-
External Provisioner 组件观察到 PVC 的 annotation 中包含
volume.beta.kubernetes.io/storage-provisioner
且其 value 是自己,于是开始创盘流程:-
获取相关 StorageClass 资源并从中获取参数,用于后面 CSI 函数调用
-
通过 unix domain socket 调用外部 CSI 插件的CreateVolume 函数
-
-
外部 CSI 插件返回成功后表示盘创建完成,此时External Provisioner 组件会在集群创建一个 PersistentVolume 资源。
-
卷控制器会将 PV 与 PVC 进行绑定。
Attaching Volumes
-
挂接由External Attacher完成
-
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 上;
? kubectl get node 10.13.136.103 -o yaml | tail -n 20
...
volumesAttached
Mounting Volumes
-
挂载由kubelet来完成
-
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 函数;
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 函数。
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 上已没有相关挂接信息;
Deleting Volumes
-
用户删除相关 PVC;
-
External Provisioner 组件观察到 PVC 删除事件,根据 PVC 的回收策略(Reclaim)执行不同操作:
-
Delete:调用外部 CSI 插件的DeleteVolume 函数以删除卷;一旦卷成功删除,Provisioner会删除集群中对应 PV 对象;
-
Retain:Provisioner不执行卷删除操作;
-
CSI 组件
sidecar组件由Kubernetes官方维护
Cluster Driver Registrar
功能
Cluster Driver Registrar 负责自动注册 CSI 驱动程序到 Kubernetes 集群中
注册范围:Cluster Driver Registrar 在整个集群范围内工作,负责集群中所有节点上的 CSI 驱动程序的注册
注册过程
-
注册过程:它会监听 Kubernetes API 中的 CSI 驱动程序配置对象(CSIDriver 对象),当有新的配置创建或更新时,Cluster Driver Registrar 将相应的 CSI 驱动程序注册到集群中。
-
功能扩展:除了注册驱动程序,Cluster Driver Registrar 还负责更新驱动程序的信息到 Kubernetes API 中的其他对象,如 CSINode 和 CSIDriver 对象。
Cluster Driver Registrar & Node Driver Registrar
-
Cluster Driver Registrar 在整个集群范围内工作,负责自动注册和更新 CSI 驱动程序的信息。
-
Node Driver Registrar 在每个节点上运行,负责启动和管理该节点上的 CSI 驱动程序,并处理与存储卷附加和卸载相关的操作。
Node Driver Registrar
功能
Node-Driver-Registrar 组件会将外部 CSI 插件注册到Kubelet,从而使Kubelet通过特定的 Unix Domain Socket 来调用外部 CSI 插件函数
Kubelet 会调用外部 CSI 插件的 NodeGetInfo、NodeStageVolume、NodePublishVolume、NodeGetVolumeStats 等函数
注册成功后的操作
-
Kubelet为本节点 Node 资源打 annotation:Kubelet调用外部 CSI 插件的NodeGetInfo 函数,其返回值 [nodeID]、[driverName] 将作为值用于 “
csi.volume.kubernetes.io/nodeid
” 键; -
Kubelet更新 Node Label:将NodeGetInfo 函数返回的 [AccessibleTopology] 值用于节点的 Label;
-
Kubelet更新 Node Status:将NodeGetInfo 函数返回的 maxAttachLimit(节点最大可挂载卷数量)更新到 Node 资源的
Status.Allocatable:attachable-volumes-csi-[driverName]=[maxAttachLimit]
-
Kubelet更新 CSINode 资源(没有则创建):将 [driverName]、[nodeID]、[maxAttachLimit]、[AccessibleTopology] 更新到 Spec 中(拓扑仅保留 Key 值);
External Provisioner
功能
External Provisioner用于创建/删除实际的存储卷,以及代表存储卷的 PV 资源。
External-Provisioner在启动时需指定参数 — provisioner,该参数指定 Provisioner 名称,与 StorageClass 中的 provisioner 字段对应。
watch集群的PVC和PV资源
External-Provisioner启动后会 watch 集群中的 PVC 和 PV 资源:
1)对于PVC资源:
-
判断 PVC 是否需要动态创建存储卷,标准如下:
-
PVC 的 annotation 中是否包含 “
volume.beta.kubernetes.io/storage-provisioner
” 键(由卷控制器创建)并且其值是否与 Provisioner 名称相等 -
PVC 对应 StorageClass 的 VolumeBindingMode 字段:
-
若为 WaitForFirstConsumer,则 PVC 的 annotation 中必须包含 “volume.kubernetes.io/selected-node” 键,且其值不为空;
-
若为 Immediate 则表示需要 Provisioner 立即提供动态存储卷;
-
-
-
通过特定的 Unix Domain Socket 调用外部 CSI 插件的 CreateVolume 函数;
-
创建 PV 资源,PV 名称为 [Provisioner 指定的 PV 前缀] – [PVC uuid]
2)对于PV资源:
-
判断 PV 是否需要删除,标准如下:
-
判断其 .Status.Phase 是否为 Release;
-
判断其 .Spec.PersistentVolumeReclaimPolicy 是否为 Delete;
-
判断其是否包含 annotation(pv.kubernetes.io/provisioned-by),且其值是否为自己;
-
-
通过特定的 Unix Domain Socket 调用外部 CSI 插件的 DeleteVolume 接口;
-
删除集群中的 PV 资源;
External Attacher
功能
External Attacher用于挂接/摘除存储卷
watch集群的VA和PV资源
External-Attacher 内部会时刻 watch 集群中的 VolumeAttachment 资源和 PersistentVolume 资源
1)对于VolumeAttachment资源:
-
从 VolumeAttachment 资源中获得 PV 的所有信息,如 volume ID、node ID、挂载 Secret 等
-
判断 VolumeAttachment 的 DeletionTimestamp 字段是否为空来判断其为卷挂接或卷摘除;
-
若为卷挂接则通过特定的 Unix Domain Socket 调用外部 CSI 插件的ControllerPublishVolume 接口;
-
若为卷摘除则通过特定的 Unix Domain Socket 调用外部 CSI 插件的ControllerUnpublishVolume 接口;
-
2)对于PV资源:
-
在挂接时为相关 PV 打上
Finalizer:external-attacher/[driver 名称]
-
当 PV 处于删除状态时(DeletionTimestamp 非空),删除
Finalizer:external-attacher/[driver 名称]
External Resizer
功能
External Resizer用于扩容存储卷
watch集群的PVC资源
External-Resizer内部会 watch 集群中的 PersistentVolumeClaim 资源
判断 PersistentVolumeClaim 资源是否需要扩容:PVC 状态需要是 Bound 且 .Status.Capacity 与 .Spec.Resources.Requests 不等
-
更新 PVC 的 .Status.Conditions,表明此时处于 Resizing 状态
-
通过特定的 Unix Domain Socket 调用外部 CSI 插件的 ControllerExpandVolume 接口
-
更新 PV 的 .Spec.Capacity
-
若 CSI 支持文件系统在线扩容,ControllerExpandVolume 接口返回值中 NodeExpansionRequired 字段为 true,External-Resizer更新 PVC 的 .Status.Conditions 为 FileSystemResizePending 状态;
-
若不支持则扩容成功,External-Resizer更新 PVC 的 .Status.Conditions 为空,且更新 PVC 的 .Status.Capacity
Volume Manager(Kubelet 组件)观察到存储卷需在线扩容,于是通过特定的 Unix Domain Socket 调用外部 CSI 插件的NodeExpandVolume 接口实现文件系统扩容
livenessprobe
livenessprobe用于检查CSI插件是否正常
通过对外暴露一个 / healthz HTTP 端口以服务 kubelet 的探针探测器,内部是通过特定的 Unix Domain Socket 调用外部 CSI 插件的 Probe 接口
第三方厂商要实现的CSI接口
IdentityServer
IdentityServer 主要用于认证 CSI 插件的身份信息
ControllerServer
ControllerServer 主要负责存储卷及快照的创建/删除以及挂接/摘除操作
NodeServer
NodeServer 主要负责存储卷挂载/卸载操作