【技术·真相】谈一谈 K8S 的存储(二)

别根据你的愿望来要求现实,应该根据现实来确定你的愿望。

郑重说明:本文适合对游戏开发感兴趣的初级及中级开发和学习者,本人力图将技术用简单的语言表达清楚。鉴于水平有限,能力一般,文章如有错漏之处,还望批评指正,谢谢。

在前面一篇文章【技术·真相】谈一谈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 挂载的三阶段

主要有三个阶段

  1. Provision/Delete(创盘/删盘)
  2. Attach/Detach(挂接/摘除)
  3. 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存储挂载的过程
  • 以存储插件的形式实现上述过程的原理

作者:我是码财小子,会点编程代码,懂些投资理财;期待你的关注,不要错过我后续的文章更新。

相关推荐
吃面不喝汤661 小时前
Flask + Swagger 完整指南:从安装到配置和注释
后端·python·flask
讓丄帝愛伱2 小时前
spring boot启动报错:so that it conforms to the canonical names requirements
java·spring boot·后端
weixin_586062022 小时前
Spring Boot 入门指南
java·spring boot·后端
蜗牛^^O^6 小时前
Docker和K8S
java·docker·kubernetes
凡人的AI工具箱8 小时前
AI教你学Python 第11天 : 局部变量与全局变量
开发语言·人工智能·后端·python
是店小二呀9 小时前
【C++】C++ STL探索:Priority Queue与仿函数的深入解析
开发语言·c++·后端
canonical_entropy9 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
我叫啥都行9 小时前
计算机基础知识复习9.7
运维·服务器·网络·笔记·后端
骅青10 小时前
kubernetes调度2
容器·kubernetes
无名指的等待71210 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端