CSI架构和原理

CSI

CSI简介

CSI的诞生背景

K8s 原生支持一些存储类型的 PV,如 iSCSI、NFS、CephFS 等等,这些 in-tree 类型的存储代码放在 Kubernetes 代码仓库中。这里带来的问题是 K8s 代码与三方存储厂商的代码强耦合:

  1. 更改 in-tree 类型的存储代码,用户必须更新 K8s 组件,成本较高

  2. in-tree 存储代码中的 bug 会引发 K8s 组件不稳定

  3. K8s 社区需要负责维护及测试 in-tree 类型的存储功能

  4. in-tree 存储插件享有与 K8s 核心组件同等的特权,存在安全隐患

  5. 三方存储开发者必须遵循 K8s 社区的规则开发 in-tree 类型存储代码

CSI 容器存储接口标准的出现解决了上述问题,将三方存储代码与 K8s 代码解耦,使得三方存储厂商研发人员只需实现 CSI 接口即可(无需关注容器平台是 K8s 还是 Swarm 等)。

Pod挂载volume的过程

  1. 用户创建一个包含PVC的Pod(使用动态存储卷);

  2. PV Controller发现这个PVC处于待绑定状态,调用Volume Plugin(in-tree或者out-of-tree)创建存储卷,并创建PV对象,然后将创建的PV与PVC绑定;

  3. Scheduler根据Pod的配置、节点状态、PV配置等信息,把Pod调度刀worker节点Node上;

  4. AD Controller发现Pod和PVC处于待挂载状态,调用Volume Plugin(in-tree或者out-of-tree)实现设备挂载到目标节点(/dev/vdb);

  5. 在worker节点上,kubelet(Volume Manager)等待设备挂载完成,通过Volume Plugin将设备挂载到指定目录:/var/lib/kubelet/pods/646154cf-dc72-11e9-b200-00163e007d53/volumes/alicloud~disk/pv-disk;

  6. 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,如果是多备份的,可以部署两个。

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 在挂载存储卷时需经历三个的阶段

  1. Provision/Delete(创盘/删盘)

  2. Attach/Detach(挂接/摘除)

  3. Mount/Unmount(挂载/卸载)

Provisioning Volumes

  • 创盘由External Provisioner来完成

  1. 集群管理员创建 StorageClass 资源,该 StorageClass 中包含 CSI 插件名称;

  2. 用户创建 PVC 资源,PVC 指定存储大小及 StorageClass;

  3. 卷控制器(PV Controller)观察到集群中新创建的 PVC 没有与之匹配的 PV,且其使用的存储类型为 out-of-tree,于是为 PVC 打 annotation:volume.beta.kubernetes.io/storage-provisioner=[out-of-tree CSI 插件名称]

  4. External Provisioner 组件观察到 PVC 的 annotation 中包含 volume.beta.kubernetes.io/storage-provisioner且其 value 是自己,于是开始创盘流程:

    1. 获取相关 StorageClass 资源并从中获取参数,用于后面 CSI 函数调用

    2. 通过 unix domain socket 调用外部 CSI 插件的CreateVolume 函数

  5. 外部 CSI 插件返回成功后表示盘创建完成,此时External Provisioner 组件会在集群创建一个 PersistentVolume 资源。

  6. 卷控制器会将 PV 与 PVC 进行绑定。

Attaching Volumes

  • 挂接由External Attacher完成

  1. AD 控制器(AttachDetachController)观察到使用 CSI 类型 PV 的 Pod 被调度到某一节点,此时AD 控制器会调用内部 in-tree CSI 插件(csiAttacher)的 Attach 函数;

  2. 内部 in-tree CSI 插件(csiAttacher)会创建一个 VolumeAttachment 对象到集群中;

  3. External Attacher 观察到该 VolumeAttachment 对象,并调用外部 CSI插件的ControllerPublish 函数以将卷挂接到对应节点上。当外部 CSI 插件挂载成功后,External Attacher会更新相关 VolumeAttachment 对象的 .Status.Attached 为 true;

  4. 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:
- devicePath: ""
  name: kubernetes.io/csi/udisk.csi.ucloud.cn^bsm-jw6svmajfjn
volumesInUse:
- kubernetes.io/csi/udisk.csi.ucloud.cn^bsm-jw6svmajfjn

Mounting Volumes

  • 挂载由kubelet来完成

  1. Volume Manager(Kubelet 组件)观察到有新的使用 CSI 类型 PV 的 Pod 调度到本节点上,于是调用内部 in-tree CSI 插件(csiAttacher)的 WaitForAttach 函数;

  2. 内部 in-tree CSI 插件(csiAttacher)等待集群中 VolumeAttachment 对象状态 .Status.Attached 变为 true;

  3. in-tree CSI 插件(csiAttacher)调用 MountDevice 函数,该函数内部通过 unix domain socket 调用外部 CSI 插件的NodeStageVolume 函数;之后插件(csiAttacher)调用内部 in-tree CSI 插件(csiMountMgr)的 SetUp 函数,该函数内部会通过 unix domain socket 调用外部 CSI 插件的NodePublishVolume 函数;

Unmounting Volumes

  1. 用户删除相关 Pod;

  2. Volume Manager(Kubelet 组件)观察到包含 CSI 存储卷的 Pod 被删除,于是调用内部 in-tree CSI 插件(csiMountMgr)的 TearDown 函数,该函数内部会通过 unix domain socket 调用外部 CSI 插件的 NodeUnpublishVolume 函数;

  3. Volume Manager(Kubelet 组件)调用内部 in-tree CSI 插件(csiAttacher)的 UnmountDevice 函数,该函数内部会通过 unix domain socket 调用外部 CSI 插件的 NodeUnpublishVolume 函数。

Detaching Volumes

  1. AD 控制器观察到包含 CSI 存储卷的 Pod 被删除,此时该控制器会调用内部 in-tree CSI 插件(csiAttacher)的 Detach 函数;

  2. csiAttacher会删除集群中相关 VolumeAttachment 对象(但由于存在 finalizer,va 对象不会立即删除);

  3. External Attacher观察到集群中 VolumeAttachment 对象的 DeletionTimestamp 非空,于是调用外部 CSI 插件的ControllerUnpublish 函数以将卷从对应节点上摘除。外部 CSI 插件摘除成功后,External Attacher会移除相关 VolumeAttachment 对象的 finalizer 字段,此时 VolumeAttachment 对象被彻底删除;

  4. AD 控制器中内部 in-tree CSI 插件(csiAttacher)观察到 VolumeAttachment 对象已删除,于是更新AD 控制器中的内部状态;同时AD 控制器更新 Node 资源,此时 Node 资源的 .Status.VolumesAttached 上已没有相关挂接信息;

Deleting Volumes

  1. 用户删除相关 PVC;

  2. 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 等函数

注册成功后的操作

  1. Kubelet为本节点 Node 资源打 annotation:Kubelet调用外部 CSI 插件的NodeGetInfo 函数,其返回值 [nodeID]、[driverName] 将作为值用于 “csi.volume.kubernetes.io/nodeid” 键;

  2. Kubelet更新 Node Label:将NodeGetInfo 函数返回的 [AccessibleTopology] 值用于节点的 Label;

  3. Kubelet更新 Node Status:将NodeGetInfo 函数返回的 maxAttachLimit(节点最大可挂载卷数量)更新到 Node 资源的 Status.Allocatable:attachable-volumes-csi-[driverName]=[maxAttachLimit]

  4. 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资源:

  1. 判断 PVC 是否需要动态创建存储卷,标准如下:

    • PVC 的 annotation 中是否包含 “volume.beta.kubernetes.io/storage-provisioner” 键(由卷控制器创建)并且其值是否与 Provisioner 名称相等

    • PVC 对应 StorageClass 的 VolumeBindingMode 字段:

      • 若为 WaitForFirstConsumer,则 PVC 的 annotation 中必须包含 “volume.kubernetes.io/selected-node” 键,且其值不为空;

      • 若为 Immediate 则表示需要 Provisioner 立即提供动态存储卷;

  2. 通过特定的 Unix Domain Socket 调用外部 CSI 插件的 CreateVolume 函数;

  3. 创建 PV 资源,PV 名称为 [Provisioner 指定的 PV 前缀] – [PVC uuid]

2)对于PV资源:

  1. 判断 PV 是否需要删除,标准如下:

    • 判断其 .Status.Phase 是否为 Release;

    • 判断其 .Spec.PersistentVolumeReclaimPolicy 是否为 Delete;

    • 判断其是否包含 annotation(pv.kubernetes.io/provisioned-by),且其值是否为自己;

  2. 通过特定的 Unix Domain Socket 调用外部 CSI 插件的 DeleteVolume 接口;

  3. 删除集群中的 PV 资源;

 

External Attacher

功能

External Attacher用于挂接/摘除存储卷

watch集群的VA和PV资源

External-Attacher 内部会时刻 watch 集群中的 VolumeAttachment 资源和 PersistentVolume 资源

1)对于VolumeAttachment资源:

  1. 从 VolumeAttachment 资源中获得 PV 的所有信息,如 volume ID、node ID、挂载 Secret 等

  2. 判断 VolumeAttachment 的 DeletionTimestamp 字段是否为空来判断其为卷挂接或卷摘除;

    • 若为卷挂接则通过特定的 Unix Domain Socket 调用外部 CSI 插件的ControllerPublishVolume 接口;

    • 若为卷摘除则通过特定的 Unix Domain Socket 调用外部 CSI 插件的ControllerUnpublishVolume 接口;

2)对于PV资源:

  1. 在挂接时为相关 PV 打上 Finalizer:external-attacher/[driver 名称]

  2. 当 PV 处于删除状态时(DeletionTimestamp 非空),删除 Finalizer:external-attacher/[driver 名称]

 

External Resizer

功能

External Resizer用于扩容存储卷

watch集群的PVC资源

External-Resizer内部会 watch 集群中的 PersistentVolumeClaim 资源

判断 PersistentVolumeClaim 资源是否需要扩容:PVC 状态需要是 Bound 且 .Status.Capacity 与 .Spec.Resources.Requests 不等

  1. 更新 PVC 的 .Status.Conditions,表明此时处于 Resizing 状态

  2. 通过特定的 Unix Domain Socket 调用外部 CSI 插件的 ControllerExpandVolume 接口

  3. 更新 PV 的 .Spec.Capacity

  4. 若 CSI 支持文件系统在线扩容,ControllerExpandVolume 接口返回值中 NodeExpansionRequired 字段为 true,External-Resizer更新 PVC 的 .Status.Conditions 为 FileSystemResizePending 状态;

  5. 若不支持则扩容成功,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 主要负责存储卷挂载/卸载操作

 
posted @ 2023-06-07 18:57  Praywu  阅读(1710)  评论(5编辑  收藏  举报