K8s应用存储与存储卷原创
「疑问」
众所周知,K8s 的持久化存储(Persistent Storage)保证了应用数据独立于应用生命周期而存在,那么是否也有如下的一些疑问:
如果 pod 中的某一个容器在运行时异常退出,被 kubelet 重新拉起之后,如何保证之前容器产生的重要数据没有丢失?
如果同一个 pod 中的多个容器想要共享数据,应该如何去做?
K8s 内部的存储流程到底是怎样的?
PV、PVC、StorageClass、Kubelet、CSI 插件等之间的调用关系又如何?
# 名词解释
- in-tree:
- 运行在 k8s 核心组件内部的存储插件;
- out-of-tree:
- 独立在 k8s 组件之外运行的存储插件;
- 主要是通过 gRPC 接口跟 k8s 组件交互,并且 k8s 提供了大量的 SideCar 组件来配合 CSI 插件实现丰富的功能
- PV:PersistentVolume
- 持久化存储卷资源,详细定义预挂载存储空间的各项指标;
- 无 Namesapces 限制,一般由 Admin 创建 PV;
- PVC:PersistentVolumeClaim
- 持久化存储卷声明,用户使用的存储接口,对存储细节无感知,属于某个 Namespaces 内;
- StorageClass:
- 存储类,创建PV存储模版类,即系统会按照 StorageClass 定义的存储模版创建存储卷(包括真实的存储空间与 PV 对象);
- 无 Namesapces 限制,一般由 Admin 创建;
- 每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV
- CSI:Container Storage Interface
- 目的是定义行业标准的 “容器存储接口”,使存储供应商(SP)基于 CSI 标准开发的插件可以在不同容器编排(CO)系统中工作,CO 系统包括 Kubernetes、Mesos、Swarm 等。
# 组件介绍
- PV Controller:
- 负责 PV/PVC 绑定及周期管理,根据需求进行数据卷的 Provision/Delete 操作;
- AD Controller:
- 负责数据卷的 Attach/Detach 操作,将设备挂接到目标节点;
- Kubelet:
- Kubelet 是在每个 Node 节点上运行的主要 “节点代理”,功能是 Pod 生命周期管理、容器健康检查、容器监控等;
- Volume Manager:
- Kubelet 中的组件,负责管理数据卷的 Mount/Umount 操作(也负责数据卷的 Attach/Detach 操作,需配置kubelet 相关参数开启该特性)、卷设备的格式化等等;
- Volume Plugins:
- 存储插件,由存储供应商开发,目的在于扩展各种存储类型的卷管理能力,实现第三方存储的各种操作能力。
- Volume Plugins 有 in-tree 和 out-of-tree 两种;
- SideCar 组件
- External Provioner:
- External Provioner 是一种 sidecar 容器,作用是调用 Volume Plugins 中的 CreateVolume 和 DeleteVolume 函数来执行 Provision/Delete 操作。
- 因为 K8s 的 PV 控制器无法直接调用 Volume Plugins 的相关函数,故由 External Provioner 通过 gRPC 来调用;
- External Attacher:
- External Attacher 是一种 sidecar 容器,作用是调用 Volume Plugins 中的 ControllerPublishVolume ControllerUnpublishVolume 函数来执行 Attach/Detach 操作。
- 因为 K8s 的 AD 控制器无法直接调用 Volume Plugins 的相关函数,故由 External Attacher 通过 gRPC 来调用。
- External Provioner:
# Volumes
疑问中提到的(1)(2)两个场景,其实都可以借助 Volumes 来很好地解决,接下来首先看一下 Pod Volumes 的常见类型:
- 本地存储,常用的有 emptydir/hostpath;
- 网络存储:网络存储当前的实现方式有两种,一种是 in-tree,另一种是 out-of-tree ;
- Projected Volumes:它其实是将一些配置信息,如 secret/configmap 用卷的形式挂载在容器中,让容器中的程序可以通过POSIX接口来访问配置数据;
- PV 与 PVC 就是今天要重点介绍的内容。
# 静态、动态存储卷
从上面的讨论我们知道了存储的相关基本概念,也知道了 PVC 是针对应用服务对存储的二次抽象,具有简洁的存储定义接口。而 PV 是具有繁琐存储细节的存储抽象,一般有专门的集群管理人员定义、维护。
根据 PV 的创建方式可以将存储卷分为动态存储和静态存储卷:
- 静态存储卷:由管理员创建的 PV
- 动态存储卷:由 Provisioner 插件创建的 PV
# 静态存储卷
静态 Provisioning:
- 首先集群管理员事先去规划这个集群中的用户会怎样使用存储,它会先预分配一些存储,也就是预先创建一些 PV;
- 然后用户在提交自己的存储需求(也就是 PVC)的时候,K8s 内部相关组件会帮助它把 PVC 和 PV 做绑定;
- 最后用户再通过 pod 去使用存储的时候,就可以通过 PVC 找到相应的 PV,它就可以使用了。
静态产生方式有什么不足呢?
上图可以看到,首先需要集群管理员预分配,预分配其实是很难预测用户真实需求的。
举一个最简单的例子:如果用户需要的是 20G,然而集群管理员在分配的时候可能有 80G 、100G 的,但没有 20G 的,这样就很难满足用户的真实需求,也会造成资源浪费。有没有更好的方式呢?
# 动态存储卷
动态Provisioning是什么意思呢?
就是说现在集群管理员不预分配 PV,他写了一个创建 PV 的模板文件(StorageClass),这个模板文件是用来表示创建某一类型存储(块存储,文件存储等)所需的一些参数,这些参数是用户不关心的,给存储本身实现有关的参数。
用户只需要提交自身的存储需求,也就是 PV C文件,并在 PVC 中指定使用的存储模板(StorageClass)。
K8s 集群中的管控组件,会结合 PVC 和 StorageClass 的信息动态,生成用户所需要的存储(PV),将 PVC 和 PV 进行绑定后,pod 就可以使用 PV 了。通过 StorageClass 配置生成存储所需要的存储模板,再结合用户的需求动态创建 PV 对象,做到按需分配,在没有增加用户使用难度的同时也解放了集群管理员的运维工作。
下面看一下 StorageClass 模板文件yaml:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-local
provisioner: localplugin.csi.alibabacloud.com
parameters:
volumeType: LVM
vgName: vg_longbridge
fsType: ext4
lvmType: "striping"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
2
3
4
5
6
7
8
9
10
11
12
13
这个模板文件叫 StorageClass,在StorageClass里面,我们需要填的重要信息:
- provisioner,provisioner 是什么?它其实就是说我当时创建 PV 和对应的存储的时候,应该用哪个存储插件来去创建(其实就是 external-provisioner sidecar container是一个controller去watch pvc/pv对象,当新建一个由storageclass创建pv的pvc(或删除pv对象),该sidecar container会grpc调用存储供应商开发的 csi-plugin CreateVolume(DeleteVolume)方法来实际创建一个外部存储volume,并新建一个pv对象写入k8s api server)。
- 这些参数是通过k8s创建存储的时候,需要指定的一些细节参数。对于这些参数,用户是不需要关心的,像这里 volumeType、vgName、fsType 和它的类型。
- reclaimPolicy跟我们刚才讲解的 PV 里的意思是一样的,就是说动态创建出来的这块 PV,当使用方使用结束、Pod 及 PVC 被删除后,这块 PV 应该怎么处理,我们这个地方写的是 delete,意思就是说当使用方 pod 和 PVC 被删除之后,这个 PV 也会被删除掉。
# PV Controller 介绍
此处借鉴 @郡宝 在云原生存储课程 (opens new window)中的流程图。
PV Controller 主要任务:
- PV、PVC 生命周期管理:创建、删除PV对象;负责PV、PVC的状态迁移
- 绑定 PVC、PV 对象;一个 PVC 必须与一个 PV 绑定后才能被应用使用,PV-Controller 会根据绑定条件和对象状态对 PV、PVC 进行Bound、Unbound 操作。
PV 控制器中有两个 Worker:
- ClaimWorker:处理 PVC 的 add /update/delete 相关事件以及 PVC 的状态迁移;
- VolumeWorker:负责 PV 的状态迁移。
PV 状态迁移(UpdatePVStatus):
在分析 PV状态迁移 之前,我们先了解下 PV 的回收策略。PV 的回收策略向集群阐述了在 PVC 释放卷的时候,应如何进行后续工作,目前可以采用三种策略:
- 1、保留(Retain):回收策略Retain使得用户可以手动回收资源。当 PersistentVolumeClaim 对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为"已释放(released)"。 由于卷上仍然存在着前一申领人的数据,该卷还不能用于其他申领。 管理员可以通过下面的步骤来手动回收该卷:
- 2、删除(Delete):对于支持Delete回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施中移除所关联的存储资产。 动态供应的卷会继承其 StorageClass 中设置的回收策略,该策略默认 为Delete。如果插件能够支持,回收策略会执行基础的擦除操作(
rm -rf /thevolume/*
),这一卷就能被重新申请了。 - 3、回收(Recycle):警告:回收策略Recycle已被废弃。取而代之的建议方案是使用动态供应。
然后看下卷的阶段(Phase):
- Available:可用资源,尚未被绑定到 PVC 上
- Bound:该卷已经被绑定
- Released:PVC 已经被删除,但该资源尚未被集群回收
- Failed:该卷的自动回收过程失败。
最后由 PV 状态迁移图可知:
- PV 初始状态为 Available,当 PV 与 PVC 绑定后,状态变为 Bound;
- 与 PV 绑定的 PVC 删除后,状态变为 Released;
- 当 PV 回收策略为 Recycle 或手动删除 PV 的 .Spec.ClaimRef 后,PV 状态变为 Available;
- 当 PV 回收策略未知或 Recycle 失败或存储卷删除失败,PV 状态变为 Failed;
- 手动删除 PV 的 .Spec.ClaimRef,PV 状态变为 Available。
PVC 状态迁移(UpdatePVCStatus):
首先看下 PVC 的阶段(Phase):
- Pending:可用资源,尚未被绑定到 PVC 上
- Bound:该卷已经被绑定
- Released:PVC 已经被删除,但该资源尚未被集群回收
- Failed:该卷的自动回收过程失败。
最后由 PVC 状态迁移图可知:
当集群中不存在满足 PVC 条件的 PV 时,PVC 状态为 Pending。在 PV 与 PVC 绑定后,PVC 状态由 Pending 变为 Bound;
与 PVC 绑定的 PV 在环境中被删除,PVC 状态变为 Lost;
再次与一个同名 PV 绑定后,PVC 状态变为 Bound。
# CSI介绍
csi 是什么?csi 的全称是 container storage interface,它是K8s社区后面对存储插件实现(out of tree)的官方推荐方式。csi 的实现大体可以分为两部分:
- 第一部分是由k8s社区驱动实现的通用的部分, csi-provisioner controller 和 csi-attacher controller;
- 另外一种是由云存储厂商实践的,对接云存储厂商的 OpenApi,主要是实现真正的 create/delete/mount/unmount 存储的相关操作,csi-controller-server和csi-node-server。
这是阿里云的一张存储概况图,很好的说明了 CSI 插件支持静态存储卷挂载和动态存储卷挂载2种方式,在静态存储卷挂载的方式中,通常需要手动编辑和创建一个PV和PVC进行挂载。当需要PV和PVC数量很大的时候,您可以创建动态存储卷进行挂载。
# K8s 存储架构
# PV、PVC 以及通过 csi 使用存储流程
此处借鉴 @郡宝 在云原生存储课程 (opens new window)中的流程图。
流程如下:
用户创建了一个包含 PVC 的 Pod,该 PVC 要求使用动态存储卷;
Scheduler 根据 Pod 配置、节点状态、PV 配置等信息,把 Pod 调度到一个合适的 Worker 节点上;
PV 控制器 watch 到该 Pod 使用的 PVC 处于 Pending 状态,于是调用 Volume Plugin(in-tree)创建存储卷,并创建 PV 对象(out-of-tree 由 External Provisioner 来处理);
AD 控制器发现 Pod 和 PVC 处于待挂接状态,于是调用 Volume Plugin 挂接存储设备到目标 Worker 节点上
在 Worker 节点上,Kubelet 中的 Volume Manager 等待存储设备挂接完成,并通过 Volume Plugin 将设备挂载到全局目录:/var/lib/kubelet/pods/[pod uid]/volumes/kubernetes.io~iscsi/[PV name](以 iscsi 为例);
Kubelet 通过 Docker 启动 Pod 的 Containers,用 bind mount 方式将已挂载到本地全局目录的卷映射到容器 中。
整个流程流程分别三个阶段:
从上图可知: pod 挂载 volume 的整个工作流程。整个流程流程分别三个阶段:Provision/Delete、Attach/Detach、Mount/Unmount,不过不是每个存储方案都会经历这三个阶段,比如 NFS 就没有 Attach/Detach 阶段。
先来看 Provision 阶段,整个过程如上图所示。其中 extenal-provisioner 和 PVController 均 watch PVC 资源。
1.当 PVController watch 到集群中有 PVC 创建时,会判断当前是否有 in-tree plugin 与之相符,如果没有则判断其存储类型为 out-of-tree 类型,于是给 PVC 打上注解 volume.beta.kubernetes.io/storage-provisioner={csi driver name}
;
2.当 extenal-provisioner watch 到 PVC 的注解 csi driver 与自己的 csi driver 一致时,调用 CSI Controller 的 CreateVolume
接口;
3.当 CSI Controller 的 CreateVolume
接口返回成功时,extenal-provisioner 会在集群中创建对应的 PV;
4.PVController watch 到集群中有 PV 创建时,将 PV 与 PVC 进行绑定。
Attach 阶段是指将 volume 附着到节点上,整个过程如上图所示。
1.ADController 监听到 pod 被调度到某节点,并且使用的是 CSI 类型的 PV,会调用内部的 in-tree CSI 插件的接口,该接口会在集群中创建一个 VolumeAttachment 资源;
2.external-attacher 组件 watch 到有 VolumeAttachment 资源创建出来时,会调用 CSI Controller 的 ControllerPublishVolume
接口
3.当 CSI Controller 的 ControllerPublishVolume
接口调用成功后,external-attacher 将对应的 VolumeAttachment 对象的 Attached 状态设为 true;
4.ADController watch 到 VolumeAttachment 对象的 Attached 状态为 true 时,更新 ADController 内部的状态 ActualStateOfWorld。
最后一步将 volume 挂载到 pod 里的过程涉及到 kubelet。
整个流程简单地说是,对应节点上的 kubelet 在创建 pod 的过程中,会调用 CSI Node 插件,执行 mount 操作。
# 操作演示
查看PVC
kubectl get pvc
查看PV
kubectl get pv
查看storageClass
kubectl get storageClass
2
3
4
5
6
7
8
# 总结
这里为大家简单总结一下。
- K8s Volume是用户Pod保存业务数据的重要接口和手段
- PVC和PV体系增强了K8s Volumes在多Pod共享/迁移/存储扩展等场景下的能力
- PV(存储)的不同供给模式(static and dynamic)可以通过多种方式为集群中的Pod供给所需的存储
# 参考资料
- https://kubernetes.io/zh/docs/concepts/storage/
- https://help.aliyun.com/document_detail/134722.html
- https://support.huaweicloud.com/basics-cce/kubernetes_0030.html
- https://www.v2k8s.com/storage/t/105#3b0d1a
- https://www.v2k8s.com/kubernetes/t/217
- https://cloud.tencent.com/developer/article/1847424