自以为什么都懂的人不可能开始学习。
郑重说明:本文适合对游戏开发感兴趣的初级及中级开发和学习者,本人力图将技术用简单的语言表达清楚。鉴于水平有限,能力一般,文章如有错漏之处,还望批评指正,谢谢。
一、背景
图灵机,一开始就是模拟人来做计算问题,信息通过无限长度的纸带来写入或者擦除,纸带就是最初的临时存储设备。
后来通用计算机问世,势必需要记录信息,于是又发明了专门用来做存储的部件。存储部件可以用来存储计算用的指令(程序)和数据(运算的输入或者输出)。
通用计算机有计算部件,如 CPU、内存。如何发起计算呢? 其实就是写好程序,启动进程,来利用计算部件如 CPU 和内存来做计算;如果进程运行结束了,我们还需要运算的结果,可以存储在文件中。
还有存储部件,如硬盘。
k8s 的更宏观的生态也有计算与存储。
可以把 pod 及里面的容器理解为计算部件,那么 k8s 势必也需要存储。
pod 和容器 会停止、会被调度到别的机器,其附带的信息也需要能跟着带过去。
二、预备知识
2.1 存储分类
-
单机块存储
-
集中式存储,如企业级 EMC 存储
-
分布式存储
-
对象存储 OSS
2.2 文件系统
文件系统是用来管理硬件上的存储空间的软件系统,类似于仓库中的货架:
文件系统可以分为以下几类
- 本地文件系统,如 Ext4、Fat32 等,本地计算机访问的文件系统
- 网络文件系统,如 NFS(数据集中在网络上某个服务器),这样可以通过网络访问非本机的存储,一般是在同一个局域网内
- 分布式文件系统(Distributed file system, DFS),数据和 NFS 存在单一节点上不同,分布式文件系统的数据分布在多个节点上
典型的分布式文件系统如 Ceph、GlusterFS,通过对应的 client 来决定数据应该存储到哪个服务器节点上
此外,大数据用的 GFS、HDFS 也属于分布式文件系统,只不过数据分片和服务器节点有专门的 namenode 服务器来管理
三、k8s 存储的演进
3.1 Volume
Volume 的提出
容器的存储存在以下两个问题:
- 容器内部存储的生命周期是短暂的,会随着容器环境的销毁而销毁,具有不稳定性
- 如果多个容器希望共享同一份存储,则仅仅依赖容器本身是很难实现的
在Kubernetes系统中,将对容器应用所需的存储资源抽象为存储卷(Volume)这个资源来解决这些问题。
Volume是与 Pod 绑定的(独立于容器)与Pod具有相同生命周期的资源对象。
我们可以将 Volume 的内容理解为目录或文件,容器如需使用某个Volume,则仅需设置 volumeMounts 将一个或多个 Volume 挂载为容器中的目录或文件,即可访问 Volume 中的数据。
Volume具体是什么类型,以及由哪个系统提供,对容器应用来说是透明的。
资源关系图:
配置方式如下:
apiVersion: v1
kind: Pod
metadata:
name: rbd
spec:
containers:
volumeMounts:
...
volumes:
...
Volume 的类型
k8s 支持的 volume 类型有多种,他们都可以以存储卷 volume 的形式挂载到容器中,成为目录或文件,如下图:
-
k8s 内部的一些资源,包括
- configmap,应用的配置
- secret,密钥文件
- downward api,Pod 或 容器的元数据
- ServiceAccountToken
- Projected Volume
-
pod 宿主机本地目录,包括
- EmptyDir,临时存储
- hostPath,主机上的某个目录
-
持久化存储卷
- 本地持久化块存储(本地各 node 节点中实现)
- 网络文件存储,如 NFS
- 分布式文件存储,如 Ceph,GlusterFS 等
- 存储厂商存储,如 EMC 的存储
- 公有云持久化存储,一般是块存储(需要使用者自己格式化成特定文件系统)
所谓 “持久化” 的存储卷 Volume,是相对于普通的 Volume 来说的,指的是该宿主机上得目录具有持久性,即该目录里的内容既不会因为容器的删除而被清理,也不会跟当前的宿主机绑定。当容器重启或者在其他节点重建之后,它仍然能通过挂载这个 Volume 访问到这些内容。前面说的宿主机本地目录 EmptyDir 和 hostPath 不具有这个特征。
对于需要持久化的 Volume,volume 中的参数非常多,部署时候用户需要知道一大堆的参数,而且不同 volume 还不一样,非常麻烦。此外,用户 Pod 中 的 volumes 中暴露过多存储组件相关的信息,也会带来安全隐患。
为此,k8s 中引入了 pv/pvc 来做部署上的解耦。(看,又是经典的引入中间层)
3.2 PV/PVC
引入一种新的资源: PV/PVC 做持久化存储后关系如下:
PV
PV 是对持久化的存储资源的抽象,由具体的存储提供商实现。
它通过插件化的机制进行管理。面向的是系统管理人员,不需要用户知道细节。
PVC
PVC 是用户对存储资源的申请,就像 Pod 消耗 Node 的CPU和内存资源一样,PVC 消耗 PV 的磁盘空间资源。
用户只需要说明申请的大小和访问模式 。
详细资源关系图
PV 的生命周期和使用它的 Pod 是相互独立的,一个 PV 任一时刻只能被一个 Pod (通过pvc)使用,但释放后又可以被其他 Pod 使用。
此时,Pod 里加入 pvc 字段来引入( Pod.volumes → pvc ),如:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pv-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
apiVersion: v1
kind: Pod
metadata:
name: rbd
spec:
containers:
volumeMounts:
...
volumes:
- name: my-pv
persistentVolumeClaim: #pvc
claimName: pv-claim
通过PVC/PV解决了开发者使用存储的困难,但是没有解决运维人员管理存储的困难。创建 pv 的工作还是很繁琐。
3.3 StorageClass
通过引入 StorageClass 来做 provision,可以动态创建网络存储和pv,省去了手动创建 pv 的麻烦。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-service
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
详细资源关系图V2
现在拓扑结构和部署目前都没有问题了,但还需要考虑代码维护的问题。
如果我们的存储资源使用的是持久化存储,而不同的存储有不同的实现,如果代码全部放到 k8s 代码库里一起发布、测试,类似下面虚线框:
这样非常不方便。需要把持久化存储的代码隔离开来。
3.4 FlexVolume插件机制
于是提供一种抽象层,使得新增的存储插件不必和 k8s 主干一起演进和测试。随后就引入了FlexVolume这种 Volume 类型。
但这种做法也有个问题:
实际执行插件代码时,是 k8s 来直接调用机器上的插件二进制/脚本程序。
当你编写完了 FlexVolume 插件的实现之后,一定要手动把它的可执行文件放在每个Node节点的插件目录下(/usr/libexec/kubernetes/kubelet-plugins/volume/exec)。
这就很麻烦了。
还有一些其他问题,比如,这些插件都是一次性的操作,挂载和反挂载的可执行程序都是独立的,没法交换一些中间的信息。
3.5 CSI 标准插件
于是社区又提出了CSI方案,彻底把存储插件的管理逻辑 和 k8s 解耦开来:
代码管理及部署关系如下:
小结
本文,我们介绍了 k8s 中存储有关的一些概念,提出的这些概念的背景和演化历程。希望对大家更好的使用和理解 k8s 有帮助。
作者:我是码财小子,会点编程代码,懂些投资理财;期待你的关注,不要错过我后续的文章更新。