一文读懂容器存储接口 CSI

  • in-tree 存储代码中的 bug 会引发 K8s 组件不稳定

  • K8s 社区需要负责维护及测试 in-tree 类型的存储功能

  • in-tree 存储插件享有与 K8s 核心组件同等的特权,存在安全隐患

  • 三方存储开发者必须遵循 K8s 社区的规则开发 in-tree 类型存储代码

CSI 容器存储接口标准的出现解决了上述问题,将三方存储代码与 K8s 代码解耦,使得三方存储厂商研发人员只需实现 CSI 接口(无需关注容器平台是 K8s 还是 Swarm 等)。

CSI 核心流程介绍

===============================================================================

在详细介绍 CSI 组件及其接口之前,我们先对 K8s 中 CSI 存储流程进行一个介绍。《一文读懂 K8s 持久化存储流程》一文介绍了 K8s 中的 Pod 在挂载存储卷时需经历三个的阶段:Provision/Delete(创盘/删盘)、Attach/Detach(挂接/摘除)和 Mount/Unmount(挂载/卸载),下面以图文的方式讲解 K8s 在这三个阶段使用 CSI 的流程。

1. Provisioning Volumes


1.jpg

1.集群管理员创建 StorageClass 资源,该 StorageClass 中包含 CSI 插件名称(provisioner:pangu.csi.alibabacloud.com)以及存储类必须的参数(parameters: type=cloud_ssd)。sc.yaml 文件如下:

2.png

2.用户创建 PersistentVolumeClaim 资源,PVC 指定存储大小及 StorageClass(如上)。pvc.yaml 文件如下:

3.png

3.**卷控制器(PersistentVolumeController)**观察到集群中新创建的 PVC 没有与之匹配的 PV,且其使用的存储类型为 out-of-tree,于是为 PVC 打 annotation:volume.beta.kubernetes.io/storage-provisioner=[out-of-tree CSI 插件名称](本例中即为 provisioner:pangu.csi.alibabacloud.com)。

4.External Provisioner 组件观察到 PVC 的 annotation 中包含 “volume.beta.kubernetes.io/storage-provisioner” 且其 value 是自己,于是开始创盘流程。

  • 获取相关 StorageClass 资源并从中获取参数(本例中 parameters 为 type=cloud_ssd),用于后面 CSI 函数调用。

  • 通过 unix domain socket 调用外部 CSI 插件CreateVolume 函数

5.外部 CSI 插件返回成功后表示盘创建完成,此时External Provisioner 组件会在集群创建一个 PersistentVolume 资源。

6.卷控制器会将 PV 与 PVC 进行绑定。

4.png

2. Attaching Volumes


5.jpg

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。

6.png

4.AD 控制器内部 in-tree CSI 插件(csiAttacher)观察到 VolumeAttachment 对象的 .Status.Attached 设置为 true,于是更新AD 控制器内部状态(ActualStateOfWorld),该状态会显示在 Node 资源的 .Status.VolumesAttached 上。

7.png

3. Mounting Volumes


8.jpg

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 函数

4. Unmounting Volumes


9.jpg

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 函数

5. Detaching Volumes


10.jpg

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 上已没有相关挂接信息。

6. Deleting Volumes


11.jpg

1.用户删除相关 PVC。

2.External Provisioner 组件观察到 PVC 删除事件,根据 PVC 的回收策略(Reclaim)执行不同操作:

  • Delete:调用外部 CSI 插件DeleteVolume 函数以删除卷;一旦卷成功删除,Provisioner会删除集群中对应 PV 对象。

  • Retain:Provisioner不执行卷删除操作。

CSI Sidecar 组件介绍

=====================================================================================

为使 K8s 适配 CSI 标准,社区将与 K8s 相关的存储流程逻辑放在了 CSI Sidecar 组件中。

1. Node Driver Registrar


1)功能

Node-Driver-Registrar 组件会将外部 CSI 插件注册到Kubelet,从而使Kubelet通过特定的 Unix Domain Socket 来调用外部 CSI 插件函数(Kubelet 会调用外部 CSI 插件的 NodeGetInfo、NodeStageVolume、NodePublishVolume、NodeGetVolumeStats 等函数)。

2)原理

Node-Driver-Registrar 组件通过Kubelet 外部插件注册机制实现注册,注册成功后:

  • 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]。

12.png

  • Kubelet更新 CSINode 资源(没有则创建):将 [driverName]、[nodeID]、[maxAttachLimit]、[AccessibleTopology] 更新到 Spec 中(拓扑仅保留 Key 值)。

2. External Provisioner


1)功能

创建/删除实际的存储卷,以及代表存储卷的 PV 资源。

2)原理

External-Provisioner在启动时需指定参数 – provisioner,该参数指定 Provisioner 名称,与 StorageClass 中的 provisioner 字段对应。

External-Provisioner启动后会 watch 集群中的 PVC 和 PV 资源。

对于集群中的 PVC 资源:

  • 判断 PVC 是否需要动态创建存储卷,标准如下:

  • PVC 的 annotation 中是否包含 “volume.beta.kubernetes.io/storage-provisioner” 键(由卷控制器创建),并且其值是否与 Provisioner 名称相等。

  • PVC 对应 StorageClass 的 VolumeBindingMode 字段若为 WaitForFirstConsumer,则 PVC 的 annotation 中必须包含 “volume.kubernetes.io/selected-node” 键(详见调度器如何处理 WaitForFirstConsumer),且其值不为空;若为 Immediate 则表示需要 Provisioner 立即提供动态存储卷。

  • 通过特定的 Unix Domain Socket 调用外部 CSI 插件CreateVolume 函数

  • 创建 PV 资源,PV 名称为 [Provisioner 指定的 PV 前缀] - [PVC uuid]。

对于集群中的 PV 资源:

  • 判断 PV 是否需要删除,标准如下:

  • 判断其 .Status.Phase 是否为 Release。

  • 判断其 .Spec.PersistentVolumeReclaimPolicy 是否为 Delete。

  • 判断其是否包含 annotation(pv.kubernetes.io/provisioned-by),且其值是否为自己。

  • 通过特定的 Unix Domain Socket 调用外部 CSI 插件DeleteVolume 接口

  • 删除集群中的 PV 资源。

3. External Attacher


1)功能

挂接/摘除存储卷。

2)原理

**External-Attacher **内部会时刻 watch 集群中的 VolumeAttachment 资源和 PersistentVolume 资源。

对于 VolumeAttachment 资源:

  • 从 VolumeAttachment 资源中获得 PV 的所有信息,如 volume ID、node ID、挂载 Secret 等。

  • 判断 VolumeAttachment 的 DeletionTimestamp 字段是否为空来判断其为卷挂接或卷摘除:若为卷挂接则通过特定的 Unix Domain Socket 调用外部 CSI 插件ControllerPublishVolume 接口;若为卷摘除则通过特定的 Unix Domain Socket 调用外部 CSI 插件ControllerUnpublishVolume 接口

对于 PersistentVolume 资源:

  • 在挂接时为相关 PV 打上 Finalizer:external-attacher/[driver 名称]。

  • 当 PV 处于删除状态时(DeletionTimestamp 非空),删除 Finalizer:external-attacher/[driver 名称]。

4. External Resizer


1)功能

扩容存储卷。

2)原理

External-Resizer内部会 watch 集群中的 PersistentVolumeClaim 资源。

对于 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 接口实现文件系统扩容。

5. livenessprobe


1)功能

检查 CSI 插件是否正常。

2)原理

通过对外暴露一个 / healthz HTTP 端口以服务 kubelet 的探针探测器,内部是通过特定的 Unix Domain Socket 调用外部 CSI 插件Probe 接口

CSI 接口介绍

=============================================================================

三方存储厂商需实现 CSI 插件的三大接口:IdentityServer、ControllerServer、NodeServer

1. IdentityServer


IdentityServer 主要用于认证 CSI 插件的身份信息。

// IdentityServer is the server API for Identity service.

type IdentityServer interface {

// 获取CSI插件的信息,比如名称、版本号

GetPluginInfo(context.Context, *GetPluginInfoRequest) (*GetPluginInfoResponse, error)

// 获取CSI插件提供的能力,比如是否提供ControllerService能力

GetPluginCapabilities(context.Context, *GetPluginCapabilitiesRequest) (*GetPluginCapabilitiesResponse, error)

// 获取CSI插件健康状况

Probe(context.Context, *ProbeRequest) (*ProbeResponse, error)

}

2. ControllerServer


ControllerServer 主要负责存储卷及快照的创建/删除以及挂接/摘除操作。

// ControllerServer is the server API for Controller service.

type ControllerServer interface {

// 创建存储卷

CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error)

// 删除存储卷

DeleteVolume(context.Context, *DeleteVolumeRequest) (*DeleteVolumeResponse, error)

// 挂接存储卷到特定节点

ControllerPublishVolume(context.Context, *ControllerPublishVolumeRequest) (*ControllerPublishVolumeResponse, error)

// 从特定节点摘除存储卷

ControllerUnpublishVolume(context.Context, *ControllerUnpublishVolumeRequest) (*ControllerUnpublishVolumeResponse, error)

// 验证存储卷能力是否满足要求,比如是否支持跨节点多读多写

ValidateVolumeCapabilities(context.Context, *ValidateVolumeCapabilitiesRequest) (*ValidateVolumeCapabilitiesResponse, error)

// 列举全部存储卷信息

ListVolumes(context.Context, *ListVolumesRequest) (*ListVolumesResponse, error)

// 获取存储资源池可用空间大小

GetCapacity(context.Context, *GetCapacityRequest) (*GetCapacityResponse, error)

// 获取ControllerServer支持功能点,比如是否支持快照能力

ControllerGetCapabilities(context.Context, *ControllerGetCapabilitiesRequest) (*ControllerGetCapabilitiesResponse, error)

// 创建快照

CreateSnapshot(context.Context, *CreateSnapshotRequest) (*CreateSnapshotResponse, error)

// 删除快照

DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*DeleteSnapshotResponse, error)

// 获取所有快照信息

ListSnapshots(context.Context, *ListSnapshotsRequest) (*ListSnapshotsResponse, error)

// 扩容存储卷

ControllerExpandVolume(context.Context, *ControllerExpandVolumeRequest) (*ControllerExpandVolumeResponse, error)

}

3. NodeServer


NodeServer 主要负责存储卷挂载/卸载操作。

// NodeServer is the server API for Node service.

type NodeServer interface {

// 将存储卷格式化并挂载至临时全局目录

NodeStageVolume(context.Context, *NodeStageVolumeRequest) (*NodeStageVolumeResponse, error)

// 将存储卷从临时全局目录卸载

NodeUnstageVolume(context.Context, *NodeUnstageVolumeRequest) (*NodeUnstageVolumeResponse, error)

// 将存储卷从临时目录bind-mount到目标目录

NodePublishVolume(context.Context, *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error)

// 将存储卷从目标目录卸载

NodeUnpublishVolume(context.Context, *NodeUnpublishVolumeRequest) (*NodeUnpublishVolumeResponse, error)

// 获取存储卷的容量信息

NodeGetVolumeStats(context.Context, *NodeGetVolumeStatsRequest) (*NodeGetVolumeStatsResponse, error)

// 存储卷扩容

NodeExpandVolume(context.Context, *NodeExpandVolumeRequest) (*NodeExpandVolumeResponse, error)

// 获取NodeServer支持功能点,比如是否支持获取存储卷容量信息

NodeGetCapabilities(context.Context, *NodeGetCapabilitiesRequest) (*NodeGetCapabilitiesResponse, error)

// 获取CSI节点信息,比如最大支持卷个数

NodeGetInfo(context.Context, *NodeGetInfoRequest) (*NodeGetInfoResponse, error)

}

K8s CSI API 对象

===================================================================================

K8s 为支持 CSI 标准,包含如下 API 对象:

  • CSINode

  • CSIDriver

  • VolumeAttachment

1. CSINode


apiVersion: storage.k8s.io/v1beta1

kind: CSINode

metadata:

name: node-10.212.101.210

spec:

drivers:

  • name: yodaplugin.csi.alibabacloud.com

nodeID: node-10.212.101.210

topologyKeys:

  • kubernetes.io/hostname

  • name: pangu.csi.alibabacloud.com

nodeID: a5441fd9013042ee8104a674e4a9666a

topologyKeys:

  • topology.pangu.csi.alibabacloud.com/zone

作用:

  1. 判断外部 CSI 插件是否注册成功。在 Node Driver Registrar 组件向 Kubelet 注册完毕后,Kubelet 会创建该资源,故不需要显式创建 CSINode 资源。

  2. 将 Kubernetes 中 Node 资源名称与三方存储系统中节点名称(nodeID)一一对应。此处Kubelet会调用外部 CSI 插件NodeServer 的 GetNodeInfo 函数获取 nodeID。

  3. 显示卷拓扑信息。CSINode 中 topologyKeys 用来表示存储节点的拓扑信息,卷拓扑信息会使得Scheduler在 Pod 调度时选择合适的存储节点。

2. CSIDriver


apiVersion: storage.k8s.io/v1beta1

kind: CSIDriver

metadata:

name: pangu.csi.alibabacloud.com

spec:

插件是否支持卷挂接(VolumeAttach)

attachRequired: true

Mount阶段是否CSI插件需要Pod信息

podInfoOnMount: true

指定CSI支持的卷模式

volumeLifecycleModes:

最后

终极手撕架构师的学习笔记:分布式+微服务+开源框架+性能优化

image

找小编(vip1024c)领取
式创建 CSINode 资源。

  1. 将 Kubernetes 中 Node 资源名称与三方存储系统中节点名称(nodeID)一一对应。此处Kubelet会调用外部 CSI 插件NodeServer 的 GetNodeInfo 函数获取 nodeID。

  2. 显示卷拓扑信息。CSINode 中 topologyKeys 用来表示存储节点的拓扑信息,卷拓扑信息会使得Scheduler在 Pod 调度时选择合适的存储节点。

2. CSIDriver


apiVersion: storage.k8s.io/v1beta1

kind: CSIDriver

metadata:

name: pangu.csi.alibabacloud.com

spec:

插件是否支持卷挂接(VolumeAttach)

attachRequired: true

Mount阶段是否CSI插件需要Pod信息

podInfoOnMount: true

指定CSI支持的卷模式

volumeLifecycleModes:

最后

终极手撕架构师的学习笔记:分布式+微服务+开源框架+性能优化

[外链图片转存中…(img-0fPyJAik-1721724110970)]

找小编(vip1024c)领取

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值