k8s篇-应用持久化存储2(存储快照与拓扑调度)

一、存储快照

快照功能:可以对线上的数据进行快速的复制、迁移等,及对重要数据误操作之后进行快速恢复。K8s中通过CSI Snapshotter controller来实现存储快照的功能。

Snapshot原理:创建snapshot过程,与pvc&pv设计类似

 PVC ------------------> StorageClass -------------> PV
 VolumeSnapshot -------> VolumeSnapshotClass ------> VolumeSnapshotContent
|______________________|___________________________|______________________|
|          User        |      Cluster Admin        |   Dynamic Generate   |

当用户需要存储快照的功能时,通过VolumeSnapshot来声明,并指定相应的VolumeSnapshotClass,之后由集群中的相关组件动态生成存储快照,及其对应的VolumeSnapshotContent。

Restore原理:有了snapshot后,如何将快照数据快速恢复过来

                         (1)
.—————————————————.   .---------> VolumeSnapshot ----> VolumeSnapshotContent
| PVC (dataSource)|==='
'—————————————————'   |  (2)
                      '---------> PV
                        restore

先在PVC对象的 spec.dataSource 中指定一个VolumeSnapshot。当提交PVC后,k8s会根据StorageClass定义动态创建一个新PV,然后k8s会找到dataSource所指向的存储快照数据,并将这些快照数据恢复到新PV中。最后Pod再指定这个PVC,就可以使用新PV的快照数据。

1、Volume Snapshot/Restore使用示例

第一步:创建一个VolumeSnapshotClass对象

apiVersion: snapshot.storage.k8s.io/v1alpha1
kind: VolumeSnapshotClass
metadata: 
  name: disk-snapshotclass
snapshotter: diskplugin.csi.alibabacloud.com

snapshotter:指定真正创建存储快照时所使用的Volume插件,这个Volume插件是要提前部署好的

第二步:创建一个VolumeSnapshot

apiVersion: snapshot.storage.k8s.io/v1alpha1
kind: VolumeSnapshot
metadata:
  name: disk-snapshot
spec:
  snapshotClassName: disk-snapshotclass
  source:
    name: disk-pvc
    kind: PersistentVolumeClaim

source:指定要做快照的数据源。提交这个VolumeSnapshot对象后,k8s会找到这个名为disk-pvc的PVC对应的PV存储,对这个PV存储做一次快照。

第三步:用存储快照恢复数据,即将快照数据恢复到新PV上

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restore-pvc
  namespace: simple
spec: 
  dataSource:
    name: disk-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: csi-disk

dataSource:在PVC对象中声明数据源来自于哪个VolumeSnapshot。提交这个PVC后,会动态生成一个新PV,这个新PV存储中的数据就来源于这个名为disk-snapshot的snapshot之前所做的快照。

 2、Volume Snapshot/Restore处理流程

【配图-待补充】

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

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

CSI Controller:由K8s社区推动实现,也就是csi-snapshottor/csi-provisioner controller,主要是通用的controller部分。

CSI Plugin:由特定的云存储厂商用自身OpenAPI实现,也叫存储的driver部分。

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

当用户提交VolumeSnapshot对象之后,会被csi-snapshottor controller watch到。之后它会通过GPPC调用到csi-plugin,csi-plugin通过 OpenAPI来真正实现存储快照的动作,等存储快照已经生成之后,会返回到 csi-snapshottor controller 中,csi-snapshottor controller 会将存储快照生成的相关信息放到 VolumeSnapshotContent 对象中并将用户提交的 VolumeSnapshot 做 bound。这个bound其实就有点类似PV和 PVC的bound一样。

使用存储快照恢复之前的数据。通过声明一个新的PVC对象,并指定他的 dataSource 为 Snapshot 对象,当提交 PVC 的时候会被 csi-provisioner watch 到,之后会通过 GRPC 去创建存储。这里创建存储有点不同,就是它里面还指定了 Snapshot 的 ID,当去云厂商创建存储时,需要多做一步操作,即将之前的快照数据恢复到新创建的存储中。之后流程返回到 csi-provisioner,它会将新创建的存储的相关信息写到一个新的 PV 对象中,新的 PV 对象被 PV controller watch 到它会将用户提交的 PVC 与 PV 做一个 bound,之后 pod 就可以通过 PVC 来使用 Restore 出来的数据了。这是 K8s 中对存储快照的处理流程。

二、Topolopy(存储拓扑调度)

拓扑是指对K8s集群中nodes的“位置”关系一种人为划分规则,通过在node的labels中设置,以标识自己属于哪个拓扑域。

常用Nodes Labels标识:

以kubernetes.io/hostname来标识,单机维度,拓扑域为node范围;

以failure-domain.beta.kubernetes.io/region来标识,在使用云存储服务时,会遇到的region概念,即地区,拓扑域为region范围;

以failure-domain.beta.kubernetes.io/zone来标识,可用区,拓扑域为zone范围;

也可自定义一个 key:value 来定义一个具体的拓扑域,可从机架维度划分为一个拓扑域,如rack:rack1,将同一个机架(rack)上的服务器(nodes)划分为一组(一个具体的拓扑域rack1),以区别另一个rack上的一组机器的“位置”关系。
 

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

k8s通过PV&PVC的设计将存储资源独立了出来,即通过不同Controllers来管理存储和计算资源。

但如果创建出来的PV有 “访问位置(.spec.nodeffinity)” 的限制,也就是这个PV指定了在特定的node上才能被访问,且由于Pod与PV创建流程的分离(比如提交PVC时就会动态创建PV,或预先手动创建好静态PV,之后再创建Pod),那当使用这个PV的Pod被创建出来时,就有可能会被调度到其他node上,而不是该PV所在的Node,也就是Pod与PV不在同一个node上,最终导致Pod无法正常运行起来。

场景1:使用Local-PV时,Pod与PV不在同一个节点上,导致Pod无法使用该PV

【配图-待补充】

正常情况是,要先创建好PV,有了PV后才能启动POD。假设有两个节点,分别是node1和node2,当用户提交PVC后,PV Controller可能会将该PVC绑定到node2上的本地PV,但等到创建Pod时(会用到这个PV),这个POD却有可能被调度到node1上,结果就是Pod与对应的本地PV不在同一个节点上,导致这个Pod启动时没法使用这个PV存储。

场景2:使用阿里云云盘时,Pod与PV不在同一个可用区内,导致Pod无法使用该PV

【配图-待补充】

如果搭建的K8s集群管理的nodes分布在单个区域多个可用区内,在创建动态PV时,创建出来的PV属于zone2,但使用该PV的Pod,在创建时却被调度到zone1上,那也会导致这个Pod无法使用这个PV存储。当前阿里云云盘还不支持跨可用区使用。

针对以上两种场景,可通过延迟绑定机制来解决:将PV和PVC的 binding操作 和 动态创建PV的操作 做了delay,delay到pod调度结果出来之后,再去做这两个操作。

通过delay机制,原本实时发生的PVC和PV的绑定过程,就被延迟到了Pod第一次调度时进行,scheduler在综合考虑所有的调度规则后(包括每个PV所在的节点位置),再来统一决定这个Pod声明的PVC,到底应该跟哪个节点的Local PV进行绑定。

在调度过程中,会结合pod的计算资源需求(如cpu/mem)及pod的PVC需求,来选择node。当知道pod运行的node之后,就可根据node上记录的拓扑信息来动态的创建这个PV,也就是保证新创建出来的PV的拓扑位置与Pod运行的node所在的拓扑位置是一致的。
 

这个过程会涉及到的3个组件:

一是PV Controller要支持延迟Binding这个操作;

二是动态生成PV的组件,如果pod调度结果出来之后,它要根据pod的拓扑信息来去动态的创建PV。

三是kube-scheduler组件,它在为pod选择node时,它不仅要考虑pod对CPU/MEM的计算资源的需求,它还要考虑这个pod对存储的需求,也就是根据Pod的PVC,它要先去看一下当前要选择的 node,能否满足能和这个PVC能匹配的PV的nodeAffinity;或者是动态生成PV的过程,它要根据StorageClass中指定的拓扑限制来check当前的 node 是不是满足这个拓扑限制,这样就能保证调度器最终选择出来的node就能满足存储本身对拓扑的限制。

1、静态PV的拓扑限制 - Local PV

第一步:创建一个no-provisioner类型的StorageClass对象(不会动态创建PV)

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

provisioner:需要指定no-provisioner,相当于告诉k8s不要去动态创建PV。
volumeBindingMode:需指定WaitForFirstConsumer,表示延迟绑定。

第二步:创建一个PVC

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

第三步:创建一个Local PV(通过nodeAffinity字段要做一些拓扑限制)

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

nodeAffinity:限制该PV只能在node1上被使用
matchExpressions.key:拓扑的key用kubernetes.io/hostname来做标记,也就是只能在node1访问

大多数情况下,Local PV都是通过静态创建的方式。因为Local PV只能是本地访问,所以在声明PV对象时,要通过nodeAffinity来限制这个 PV只能在指定的node上访问,也就是给这个PV加上拓扑限制。


storageClassname:指定一个storageClass名称,主要用到它的VolumeBindingMode字段,值为WaitForFirstConsumer,表示要延迟绑定该PV。

虽是通过静态方式创建PV,但还需要手动指定storageClassname的原因是,Local PV需要用到延迟绑定机制,才能正常被Pod使用,而延迟机制是需要用到storageClass对象中的VolumeBindingMode字段来设置启用。

所以,当用户提交PVC后,即使集群中有相关的PV能被该PVC匹配到,也暂时不能做匹配,也就是PV controller不能马上去做binding操作,此时就要通过一种手段来告诉PV controller,什么情况下是不能立即做binding,这里storageClass就是为了起到这个作用而存在的。

它的provisioner要指定为no-provisioner,相当于告诉k8s不要去动态创建PV,它主要用到storageclass的VolumeBindingMode字段,值为WaitForFirstConsumer,表示要延迟绑定PV。
当用户提交PVC后, pv controller看到这个PVC,会找到相应的storageClass,发现这个BindingMode是延迟绑定,它就不会做任何事情。

而等到真正使用这个PVC的POD,在被调度时,并调度在符合pv nodeaffinity的node的上后,这个pod所使用的PVC才会真正与PV做绑定,从而保证最终创建出来的pod能访问这块Local PV。

2、动态PV的拓扑限制

第一步:创建一个StorageClass对象(通过allowedTopologies字段要做一些拓扑限制)

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

volumeBindingMode:需指定WaitForFirstConsumer,表示延迟绑定。
allowedTopologies:拓扑限制是可用区级别,意思就是动态创建出来的PV有拓扑位置的限制,该PV能被cn-hangzhou-d这个可用区访问。然后等到调度Pod时,发现Pod声明的PVC正好对应这个storageClass,调度器就会选择位于该可用区的node来调度,这样Pod与PV都会落在同一个可用区的node上,它们在同一个拓扑域。

第二步:创建一个PVC

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

当该PVC被创建后,由于对应StorageClass的BindingMode为WaitForFirstConsumer,所以并不会马上动态创建出PV对象。而是要等到使用该PVC对象的第一个POD调度结果出来之后,而且kube-scheduler在调度POD时,选择满足StorageClass.allowedTopologied中指定的拓扑限制的Nodes。

3、存储拓扑调度的处理流程

【配图-待补充】

第一步:首先PV Controller对需要Delay Binding(通过StorageClass设置)的PVC暂不做任何处理;
第二步: Scheduler为pod选择一个node。

正常选择Pod的流程:

首先用户提交完pod之后,会被调度器watch到,它就会去首先做预选,预选就是说它会将集群中的所有node都来与这个pod它需要的资源做匹配;

如果匹配上,就相当于这个node可使用,当然可能不止一个node,最终会选出来一批node;
然后再经过第二个阶段优选,优选就相当于要对这些node做一个打分的过程,通过打分找到最匹配的一个node;

之后调度器将调度结果写到pod里面的 spec.nodeName 字段里面,然后会被相应的node上面的 kubelet watch到,最后就开始创建pod的整个流程。

加上PVC后的选择流程:

先就要找到pod中使用的所有PVC,找到已经bound的PVC,及需要延迟绑定的这些PVC;
对于已经bound的PVC,要check一下它对应的PV里面的nodeAffinity与当前node的拓扑是否匹配 。如果不匹配,就说明这个node不能被调度。如果匹配,继续往下走,就要去看一下需要延迟绑定的 PVC;

对于需要延迟绑定的PVC。先去获取集群中存量的PV,满足PVC需求的,先把它全部捞出来,然后再将它们一一与当前的node labels上的拓扑做匹配,如果它们(存量的 PV)都不匹配,那就说明当前的存量的PV不能满足需求,就要进一步去看一下,如果要动态创建PV,当前node是否满足拓扑限制,也就是还要进一步去 check StorageClass 中的拓扑限制,如果 StorageClass 中声明的拓扑限制与当前的 node 上面已经有的 labels 里面的拓扑是相匹配的,那其实这个 node 就可以使用,如果不匹配,说明该 node 就不能被调度。

经过这上面步骤之后,就找到了所有即满足pod计算资源需求又满足pod存储资源需求的所有nodes。

第三步:当node选出来之后,调度器内部做的一个优化。就是更新经过预选和优选之后,pod的node信息,以及PV和PVC在scheduler中做的一些cache信息。

第四步:已经选择出来node的Pod,不管其使用的PVC是要binding已经存在的PV,还是要做动态创建PV,这时就可以开始做。由调度器来触发,调度器它就会去更新 PVC 对象和 PV 对象里面的相关信息,然后去触发 PV controller 去做 binding 操作,或者是由 csi-provisioner 去做动态创建流程。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值