需求
应用或服务上K8S时,我们可能会面对如何选择合适的持久卷来进一步保障其稳定运行。而在K8S中我们常见存储方面的知识点如下:
Pod Volume,如:emptydir、hostpath;
持久卷(Persistent Volume)
持久卷申领(PersistentVolumeClaim,PVC)
存储类(StorageClass)
静态制备(Static Provisioning)
动态制备(Dynamic Provisioning)
以上知识点中基于存算分离设计理念的PV/PVC是整个存储的核心,而基于存储类的静态制备和动态制备可满足我们对不同存储场景的需求。
存储的分配方式
关于存储的核心知识,我们在此直接引用阿里云云原生更加专业的文章来进行解析。
阿里云云原生https://developer.aliyun.com/article/720251
Pod Volumes
首先来看一下 Pod Volumes 的使用场景:
场景一:如果 pod 中的某一个容器在运行时异常退出,被 kubelet 重新拉起之后,如何保证之前容器产生的重要数据没有丢失?
场景二:如果同一个 pod 中的多个容器想要共享数据,应该如何去做?
以上两个场景,其实都可以借助 Volumes 来很好地解决,接下来首先看一下 Pod Volumes 的常见类型:
1.本地存储,常用的有 emptydir/hostpath;
2.网络存储:网络存储当前的实现方式有两种,一种是 in-tree,它的实现代码是放在 K8s 代码仓库中的,随着 K8s 对存储类型支持的增多,这种方式会给 K8s 本身的维护和发展带来很大的负担;而第二种实现方式是 out-of-tree,它的实现其实是给 K8s 本身解耦的,通过抽象接口将不同存储的 driver 实现从 K8s 代码仓库中剥离,因此 out-of-tree 是后面社区主推的一种实现网络存储插件的方式;
3.Projected Volumes:它其实是将一些配置信息,如 secret/configmap 用卷的形式挂载在容器中,让容器中的程序可以通过 POSIX 接口来访问配置数据;
4.PV 与 PVC 就是今天要重点介绍的内容。
Persistent Volumes
接下来看一下 PV(Persistent Volumes)。既然已经有了 Pod Volumes,为什么又要引入 PV 呢?我们知道 pod 中声明的 volume 生命周期与 pod 是相同的,以下有几种常见的场景:
场景一:pod 重建销毁,如用 Deployment 管理的 pod,在做镜像升级的过程中,会产生新的 pod并且删除旧的 pod ,那新旧 pod 之间如何复用数据?
场景二:宿主机宕机的时候,要把上面的 pod 迁移,这个时候 StatefulSet 管理的 pod,其实已经实现了带卷迁移的语义。这时通过 Pod Volumes 显然是做不到的;
场景三:多个 pod 之间,如果想要共享数据,应该如何去声明呢?我们知道,同一个 pod 中多个容器想共享数据,可以借助 Pod Volumes 来解决;当多个 pod 想共享数据时,Pod Volumes 就很难去表达这种语义;
场景四:如果要想对数据卷做一些功能扩展性,如:snapshot、resize 这些功能,又应该如何去做呢?
以上场景中,通过 Pod Volumes 很难准确地表达它的复用/共享语义,对它的扩展也比较困难。因此 K8s 中又引入了 **Persistent Volumes **概念,它可以将存储和计算分离,通过不同的组件来管理存储资源和计算资源,然后解耦 pod 和 Volume 之间生命周期的关联。这样,当把 pod 删除之后,它使用的 PV 仍然存在,还可以被新建的 pod 复用。
PVC 设计意图
了解 PV 后,应该如何使用它呢?用户在使用 PV 时其实是通过 PVC,为什么有了 PV 又设计了 PVC 呢?主要原因是为了简化 K8s 用户对存储的使用方式,做到职责分离。通常用户在使用存储的时候,只用声明所需的存储大小以及访问模式。访问模式是什么?其实就是:我要使用的存储是可以被多个 node 共享还是只能单 node 独占访问(注意是 node level 而不是 pod level)?只读还是读写访问?用户只用关心这些东西,与存储相关的实现细节是不需要关心的。通过 PVC 和 PV 的概念,将用户需求和实现细节解耦开,用户只用通过 PVC 声明自己的存储需求。PV是有集群管理员和存储相关团队来统一运维和管控,这样的话,就简化了用户使用存储的方式。可以看到,PV 和 PVC 的设计其实有点像面向对象的接口与实现的关系。用户在使用功能时,只需关心用户接口,不需关心它内部复杂的实现细节。既然 PV 是由集群管理员统一管控的,接下来就看一下 PV 这个对象是怎么产生的。
Static Volume Provisioning
第一种产生方式:静态产生方式 - 静态 Provisioning。
静态 Provisioning:由集群管理员事先去规划这个集群中的用户会怎样使用存储,它会先预分配一些存储,也就是预先创建一些 PV;然后用户在提交自己的存储需求(也就是 PVC)的时候,K8s 内部相关组件会帮助它把 PVC 和 PV 做绑定;之后用户再通过 pod 去使用存储的时候,就可以通过 PVC 找到相应的 PV,它就可以使用了。静态产生方式有什么不足呢?可以看到,首先需要集群管理员预分配,预分配其实是很难预测用户真实需求的。举一个最简单的例子:如果用户需要的是 20G,然而集群管理员在分配的时候可能有 80G 、100G 的,但没有 20G 的,这样就很难满足用户的真实需求,也会造成资源浪费。有没有更好的方式呢?
Dynamic Volume Provisioning
第二种访问方式:动态 Dynamic Provisioning。
动态供给是什么意思呢?就是说现在集群管理员不预分配 PV,他写了一个模板文件,这个模板文件是用来表示创建某一类型存储(块存储,文件存储等)所需的一些参数,这些参数是用户不关心的,给存储本身实现有关的参数。用户只需要提交自身的存储需求,也就是 PVC 文件,并在 PVC 中指定使用的存储模板(StorageClass)。K8s 集群中的管控组件,会结合 PVC 和 StorageClass 的信息动态,生成用户所需要的存储(PV),将 PVC 和 PV 进行绑定后,pod 就可以使用 PV 了。通过 StorageClass 配置生成存储所需要的存储模板,再结合用户的需求动态创建 PV 对象,做到按需分配,在没有增加用户使用难度的同时也解放了集群管理员的运维工作。
存储的使用
在此我们使用NFS来进一步演示存储的分配和使用。
静态PV使用
通过分析以上代码,可得出:
存储管理员使用静态制备应预先规划PV资源及存储空间;
集群管理员通过PVC使用了其中5G的资源,并将其挂载到容器内部/data挂载点;
静态PV虽然简单,对于单一的存储需求还是比较方便的,但如果是多样性的空间资源需求下,使用静态PV就显得有点不合时宜了。
动态PV使用
由于Kubernetes 不包含内部 NFS 驱动,我们需要使用外部驱动为 NFS 创建 StorageClass 。
驱动手动部署
通过分析以上代码,并和静态PV对比可得出:
存储管理员通过外部NFS插件即可实现:预先提供NFS存储模板,而不必提前预分配存储空间;
集群管理员通过PV只需绑定storageclass并按需使用相应的存储空间即可;
通过动态PV,存储管理员可接入不同级别的存储卷(storageclass),如:
不同性能级别的块存储;
不同性能级别的文件存储;
集群管理员需要能够根据应用或服务不同使用场景,选择对应的的 PersistentVolume, 并且这些 PV 卷之间的差别不仅限于卷大小和访问模式,同时又不将卷是如何实现的这些细节暴露给用户。
总结
通过学习K8S存储的几个概念,在后续的K8S集成更多的应用或服务时,我们就可以根据使用场景、不同性能级别来更好的分配与使用存储资源了。