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> ...
随机推荐
- urlopen()方法的源代码
import urllib.request # 获取目标网址 url = 'https://www.baidu.com/' # 添加请求头 headers = {'User-Agent': 'Mozi ...
- 垃圾回收之G1收集过程
G1 中提供了 Young GC.Mixed GC 两种垃圾回收模式,这两种垃圾回收模式,都是 Stop The World(STW) 的. G1 没有 fullGC 概念,需要 fullGC 时,调 ...
- JavaScript中计时器requestAnimationFrame、setTimeout、setInterval、setImmediate的使用和区别
在JavaScript中,我们经常使用requestAnimationFrame.setTimeout.setInterval和setImmediate来控制代码的执行时机.它们各有特点和适用场景: ...
- R语言数据加工厂——plyr包使用
plyr包是Hadley Wickham大神为解决split – apply – combine问题而写的一个包,其动机在与提供超越for循环和内置的apply函数族的一个一揽子解决方案.使用plyr ...
- [软件测试] sonar 常见问题及修复思路【待完善】
1 sonar 概述 sonar 是什么? Sonar 是一个用于代码质量管理的开放平台.通过插件机制,Sonar 可以集成不同的测试工具,代码分析工具,以及持续集成工具. 与持续集成工具(例如 Hu ...
- 碉堡!“万物皆可分”标记模型上线「GitHub 热点速览」
这周有个让人眼前一亮的图像识别模型 segment-anything,它能精细地框出所有可见物体,它标记出的物体边界线清晰可见.如此出色的模型,自然获得了不少人的赞赏,开源没几天,就拿下了 18k+ ...
- SQL Case条件判断语句
问题描述:在表中取到一些值做出判断,配合监控监测一些表中的数据.使用select case when if 来做条件查询判断 CASE 表达式遍历条件并在满足第一个条件时返回一个值(类似于 if-th ...
- Java开发准备
1.Java是一门面向对象的高级语言 JDK:是java development kit的缩写,意思是java程序开发的工具包. 可以用来开发Java和运行Java程序 JRE:Java Runtim ...
- Kubernetes集群调度增强之超容量扩容
作者:京东科技 徐宪章 1 什么是超容量扩容 超容量扩容功能,是指预先调度一定数量的工作节点,当业务高峰期或者集群整体负载较高时,可以使应用不必等待集群工作节点扩容,从而迅速完成应用横向扩容.通常情况 ...
- docker的安装(linux、centos)
环境:centos7 1.先确定linux是否是centos7 cat /etc/redhat-release 2.如果自己的linux上之前有安装docker,先卸载.如果没有,则直接跳过这一步. ...