LiFengMing LiFengMing
首页
云原生
中间件
工具导航
资源导航
  • 分类
  • 标签
  • 归档
关于作者
GitHub (opens new window)

LiFengMing

IT届李哥
首页
云原生
中间件
工具导航
资源导航
  • 分类
  • 标签
  • 归档
关于作者
GitHub (opens new window)
  • 编程语言

  • 问题排查手册

  • 容器编排技术

    • Linux 之 iptables 原理分析
    • K8s常见问题排查技巧
    • K8s应用存储与存储卷
      • 名词解释
      • 组件介绍
      • Volumes
      • 静态、动态存储卷
        • 静态存储卷
        • 动态存储卷
        • PV Controller 介绍
      • CSI介绍
        • K8s 存储架构
        • PV、PVC 以及通过 csi 使用存储流程
      • 操作演示
      • 总结
      • 参考资料
    • k8s scheduler调度器原理以及核心源码分析
    • k8s scheduler调度算法及源码分析
    • k8s scheduler之拓展调度器
  • 云原生
  • 容器编排技术
LiFengMing
2021-09-26
目录

K8s应用存储与存储卷原创

「疑问」

众所周知,K8s 的持久化存储(Persistent Storage)保证了应用数据独立于应用生命周期而存在,那么是否也有如下的一些疑问:

  • 如果 pod 中的某一个容器在运行时异常退出,被 kubelet 重新拉起之后,如何保证之前容器产生的重要数据没有丢失?

  • 如果同一个 pod 中的多个容器想要共享数据,应该如何去做?

  • K8s 内部的存储流程到底是怎样的?

  • PV、PVC、StorageClass、Kubelet、CSI 插件等之间的调用关系又如何?

# 名词解释

  • in-tree:
    • 运行在 k8s 核心组件内部的存储插件;
  • out-of-tree:
    • 独立在 k8s 组件之外运行的存储插件;
    • 主要是通过 gRPC 接口跟 k8s 组件交互,并且 k8s 提供了大量的 SideCar 组件来配合 CSI 插件实现丰富的功能
  • PV:PersistentVolume
    • 持久化存储卷资源,详细定义预挂载存储空间的各项指标;
    • 无 Namesapces 限制,一般由 Admin 创建 PV;
  • PVC:PersistentVolumeClaim
    • 持久化存储卷声明,用户使用的存储接口,对存储细节无感知,属于某个 Namespaces 内;
  • StorageClass:
    • 存储类,创建PV存储模版类,即系统会按照 StorageClass 定义的存储模版创建存储卷(包括真实的存储空间与 PV 对象);
    • 无 Namesapces 限制,一般由 Admin 创建;
    • 每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV
  • CSI:Container Storage Interface
    • 目的是定义行业标准的 “容器存储接口”,使存储供应商(SP)基于 CSI 标准开发的插件可以在不同容器编排(CO)系统中工作,CO 系统包括 Kubernetes、Mesos、Swarm 等。

# 组件介绍

  • PV Controller:
    • 负责 PV/PVC 绑定及周期管理,根据需求进行数据卷的 Provision/Delete 操作;
  • AD Controller:
    • 负责数据卷的 Attach/Detach 操作,将设备挂接到目标节点;
  • Kubelet:
    • Kubelet 是在每个 Node 节点上运行的主要 “节点代理”,功能是 Pod 生命周期管理、容器健康检查、容器监控等;
  • Volume Manager:
    • Kubelet 中的组件,负责管理数据卷的 Mount/Umount 操作(也负责数据卷的 Attach/Detach 操作,需配置kubelet 相关参数开启该特性)、卷设备的格式化等等;
  • Volume Plugins:
    • 存储插件,由存储供应商开发,目的在于扩展各种存储类型的卷管理能力,实现第三方存储的各种操作能力。
    • Volume Plugins 有 in-tree 和 out-of-tree 两种;
  • SideCar 组件
    • External Provioner:
      • External Provioner 是一种 sidecar 容器,作用是调用 Volume Plugins 中的 CreateVolume 和 DeleteVolume 函数来执行 Provision/Delete 操作。
      • 因为 K8s 的 PV 控制器无法直接调用 Volume Plugins 的相关函数,故由 External Provioner 通过 gRPC 来调用;
    • External Attacher:
      • External Attacher 是一种 sidecar 容器,作用是调用 Volume Plugins 中的 ControllerPublishVolume ControllerUnpublishVolume 函数来执行 Attach/Detach 操作。
      • 因为 K8s 的 AD 控制器无法直接调用 Volume Plugins 的相关函数,故由 External Attacher 通过 gRPC 来调用。

# Volumes

疑问中提到的(1)(2)两个场景,其实都可以借助 Volumes 来很好地解决,接下来首先看一下 Pod Volumes 的常见类型:

  1. 本地存储,常用的有 emptydir/hostpath;
  2. 网络存储:网络存储当前的实现方式有两种,一种是 in-tree,另一种是 out-of-tree ;
  3. Projected Volumes:它其实是将一些配置信息,如 secret/configmap 用卷的形式挂载在容器中,让容器中的程序可以通过POSIX接口来访问配置数据;
  4. PV 与 PVC 就是今天要重点介绍的内容。

# 静态、动态存储卷

从上面的讨论我们知道了存储的相关基本概念,也知道了 PVC 是针对应用服务对存储的二次抽象,具有简洁的存储定义接口。而 PV 是具有繁琐存储细节的存储抽象,一般有专门的集群管理人员定义、维护。

根据 PV 的创建方式可以将存储卷分为动态存储和静态存储卷:

  • 静态存储卷:由管理员创建的 PV
  • 动态存储卷:由 Provisioner 插件创建的 PV

# 静态存储卷

静态存储卷

静态 Provisioning:

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

静态产生方式有什么不足呢?

上图可以看到,首先需要集群管理员预分配,预分配其实是很难预测用户真实需求的。

举一个最简单的例子:如果用户需要的是 20G,然而集群管理员在分配的时候可能有 80G 、100G 的,但没有 20G 的,这样就很难满足用户的真实需求,也会造成资源浪费。有没有更好的方式呢?

# 动态存储卷

动态存储卷

动态Provisioning是什么意思呢?

  • 就是说现在集群管理员不预分配 PV,他写了一个创建 PV 的模板文件(StorageClass),这个模板文件是用来表示创建某一类型存储(块存储,文件存储等)所需的一些参数,这些参数是用户不关心的,给存储本身实现有关的参数。

  • 用户只需要提交自身的存储需求,也就是 PV C文件,并在 PVC 中指定使用的存储模板(StorageClass)。

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

下面看一下 StorageClass 模板文件yaml:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
    name: csi-local
provisioner: localplugin.csi.alibabacloud.com
parameters:
    volumeType: LVM
    vgName: vg_longbridge
    fsType: ext4
    lvmType: "striping"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
1
2
3
4
5
6
7
8
9
10
11
12
13

这个模板文件叫 StorageClass,在StorageClass里面,我们需要填的重要信息:

  • provisioner,provisioner 是什么?它其实就是说我当时创建 PV 和对应的存储的时候,应该用哪个存储插件来去创建(其实就是 external-provisioner sidecar container是一个controller去watch pvc/pv对象,当新建一个由storageclass创建pv的pvc(或删除pv对象),该sidecar container会grpc调用存储供应商开发的 csi-plugin CreateVolume(DeleteVolume)方法来实际创建一个外部存储volume,并新建一个pv对象写入k8s api server)。
  • 这些参数是通过k8s创建存储的时候,需要指定的一些细节参数。对于这些参数,用户是不需要关心的,像这里 volumeType、vgName、fsType 和它的类型。
  • reclaimPolicy跟我们刚才讲解的 PV 里的意思是一样的,就是说动态创建出来的这块 PV,当使用方使用结束、Pod 及 PVC 被删除后,这块 PV 应该怎么处理,我们这个地方写的是 delete,意思就是说当使用方 pod 和 PVC 被删除之后,这个 PV 也会被删除掉。

# PV Controller 介绍

此处借鉴 @郡宝 在云原生存储课程 (opens new window)中的流程图。

PV Controller 主要任务:

  • PV、PVC 生命周期管理:创建、删除PV对象;负责PV、PVC的状态迁移
  • 绑定 PVC、PV 对象;一个 PVC 必须与一个 PV 绑定后才能被应用使用,PV-Controller 会根据绑定条件和对象状态对 PV、PVC 进行Bound、Unbound 操作。

PV 控制器中有两个 Worker:

  • ClaimWorker:处理 PVC 的 add /update/delete 相关事件以及 PVC 的状态迁移;
  • VolumeWorker:负责 PV 的状态迁移。

PV 状态迁移(UpdatePVStatus):

在分析 PV状态迁移 之前,我们先了解下 PV 的回收策略。PV 的回收策略向集群阐述了在 PVC 释放卷的时候,应如何进行后续工作,目前可以采用三种策略:

  • 1、保留(Retain):回收策略Retain使得用户可以手动回收资源。当 PersistentVolumeClaim 对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为"已释放(released)"。 由于卷上仍然存在着前一申领人的数据,该卷还不能用于其他申领。 管理员可以通过下面的步骤来手动回收该卷:
  • 2、删除(Delete):对于支持Delete回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施中移除所关联的存储资产。 动态供应的卷会继承其 StorageClass 中设置的回收策略,该策略默认 为Delete。如果插件能够支持,回收策略会执行基础的擦除操作(rm -rf /thevolume/*),这一卷就能被重新申请了。
  • 3、回收(Recycle):警告:回收策略Recycle已被废弃。取而代之的建议方案是使用动态供应。

然后看下卷的阶段(Phase):

  • Available:可用资源,尚未被绑定到 PVC 上
  • Bound:该卷已经被绑定
  • Released:PVC 已经被删除,但该资源尚未被集群回收
  • Failed:该卷的自动回收过程失败。

最后由 PV 状态迁移图可知:

  1. PV 初始状态为 Available,当 PV 与 PVC 绑定后,状态变为 Bound;
  2. 与 PV 绑定的 PVC 删除后,状态变为 Released;
  3. 当 PV 回收策略为 Recycle 或手动删除 PV 的 .Spec.ClaimRef 后,PV 状态变为 Available;
  4. 当 PV 回收策略未知或 Recycle 失败或存储卷删除失败,PV 状态变为 Failed;
  5. 手动删除 PV 的 .Spec.ClaimRef,PV 状态变为 Available。

PVC 状态迁移(UpdatePVCStatus):

首先看下 PVC 的阶段(Phase):

  • Pending:可用资源,尚未被绑定到 PVC 上
  • Bound:该卷已经被绑定
  • Released:PVC 已经被删除,但该资源尚未被集群回收
  • Failed:该卷的自动回收过程失败。

最后由 PVC 状态迁移图可知:

  1. 当集群中不存在满足 PVC 条件的 PV 时,PVC 状态为 Pending。在 PV 与 PVC 绑定后,PVC 状态由 Pending 变为 Bound;

  2. 与 PVC 绑定的 PV 在环境中被删除,PVC 状态变为 Lost;

  3. 再次与一个同名 PV 绑定后,PVC 状态变为 Bound。

# CSI介绍

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

  • 第一部分是由k8s社区驱动实现的通用的部分, csi-provisioner controller 和 csi-attacher controller;
  • 另外一种是由云存储厂商实践的,对接云存储厂商的 OpenApi,主要是实现真正的 create/delete/mount/unmount 存储的相关操作,csi-controller-server和csi-node-server。

k8s-csi

这是阿里云的一张存储概况图,很好的说明了 CSI 插件支持静态存储卷挂载和动态存储卷挂载2种方式,在静态存储卷挂载的方式中,通常需要手动编辑和创建一个PV和PVC进行挂载。当需要PV和PVC数量很大的时候,您可以创建动态存储卷进行挂载。

# K8s 存储架构

# PV、PVC 以及通过 csi 使用存储流程

此处借鉴 @郡宝 在云原生存储课程 (opens new window)中的流程图。

流程如下:

  1. 用户创建了一个包含 PVC 的 Pod,该 PVC 要求使用动态存储卷;

  2. Scheduler 根据 Pod 配置、节点状态、PV 配置等信息,把 Pod 调度到一个合适的 Worker 节点上;

  3. PV 控制器 watch 到该 Pod 使用的 PVC 处于 Pending 状态,于是调用 Volume Plugin(in-tree)创建存储卷,并创建 PV 对象(out-of-tree 由 External Provisioner 来处理);

  4. AD 控制器发现 Pod 和 PVC 处于待挂接状态,于是调用 Volume Plugin 挂接存储设备到目标 Worker 节点上

  5. 在 Worker 节点上,Kubelet 中的 Volume Manager 等待存储设备挂接完成,并通过 Volume Plugin 将设备挂载到全局目录:/var/lib/kubelet/pods/[pod uid]/volumes/kubernetes.io~iscsi/[PV name](以 iscsi 为例);

  6. Kubelet 通过 Docker 启动 Pod 的 Containers,用 bind mount 方式将已挂载到本地全局目录的卷映射到容器 中。

整个流程流程分别三个阶段:

从上图可知: pod 挂载 volume 的整个工作流程。整个流程流程分别三个阶段:Provision/Delete、Attach/Detach、Mount/Unmount,不过不是每个存储方案都会经历这三个阶段,比如 NFS 就没有 Attach/Detach 阶段。

先来看 Provision 阶段,整个过程如上图所示。其中 extenal-provisioner 和 PVController 均 watch PVC 资源。

1.当 PVController watch 到集群中有 PVC 创建时,会判断当前是否有 in-tree plugin 与之相符,如果没有则判断其存储类型为 out-of-tree 类型,于是给 PVC 打上注解 volume.beta.kubernetes.io/storage-provisioner={csi driver name};

2.当 extenal-provisioner watch 到 PVC 的注解 csi driver 与自己的 csi driver 一致时,调用 CSI Controller 的 CreateVolume 接口;

3.当 CSI Controller 的 CreateVolume 接口返回成功时,extenal-provisioner 会在集群中创建对应的 PV;

4.PVController watch 到集群中有 PV 创建时,将 PV 与 PVC 进行绑定。

Attach 阶段是指将 volume 附着到节点上,整个过程如上图所示。

1.ADController 监听到 pod 被调度到某节点,并且使用的是 CSI 类型的 PV,会调用内部的 in-tree CSI 插件的接口,该接口会在集群中创建一个 VolumeAttachment 资源;

2.external-attacher 组件 watch 到有 VolumeAttachment 资源创建出来时,会调用 CSI Controller 的 ControllerPublishVolume 接口

3.当 CSI Controller 的 ControllerPublishVolume 接口调用成功后,external-attacher 将对应的 VolumeAttachment 对象的 Attached 状态设为 true;

4.ADController watch 到 VolumeAttachment 对象的 Attached 状态为 true 时,更新 ADController 内部的状态 ActualStateOfWorld。

最后一步将 volume 挂载到 pod 里的过程涉及到 kubelet。

整个流程简单地说是,对应节点上的 kubelet 在创建 pod 的过程中,会调用 CSI Node 插件,执行 mount 操作。

# 操作演示

查看PVC
kubectl get pvc

查看PV
kubectl get pv

查看storageClass
kubectl get storageClass
1
2
3
4
5
6
7
8

# 总结

这里为大家简单总结一下。

  • K8s Volume是用户Pod保存业务数据的重要接口和手段
  • PVC和PV体系增强了K8s Volumes在多Pod共享/迁移/存储扩展等场景下的能力
  • PV(存储)的不同供给模式(static and dynamic)可以通过多种方式为集群中的Pod供给所需的存储

# 参考资料

  • https://kubernetes.io/zh/docs/concepts/storage/
  • https://help.aliyun.com/document_detail/134722.html
  • https://support.huaweicloud.com/basics-cce/kubernetes_0030.html
  • https://www.v2k8s.com/storage/t/105#3b0d1a
  • https://www.v2k8s.com/kubernetes/t/217
  • https://cloud.tencent.com/developer/article/1847424
#PV#PVC#Volumes#csi#StorageClass
上次更新: 2025/01/19, 23:15:59
K8s常见问题排查技巧
k8s scheduler调度器原理以及核心源码分析

← K8s常见问题排查技巧 k8s scheduler调度器原理以及核心源码分析→

最近更新
01
云原生资源
05-25
02
快速搭建Spring项目
03-27
03
kafka版本迭代说明
03-11
更多文章>
Theme by Vdoing | Copyright © 2018-2025 LiFengMing | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式