别根据你的愿望来要求现实,应该根据现实来确定你的愿望。
郑重说明:本文适合对游戏开发感兴趣的初级及中级开发和学习者,本人力图将技术用简单的语言表达清楚。鉴于水平有限,能力一般,文章如有错漏之处,还望批评指正,谢谢。
在前面一篇文章【技术·真相】谈一谈K8S的存储(一) 中我们谈到了k8s存储的演进过程,这篇文章我们来研究一下其中涉及的原理。
一、K8S 如何挂载 Volume
回忆一下前面(上一篇文章)的图,忙了一坨,最终的目的是啥?
就是希望 Pod 中的容器可以借助 Volume 来操作目录或者文件。操作前 Volume 目录需要挂载。
中间的 PV/PVC、插件等都是为了实现挂载而引入的概念。
k8s 要将 Pod 的 Volume 要以目录的形式创建到 Pod 所在节点的宿主机上。(宏观建立在微观基础上)
k8s 最终把某种类型的 "Volume目录" 定位到 Pod 所在 Node 的一个临时目录,注意,它是 Pod 级别的,具体地址是:
xml
/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
我们去一个node 宿主机器上看看正在运行的 Pod 对应的 Volume:

具体挂载,各个类型 Volume 的操作不一样,下面分别来看一下。
1.1 emptyDir、configMap、secret、downwardAPI
这几种 Volume 类型,<Volume目录>中的文件都会随着 Pod 的消亡而被删除。
当一个 Pod 被调度到某一个Node节点上时,是直接在节点宿主机上创建<Volume目录>,然后可以供 Pod 中的容器进行挂载。Pod被删除时,也会把这个<Volume目录>删掉。可以认为这种临时的目录和 pod 绑定,其生命周期也一致。
这几种方式无法持久化文件数据。
1.2 hostPath
这种方式是把 hostPath 指向的目录供 Pod 中的容器挂载。Pod 被调度到哪个 Node 节点,就在哪个节点 宿主机上创建这个目录。
hostPath 的目录是用户指定的,因此不涉及 <Volume 目录>。
Pod 删除时,hostPath 目录里的内容虽然可以保留下来,但是 Pod 很可能会迁移到其他 Node 上,因此这种的方式也不能持久化文件数据。
1.3 远程块存储(如云存储)
对于这种 Volume,挂载之后,可以做到持久化 Volume 里的数据。
那这里的持久化,具体是怎么做的呢?
其实,就是将宿主机上的 Volume 目录映射到一个远端磁盘,这样,写 Volume 目录文件的数据就能保存在远端。即使切换 Node,数据也能再次从远端恢复。
这其中还分两个阶段:
阶段1:attach
远端磁盘是一个公有云提供的块设备,我们就需要调用其 API 将块存储 attach 到本地宿主机上的块设备。
完成后可以用 lsblk 查看,可以拿到这个块设备的 ID:
阶段2:mount
此时本地已经有 attach 到的块设备,接下来的 mount 又有两步:
- 格式化块设备,使其有某个文件系统(还记得上篇文章中提到仓库里的货架吗?),才方便管理文件和使用文件。
shell
$ mkfs.ext4 .... /dev/<块设备ID>
- 有了文件系统之后,直接 mkdir 创建目录,即可完成 mount
shell
$ mkdir -p <Volume目录>
mount 完成之后结果如下:
1.4 网络文件系统/分布式文件系统
如果是远程文件系统或分布式文件系统,如 NFS 或 Ceph(可以再回去看下上篇文章提到的这类存储的结构),它没有什么存储设备需要映射到本地宿主机上,可以直接使用。而且已经是文件系统了,也不需要格式化了。直接一个命令将本地 Node 中 <Volume 目录> mount 到远端 NFS 上的根目录(可能是远程局域网某台机器上的目录)即可:
ruby
$ mount -t nfs <NFS服务器地址>:/ <Volume目录>

二、挂载的实现:CSI 运行机制
2.1 挂载的三阶段
主要有三个阶段
- Provision/Delete(创盘/删盘)
- Attach/Detach(挂接/摘除)
- Mount/Unmount(挂载/卸载)
用一张图来表示就是:

2.2 插件角色
上图中,以插件形式存在的角色有两种:CSI controller 和 CSINode,用蓝色线框展示。
CSI controller
集群内单pod部署,用来管理存储API资源和存储卷Volume。
scss
service Identity {
//返回driver的信息,比如名字,版本
rpc GetPluginInfo(GetPluginInfoRequest)
returns (GetPluginInfoResponse) {}
//返回driver提供的能力,比如是否提供Controller Service,volume 访问能能力
rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
returns (GetPluginCapabilitiesResponse) {}
//探针
rpc Probe (ProbeRequest)
returns (ProbeResponse) {}
}
service Controller {
//创建卷
rpc CreateVolume (CreateVolumeRequest)
returns (CreateVolumeResponse) {}
//attach 卷
rpc ControllerPublishVolume (ControllerPublishVolumeRequest)
returns (ControllerPublishVolumeResponse) {}
rpc CreateSnapshot (CreateSnapshotRequest)
returns (CreateSnapshotResponse) {}
rpc ControllerExpandVolume (ControllerExpandVolumeRequest)
returns (ControllerExpandVolumeResponse) {}
}
CSINode
每台主机部署一个,用来对主机上的Volume进行管理。
接口主要是 stage 和 mount
scss
service Node {
//如果存储卷没有格式化,首先要格式化。然后把存储卷mount到一个临时的
//目录(这个目录通常是节点上的一个全局目录)。再通过NodePublishVolume
//将存储卷mount到pod的目录中。mount过程分为2步,原因是为了支持多个pod
//共享同一个volume(如NFS)
rpc NodeStageVolume (NodeStageVolumeRequest)
returns (NodeStageVolumeResponse) {}
//将存储卷从临时目录mount到目标目录(pod目录)
rpc NodePublishVolume (NodePublishVolumeRequest)
returns (NodePublishVolumeResponse) {}
//返回可用于该卷的卷容量统计信息
rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
returns (NodeGetVolumeStatsResponse) {}
//返回节点信息
rpc NodeGetInfo (NodeGetInfoRequest)
returns (NodeGetInfoResponse) {}
}
2.3 基本流程
Provision:PV的创建与PVC/PV 的绑定

绑定完成后,pvc 处于 bound 状态。如:

Attach
把远程磁盘attach到宿主机,成为宿主机的一个块设备。

AD 控制器(AttachDetachController)观察到使用 CSI 类型 PV 的 Pod 被调度到某一节点,此时AD 控制器 会调用内部 in-tree CSI 插件(CSIAttacher) 的 Attach 函数将 Volume 挂接到 Pod 所在的 Node上。
Mount
把块设备格式化成文件系统(NFS不需要),并 mount(挂载)到宿主机上。例如:
挂载到宿主机的以下 pod 目录(方便 pod内的容器共享):
bash
/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
流程如下:

容器使用 volume
kubelet 在向 Docker 发起 CRI 请求,将宿主机pod临时目录下的/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字> 挂载进容器的某个path,供容器使用。
至此,我们k8s存储的内容节介绍完了。
总结
本文作为k8s 存储介绍的第二部分,介绍了以下内容:
- 8ks存储挂载的过程
- 以存储插件的形式实现上述过程的原理
作者:我是码财小子,会点编程代码,懂些投资理财;期待你的关注,不要错过我后续的文章更新。