8. 应用存储和持久化数据卷:核心知识

本文由 CNCF + Alibaba 云原生技术公开课 整理而来

Volumes 介绍

  • Pod 存在的问题:

Kubernetes 集群通过 Deployment 解决了 Pod 的更新、回滚及期望数量维持等问题。但 Pod 是一个逻辑概念,它对应的真正运行的还是容器,容器运行就会存在数据持久化的问题,而 Pod 在 Kubernetes 集群中是允许在各个 Node 之间漂移的,如何解决 Pod 的这两个问题:

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

2. 同一 Pod 中的多个容器如何共享数据?

对于 Pod 数据持久化的这两个问题,Kubernetes 借助 Volume 来解决,Pod Volumes 的常用类型有这些:

1. 本地存储,常用的有 emptyDir 和 hostPath

2. 网络存储,网络存储当前的实现方式有两种:in-tree,这种方式的代码是放在 Kubernetes 代码仓库中的,随着 Kubernetes 对存储类型支持的增多,这种方式会对 Kubernetes 本身的维护和发展带来很大负担;
   out-of-tree,它可以给 Kubernetes 本身解耦,通过抽象接口将不同存储驱动的实现代码剥离于 Kubernetes 代码仓库,各云厂商关于 Kubernetes 的网络存储插件都是基于 out-of-tree 方式实现的,Kubernetes 社区主推这种方式来实现网络存储插件
   
3. Projected Volumes,它其实是将一些配置信息,如前面提到的 Secret、ConfigMap 用 Volumes 形式挂载在容器中,让容器中的程序可以通过 POSIX 接口来访问配置数据
  • Persistent Volume

Pod 中声明的 Volume 的生命周期与 Pod 相同,这样会无法满足这些场景:

1. Pod 销毁重建(如 Deployment 管理的 Pod 升级)

2. 宿主机故障迁移(如 StatefulSet 管理的 Pod 带 远程 Volume 迁移)

3. 多 Pod 共享同一个数据 Volume

4. 数据 Volume snapshot、resize 等功能的扩展实现

使用 Pod Volumes 无法准确实现数据 Volume 的复用和共享,snapshot、resize 等功能的扩展很难实现。如果能将存储和计算分离,使用不同的 Controller 管理存储与计算资源,解耦 PodVolume 的生命周期关联,就可以很好地满足以上场景。

因此,Kubernetes 引入了新的资源对象 Persistent Volume(PV) ,它可以将存储和计算分离。当把 Pod 删除之后,Pod 使用的 Persistent Volume 仍然存在,还可以被新建的 Pod 复用。

  • Persistent Volume Claim

有了 Persistent Volume 之后,Kubernetes 又引入了与之对应使用的资源对象 Persistent Volume Claim(PVC),这样做的目的在于:

1. 职责分离,PVC 中只需要声明自己需要存储的 size、access mode等业务真正关心的存储需求,PV 和其对应的后端存储信息则由集群管理员同一运维和管控,安全访问策略更容易控制

2. PVC 简化了用户对存储的需求,PVC 是用户对 PV 资源的请求,PV 才是存储的实际信息的承载体。通过 Controller Manager 中的 PersistentVolumeController 将 PVC 与 合适的 PV 绑定到一起,从而满足用户对存储的实际需求

3. PVC 可以理解为面向对象编程中抽象出来的接口,PV 是接口对应的实现

用户在使用 PV 时其实是通过 PVC 来完成的,通常用户在使用存储的时候,只需要声明所需的存储大小及访问模式,用户并不需要关心存储相关的实现细节。访问模式有 3 种:

ReadWriteOnce – 单节点读写

ReadOnlyMany – 多节点只读

ReadWriteMany – 多节点读写

通过 PVPVC,将用户需求和实现细节解耦,用户只需要通过 PVC 声明自己的存储需求,PV 由集群管理员统一管控,PV 这个资源对象的产生有 Static Volume ProvisioningDynamic Volume Provisioning 两种方式。

  • Static Volume Provisioning

静态供给:由集群管理员事先去规划这个集群中的用户会怎样使用存储,他会预先创建一些 PV,然后用户再提交自己的 PVC,Kubernetes 内部相关组件会把 PVCPV 绑定,之后用户在通过 Pod 使用存储时就可以通过 PVC 找到对应的 PV

可以看到,静态方式需要集群管理员预分配,但预分配很难预测用户真实需求。例如,用户只申请了 20G 的存储需求,但实际上分配了 50G,这就会造成存储资源的浪费;还有可能预先分配的 PV 无法满足用户的存储需求,这就是更无法接受的结果。另外,如果集群规模很大,对存储资源的需求就会非常频繁,此时如果依靠集群管理员预分配,难免会导致需求无法满足的情况,这显然并不可取。

  • Dynamic Volume Provisioning

动态供给:集群管理员并不会预先创建 PV,他会写一个模板文件,这个模板文件用来表示创建某一类型存储(块存储、文件存储等)所需的一些参数,这些参数与存储实现有关。用户只需要提交自己的存储需求,并在 PVC 文件中指定要使用的存储模板(StorageClass)即可。

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


Volumes 用法

下面简单介绍 Pod Volumes 的用法。

  • Pod Volumes 语法:

test-pod Pod yaml 文件示例:

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: container-1
    image: ubuntu:18.04
    volumeMounts:
    - name: cache-volume
      mountPath: /cache
      subPath: cache1
    - name: hostpath-volume
      mountPath: /data
      readOnly: true
  - name: container-2
    image: ubuntu:18.04
    volumeMounts:
    - name: cache-volume
      mountPath: /cache
      subPath: cache2
  volumes:
  - name: cache-volume
    emptyDir: {
    
    }
  - name: hostpath-volume
    hostPath:
      path: /tmp/data

文件解析:

.spec.volumes  声明 Pod 的 Volumes 信息

.spec.containers.volumeMounts  声明 Containers 如何使用 Pod 的 Volumes

.spec.containers.volumeMounts.mountPath  声明 Pod 的 Volumes 在 Containers 内部的挂载路径

.spec.containers.volumeMounts.subPath  声明多个 Container 在共享同一个 Volume 时数据存储的子路径

.spec.containers.volumeMounts.readOnly  声明该 Volume 为只读挂载

emptyDirhostPath 都是本地存储。emptyDir 是在 Pod 创建的过程中临时创建的一个目录,这个目录随着 Pod 删除而被删除,里面的数据会被清空掉;hostPath 顾名思义,就是宿主机上的一个路径,在 Pod 删除之后,这个目录还是存在的,它的数据也不会被丢失。

  • PV 语法:

nas-csi-pv PersistentVolume yaml 文件示例:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nas-csi-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPloicy: Retain
  csi:
    driver: nasplugin.csi.alibabacloud.com
    volumeHandle: data-id
    volumeAttributes:
      host: "xxx.cn-hangzhou.nas.aliyuncs.com"
      path: "/k8s"
      vers: "4.0"

文件解析:

apiVersion: v1     表示 PersistentVolume 的 API 版本是 v1

kind        表示 Kubernetes 资源类型是 PersistentVolume

metadata    表示 PersistentVolume 的元数据,元数据通常包含 name、labels、annotations 等

spec    表示 PersistentVolume 的期望状态,.spec.capacity.storage 表示期望的 PersistentVolume 容量大小;.spec.accessModes 表示 PersistentVolume 的访问模式;
        .spec.csi 表示 Container Storage Interface,云厂商的 CSI 各不相同,.spec.csi.driver 需要提前在 Node 上部署

nas-pvc PersistentVolumeClaim yaml 文件示例:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nas-pvc
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

文件解析:

apiVersion: v1     表示 PersistentVolumeClaim 的 API 版本是 v1

kind        表示 Kubernetes 资源类型是 PersistentVolumeClaim

metadata    表示 PersistentVolumeClaim 的元数据,元数据通常包含 name、namespace、labels、annotations 等

spec    表示 PersistentVolumeClaim 的期望状态,.spec.accessModes 表示需要的 PersistentVolume 的访问模式;.spec.resources.requests.storage 表示需要的 PersistentVolume 容量大小

PVC 期望的状态如果有 PV 能够满足,则 Kubernetes 内部相关组件会把 PVCPV 绑定。 Pod yaml 文件中使用上面的 PVC

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80
    volumeMounts:
    - name: nginx-pvc
      mountPath: /data
  volumes:
  - name: nginx-pvc
    persistentVolumeClaim:
      claimName: nas-pvc                #指定 PVC 名称
  • SC 语法:

SC 就是动态创建 PV 的模板,为创建 PV 对象提供必要的参数。

alicloud-nas-subpath-public StorageClass yaml 文件示例:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: alicloud-nas-subpath-public
provisioner: nasplugin.csi.alibabacloud.com
mountOptions:
- nolock,tcp,noresvport
- vers=4
parameters:
  volumeAs: subpath
  server: "xxxxxx.cn-hangzhou.nas.aliyuncs.com:/"
reclaimPolicy: Retain

文件解析:

apiVersion: v1     表示 StorageClass 当前所属的组是 storage.k8s.io,版本是 v1

kind        表示 Kubernetes 资源类型是 StorageClass

metadata    表示 StorageClass 的元数据,元数据通常包含 name、labels、annotations 等

provisioner    表示 StorageClass 的插件名称

mountOptions    表示插件的挂载选项

parameters      表示插件的参数

reclaimPolicy   表示 StorageClass 动态分配的 PV 的回收策略

每个StorageClass 都包含 provisionerparametersreclaimPolicy字段,这些字段会在 StorageClass 动态分配 PersistentVolume 时会使用到。

nas-pvc PersistentVolumeClaim yaml 文件示例:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nas-pvc
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  sotrageClassName: alicloud-nas-subpath-public

文件解析:

apiVersion: v1     表示 PersistentVolumeClaim 的 API 版本是 v1

kind        表示 Kubernetes 资源类型是 PersistentVolumeClaim

metadata    表示 PersistentVolumeClaim 的元数据,元数据通常包含 name、namespace、labels、annotations 等

spec    表示 PersistentVolumeClaim 的期望状态,.spec.accessModes 表示需要的 PersistentVolume 的访问模式;.spec.resources.requests.storage 表示需要的 PersistentVolume 容量大小;.sepc.sotrageClassName 表示 StorageClass 的名称

PVC 期望的状态如果有 PV 能够满足,则 Kubernetes 内部相关组件会把 PVCPV 绑定。 Pod yaml 文件中使用上面的 PVC

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80
    volumeMounts:
    - name: nginx-pvc
      mountPath: /data
  volumes:
  - name: nginx-pvc
    persistentVolumeClaim:
      claimName: nas-pvc                #指定 PVC 名称

在动态创建 PV 时,与静态创建不同之处在于,PVC 需要增加 .sepc.sotrageClassName 字段来指定 StorageClass 的名称,而 Pod 在使用 PVC 时并不需要改变。

注意:PVSC 不区分 namespace;PVC 区分 namespace。

  • PV Spec 重要字段解析:
Capacity:存储容量大小

AccessModes:访问模式

    ReadWriteOnce:只允许单个 Node 读写访问
    
    ReadOnlyMany:允许多个 Node 只读访问
    
    ReadWriteMany:允许多个 Node 读写访问

ReclaimPolicy:回收策略
    
    Recycle:回收(已废弃,不推荐使用)
    
    Delete:删除,PVC 被删除之后,PV 也会被删除
    
    Retain:保留,由管理员手动处理

StorageClassName:StorageClassName 名称

NodeAffinity:Node 亲和度,声明对 Node 的限制

架构设计

  • PVPVC 的处理流程:

CSI 的全称是 Container Storage Interface,它是 Kubernetes 社区对存储插件实现(out of tree)的官方推荐方式。CSI 的实现大体可以分为两部分:

一部分是由 Kubernetes 社区驱动实现的通用的部分,例如 CSI-Provisioner ControllerCSI-Attacher Controller。另外一种是由云厂商实现的,对接云存储厂商的 OpenApi,主要是实现存储真正的 create/delete/mount/unmount 相关操作,对应到 CSI-Controller-ServerCSI-Node-Server

当提交 yaml 文件之后,Kubernetes 内部首先会在集群中生成一个 PVC 对象,然后 PVC 对象会被 CSI-Provisioner Controller watch 到,CSI-Provisioner Controller 会结合 PVC 对象以及 PVC 对象中声明的 StorageClass,通过 GRPC 调用 CSI-Controller-Server,之后到云存储服务这边创建真正的存储,并创建出来 PV 对象。最后,由集群中的 PV ControllerPVCPV 对象做绑定操作, 之后这个 PV 就可以被使用了。

用户在提交 Pod 之后,首先会被调度器调度选中某一个合适的 Node,然后该 Node 上的 Kubelet 在创建 Pod 流程中会先通过 CSI-Node-Server 将前面创建的 PV 挂载到 Pod 可以使用的路径,之后 Kubelet 才开始创建并启动 Pod 中的所有 Container

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

主要分为三个阶段:

  1. Create 阶段:用户提交完 PVC,由 CSI-Provisioner 创建存储,并生成 PV 对象,之后 PV ControllerPVC 及生成的 PV 对象做绑定;

  2. Attach 阶段:之后用户在提交 Pod yaml 时,首先会被调度到某一个合适的 Node,等 Node 被选出来之后,会被 AD Controller watch 到,它会去查找 Pod 中使用了哪些 PV。然后它会生成一个内部的对象叫 VolumeAttachment 对象,从而去触发 CSI-Attacher 去调用 CSI-Controller-Server 去做真正的 Attach 操作,Attach 操作会调到云存储厂商的 OpenAPI;

  3. Mount 阶段:发生在 Kubelet 创建 Pod 的过程中,它会先去做一个 Mount 操作,是为了将已经 Attach 到 Node 上面的那块盘,进一步 Mount 到 Pod 可以使用的一个具体路径,之后 Kubelet 才开始创建并启动容器。


猜你喜欢

转载自blog.csdn.net/miss1181248983/article/details/111144047