文件系统小册(Fuse&Posix&K8s csi)【3 K8s csi】

文件系统小册(Fuse&Posix&K8s csi)【3 K8s csi】

往期文章:

0 核心知识点速递(csi plugin)

  1. CSI(Container Storage Interface):k8s提供接口,各第三方存储厂商实现接口,k8s通过rpc调用接口实现对第三方存储的操控(创建、删除等)。
  2. 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 插件的核心逻辑,而不必关心如何将其正确注册到每个节点上,降低了集成的复杂度
  1. csi plugin各组件部署方式:
  • csi plugin的controller server与External plugin作为容器,可使用Deployment部署
  • csi plugin的node server与node driver registrar作为容器,使用Daemonset部署(每个节点都有)
  1. 流程:

    ①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组件二进制文件一起发布,这样就会存在一些问题。

  1. 如果第三方存储厂商发现有问题需要修复或者优化,即使修复后也不能单独发布,需要与kubernetes一起发布,对于k8s本身而言,不仅要考虑自身的正常迭代发版,还需要考虑到第三方存储厂商的迭代发版,这里就存在双方互相依赖、制约的问题,不利于双方快速迭代;
  2. 另外第三方厂商的代码跟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的存在。

  1. 注册 CSI 插件: Node-Driver-Registrar 负责将 CSI 插件注册到节点的 Kubelet 上,这样 Kubelet 就能够识别并使用该 CSI 插件来管理存储卷。它通过监听 Kubelet 的插件注册机制,创建必要的插件目录结构和 socket 文件,确保 Kubelet 能够发现并调用 CSI 插件提供的服务。
  2. 维护插件信息: Node-Driver-Registrar 会定期更新节点上的插件信息,包括插件的健康状况、版本等,确保 Kubelet 能够实时了解 CSI 插件的状态。
  3. 简化部署和管理: 通过使用 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. 存储创建

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)

官网地址:

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:

  1. csi-provisioner:pod里包含两个container,csi-provisioner、csi-s3
    • csi-provisioner:负责处理存储卷的创建、删除请求。它会根据PVC的存储类找到相应的CSI驱动,并调用该驱动的CreateVolume接口在后端存储系统中创建卷。当PVC不再需要时,它也会调用DeleteVolume来清理存储资源。
    • 针对S3兼容存储服务的CSI插件,它与csi-provisioner协同工作,实现与S3存储的交互。它可能包含实现特定于S3的逻辑,比如认证、卷创建和删除的细节处理,以响应csi-provisioner的调用。
  2. 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架构图:

参考文章:https://www.cnblogs.com/lianngkyle/p/15055552.html

相关推荐
wclass-zhengge28 分钟前
K8S篇(基本介绍)
云原生·容器·kubernetes
颜淡慕潇35 分钟前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
川石课堂软件测试3 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
昌sit!9 小时前
K8S node节点没有相应的pod镜像运行故障处理办法
云原生·容器·kubernetes
A ?Charis11 小时前
Gitlab-runner running on Kubernetes - hostAliases
容器·kubernetes·gitlab
wclass-zhengge12 小时前
Docker篇(Docker Compose)
运维·docker·容器
茶馆大橘12 小时前
微服务系列五:避免雪崩问题的限流、隔离、熔断措施
java·jmeter·spring cloud·微服务·云原生·架构·sentinel
北漂IT民工_程序员_ZG12 小时前
k8s集群安装(minikube)
云原生·容器·kubernetes
coding侠客12 小时前
揭秘!微服务架构下,Apollo 配置中心凭啥扮演关键角色?
微服务·云原生·架构
梦魇梦狸º15 小时前
腾讯轻量云服务器docker拉取不到镜像的问题:拉取超时
docker·容器·github