kubernetes中的local persistent volume

栏目: 服务器 · 发布时间: 5年前

内容简介:在kubernetes 1.14版本中,首先:kubernetes提供了一套卷插件(volume plugin)标准,使得k8s集群的工作负载可以使用多种块存储和文件存储。大部分磁盘插件都使用了远程存储,这是为了让持久化的数据与计算节点彼此独立,但远程存储通常无法提供本地存储那么强的读写性能。有了LPV 插件,kubernetes负载现在可以用同样的volume api,在容器中使用本地磁盘。

什么是Local Persistent Volumes

在kubernetes 1.14版本中, Local Persistent Volumes (以下简称LPV)已变为正式版本(GA),LPV的概念在1.7中被首次提出(alpha),并在1.10版本中升级到beat版本。现在用户终于可以在生产环境中使用LPV的功能和API了。

首先: Local Persistent Volumes 代表了直接绑定在计算节点上的一块本地磁盘。

kubernetes提供了一套卷插件(volume plugin)标准,使得k8s集群的工作负载可以使用多种块存储和文件存储。大部分磁盘插件都使用了远程存储,这是为了让持久化的数据与计算节点彼此独立,但远程存储通常无法提供本地存储那么强的读写性能。有了LPV 插件,kubernetes负载现在可以用同样的volume api,在容器中使用本地磁盘。

这跟hostPath有什么区别

hostPath是一种volume,可以让pod挂载宿主机上的一个文件或目录(如果挂载路径不存在,则mkdir创建为目录并挂载)。

最大的不同在于调度器能理解磁盘和node的对应关系,一个使用hostPath的pod,当他被重新调度时,很有可能被调度到与原先不同的node上,这就导致pod内数据丢失了。而使用LPV的pod,总会被调度到同一个node上(否则就调度失败)。

如何使用LPV

首先 需要创建StorageClass

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

注意到这里 volumeBindingMode 字段的值是 WaitForFirstConsumer 。这种bindingmode意味着:

kubernetes的pv控制器会将这类pv的binding或provisioning(可以理解为动态create)延迟,直到有一个使用了对应pvc的pod被创建出来且该pod被调度完毕。这时候才会将pv和pvc进行binding,并且这时候pv的选择会结合调度的node和pv的nodeaffinity。

接下来, 提前准备 好的provisioner会动态创建PV。

$ kubectl get pv
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM  STORAGECLASS   REASON      AGE
local-pv-27c0f084   368Gi      RWO            Delete           Available          local-storage              8s
local-pv-3796b049   368Gi      RWO            Delete           Available          local-storage              7s
local-pv-3ddecaea   368Gi      RWO            Delete           Available          local-storage              7s

LPV的详细内容如下:

$ kubectl describe pv local-pv-ce05be60 
Name:        local-pv-ce05be60
Labels:        <none>
Annotations:    pv.kubernetes.io/provisioned-by=local-volume-provisioner-minikube-18f57fb2-a186-11e7-b543-080027d51893
StorageClass:    local-fast
Status:        Available
Claim:        
Reclaim Policy:    Delete
Access Modes:    RWO
Capacity:    1024220Ki
NodeAffinity:
  Required Terms:
      Term 0:  kubernetes.io/hostname in [my-node]
Message:    
Source:
    Type:    LocalVolume (a persistent volume backed by local storage on a node)
    Path:    /mnt/disks/vol1
Events:        <none>

当然,也可以不使用provisioner,而是手动创建PV。但是必须要注意的是,LPV必须要填写nodeAffinity。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  capacity:
    storage: 100Gi
  # volumeMode field requires BlockVolume Alpha feature gate to be enabled.
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - example-node

接下来可以创建各种workload,在workload的模板中生命volumeClaimTemplates。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: local-test
spec:
  serviceName: "local-service"
  replicas: 3
  selector:
    matchLabels:
      app: local-test
  template:
    metadata:
      labels:
        app: local-test
    spec:
      containers:
      - name: test-container
        image: k8s.gcr.io/busybox
        command:
        - "/bin/sh"
        args:
        - "-c"
        - "sleep 100000"
        volumeMounts:
        - name: local-vol
          mountPath: /usr/test-pod
  volumeClaimTemplates:
  - metadata:
      name: local-vol
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "local-storage"
      resources:
        requests:
          storage: 368Gi

注意到这里 volumeClaimTemplates.spec.storageClassNamelocal-storage 即我们一开始创建的storageclass实例的名字。

上面这个statefulset创建后,控制器会为其创建对应的PVC,并bind到某个pv。 同时,调度器在调度该pod时,predicate算法中也会过滤掉“与LPV的affinity”不匹配的node。

如何删除这个pv

一定要按照流程来 , 要不然会删除失败

  • 删除使用这个pv的pod
  • 从node上移除这个磁盘(按照一个pv一块盘)
  • 删除pvc
  • 删除pv

对LPV延迟绑定的代码解读

所有的关键在于 volumeBinder 这个结构 这个继承了接口,包括:

type SchedulerVolumeBinder interface {
    FindPodVolumes(pod *v1.Pod, node *v1.Node) 
    AssumePodVolumes(assumedPod *v1.Pod, nodeName string) 
    BindPodVolumes(assumedPod *v1.Pod) error
    GetBindingsCache() PodBindingCache
}

FindPodVolumes

了解调度器原理的应该知道,调度器的predicate算法,在调度pod时,会逐个node的去进行predicate,以确认这个node是否可以调度。我们称之为预选阶段。

VolumeBindingChecker 是一个检查器,在调度器的算法工厂初始化的最后一步,会向工厂中注册检查算法,这样调度器在进行predicate时,最后一步会执行对volumeBinding的检查。我们看 func (c *VolumeBindingChecker) predicate 方法就能看到,这里面执行了 FindPodVolumes ,并且判断返回的几个值是否为true,或err是否为空:

unboundSatisfied, boundSatisfied, err := c.binder.Binder.FindPodVolumes(pod, node)

boundSatisfied 为false表示pod绑定的pv 与当前计算的node亲和性不过关。

unboundSatisfied 为false表示pod中申明的未bound的pvc,在集群内的pv中找不到可以匹配的。

就这样,调度器会反复去重试调度,反复执行 FindPodVolumes ,直到我们(或者provisoner)创建出了PV,比如这时新建的PV,其nodeAffinity对应到了node A。这次调度,在对node A进行predicate计算时,发现pod中申明的、未bound的pvc,在集群中有合适的pv,且该pv的nodeAffinity就是node A,于是返回的 unboundSatisfied 为 true, 调度器最终找到了一个合适的node。

那么,调度器接下来要对pod执行assume,在对pod assume之前,调度器要先对pod中bind的volume进行assume。见 func (sched *Scheduler) assumeAndBindVolumes(assumed *v1.Pod, host string) error 。这个函数里,我们调用了 volumeBinderAssumePodVolumes 方法。

AssumePodVolumes

assume是假设的意思,顾名思义,这个方法会先在调度器的缓存中,假定pod已经调度到node A上,对缓存中的pv、pvc、binding等资源进行更新,看是否能成功,它会返回一些讯息:

allBound, bindingRequired, err := sched.config.VolumeBinder.Binder.AssumePodVolumes(assumed, host)

allBound 为true表示所有的pv、pvc,在缓存中已经是bind。如果为false,会最终导致本次调度失败。

bindingRequired 为true表示有一些pv需要和pvc bind起来。如果为true,调度器会向 volumeBinderBindQueue 中写入一个用例。这个队列会被一个worker轮询,并进行对应的工作。

什么工作呢? BindPodVolumes

BindPodVolumes

调度器在Run起来的时候,会启动一个协程,反复执行bindVolumesWorker。在这个worker中我们可以看到,他尝试从 volumeBinderBindQueue 中取出任务,进行 BindPodVolumes ,成功则该任务Done,失败则报错重试。

阅读 BindPodVolumes 这个方法,很简单,从缓存中找到对应的pod、pv、pvc等内容,更新到APIserver中。

由于我们在 AssumePodVolumes 中已经更新了缓存,所以这里更新到apiserver的操作,会真正地将pv和pvc bind起来。

之后呢?

在worker中我们看到,如果 BindPodVolumes 成功,依然会构造一个pod调度失败的事件,并更新pod的状态为 PodScheduled ,这么做是为了将pod放回调度队列,让调度器再去调度一次。

我们假设pod中只申明了一个LPV,在刚刚描述的这次 BindPodVolumes 操作中已经在apiserver中对这个LPV,和pod中的pvc进行了bind。那么,下一次调度器调度pod时,在 AssumePodVolumes 时会发现已经 allBound ,调度器会继续后续的操作,最终pod被成功地调度(创建出Binding资源,apiserver将pod的nodeName更新)。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Remote

Remote

Jason Fried、David Heinemeier Hansson / Crown Business / 2013-10-29 / CAD 26.95

The “work from home” phenomenon is thoroughly explored in this illuminating new book from bestselling 37signals founders Fried and Hansson, who point to the surging trend of employees working from hom......一起来看看 《Remote》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具