• 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删除

    copy
    ❯ 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

    copy
    ❯ 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 调度时选择合适的存储节点。

    copy
    ❯ 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 上;

    copy
    ❯ 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 主要负责存储卷挂载/卸载操作

     

    本文作者:Praywu

    本文链接:https://www.cnblogs.com/hgzero/p/17464313.html

    版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

  • 相关阅读:
    react 父组件调用子组件的属性或方法
    [SWPU2019]Web1
    Python每日练习:使用百度AI识别表情包并抓取
    CAN通信----基本原理
    淘宝详情API接口
    .NET开源、跨平台、使用简单的面部识别库
    零犀科技携手集智俱乐部:“因果派”论坛成功举办,“因果革命”带来下一代可信AI
    javaWeb监听器Listener -自定义监听器类实现(二)
    【AI视野·今日NLP 自然语言处理论文速览 第三十八期】Thu, 21 Sep 2023
    构建Docker基础镜像(ubuntu20.04+python3.9.10+pytorch-gpu-cuda11.8)
  • 原文地址:https://www.cnblogs.com/hgzero/p/17464313.html