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,如果是多备份的,可以部署两个。
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:
- devicePath: ""
name: kubernetes.io/csi/udisk.csi.ucloud.cn^bsm-jw6svmajfjn
volumesInUse:
- kubernetes.io/csi/udisk.csi.ucloud.cn^bsm-jw6svmajfjn
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 主要负责存储卷挂载/卸载操作
CSI架构和原理的更多相关文章
- HBase的基本架构及其原理介绍
1.概述:最近,有一些工程师问我有关HBase的基本架构的问题,其实这个问题仅仅说架构是非常简单,但是需要理解.在这里,我觉得可以用HDFS的架构作为借鉴.(其实像Hadoop生态系统中的大部分组建的 ...
- SQL Server AlwaysOn架构及原理
SQL Server AlwaysOn架构及原理 SQL Server2012所支持的AlwaysOn技术集中了故障转移群集.数据库镜像和日志传送三者的优点,但又不相同.故障转移群集的单位是SQL实例 ...
- 爱莲(iLinkIT)的架构与原理
随着移动互联网时代的到来,手机正在逐步替代其他的设备,手机是电话.手机是即时通讯,手机是相机,手机是导航仪,手机是钱包,手机是音乐播放器……. 除此之外,手机还是一个大大的U盘,曾几何时,我们用一根长 ...
- Hbase架构与原理
Hbase架构与原理 HBase是一个分布式的.面向列的开源数据库,该技术来源于 Fay Chang所撰写的Google论文"Bigtable:一个结构化数据的分布式存储系统".就 ...
- [转帖]万字详解Oracle架构、原理、进程,学会世间再无复杂架构
万字详解Oracle架构.原理.进程,学会世间再无复杂架构 http://www.itpub.net/2019/04/24/1694/ 里面的图特别好 数据和云 2019-04-24 09:11:59 ...
- HDFS架构及原理
原文链接:HDFS架构及原理 引言 进入大数据时代,数据集的大小已经超过一台独立物理计算机的存储能力,我们需要对数据进行分区(partition)并存储到若干台单独的计算机上,也就出现了管理网络中跨多 ...
- Spark基本架构及原理
Hadoop 和 Spark 的关系 Spark 运算比 Hadoop 的 MapReduce 框架快的原因是因为 Hadoop 在一次 MapReduce 运算之后,会将数据的运算结果从内存写入到磁 ...
- Oracle rac架构和原理
Oracle RAC Oracle Real Application Cluster (RAC,实时应用集群)用来在集群环境下实现多机共享数据库,以保证应用的高可用性:同时可以自动实现并行处理 ...
- storm架构及原理
storm 架构与原理 1 storm简介 1.1 storm是什么 如果只用一句话来描述 storm 是什么的话:分布式 && 实时 计算系统.按照作者 Nathan Marz 的说 ...
- atitit.jndi的架构与原理以及资源配置and单元测试实践
atitit.jndi的架构与原理以及资源配置and单元测试实践 1. jndi架构 1 2. jndi实现原理 3 3. jndi资源配置 3 3.1. resin <database> ...
随机推荐
- 【实战】SpringBoot+uniapp+uview打造H5+小程序+APP入门学习的聊天小项目
JavaDog Chat v1.0.0 基于SpringBoot+uniapp简单通讯聊天软件 项目介绍 JavaDog Chat 简单通讯聊天软件是基于SpringBoot+MybatisPlus+ ...
- Linux中Python自动输入sudo 密码【管道 sudo参数 stdin&stdout】
一.背景和需求 背景: 由于docker服务进程都是以root帐号的身份运行的,所以用docker跑abpred出来的文件所有者都是root, 而我作为一般用户,操作这个文件不够权限,运行代码时需要s ...
- FileReader之获取文本文件内容为字符串
FileReader之获取文本文件内容为字符串 FileReader官网描述: FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 Fil ...
- Dijkstra(迪杰斯特拉)算法C++实现&讲解
Dijkstra迪杰斯特拉算法及C++实现 Dijkstra算法是典型的最短路径路由算法,用来计算一个节点到其他所有节点的最短路径.算法的基本思想和流程是:1. 初始化出发点到其它各点的距离dist[ ...
- 3d基础 - 从模型坐标到屏幕坐标
在 3D 引擎中,场景通常被描述为三维空间中的模型或对象,每个模型对象由许多三维顶点组成.最终,这些模型对象将在平面屏幕上呈现和显示. 渲染场景始终相对于摄像机,因此,还必须相对于摄像机的视图定义场景 ...
- (数据科学学习手札151)速通pandas2.0新版本干货内容
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,前两天pandas正式发布了其 ...
- flask-login使用方法
烧瓶登录 Flask-Login 为 Flask 提供用户会话管理.它处理登录.注销和长时间记住用户会话的常见任务. 它会: 将活动用户的 ID 存储在Flask Session中,让您轻松登录和注销 ...
- 51nod 1153 选择子序列
51nod 选择子序列 这道题是\(Bunny\)学长在给我们的模拟赛中的一道题. 食用单调栈,处理每个数\(a_i\)左右第一个比自己大的数的下标\(left_i\),\(right_i\),并且建 ...
- Meta AI 开源万物可分割 AI 模型(SAM)
开始 4 月 6 日,根据 Meta AI 官方博客,Meta AI 宣布推出了一个 AI 模型 Segment Anything Model(SAM,分割一切模型).据介绍,该模型能够根据文本指令等 ...
- JUC(七)分支合并框架
JUC分支合并框架 简介 Fork/Join可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务的结果合并称为最终的计算结果. Fork:负责将任务拆分 Join:合并拆分任务 ForkJoi ...