文件系统小册(Fuse&Posix&K8s csi)【3 K8s csi】
往期文章:
0 核心知识点速递(csi plugin)
- CSI(Container Storage Interface):k8s提供接口,各第三方存储厂商实现接口,k8s通过rpc调用接口实现对第三方存储的操控(创建、删除等)。
- CSI核心组件:
- (1) csi plugin:controller server+node server+External plugin
- controller server:csi插件的控制面组件,负责存储卷生命周期等,不涉及节点上的具体挂载和卸载。
- 创建删除存储卷(Volume)
- 创建删除卷快照(Snapshot)
- 验证卷配置(validateVolumeCapabilites)
- 扩展卷大小(ControllerExpandVolume)
- 将卷与Pod绑定(ControllerPublishVolume)
- 解绑(ControllerUnPublishVolume)
- node server:csi插件的节点面组件。在k8s节点执行实际的存储操作,这些操作实际影响到pod的运行
- 准备卷(NodeStageVolume):对卷进行格式化等,将卷挂载到宿主机【远端存储挂宿主机】
- 卸载卷(NodeUnstageVolume):从宿主机卸载卷
- 挂载卷(NodePublishVolume):将卷挂载到Pod运行路径,使pod可以访问【宿主机挂pod】
- 卸载卷(NodeUnPublishVolume):从pod运行路径卸载卷【从pod挂载点卸载卷】
- 获取节点信息(NodeGetInfo):提供节点信息,如节点ID
- 获取节点的存储能力(NodeGetCapabilities)和状态(NodeGetVolumeStats)
- External plugin:调用csi plugin做系列操作,完成对存储的操控
- external provisioner:监听pvc创建,调用csi plugin创建远程存储,进而创建pv
- external attacher:监听volumeAttachment(记录了pv的挂载信息,如挂载到哪个node节点,由哪个csi plugin来挂载等),调用csi plugin做Attach/Dettach
- external resizer:监听pvc,当pvc声明的容量(spec.resources.requests.storage)变化时,做对应的扩容操作。
- node driver Registrar:实现csi plugin中NodeServer的注册,让kubelet感知csi plugin的存在。
- (2)node driver registrar:注册csi插件
- 注册csi插件:将csi插件注册到kubelet上。这样 Kubelet 就能够识别并使用该 CSI 插件来管理存储卷。它通过监听 Kubelet 的插件注册机制,创建必要的插件目录结构和 socket 文件,确保 Kubelet 能够发现并调用 CSI 插件提供的服务。不属于csi plugin的组成部分。
- 维护插件信息:定期更新节点上的插件信息,包括插件的健康状况、版本等,确保 Kubelet 能够实时了解 CSI 插件的状态
- 简化部署和管理:通过使用 Node-Driver-Registrar,CSI 插件的部署和管理变得更加简单。开发者或存储供应商只需关注 CSI 插件的核心逻辑,而不必关心如何将其正确注册到每个节点上,降低了集成的复杂度
- csi plugin各组件部署方式:
- csi plugin的controller server与External plugin作为容器,可使用Deployment部署
- csi plugin的node server与node driver registrar作为容器,使用Daemonset部署(每个节点都有)
流程:
①k8s创建与挂载volume(ceph csi plugin为例)::
- 用户创建pvc,pv controller监听到pvc创建,寻找合适的pv与之绑定。若无合适pv,则添加annotation:volume.beta.kubernetes.io/storage-provisioner,让external-provisioner创建存储与pv对象
- external-provisioner watch到pvc创建或更新事件,根据annotation判断是否是自己负责创建操作,如果是则调用csi-plugin ControllerServer创建存储并创建pv对象。
- pv controller将创建成功pv与pvc绑定。
- 用户创建挂载pvc的pod
- kube-scheduler watch到pod的创建,为其寻找合适的node调度。pod调度完成后,AD controller/volume manager监听到pod声明的Volume没有进行Attach操作,调用csi attacher来做attach操作(实际上只是创建volumeAttachement对象)。
- external attacher监听到VolumeAttachment对象创建,调用csi plugin进行attach操作(如:Volume plugin是ceph csi,external attacher组件watch到VolumeAttachment对象创建后,只修改该对象的状态属性,但真正的attach操作让kubelet中的Volume manager调用Volume plugin,即:ceph csi来完成)
- csi plugin controller server进行attach操作,将Volume挂载到pod所在节点,成为如/dev/vdb的设备
- attach完后,Volume manager监听到pod声明的Volume没有进行mount操作,将调用csi mounter来进行mount操作
- csi mounter调用csi plugin node server进行mount操作,将node节点attach得到的如/dev/vdb设备挂载到指定目录
②csi实现存储创建过程(ceph csi plugin为例):
- 用户建pvc
- pv controller监听pvc对象,找合适pv,若没有则给pvc对象添加annotation(让external Provisioner知道这个pvc对应的pv由自己来创建)
- external Provisioner监听到pvc新增,找到对应的annotation:
volume.beta.kubernetes.io/storage-provisioner
,如果有则自己来调用ceph csi组件创建远程存储- ceph底层存储创建完后,external Provisioner根据存储信息,创建对应的pv对象(这里的pv对象使用了提前绑定特性,将pvc信息填入了pv对象的spec.claimRef属性)。
- pv controller监听pvc对象,将external Provisioner创建出的pv与pvc绑定
③csi实现存储扩容(ceph csi plugin为例):
- 修改pvc对象(修改申请存储大小pvc.spec.resources.requests.storage的值)
- external resizer监听到pvc的update事件,发现pvc.Spec.Resources.Requests.storgage比pvc.Status.Capacity.storgage大(预期的容量比当前的容量大),于是调ceph-csi组件进行 controller端扩容
- ceph csi组件调ceph存储,进行底层存储扩容
- 底层扩容完后,external resizer更新pv对象的.Spec.Capacity.storgage为扩容后的存储大小
- kubelet的volume manager在reconcile()调谐过程中发现pv.Spec.Capacity.storage大于pvc.Status.Capacity.storage,于是调ceph csi组件进行 node端扩容
- ceph csi组件对node上存储对应的文件系统扩容
- 扩容完成后,kubelet更新pvc.Status.Capacity.storage值为扩容后大小
④存储挂载mount(ceph csi plugin为例):
- 用户创建一个挂载了pvc的pod
- AD controller或Volume manager中的reconcile发现有volume未执行Attach操作,便创建VolumeAttachment对象执行attach操作
- external-attacher组件list/watch VolumeAttachement对象,更新VolumeAttachment.status.attached=true
- AD controller更新node对象的.Status.VolumesAttached属性值,将该volume记为attached
- kubelet中的volume manager获取node.Status.VolumesAttached属性值,发现volume已被标记为attached
- volume manager中的reconcile()调用ceph-csi组件的NodeStageVolume与NodePublishVolume完成存储的挂载
⑤解除存储挂载umount(ceph csi plugin为例):
- 用户删除了声明pvc的pod
- AD controller或volume manager中的reconcile()发现有volume未执行dettach操作,于是进行dettach操作,即删除VolumeAttachment对象
- AD controller或volume manager等待VolumeAttachment对象删除成功
- AD controller更新node对象的.Status.VolumesAttached属性值,将标记为attached的该volume从属性值中去除
- kubelet中的volume manager获取node.Status.VolumesAttached属性值,找不到相关的volume信息
- volume manager中的reconcile()调用ceph-csi组件的NodeUnpublishVolume与NodeUnstageVolume完成存储的解除挂载操作
⑥删除存储(ceph csi plugin为例):
- 用户删除pvc对象
- pv controller发现pv绑定的pvc对象被删除,更新pv状态为released
- external-provisioner watch到pv更新事件,并检查pv的状态是否为released,以及回收策略是否为delete
- 确认了pv对象的状态以及回收策略之后,接下来external-provisioner组件会调用ceph-csi的DeleteVolume来删除存储;
- ceph-csi组件的DeleteVolume方法,调用ceph集群命令,删除底层存储
- 删除底层存储后,external-provisioner组件删除pv对象
1 概念
1. CSI是什么:容器存储接口
kubernetes的设计初衷是支持可插拔架构,从而利于扩展kubernetes的功能。在此架构思想下,kubernetes提供了3个特定功能的接口,分别是容器网络接口CNI、容器运行时接口CRI和
容器存储接口CSI(Container Storage Interface)
。kubernetes通过调用这几个接口,来完成相应的功能。
- CSI的目的是定义行业标准"容器存储接口",使存储供应商(SP)能够开发一个符合CSI标准的插件并使其可以在多个容器编排(CO)系统中工作。CO包括Cloud Foundry, Kubernetes, Mesos等。
- kubernetes将通过CSI接口来跟第三方存储厂商进行通信,来操作存储,从而提供容器存储服务。
2. 为什么要CSI:定义规范,其他存储厂商实现。易于开发和维护
其实在没有CSI之前kubernetes就已经提供了强大的存储卷插件系统,但是这些插件系统实现是kubernetes代码的一部分,需要随kubernetes组件二进制文件一起发布,这样就会存在一些问题。
- 如果第三方存储厂商发现有问题需要修复或者优化,即使修复后也不能单独发布,需要与kubernetes一起发布,对于k8s本身而言,不仅要考虑自身的正常迭代发版,还需要考虑到第三方存储厂商的迭代发版,这里就存在双方互相依赖、制约的问题,不利于双方快速迭代;
- 另外第三方厂商的代码跟kubernetes代码耦合在一起,还会引起安全性、可靠性问题,还增加了kubernetes代码的复杂度以及后期的维护成本等等。
- 基于以上问题,kubernetes将存储体系抽象出了外部存储组件接口即CSI,kubernetes通过grpc接口与第三方存储厂商的存储卷插件系统进行通信。(类似Java JDBC,定义一套规则,其他厂商来实现)
- 这样一来,对于第三方存储厂商来说,既可以单独发布和部署自己的存储插件,进行正常迭代,而又无需接触kubernetes核心代码,降低了开发的复杂度。同时,对于kubernetes来说,这样不仅降低了自身的维护成本,还能为用户提供更多的存储选项。
3. CSI架构:kubernetes通过grpc接口与第三方存储厂商的存储卷插件系统进行通信,来操作存储
2 涉及到的K8s相关组件
官网:https://kubernetes.io/zh-cn/docs/concepts/storage/persistent-volumes/
1. PV(PersistentVolume):持久化卷,相当于一块盘
持久存储卷,集群级别资源,代表了存储卷资源,记录了该存储卷资源的相关信息。
- 回收策略
(1)retain:保留策略,当删除pvc的时候,保留pv与外部存储资源。
(2)delete:删除策略,当与pv绑定的pvc被删除的时候,会从k8s集群中删除pv对象,并执行外部存储资源的删除操作。
(3)resycle(已废弃)- pv状态迁移:available --> bound --> released
2. PVC(PersistentVolumeClaim):声明,告诉k8s你要多大的"盘"
持久存储卷声明,namespace级别资源,代表了用户对于存储卷的使用需求声明。
- pvc状态迁移:pending --> bound
- accessModes:
- ReadWriteOnce:卷可以被一个节点以读写方式挂载。 ReadWriteOnce 访问模式仍然可以在同一节点上运行的多个 Pod 访问该卷。 对于单个 Pod 的访问,请参考 ReadWriteOncePod 访问模式。
- ReadOnlyMany:卷可以被多个节点以只读方式挂载
- ReadWriteMany:卷可以被多个节点以读写方式挂载
- ReadWriteOncePod:整个集群中只有一个 Pod 可以读取或写入该 PVC, 请使用 ReadWriteOncePod 访问模式
pvc.yaml:
yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test
namespace: test
spec:
accessModes:
- ReadWriteMany # 卷可以被多个节点以读写方式挂载
resources:
requests:
storage: 10Gi
storageClassName: csi-cephfs-sc # sc名称
volumeMode: Filesystem
3. SC(StorageClass):定义PV模板,动态创建PV
定义了创建pv的模板信息,集群级别资源,用于动态创建pv
- 如果我们想要新增PV只有通过yaml方式去一个一个apply yaml手动的话,对于运维成本太过巨大。因此k8s提供了SC(StorageClass)允许我们动态创建pv。sc中定义pv模板,比如回收策略等。
yml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-rbd-sc
parameters:
clusterID: ceph01
imageFeatures: layering
imageFormat: "2"
mounter: rbd
pool: kubernetes
provisioner: rbd.csi.ceph.com
reclaimPolicy: Delete
volumeBindingMode: Immediate
4. VolumeAttachment:记录PV挂载信息
VolumeAttachment 记录了pv的相关挂载信息,如挂载到哪个node节点,由哪个volume plugin来挂载等。
- AD Controller 创建一个 VolumeAttachment,而 External-attacher 则通过观察该 VolumeAttachment,根据其状态属性来进行存储的挂载和卸载操作。
va.yaml
yaml
apiVersion: storage.k8s.io/v1
kind: VolumeAttachment
metadata:
name: csi-123456
spec:
attacher: cephfs.csi.ceph.com
nodeName: 192.168.1.10
source:
persistentVolumeName: pvc-123456
status:
attached: true
5. CSINode:记录CSI Plugin信息,nodeId、drivername等
CSINode 记录了csi plugin的相关信息(如nodeId、driverName、拓扑信息等)。
- 当Node Driver Registrar向kubelet注册一个csi plugin后,会创建(或更新)一个CSINode对象,记录csi plugin的相关信息。
csiNode.yaml:
yaml
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
name: 192.168.1.10
spec:
drivers:
- name: cephfs.csi.ceph.com
nodeID: 192.168.1.10
topologyKeys: null
- name: rbd.csi.ceph.com
nodeID: 192.168.1.10
topologyKeys: null
查看节点的csi node
bash
# 查看csi node详细信息
kubectl get csinodes.storage.k8s.io worker01 -o yaml
3 各组件作用
CSI架构图:
1. volume plugin:k8s内部插件、csi plugin、flexvolume等
扩展各种存储类型的卷的管理能力,实现第三方存储的各种操作能力与k8s存储系统的结合。调用第三方存储的接口或命令,从而提供数据卷的创建/删除、attach/detach、mount/umount的具体操作实现,可以认为是第三方存储的代理人。前面分析组件中的对于数据卷的创建/删除、attach/detach、mount/umount操作,全是调用volume plugin来完成。
根据源码所在位置,volume plugin分为in-tree与out-of-tree。
in-tree:在k8s源码内部实现,和k8s一起发布、管理,更新迭代慢、灵活性差。
out-of-tree:代码独立于k8s,由存储厂商实现,有csi、flexvolume两种实现。
①CSI Plugin:ControllerServer+NodeServer+(External Plugins)
一个完整的CSI插件通常由Controller Server和Node Server组成,它们各自负责一部分存储操作,共同实现Kubernetes与存储系统的全面集成。此外,还有其他辅助组件,如上述的External Plugins,它们与Controller Server和Node Server配合,提供更高级的功能,如动态卷供应、快照管理和扩缩容等。
csi plugin:分为ControllerServer与NodeServer,各负责不同的存储操作
- Controller Server:
功能: Controller Server 是 CSI 插件的控制面组件,它处理与存储卷生命周期管理相关的操作,这些操作通常在集群级别执行,不直接涉及节点上的具体挂载和卸载。这些操作包括:
- 创建、删除存储卷(Volume)。
- 创建、删除卷快照(Snapshot)。
- 验证卷配置(ValidateVolumeCapabilities)。
- 扩展卷大小(ControllerExpandVolume)。
- 将卷与Pod绑定(ControllerPublishVolume)、解绑(ControllerUnpublishVolume)。
- Node Server:
功能: Node Server 是 CSI 插件的节点面组件,它负责在Kubernetes节点上执行实际的存储操作,这些操作直接影响到Pod的运行。Node Server 的主要任务包括:
- 准备卷(NodeStageVolume):在节点上准备存储卷,使其可用于Pod。
- 卸载卷(NodeUnstageVolume):从节点上解除卷的准备状态。
- 挂载卷(NodePublishVolume):将准备好的卷挂载到Pod的运行路径,使Pod可以访问。
- 卸载卷(NodeUnpublishVolume):从Pod的运行路径上卸载卷。
- 获取节点信息(NodeGetInfo):提供关于节点的信息,如节点ID等。
- 获取节点的存储能力(NodeGetCapabilities)和状态(NodeGetVolumeStats)。
ControllerServer 与NodeServer联系:
- 协作:Controller Server 和 Node Server 通过Kubernetes的API和CSI接口相互协作。Controller Server 处理集群级别的操作,如创建卷,然后通知Node Server在适当节点上执行实际的挂载和准备操作。Node Server则根据Controller Server的指示来处理节点级别的存储操作。
- 通信:Controller Server 和 Node Server 之间的通信通常通过Kubernetes API和CSI定义的gRPC接口进行。
②external plugin:external-provisioner、external-attacher、external-resizer等(辅助CSI plugin共同完成存储操作)
external plugin包括了external-provisioner、external-attacher、external-resizer、external-snapshotter等。external plugin辅助csi plugin组件,共同完成了存储相关操作。external plugin负责watch pvc、volumeAttachment等对象,然后调用volume plugin来完成存储的相关操作。
- external-provisioner 主要watch pvc对象,然后调用csi plugin来
创建存储,最后创建pv对象
;- external-attacher 主要watch volumeAttachment对象,然后调用csi plugin来做
attach/dettach操作
;- external-resizer 主要watch pvc对象,然后调用csi plugin来做
存储的扩容操作
等。
③Node-Driver-Registrar:实现csi plugin(node server)的注册,让kubelet感知csi plugin存储
Node-Driver-Registrar组件负责实现csi plugin(NodeServer)的注册,让kubelet感知csi plugin的存在。
- 注册 CSI 插件: Node-Driver-Registrar 负责将 CSI 插件注册到节点的 Kubelet 上,这样 Kubelet 就能够识别并使用该 CSI 插件来管理存储卷。它通过监听 Kubelet 的插件注册机制,创建必要的插件目录结构和 socket 文件,确保 Kubelet 能够发现并调用 CSI 插件提供的服务。
- 维护插件信息: Node-Driver-Registrar 会定期更新节点上的插件信息,包括插件的健康状况、版本等,确保 Kubelet 能够实时了解 CSI 插件的状态。
- 简化部署和管理: 通过使用 Node-Driver-Registrar,CSI 插件的部署和管理变得更加简单。开发者或存储供应商只需关注 CSI 插件的核心逻辑,而不必关心如何将其正确注册到每个节点上,降低了集成的复杂度。
与 CSI Plugin 的关系:
- 互补角色:Node-Driver-Registrar 与 CSI 插件是互补的,它不是 CSI 插件的一部分,而是作为一个辅助容器与 CSI 插件一同部署在每个节点上。CSI 插件负责实际的存储操作(如创建、挂载、卸载卷等),而 Node-Driver-Registrar 则负责确保这些插件能够被 Kubelet 正确识别和使用。
- 协同工作:在实际操作中,Node-Driver-Registrar 通过读取 CSI 插件提供的元数据(如插件名称、版本等),并利用这些信息与 Kubelet 交互,确保 CSI 插件能够无缝集成到 Kubernetes 存储框架中。没有 Node-Driver-Registrar,CSI 插件虽然可以部署,但在节点层面可能无法被 Kubelet 自动发现和使用,从而影响到存储卷的正常生命周期管理。
④组件化部署:Deployment+Daemonset
- csi plugin controllerServer与external plugin作为容器,使用deployment部署,多副本可实现高可用;
- 而csi plugin NodeServer与Node-Driver-Registrar(csi plugin的注册,让kubelet感知csi plugin存在)作为容器,使用daemonset部署,即每个node节点都有。
2. kube-controller-manager
①PV controller:创建/删除底层存储,创建/删除pv对象,pv与pvc对象的状态变更【in-tree、out-tree CSI】
负责pv、pvc的绑定与生命周期管理(如创建/删除底层存储,创建/删除pv对象,pv与pvc对象的状态变更)。
(1)in-tree:创建/删除底层存储、创建/删除pv对象的操作,由PV controller调用volume plugin(in-tree)来完成。
(2)out-tree CSI:创建/删除底层存储、创建/删除pv对象的操作由external-provisioner与csi plugin共同来完成。
②AD controller:Attachment/Detachment 控制器
AD Cotroller全称Attachment/Detachment 控制器,主要负责创建、删除VolumeAttachment对象,并调用volume plugin来做存储设备的Attach/Detach操作(将数据卷挂载到特定node节点上/从特定node节点上解除挂载),以及更新node.Status.VolumesAttached等。
不同的volume plugin的Attach/Detach操作逻辑有所不同
- 对于csi plugin(out-tree volume plugin)来说,AD controller的Attach/Detach操作只是修改VolumeAttachment对象的状态,而不会真正的将数据卷挂载到节点/从节点上解除挂载,真正的节点存储挂载/解除挂载操作由kubelet中volume manager调用csi plugin来完成。
3. kubelet
①volume manager
主要是管理卷的Attach/Detach(与AD controller作用相同,通过kubelet启动参数控制哪个组件来做该操作)、mount/umount等操作。
- 对于csi来说,volume manager的Attach/Detach操作只创建/删除VolumeAttachment对象,而不会真正的将数据卷挂载到节点/从节点上解除挂载;
- csi-attacher组件也不会做挂载/解除挂载操作,只是更新VolumeAttachment对象,真正的节点存储挂载/解除挂载操作由kubelet中volume manager调用调用csi plugin来完成。
②kubernetes创建与挂载volume(in-tree volume plugin)
kubernetes通过in-tree volume plugin来创建与挂载volume的流程如下:
- (1)用户创建pvc;
- (2)PV controller watch到pvc的创建,寻找合适的pv与之绑定。
- (3)(4)当找不到合适的pv时,将调用volume plugin来创建volume,并创建pv对象,之后该pv对象与pvc对象绑定。
- (5)用户创建挂载pvc的pod;
- (6)kube-scheduler watch到pod的创建,为其寻找合适的node调度。
- (7)(8)pod调度完成后,AD controller/volume manager watch到pod声明的volume没有进行attach操作,将调用volume plugin来做attach操作。
- (9)volume plugin进行attach操作,将volume挂载到pod所在node节点,成为如/dev/vdb的设备。
- (10)(11)attach操作完成后,volume manager watch到pod声明的volume没有进行mount操作,将调用volume plugin来做mount操作。
- (12)volume plugin进行mount操作,将node节点上的第(9)步得到的/dev/vdb设备挂载到指定目录。
③kubernetes创建与挂载volume(out-of-tree volume plugin,csi-plugin ...)
kubernetes通过out-of-tree volume plugin来创建与挂载volume的流程,以csi-plugin为例:
- (1)用户创建pvc;
- (2)PV controller watch到pvc的创建,寻找合适的pv与之绑定。当寻找不到合适的pv时,将更新pvc对象,添加annotation:volume.beta.kubernetes.io/storage-provisioner,让external-provisioner组件开始开始创建存储与pv对象的操作。
- (3)external-provisioner组件watch到pvc的创建/更新事件,判断annotation:volume.beta.kubernetes.io/storage-provisioner的值,即判断是否是自己来负责做创建操作,是则调用csi-plugin ControllerServer来创建存储,并创建pv对象(这里的pv对象使用了提前绑定特性,将pvc信息填入了pv对象的spec.claimRef属性)。
- (4)PV controller将上一步创建的pv与pvc绑定。
- (5)用户创建挂载pvc的pod;
- (6)kube-scheduler watch到pod的创建,为其寻找合适的node调度。
- (7)(8)pod调度完成后,AD controller/volume manager watch到pod声明的volume没有进行attach操作,将调用csi-attacher来做attach操作(实际上只是创建volumeAttachement对象)。
- (9)external-attacher组件watch到volumeAttachment对象的新建,调用csi-plugin进行attach操作(如果volume plugin是ceph-csi,external-attacher组件watch到volumeAttachment对象的新建后,只是修改该对象的状态属性,不会做attach操作,真正的attach操作由kubelet中的volume manager调用volume plugin ceph-csi来完成)。
- (10)csi-plugin ControllerServer进行attach操作,将volume挂载到pod所在node节点,成为如/dev/vdb的设备。
- (11)(12)attach操作完成后,volume manager watch到pod声明的volume没有进行mount操作,将调用csi-mounter来做mount操作。
- (13)csi-mounter调用csi-plugin NodeServer进行mount操作,将node节点上的第(10)步得到的/dev/vdb设备挂载到指定目录。
4 k8s CSI存储流程具体分析(csi plugin:ceph-csi)
kubernetes存储相关操作流程具体分析(out-of-tree volume plugin,以csi plugin:ceph-csi为例)
1. 存储创建
- (1)用户创建pvc对象;
- (2)pv controller监听pvc对象,寻找现存的合适的pv对象,与pvc对象绑定。当找不到现存合适的pv对象时,将更新pvc对象,添加annotation:volume.beta.kubernetes.io/storage-provisioner,让external-provisioner组件开始开始创建存储与pv对象的操作;当找到时,将pvc与pv绑定,结束操作。
- (3)external-provisioner组件监听到pvc的新增事件,判断pvc的annotation:volume.beta.kubernetes.io/storage-provisioner的值,即判断是否是自己来负责做创建操作,是则调用ceph-csi组件进行存储的创建;
- (4)ceph-csi组件调用ceph创建底层存储;
- (5)底层存储创建完成后,external-provisioner根据存储信息,拼接pv对象,创建pv对象(这里的pv对象使用了提前绑定特性,将pvc信息填入了pv对象的spec.claimRef属性);
- (6)pv controller监听pvc对象,将第(5)步创建的pv对象,与pvc对象绑定。
2. 存储扩容
- (1)修改pvc对象,修改申请存储大小(pvc.spec.resources.requests.storage);
- (2)修改成功后,external-resizer监听到该pvc的update事件,发现pvc.Spec.Resources.Requests.storgage比pvc.Status.Capacity.storgage大,于是调ceph-csi组件进行 controller端扩容;
- (3)ceph-csi组件调用ceph存储,进行底层存储扩容;
- (4)底层存储扩容完成后,external-resizer组件更新pv对象的.Spec.Capacity.storgage的值为扩容后的存储大小;
- (5)kubelet的volume manager在reconcile()调谐过程中发现pv.Spec.Capacity.storage大于pvc.Status.Capacity.storage,于是调ceph-csi组件进行 node端扩容;
- (6)ceph-csi组件对node上存储对应的文件系统扩容;
- (7)扩容完成后,kubelet更新pvc.Status.Capacity.storage的值为扩容后的存储大小。
3. 存储挂载(mount)
kubelet启动参数--enable-controller-attach-detach,该启动参数设置为 true 表示启用 Attach/Detach controller进行Attach/Detach 操作,同时禁用 kubelet 执行 Attach/Detach 操作(默认值为 true)。实际上Attach/Detach 操作就是创建/删除VolumeAttachment对象。
(1)kubelet启动参数--enable-controller-attach-detach=true,Attach/Detach controller进行Attach/Detach 操作。
(2)kubelet启动参数--enable-controller-attach-detach=false,kubelet端volume manager进行Attach/Detach 操作。
流程分析:
- (1)用户创建一个挂载了pvc的pod;
- (2)AD controller或volume manager中的reconcile()发现有volume未执行attach操作,于是进行attach操作,即创建VolumeAttachment对象;
- (3)external-attacher组件list/watch VolumeAttachement对象,更新VolumeAttachment.status.attached=true;
- (4)AD controller更新node对象的.Status.VolumesAttached属性值,将该volume记为attached;
- (5)kubelet中的volume manager获取node.Status.VolumesAttached属性值,发现volume已被标记为attached;
- (6)于是volume manager中的reconcile()调用ceph-csi组件的NodeStageVolume与NodePublishVolume完成存储的挂载。
4. 解除存储挂载(umount)
流程图:
(1)kubelet启动参数--enable-controller-attach-detach=true,Attach/Detach controller进行Attach/Detach 操作。
(2)kubelet启动参数--enable-controller-attach-detach=false,kubelet端volume manager进行Attach/Detach 操作。
流程分析:
- (1)用户删除声明了pvc的pod;
- (2)AD controller或volume manager中的reconcile()发现有volume未执行dettach操作,于是进行dettach操作,即删除VolumeAttachment对象;
- (3)AD controller或volume manager等待VolumeAttachment对象删除成功;
- (4)AD controller更新node对象的.Status.VolumesAttached属性值,将标记为attached的该volume从属性值中去除;
- (5)kubelet中的volume manager获取node.Status.VolumesAttached属性值,找不到相关的volume信息;
- (6)于是volume manager中的reconcile()调用ceph-csi组件的NodeUnpublishVolume与NodeUnstageVolume完成存储的解除挂载操作。
5. 删除存储
流程图:
流程分析:
- (1)用户删除pvc对象;
- (2)pv controller发现与pv绑定的pvc对象被删除,于是更新pv的状态为released;
- (3)external-provisioner watch到pv更新事件,并检查pv的状态是否为released,以及回收策略是否为delete;
- (4)确认了pv对象的状态以及回收策略之后,接下来external-provisioner组件会调用ceph-csi的DeleteVolume来删除存储;
- (5)ceph-csi组件的DeleteVolume方法,调用ceph集群命令,删除底层存储;
- (6)删除底层存储后,external-provisioner组件删除pv对象。
5 实战(对象挂载到本地文件k8s-csi-s3)
官网地址:
- https://github.com/yandex-cloud/k8s-csi-s3
- https://github.com/yandex-cloud/geesefs
- 基于Go实现的高性能将S3挂载为本地文件系统的工具
5.1 Dockerfile
yml
FROM golang:1.19-alpine as gobuild
WORKDIR /build
ADD go.mod go.sum /build/
RUN go mod download -x
ADD cmd /build/cmd
ADD pkg /build/pkg
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o ./s3driver ./cmd/s3driver
FROM alpine:3.17
LABEL maintainers="Vitaliy Filippov <vitalif@yourcmc.ru>"
LABEL description="csi-s3 slim image"
RUN apk add --no-cache fuse mailcap rclone
RUN apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/community s3fs-fuse
ADD https://github.com/yandex-cloud/geesefs/releases/latest/download/geesefs-linux-amd64 /usr/bin/geesefs
RUN chmod 755 /usr/bin/geesefs
COPY --from=gobuild /build/s3driver /s3driver
ENTRYPOINT ["/s3driver"]
5.2 Makefile
yml
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
.PHONY: test build container push clean
REGISTRY_NAME=cr.yandex/crp9ftr22d26age3hulg
REGISTRY_NAME2=cr.il.nebius.cloud/crll7us9n6i5j3v4n92m
IMAGE_NAME=csi-s3
IMAGE_NAME2=yandex-cloud/csi-s3/csi-s3-driver
VERSION ?= 0.41.0
IMAGE_TAG=$(REGISTRY_NAME)/$(IMAGE_NAME):$(VERSION)
TEST_IMAGE_TAG=$(IMAGE_NAME):test
build:
CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o _output/s3driver ./cmd/s3driver
test:
docker build -t $(TEST_IMAGE_TAG) -f test/Dockerfile .
docker run --rm --privileged -v $(PWD):/build --device /dev/fuse $(TEST_IMAGE_TAG)
container:
docker build -t $(IMAGE_TAG) .
push: container
docker tag $(IMAGE_TAG) $(REGISTRY_NAME)/$(IMAGE_NAME):latest
docker tag $(IMAGE_TAG) $(REGISTRY_NAME)/$(IMAGE_NAME2):$(VERSION)
docker tag $(IMAGE_TAG) $(REGISTRY_NAME)/$(IMAGE_NAME2):latest
docker push $(IMAGE_TAG)
docker push $(REGISTRY_NAME)/$(IMAGE_NAME)
docker push $(REGISTRY_NAME)/$(IMAGE_NAME2)
docker push $(REGISTRY_NAME)/$(IMAGE_NAME2):$(VERSION)
clean:
go clean -r -x
-rm -rf _output
5.3 helm/values.yml
yml
---
images:
# Source: quay.io/k8scsi/csi-node-driver-registrar:v1.2.0
registrar: cr.yandex/crp9ftr22d26age3hulg/yandex-cloud/csi-s3/csi-node-driver-registrar:v1.2.0
# Source: quay.io/k8scsi/csi-provisioner:v2.1.0
provisioner: cr.yandex/crp9ftr22d26age3hulg/yandex-cloud/csi-s3/csi-provisioner:v2.1.0
# Main image
csi: cr.yandex/crp9ftr22d26age3hulg/yandex-cloud/csi-s3/csi-s3-driver:0.41.0
storageClass:
# Specifies whether the storage class should be created
create: true
# Name
name: csi-s3
# Use a single bucket for all dynamically provisioned persistent volumes
singleBucket: ""
# mounter to use - either geesefs, s3fs or rclone (default geesefs)
mounter: geesefs
# GeeseFS mount options
mountOptions: "--memory-limit 1000 --dir-mode 0777 --file-mode 0666"
# Volume reclaim policy
reclaimPolicy: Delete
# Annotations for the storage class
# Example:
# annotations:
# storageclass.kubernetes.io/is-default-class: "true"
annotations: {}
secret:
# Specifies whether the secret should be created
create: true
# Name of the secret
name: csi-s3-secret
# S3 Access Key
accessKey: ""
# S3 Secret Key
secretKey: ""
# Endpoint
endpoint: https://storage.yandexcloud.net
# Region
region: ""
tolerations:
all: false
node: []
controller: []
nodeSelector: {}
kubeletPath: /var/lib/kubelet
部署:感受csi部署过程
- 详细部署流程可参考官网:https://github.com/yandex-cloud/k8s-csi-s3
- node server:csi插件的节点面组件。在k8s节点执行实际的存储操作,这些操作实际影响到pod的运行。
- 准备卷(NodeStageVolume):对卷进行格式化等,将卷挂载到宿主机【远端存储挂宿主机】
- 卸载卷(NodeUnstageVolume):从宿主机卸载卷
- 挂载卷(NodePublishVolume):将卷挂载到Pod运行路径,使pod可以访问【宿主机挂pod】
- 卸载卷(NodeUnPublishVolume):从pod运行路径卸载卷【从pod挂载点卸载卷】
- 获取节点信息(NodeGetInfo):提供节点信息,如节点ID
- 获取节点的存储能力(NodeGetCapabilities)和状态(NodeGetVolumeStats)
通过helm部署成功后会有对应的几个pod:
- csi-provisioner:pod里包含两个container,csi-provisioner、csi-s3
- csi-provisioner:
负责处理存储卷的创建、删除请求
。它会根据PVC的存储类找到相应的CSI驱动,并调用该驱动的CreateVolume接口在后端存储系统中创建卷。当PVC不再需要时,它也会调用DeleteVolume来清理存储资源。- 针对S3兼容存储服务的CSI插件,它与csi-provisioner协同工作,实现与S3存储的交互。它可能包含实现特定于S3的逻辑,比如认证、卷创建和删除的细节处理,以响应csi-provisioner的调用。
- csi-s3-xxx():pod里包含两个container,driver-registrar、csi-s3
- driver-registrar:
在每个节点上注册CSI驱动
。它负责将CSI插件的节点信息注册到Kubernetes API服务器,使得Kubernetes可以识别并使用该节点上的CSI插件。这包括向Kubernetes注册节点的服务端点,以便于Kubernetes在需要挂载或卸载卷时能找到正确的节点和插件。- csi-s3:
在每个节点上运行,负责执行实际的卷挂载和卸载操作
。当Kubernetes需要在某个节点上挂载或卸载一个卷时,它会通过gRPC调用此容器中的CSI插件逻辑。csi-s3容器会执行NodeStageVolume、NodePublishVolume、NodeUnstageVolume、NodeUnpublishVolume等操作,确保卷正确地挂载到Pod的指定目录下,或在不再需要时正确卸载和清理。对于特定于S3的存储,它还会处理与S3存储系统的直接交互,如认证、数据路径操作等。
总结
为了解决第三方存储厂商的存储卷插件代码集成到kubernetes代码中所带来的各种问题,kubernetes将存储体系抽象出了外部存储组件接口即CSI。
- kubernetes通过grpc接口与第三方存储厂商的存储卷插件系统进行通信,来操作存储,从而提供容器存储服务。
这样一来,对于第三方存储厂商来说,既可以单独发布和部署自己的存储插件,进行正常迭代,而又无需接触kubernetes核心代码,降低了开发的复杂度。同时,对于kubernetes来说,这样不仅降低了自身的维护成本,还能为用户提供更多的存储选项。
K8s CSI架构图: