云原生|容器存储接口 CSI
Posted 云服务圈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了云原生|容器存储接口 CSI相关的知识,希望对你有一定的参考价值。
导读:在一文我们重点介绍了 K8s 内部的存储流程,以及 PV、PVC、StorageClass、Kubelet 等之间的调用关系。接下来本文将将重点放在 CSI(Container Storage Interface)容器存储接口上,探究什么是 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 核心流程介绍
1. Provisioning Volumes
1. 集群管理员创建 StorageClass 资源,该 StorageClass 中包含 CSI 插件名称(provisioner:pangu.csi.alibabacloud.com)以及存储类必须的参数(parameters: type=cloud_ssd)。sc.yaml 文件如下:
2. 用户创建 PersistentVolumeClaim 资源,PVC 指定存储大小及 StorageClass(如上)。pvc.yaml 文件如下:
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 进行绑定。
2. Attaching Volumes
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。
3. Mounting Volumes
4. Unmounting Volumes
5. Detaching Volumes
6. Deleting Volumes
-
Delete:调用外部 CSI 插件的 DeleteVolume 函数 以删除卷;一旦卷成功删除,Provisioner 会删除集群中对应 PV 对象。 -
Retain:Provisioner 不执行卷删除操作。
CSI Sidecar 组件介绍
1. Node Driver Registrar
1)功能
2)原理
Node-Driver-Registrar 组件通过实现注册,注册成功后:
-
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 值)。
2. External Provisioner
1)功能
2)原理
-
判断 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 是否需要删除,标准如下:
-
判断其 .Status.Phase 是否为 Release。 -
判断其 .Spec.PersistentVolumeReclaimPolicy 是否为 Delete。 -
判断其是否包含 annotation(pv.kubernetes.io/provisioned-by),且其值是否为自己。 -
-
通过特定的 Unix Domain Socket 调用外部 CSI 插件的 DeleteVolume 接口 。
-
删除集群中的 PV 资源。
3. External Attacher
1)功能
2)原理
-
从 VolumeAttachment 资源中获得 PV 的所有信息,如 volume ID、node ID、挂载 Secret 等。
-
判断 VolumeAttachment 的 DeletionTimestamp 字段是否为空来判断其为卷挂接或卷摘除:若为卷挂接则通过特定的 Unix Domain Socket 调用外部 CSI 插件的 ControllerPublishVolume 接口 ;若为卷摘除则通过特定的 Unix Domain Socket 调用外部 CSI 插件的 ControllerUnpublishVolume 接口 。
-
在挂接时为相关 PV 打上 Finalizer:external-attacher/[driver 名称]。
-
当 PV 处于删除状态时(DeletionTimestamp 非空),删除 Finalizer:external-attacher/[driver 名称]。
4. External Resizer
1)功能
2)原理
-
判断 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。
5. livenessprobe
1)功能
2)原理
CSI 接口介绍
1. IdentityServer
// 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 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 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 对象
-
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
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:
Persistent
3. VolumeAttachment
apiVersion: storage.k8s.io/v1
kind: VolumeAttachment
metadata:
annotations:
21481ae252a2457f9abcb86a3d02ba05 :
finalizers:
external-attacher/pangu-csi-alibabacloud-com
name: csi-0996e5e9459e1ccc1b3a7aba07df4ef7301c8e283d99eabc1b69626b119ce750
spec:
attacher: pangu.csi.alibabacloud.com
nodeName: node-10.212.101.241
source:
persistentVolumeName: pangu-39aa24e7-8877-11eb-b02f-021234350de1
status:
attached: true
支持特性
1. 拓扑支持
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-pangu
provisioner: pangu.csi.alibabacloud.com
parameters:
type: cloud_ssd
volumeBindingMode: Immediate
allowedTopologies:
matchLabelExpressions:
key: topology.pangu.csi.alibabacloud.com/zone
values:
zone-1
zone-2
-
对于 WaitForFirstConsumer -
当 PVC 的 anno 中包含 "volume.kubernetes.io/selected-node" 且不为空,则先获取对应节点 CSINode 的 TopologyKeys,然后根据该 TopologyKeys 键从 Node 资源的 Label 获取 Values 值,最后拿该 Values 值与 StorageClass 的 AllowedTopologies 比对,判断其是否包含于其中;若不包含则报错。
-
对于 Immediately -
将 StorageClass 的 AllowedTopologies 的值填进来,若 StorageClass 没有设置 AllowedTopologies 则将所有包含 TopologyKeys 键的节点 Value 添进来。
Scheduler 如何处理使用存储卷调度
基于社区 1.18 版本调度器
-
预选(Filter) :筛选满足Pod调度要求的节点列表。
-
优选(Score) :通过内部的优选算法为节点打分,获得最高分数的节点即为选中的节点。
-
绑定(Bind) :调度器将调度结果通知给 kube-apiserver,更新 Pod 的 .spec.nodeName 字段。
-
获取 Pod 的 boundClaims、claimsToBind 以及 unboundClaimsImmediate。 -
boundClaims:已 Bound 的 PVC -
claimsToBind:PVC 对应 StorageClass 的 VolumeBindingMode 为 VolumeBindingWaitForFirstConsumer -
unboundClaimsImmediate:PVC 对应 StorageClass 的 VolumeBindingMode 为 VolumeBindingImmediate -
若 len(unboundClaimsImmediate) 不为空,表示这种 PVC 需要立即绑定 PV(即存 PVC 创建后,立刻动态创建 PV 并将其绑定到 PVC,该过程不走调度),若 PVC 处于 unbound 阶段则报错。 -
若 len(boundClaims) 不为空,则检查 PVC 对应 PV 的节点亲和性与当前节点的 Label 是否冲突,若冲突则报错(可检查 Immediate 类型的 PV 拓扑)。 -
若 len(claimsToBind) 不为空 -
先检查环境中已有的 PV 能否与该 PVC 匹配(findMatchingVolumes),将能够匹配 PVC 的 PV 记录在调度器的 cache 中。 -
未匹配到 PV 的 PVC 走动态调度流程,动态调度主要通过 StorageClass 的 AllowedTopologies 字段判断当前调度节点是否满足拓扑要求(针对 WaitForFirstConsumer 类型的 PVC)。
调度器会先 Assume PV/PVC,再 Assume Pod。
-
更改调度器 cache 中已经 Match 的 PV 信息:设置 annotation:pv.kubernetes.io/bound-by-controller="yes"。 -
更改调度器 cache 中未匹配到 PV 的 PVC,设置 annotation:volume.kubernetes.io/selected-node=【所选节点】。 -
-
更改调度器 cache 中 Pod 的 .Spec.NodeName 为【所选节点】。
-
调用 Kubernetes 的 API 更新集群中 PV/PVC 资源,使其与调度器 Cache 中的 PV/PVC 一致。
-
检查 PV/PVC 状态:
-
检查所有 PVC 是否已处于 Bound 状态。 -
检查所有 PV 的 NodeAffinity 是否与节点 Label 冲突。 -
-
调度器执行 Bind 操作:调用 Kubernetes 的 API 更新 Pod 的 .Spec.NodeName 字段。
2. 存储卷扩容
3. 单节点卷数量限制
4. 存储卷监控
-
kubelet_volume_stats_capacity_bytes:存储卷容量 -
kubelet_volume_stats_used_bytes:存储卷已使用容量 -
kubelet_volume_stats_available_bytes:存储卷可使用容量 -
kubelet_volume_stats_inodes:存储卷 inode 总量 -
kubelet_volume_stats_inodes_used:存储卷 inode 使用量 -
kubelet_volume_stats_inodes_free:存储卷 inode 剩余量
5. Secret
-
csi.storage.k8s.io/provisioner-secret-name -
csi.storage.k8s.io/provisioner-secret-namespace -
csi.storage.k8s.io/controller-publish-secret-name -
csi.storage.k8s.io/controller-publish-secret-namespace -
csi.storage.k8s.io/node-stage-secret-name -
csi.storage.k8s.io/node-stage-secret-namespace -
csi.storage.k8s.io/node-publish-secret-name -
csi.storage.k8s.io/node-publish-secret-namespace -
csi.storage.k8s.io/controller-expand-secret-name -
csi.storage.k8s.io/controller-expand-secret-namespace
6. 块设备
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-example
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
volumeClaimTemplates:
metadata:
name: html
spec:
accessModes:
ReadWriteOnce
volumeMode: Block
storageClassName: csi-pangu
resources:
requests:
storage: 40Gi
template:
metadata:
labels:
app: nginx
spec:
containers:
name: nginx
image: nginx
volumeDevices:
devicePath: "/dev/vdb"
name: html
7. 卷快照/卷克隆能力
以上是关于云原生|容器存储接口 CSI的主要内容,如果未能解决你的问题,请参考以下文章