应用存储与持久化

Pod Volumes 的常见类型:

  本地存储,常用的有 emptydir/hostpath;

    emptyDir 其实是在 pod 创建的过程中会临时创建的一个目录,这个目录随着 pod 删除也会被删除,里面的数据会被清空掉;hostPath 顾名思义,其实就是宿主机上的一个路径,在 pod 删除之后,这个目录还是存在的,它的数据也不会被丢失。这就是它们两者之间一个细微的差别。

  网络存储,in-tree,它的实现的代码是放在 K8s 代码仓库中的;out-of-tree,通过抽象接口将不同存储的driver实现从k8s代码仓库中剥离。

  Projected Volumes:将一些配置信息,如 secret/configmap 用卷的形式挂载在容器中,让容器中的程序可以通过POSIX接口来访问配置数据;

  PV 与 PVC

为什么需要pv(Persistent Volumes)

  pod 中声明的 volume 生命周期与 pod 是相同的

    镜像升级的过程中,会产生新的 pod并且删除旧的 pod ,那新旧 pod 之间如何复用数据

    pod 迁移导致的卷迁移

    当多个 pod 想共享数据时,Pod Volumes 很难表达这种语义

    对数据卷做一些功能扩展性,如:snapshot、resize 这些功能

  Persistent Volumes ,将存储和计算分离,通过不同的组件来管理存储资源和计算资源,然后解耦 pod 和 Volume 之间生命周期的关联。这样,当把 pod 删除之后,它使用的PV仍然存在,还可以被新建的 pod 复用。

  静态 Provisioning:由集群管理员事先去规划这个集群中的用户会怎样使用存储,它会先预分配一些存储,也就是预先创建一些 PV;然后用户在提交自己的存储需求(也就是 PVC)的时候,K8s 内部相关组件会帮助它把 PVC 和 PV 做绑定;之后用户再通过 pod 去使用存储的时候,就可以通过 PVC 找到相应的 PV,它就可以使用了。

    预分配的问题在于很难预测用户真实需求。

  Dynamic Provisioning:结合 PVC 和 StorageClass 的信息动态,生成用户所需要的存储(PV),将 PVC 和 PV 进行绑定后,pod 就可以使用 PV 了。通过 StorageClass 配置生成存储所需要的存储模板,再结合用户的需求动态创建 PV 对象,做到按需分配,在没有增加用户使用难度的同时也解放了集群管理员的运维工作。

    StorageClass类似于动态创建pv的模板

  重要字段:

    Capacity:这个很好理解,就是存储对象的大小;

    AccessModes:也是用户需要关心的,就是说我使用这个 PV 的方式。它有三种使用方式:一种是单 node 读写访问;第二种是多个 node 只读访问,是常见的一种数据的共享方式;第三种是多个 node 上读写访问。

    tips:用户在提交 PVC 的时候,最重要的两个字段 —— Capacity 和 AccessModes。在提交 PVC 后,k8s 集群中的相关组件是如何去找到合适的 PV 呢?首先它是通过为 PV 建立的 AccessModes 索引找到所有能够满足用户的 PVC 里面的 AccessModes 要求的 PV list,然后根据PVC的 Capacity,StorageClassName, Label Selector 进一步筛选 PV,如果满足条件的 PV 有多个,选择 PV 的 size 最小的,accessmodes 列表最短的 PV,也即最小适合原则。

    ReclaimPolicy:这个就是刚才提到的,我的用户方 PV 的 PVC 在删除之后,我的 PV 应该做如何处理?常见的有三种方式:第一种方式recycle,现在 K8s 中已经不推荐使用了;第二种方式 delete,也就是说 PVC 被删除之后,PV 也会被删除;第三种方式 Retain,就是保留,保留之后,后面这个 PV 需要管理员来手动处理。

    StorageClassName:StorageClassName 这个我们刚才说了,我们动态 Provisioning 时必须指定的一个字段,就是说我们要指定到底用哪一个模板文件来生成 PV ;

    NodeAffinity:就是说我创建出来的 PV,它能被哪些 node 去挂载使用,其实是有限制的。然后通过 NodeAffinity 来声明对node的限制,这样其实对 使用该PV的pod调度也有限制,就是说 pod 必须要调度到这些能访问 PV 的 node 上,才能使用这块 PV。

  pv状态变换:

    在创建 PV 对象后,它会处在短暂的pending 状态;等真正的 PV 创建好之后,它就处在 available 状态。

    available 状态意思就是可以使用的状态,用户在提交 PVC 之后,被 K8s 相关组件做完 bound(即:找到相应的 PV),这个时候 PV 和 PVC 就结合到一起了,此时两者都处在 bound 状态。当用户在使用完 PVC,将其删除后,这个 PV 就处在 released 状态,按照ReclaimPolicy处理。

    当 PV 已经处在 released 状态下,它是没有办法直接回到 available 状态,也就是说接下来无法被一个新的 PVC 去做绑定。如果我们想把已经 released 的 PV 复用,第一种方式:我们可以新建一个 PV 对象,然后把之前的 released 的 PV 的相关字段的信息填到新的 PV 对象里面,这样的话,这个 PV 就可以结合新的 PVC 了;第二种是我们在删除 pod 之后,不要去删除 PVC 对象,这样给 PV 绑定的 PVC 还是存在的,下次 pod 使用的时候,就可以直接通过 PVC 去复用。K8s中的 StatefulSet 管理的 Pod 带存储的迁移就是通过这种方式。

为什么需要pvc(Persistent Volumes Claim)

  通过 PVC 和 PV 的概念,将用户需求和实现细节解耦开,用户只用通过 PVC 声明自己的存储需求。PV是有集群管理员和存储相关团队来统一运维和管控,这样的话,就简化了用户使用存储的方式。可以看到,PV 和 PVC 的设计其实有点像面向对象的接口与实现的关系。用户在使用功能时,只需关心用户接口,不需关心它内部复杂的实现细节。

  K8s 内部对 pvc 对象有保护机制,在删除 pvc 对象时如果发现有 pod 在使用 pvc,pvc 是删除不掉的

  pvc绑定流程:size符合,volumeMode(filesystem/block)符合,label符合,sc符合,acessMode符合,符合上述且size最小者

csi

  csi 的全称是 container storage interface,它是K8s社区后面对存储插件实现(out of tree)的官方推荐方式。csi 的实现大体可以分为两部分:

    第一部分是由k8s社区驱动实现的通用的部分,像我们这张图中的 csi-provisioner和 csi-attacher controller;

    另外一种是由云存储厂商实践的,对接云存储厂商的 OpenApi,主要是实现真正的 create/delete/mount/unmount 存储的相关操作,对应到上图中的csi-controller-server和csi-node-server

  当用户提交 yaml 之后,k8s内部的处理流程。用户在提交 PVC yaml 的时候,首先会在集群中生成一个 PVC 对象,然后 PVC 对象会被 csi-provisioner controller watch到,csi-provisioner 会结合 PVC 对象以及 PVC 对象中声明的 storageClass,通过 GRPC 调用 csi-controller-server,然后,到云存储服务这边去创建真正的存储,并最终创建出来 PV 对象。最后,由集群中的 PV controller 将 PVC 和 PV 对象做 bound 之后,这个 PV 就可以被使用了。

  用户在提交 pod 之后,首先会被调度器调度选中某一个合适的node,之后该 node 上面的 kubelet 在创建 pod 流程中会通过首先 csi-node-server 将我们之前创建的 PV 挂载到我们 pod 可以使用的路径,然后 kubelet 开始  create && start pod 中的所有 container。

  总的来说,有三个阶段:第一个 create 阶段,主要是创建存储;第二个 attach 阶段,就是将那块存储挂载到 node 上面(通常为将存储load到node的/dev下面);第三个 mount 阶段,将对应的存储进一步挂载到 pod 可以使用的路径。这就是我们的 PVC、PV、已经通过CSI实现的卷从创建到使用的完整流程。

    

  

 

posted @ 2023-10-31 15:59  花都八达鸟  阅读(28)  评论(0)    收藏  举报