9. 应用存储和持久化数据卷:存储快照和拓扑调度

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

基本知识

  • 存储快照产生背景:

在使用存储时,为了提高数据操作的容错性,我们通常有需要对线上数据进行 Snapshot,以及能快速 Restore 的能力。另外,当需要对线上数据进行快速的复制以及迁移等动作,如进行环境的复制、数据开发等功能时,都可以通过存储快照来满足需求,而 Kubernetes 中通过 CSI Snapshotter Controller 来实现存储快照的功能。

Kubernetes 支持三种类型的卷插件:in-treeFlexCSI,仅 CSI 驱动程序支持快照(不适用于 in-treeFlex)。要使用 Kubernetes 快照功能,请确保在集群上部署实现快照的 CSI 驱动程序。

  • 存储快照用户接口-Snapshot

Kubernetes 中通过 PVC 以及 PV 的设计体系来简化用户对存储的使用,而存储快照的设计其实是仿照 PVC & PV 体系的设计思想。当用户需要存储快照的功能时,可以通过 VolumeSnapshot 对象来声明,并指定相应的 VolumeSnapshotClass 对象,之后由集群中的相关组件动态生成存储快照以及存储快照对应的对象 VolumeSnapshotContent

VolumeSnapshotContent 是一种快照,从已提供的集群中的 Volume 获取,就像 PV 是集群中的资源一样,它也是集群中的资源。

VolumeSnapshot 是用户对快照 VolumeSnapshotContent 对象的请求,类似于 PVC

VolumeSnapshotClass 是管理员创建用于生成快照的模板, 允许指定属于 VolumeSnapshot 的不同属性,类似于 SC

动态生成 VolumeSnapshotContent 和动态生成 PV 的流程是非常相似的。

User → Cluster Admin → Dynamic Provisioning

存储:PVC → StorageClass → PV

快照:VolumeSnapshot → VolumeSnapshotClass → VolumeSnapshotContent
  • 存储快照用户接口-Restore

有了存储快照之后,如何将快照数据快速恢复过来呢?

PersistentVolumeClaim 的扩展字段 .spec.dataSource 可以指定为 VolumeSnapshot 对象,从而根据 PersistentVolumeClaim 对象生成新的 PersistentVolume 对象,对应的存储数据是从 VolumeSnapshot 关联的 VolumeSnapshotContent 对象中 Restore 出来的。

借助 PVC 对象将其扩展字段 .spec.dataSource 指定为 VolumeSnapshot 对象。这样当 PVC 提交之后,会由集群中的相关组件找到所指向的存储快照数据,然后将存储快照数据恢复到新创建对应的存储及 PV 对象,这样数据就恢复过来了,这就是存储快照的 Restore

  • 拓扑含义:

这里说的拓扑是 Kubernetes 集群中为管理的 Node 划分的一种“位置”关系,可以理解为:两个 Node 只要它们的 labels 中有一个相同的 label 的,那么这两个 Node 就属于同一个拓扑。

常见的有三种:

第一种,在使用云存储服务时经常会遇到 region,也就是地区的概念,拓扑域为 Region 范围,Kubernetes 中常通过 label failure-domain.beta.kubernetes.io/region 来标识。
        这个是为了标识单个 Kubernetes 集群管理的跨 region 的 Nodes 到底属于哪个地区
        
第二种,比较常用的是可用区,也就是 available zone,拓扑域为 Zone 范围,Kubernetes 中常通过 label failure-domain.beta.kubernetes.io/zone 来标识。
        这个是为了标识单个 Kubernetes 集群管理的跨 zone 的 Nodes 到底属于哪个可用区
        
第三种,是 hostname,也就是单机维度,拓扑域为 Node 范围,Kubernetes 中常通过 label kubernetes.io/hostname 来标识

上面三个拓扑是比较常见的,而拓扑还可以自定义。可以定义一个字符串来表示一个拓扑域,这个 key 所对应的 value 其实就是 拓扑域下不同的拓扑位置。

  • 存储拓扑调度产生背景:

kubernetes 中通过 PVC & PV 体系将计算资源和存储资源分开管理了。如果创建出来的 PV 有“访问位置”的限制,即它通过 .spec.nodeAffinity 来指定哪些 Node 可以访问自己,而 kubernetes 中创建 Pod 的流程和创建 PV 的流程可以认为是并行进行的,这样就没办法保证 Pod 最终运行的 Node 能够访问到有“位置限制”的 PV 对应的存储,最终导致 Pod 无法运行。

Kubernetes 中怎样去解决这个问题呢?Kubernetes 中将 PVPVC 的绑定操作和动态创建 PV 的操作做了延迟,延迟到 Pod 调度结果出来之后,再去完成 PVPVC 的绑定操作和动态创建 PV 的操作。

这样做的好处在于:

首先,如果要使用的 PV 是预分配的,如 Local PV,其实使用这块 PV 的 Pod 它对应的 PVC 其实还没有做绑定,就可以通过调度器在调度的过程中,
    结合 Pod 的计算资源需求(如 cpu/mem) 以及 Pod 的 PVC 需求,选择的 Node 既要满足计算资源的需求又要 Pod 使用的 PVC 要能绑定的 PV 的 nodeAffinity 限制

其次,对动态生成 PV 的场景其实就相当于是如果知道 Pod 运行的 Node 之后,就可以根据 Node 上记录的拓扑信息来动态的创建这个 PV,也就是保证新创建出来的 PV 的拓扑位置
    与运行的 Node 所在的拓扑位置是一致的。假设知道 Pod 要运行到可用区 1,那之后创建存储时指定在可用区 1 创建即可

为了实现延迟绑定和延迟创建 PV,需要在 Kubernetes 中的改动涉及到的组件有三个:

首先,PV Controller 需要支持延迟绑定操作

其次,动态生成 PV 的组件,如果 Pod 调度结果出来之后,它要根据 Pod 的拓扑信息去动态地创建 PV

最后,Kube-Scheduler 在为 Pod 选择 Node 时,不仅要考虑 Pod 对计算资源的需求,还要考虑 Pod 对存储的需求。
    也就是根据 Pod 的 PVC 判断当前要选择的 Node ,能否满足能和 PVC 匹配的 PV 的 nodeAffinity;或者是动态生成 PV 的过程,
    它要根据 StorageClass 中指定的拓扑限制来检查当前的 Node 是否满足这个拓扑先知,保证调度器最终选择出来的 Node 满足存储本身对拓扑的限制。

存储快照用法

  • Volume Snapshot/Restore 示例:

csi-hostpath-snapclass VolumeSnapshotClass yaml 文件示例:

apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshotClass
metadata:
  name: csi-hostpath-snapclass
  annotations:
    snapshot.storage.kubernetes.io/is-default-class: "true"
driver: hostpath.csi.k8s.io 
deletionPolicy: Delete
parameters:

文件解析:

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

kind        表示 Kubernetes 资源类型是 VolumeSnapshotClass

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

driver      表示配置 VolumeSnapshot 的 CSI Volume Plugin

deletionPolicy      表示 VolumeSnapshotClass 的删除策略,可以是 Retain 或 Delete

parameters      表示 driver 的参数

每个 VolumeSnapshotClass 都包含 driverdeletionPolicyparameters 字段, 在需要动态配置属于该 VolumeSnapshotClassVolumeSnapshot 时使用。

VolumeSnapshotClass 必须指定 driver 字段,用于确定配置 VolumeSnapshotCSI Volume Pluginparameters 用于指定 CSI Volume Plugin 特有的参数。

VolumeSnapshotClass 具有 deletionPolicy 属性。用于配置当所绑定的 VolumeSnapshot 对象将被删除时,如何处理 VolumeSnapshotContent 对象。

如果删除策略是 Delete,那么底层的存储快照会和 VolumeSnapshotContent 对象 一起删除。如果删除策略是 Retain,那么底层快照和 VolumeSnapshotContent 对象都会被保留。

disk-snapshot VolumeSnapshot yaml 文件示例:

apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshot
metadata:
  name: new-snapshot-test
spec:
  volumeSnapshotClassName: csi-hostpath-snapclass
  source:
    persistentVolumeClaimName: pvc-test
apiVersion: v1      表示 VolumeSnapshot 当前所属的组是 snapshot.storage.k8s.io,版本是 v1beta1

kind        表示 Kubernetes 资源类型是 VolumeSnapshot

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

spec      表示 VolumeSnapshot 的期望状态;.spec.volumeSnapshotClassName 表示指定的 VolumeSnapshotClass 对象;.spec.source 表示指定的快照数据源 PersistentVolumeClaim

restore-pvc PersistentVolumeClaim yaml 文件示例:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restore-pvc
spec:
  dataSource:
    name: new-snapshot-test             #指定 VolumeSnapshot 名称
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: csi-disk

当在 PVC 中指定 .spec.dataSource.name 字段为 VolumeSnapshot 对象名称,即声明 PVC 对象的数据源来源于哪个 VolumeSnapshot 对象,会由集群中的相关组件动态生成新的 PV,这个新的 PV 存储中的数据就来源于 VolumeSnapshot 绑定的 VolumeSnapshotContent 对象。

注意:VolumeSnapshotContentVolumeSnapshotClass 不区分 namespace;VolumeSnapshot 区分 namespace。

  • Static Provisioning PV 限制拓扑示例:

local-storage StorageClass yaml 文件示例:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

local-pvc PersistentVolumeClaim yaml 文件示例:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: local-pvc
spec:
  storageClassName: local-storage
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

local-pv PersistentVolume yaml 文件示例:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /share
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - node1

Local PV 大部分都会先去声明 PV 对象,如果在 PV 对象中通过 nodeAffinity 来限制 PV 只能在单个 Node 上访问,即给这个 PV 加上拓扑限制。

上面会创建一个 no-provisioner StorageClass 对象,它的 provisioner 字段指定的是 no-provisioner,相当于告诉 Kubernetes 它不会去动态创建 PV,它的 VolumeBindingMode 字段指定为 WaitForFirstConsumer,即延迟绑定操作。结合 PersistentVolume 对象的 .spec.storageClassName 字段,目的是告诉 PV Controller 遇到 .spec.storageClassNamelocal-storagePVC 暂不做绑定操作。

而创建的 PV 对象被限制在 node1 上使用,当用户开始提交 PVC 的时候,PV Controller 在看到这个 PVC 时,它会找到相应的 storageClass,发现 volumeBindingMode 是延迟绑定,因此它就不会做任何事情。

之后当真正使用这个 PVCPod 在调度时,当它恰好调度在符合 PV nodeaffinityNode 的上面后,这个 Pod 所使用的 PVC 才会真正地与 PV 做绑定操作,这样就保证 Pod 调度到这台 Node 上之后,PVC 才与 PV 绑定,最终保证的是创建出来的 Pod 能访问这块 Local PV,也就是预分配场景下怎么去满足 PV 的拓扑限制。

  • Dynamic Provisioning PV 限制拓扑示例:

csi-disk StorageClass yaml 文件示例:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: csi-disk
privisioner: diskplugin.csi.alibabacloud.com
parameters:
  regionId: cn-hangzhou
  fsType: ext4
  type: cloud_ssd
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
  - key: topology.diskplugin.csi.alibabacloud.com/zone
    values:
    - cn-hangzhou-d
reclaimPolicy: Delete

disk-pvc PersistentVolumeClaim yaml 文件示例:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: disk-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: csi-disk

当该 PVC 对象被创建之后,由于对应 StorageClassvolumeBindingMode 字段为 WaitForFirstConsumer,因此并不会马上动态生成 PV 对象,而是要等到使用该 PVC 对象的第一个 Pod 调度结果出来之后,而且 Kube-Scheduler 在调度 Pod 的时候会去选择满足 StorageClass.allowedTopologies 中指定的拓扑限制的 Nodes

拓扑限制是可用区的级别,有两层含义:

第一层含义:在动态创建 PV 时,创建出来的 PV 必须是在这个可用区可以访问的

第二层含义:因为 StorageClass 声明的是延迟绑定,Kube-Scheduler 在发现使用它的 PVC 正好对应的是该 StorageClass 时,调度 Pod 就要选择位于它指定可用区的 Nodes

总之,就是要从两方面保证,一是动态创建出来的存储要能被这个可用区访问,二是 Kube-Scheduler 在选择 Node 时,要保证存储和使用存储的这个 Pod 它所对应的 Node,它们之间的拓扑域是在同一个拓扑域,用户在写 PVC 文件的时候主要是在 StorageClass 中要做一些拓扑限制。


处理流程

  • Volume Snapshot/Restore 处理流程:

Kubernetes 中对存储的扩展功能都是推荐通过 CSI out-of-tree 的方式来实现的。

CSI 实现存储扩展主要包含两部分:

第一部分:由 Kubernetes 社区推动实现的 CSI Controller 部分,csi-snapshottor controller 以及 csi-provisioner controller 这些主要是通用的 controller 部分

第二部分:由特定的云存储厂商用自身 OpenAPI 实现的不同的 csi-plugin 部分,即存储的 driver 部分

两部分组件通过 unix domain socket 通信连接到一起。有这两部分,才能形成一个真正的存储扩展功能。

当用户提交 VolumeSnapshot 对象之后,会被 csi-snapshottor controller Watch 到。之后它会通过 GPPC 调用到 csi-plugincsi-plugin 通过 OpenAPI 来真正实现存储快照的动作,等存储快照已经生成之后,会返回到 csi-snapshottor controller 中,csi-snapshottor controller 会将存储快照生成的相关信息放到 VolumeSnapshotContent 对象中并将用户提交的 VolumeSnapshot 做 绑定。这个绑定就类似于 PVPVC 的 绑定。

有了存储快照,如何去使用存储快照恢复之前的数据呢?通过声明一个新的 PVC 对象,并且指定它的 dataSourceVolumeSnapshot 对象,当提交 PVC 时会被 csi-provisioner Watch 到,之后会通过 GRPC 去创建存储。这里创建存储还指定了 VolumeSnapshot 的 ID,当去云厂商创建存储时,需要多做一步操作,即将之前的快照数据恢复到新创建的存储中。之后流程返回到 csi-provisioner,它会将新创建的存储的相关信息写到一个新的 PV 对象中,新的 PV 对象被 PV controller Watch 到,它会将用户提交的 PVCPV 做一个绑定,之后 Pod 就可以通过 PVC 来使用 Restore 出来的数据了。这是 Kubernetes 中对存储快照的处理流程。

  • Volume Topology-aware Scheduling 处理流程:
第一步:通过 StorageClass 声明延迟绑定

第二步:找到 Pod 使用的所有 PVC,根据 PVC 找到满足 Pod 计算资源需求和存储资源需求的 Nodes,选出最优选的 Node

第三步:Kube-Scheduler 会更新经过预选和优选之后 Pod 的 Node信息,以及 PVC 和 PV 的 cache 信息

第四步:已经选出 Node 的 Pod,它使用的 PVC 会绑定已存在的 PV 或动态创建的 PV。Kube-Scheduler 会去触发 PV Controller 去做绑定操作,或者是触发 csi-provisioner 去做动态创建流程

猜你喜欢

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