存储卷的概念

容器中的存储都是临时的,因此Pod重启的时候,内部的数据会发生丢失。实际应用中,我们有些应用是无状态,有些应用则需要保持状态数据,确保Pod重启之后能够读取到之前的状态数据,有些应用则作为集群提供服务。
这三种服务归纳为无状态服务、有状态服务以及有状态的集群服务,其中后面两个存在数据保存与共享的需求,因此就要采用容器外的存储方案。

K8S的存储系统从基础到高级又大致分为三个层次:普通Volume,Persistent Volume 和动态存储供应。

Kubernetes中存储中有四个重要的概念:
Volume、PersistentVolume(PV)、PersistentVolumeClaim(PVC)、StorageClass。
掌握了这四个概念,就掌握了Kubernetes中存储系统的核心。一张图来说明这四者之间的关系。

Kubernetes Storage(Kubernetes 存储)介绍 - 图1

存储卷 Volumes类型

Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。 当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁 持久卷。对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失
卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放 的内容。

使用卷时, 在 .spec.volumes 字段中设置为 Pod 提供的卷,并在 .spec.containers[*].volumeMounts 字段中声明卷在容器中的挂载位置。 容器中的进程看到的是由它们的 Docker 镜像和卷组成的文件系统视图。 Docker 镜像 位于文件系统层次结构的根部。各个卷则挂载在镜像内的指定路径上。

  1. #查看k8s支持的存储类型 ref: https://kubernetes.io/docs/concepts/storage/volumes/
  2. $ kubectl explain pod.spec.volumes
  3. KIND: Pod
  4. VERSION: v1
  5. RESOURCE: volumes <[]Object>
  6. DESCRIPTION:
  7. List of volumes that can be mounted by containers belonging to the pod.
  8. More info: https://kubernetes.io/docs/concepts/storage/volumes
  9. Volume represents a named volume in a pod that may be accessed by any
  10. container in the pod.
  11. FIELDS:
  12. awsElasticBlockStore <Object>
  13. azureDisk <Object>
  14. azureFile <Object>
  15. cephfs <Object>
  16. cinder <Object>
  17. configMap <Object>
  18. csi <Object>
  19. downwardAPI <Object>
  20. emptyDir <Object>
  21. ephemeral <Object>
  22. fc <Object>
  23. flexVolume <Object>
  24. flocker <Object> # (已弃用)
  25. gcePersistentDisk <Object>
  26. gitRepo <Object> # (已弃用)
  27. glusterfs <Object>
  28. hostPath <Object>
  29. iscsi <Object>
  30. name <string> -required-
  31. nfs <Object>
  32. persistentVolumeClaim <Object>
  33. photonPersistentDisk <Object>
  34. portworxVolume <Object>
  35. projected <Object>
  36. quobyte <Object>
  37. rbd <Object>
  38. scaleIO <Object> # (已弃用)
  39. secret <Object>
  40. storageos <Object>
  41. vsphereVolume <Object>

临时存储(emptydir)

emptyDir在Pod被分配到Node上之后创建,并且在Pod运行期间一直存在。初始的时候为一个空文件夹,当Pod从Node中移除时,emptyDir将被永久删除。Container的意外退出并不会导致emptyDir被删除。emptyDir适用于一些临时存放数据的场景。默认情况下,emptyDir存储在Node支持的介质上,不管是磁盘、SSD还是网络存储,也可以设置为Memory。

apiVersion: v1
kind: Pod
metadata:
  name: tomcat-ccb
  namespace: default
  labels:
    app: tomcat
    node: devops-103
spec:
  containers:
  - name: tomcat
    image: docker.io/tomcat
    volumeMounts:
    - name: tomcat-storage
      mountPath: /data/tomcat
    - name: cache-storage
      mountPath: /data/cache
    ports:
    - containerPort: 8080
      protocol: TCP
    env:
      - name: GREETING
        value: "Hello from devops-103"
  volumes:
  - name: tomcat-storage
    hostPath:
      path: /home/es
  - name: cache-storage
    emptyDir: {}


半持久化存储(hostpath)

hostpath

hostPath类型则是映射node文件系统中的文件或者目录到pod里。在使用hostPath类型的存储卷时,也可以设置type字段,支持的类型有文件、目录、File、Socket、CharDevice和BlockDevice。提供了“如果文件或目录不存在,就创建一个”的功能。

通常使用场景为:当运行的容器需要访问Docker内部结构时,如使用hostPath映射/var/lib/docker到容器。

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data
      # this field is optional
      type: Directory

local

local类型作为静态资源被PersistentVolume使用,不支持Dynamic provisioning。与hostPath相比,因为能够通过PersistentVolume的节点亲和策略来进行调度,因此比hostPath类型更加适用。local类型也存在一些问题,如果Node的状态异常,那么local存储将无法访问,从而导致Pod运行状态异常。使用这种类型存储的应用必须能够承受可用性的降低、可能的数据丢失等。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: www
spec:
  capacity:
    storage: 100Mi
  volumeMode: Filesystem
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /home/es
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - devops-102
          - devops-103

更多的内容可以参考本文


持久化存储(pvc,pv,nfs)

PVC和PV的概念

前面提到kubernetes提供那么多存储接口,但是首先kubernetes的各个Node节点能管理这些存储,但是各种存储参数也需要专业的存储工程师才能了解,由此我们的kubernetes管理变的更加复杂的。由此kubernetes提出了PV和PVC的概念,这样开发人员和使用者就不需要关注后端存储是什么,使用什么参数等问题。
如下图:

Kubernetes Storage(Kubernetes 存储)介绍 - 图2

PV介绍

PersistentVolume(PV)是集群中已由管理员配置的一段网络存储。 集群中的资源就像一个节点是一个集群资源。 PV是诸如卷之类的卷插件,但是具有独立于使用PV的任何单个pod的生命周期。 该API对象捕获存储的实现细节,即NFS,iSCSI或云提供商特定的存储系统。
Kubernetes Storage(Kubernetes 存储)介绍 - 图3

PVC 介绍

PersistentVolumeClaim(PVC)是用户存储的请求。PVC的使用逻辑:在pod中定义一个存储卷(该存储卷类型为PVC),定义的时候直接指定大小,pvc必须与对应的pv建立关系,pvc会根据定义去pv申请,而pv是由存储空间创建出来的。

PV和PVC的关联关系

pv和pvc是kubernetes抽象出来的一种存储资源。虽然PersistentVolumeClaims允许用户使用抽象存储资源,但是常见的需求是,用户需要根据不同的需求去创建PV,用于不同的场景。而此时需要集群管理员提供不同需求的PV,而不仅仅是PV的大小和访问模式,但又不需要用户了解这些卷的实现细节。 对于这样的需求,此时可以采用StorageClass资源。这个在前面就已经提到过此方案。

persistentVolumeClaim —>PVC(存储卷创建申请)
当你需要创建一个存储卷时,只需要进行申请对应的存储空间即可使用,这就是PVC。其关联关系如图:

Kubernetes Storage(Kubernetes 存储)介绍 - 图4
在Pod上定义一个PVC,该PVC要关联到当前名称空间的PVC资源,该PVC只是一个申请,PVC需要和PV进行关联。PV属于存储上的一部分存储空间。但是该方案存在的问题是,我们无法知道用户是什么时候去创建Pod,也不知道创建Pod时定义多大的PVC,那么如何实现按需创建呢???

不需要PV层,把所有存储空间抽象出来,这一个抽象层称为存储类,当用户创建PVC需要用到PV时,可以向存储类申请对应的存储空间,存储类会按照需求创建对应的存储空间,这就是PV的动态供给,如图:
Kubernetes Storage(Kubernetes 存储)介绍 - 图5
总结:

k8s要使用存储卷,需要2步:
1、在pod定义volume,并指明关联到哪个存储设备
2、在容器使用volume mount进行挂载

PV和PVC的生命周期


PV是集群中的资源,PVC是对这些资源的请求,也是对资源的索赔检查。 PV和PVC之间的相互作用遵循这个生命周期:

Provisioning(配置)---> Binding(绑定)--->Using(使用)---> Releasing(释放) ---> Recycling(回收)
  • Provisioning

这里有两种PV的提供方式:静态或者动态

静态—>直接固定存储空间:
    集群管理员创建一些 PV。它们携带可供集群用户使用的真实存储的详细信息。 它们存在于Kubernetes API中,可用于消费。

动态—>通过存储类进行动态创建存储空间:
    当管理员创建的静态 PV 都不匹配用户的 PVC 时,集群可能会尝试动态地为 PVC 配置卷。此配置基于 StorageClasses:PVC 必须请求存储类,并且管理员必须已创建并配置该类才能进行动态配置。 要求该类的声明有效地为自己禁用动态配置。

  • Binding

在动态配置的情况下,用户创建或已经创建了具有特定数量的存储请求和特定访问模式的PersistentVolumeClaim。 主机中的控制回路监视新的PVC,找到匹配的PV(如果可能),并将 PVC 和 PV 绑定在一起。 如果为新的PVC动态配置PV,则循环将始终将该PV绑定到PVC。 否则,用户总是至少得到他们要求的内容,但是卷可能超出了要求。 一旦绑定,PersistentVolumeClaim绑定是排他的,不管用于绑定它们的模式。

如果匹配的卷不存在,PVC将保持无限期。 随着匹配卷变得可用,PVC将被绑定。 例如,提供许多50Gi PV的集群将不匹配要求100Gi的PVC。 当集群中添加100Gi PV时,可以绑定PVC。

  • Using

Pod使用PVC作为卷。 集群检查声明以找到绑定的卷并挂载该卷的卷。 对于支持多种访问模式的卷,用户在将其声明用作pod中的卷时指定所需的模式。

一旦用户有声明并且该声明被绑定,绑定的PV属于用户,只要他们需要它。 用户通过在其Pod的卷块中包含PersistentVolumeClaim来安排Pods并访问其声明的PV。

  • Releasing

当用户完成卷时,他们可以从允许资源回收的API中删除PVC对象。 当声明被删除时,卷被认为是“释放的”,但是它还不能用于另一个声明。 以前的索赔人的数据仍然保留在必须根据政策处理的卷上.

  • Reclaiming

PersistentVolume的回收策略告诉集群在释放其声明后,该卷应该如何处理。 目前,卷可以是保留,回收或删除。 保留可以手动回收资源。 对于那些支持它的卷插件,删除将从Kubernetes中删除PersistentVolume对象,以及删除外部基础架构(如AWS EBS,GCE PD,Azure Disk或Cinder卷)中关联的存储资产。 动态配置的卷始终被删除

  • Recycling

如果受适当的卷插件支持,回收将对卷执行基本的擦除(rm -rf / thevolume / *),并使其再次可用于新的声明。


分布式存储(glusterfs,rbd,cephfs,云存储EBS等)

StorageClass(动态创建PV)

上面介绍的PV和PVC模式是需要先创建好PV,然后定义好PVC进行一对一的Bond,但是如果PVC请求成千上万,那么就需要创建成千上万的PV,对于运维人员来说维护成本很高,Kubernetes提供一种自动创建PV的机制,叫StorageClass,它的作用就是创建PV的模板。
image.png

具体来说,StorageClass会定义一下两部分:

  • PV的属性 ,比如存储的大小、类型等;
  • 创建这种PV需要使用到的存储插件,比如Ceph等;

有了这两部分信息,Kubernetes就能够根据用户提交的PVC,找到对应的StorageClass,然后Kubernetes就会调用 StorageClass声明的存储插件,创建出需要的PV。

利用动态容量供给的功能,就可以实现动态创建PV的能力。我们不需要关心pv的创建,只需要定义好所需要的存储类型,大小等因素。
动态容量供给 Dynamic Volume Provisioning 主要依靠StorageClass。

下面是存储类的一个示例,它创建一个名称为slow的存储类,使用gce供应者

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard

更多云上使用动态创建pv可以参考
https://www.alibabacloud.com/help/zh/doc-detail/134859.htm?spm=a2c63.p38356.b99.305.68428978BIvp7i
https://www.yuque.com/qinxi-cvygi/ires9z/vygqvh

# 查看k8s支持的storageclass类型参数
$ kubectl explain storageclass  #storageclass也是k8s上的资源
KIND:     StorageClass
VERSION:  storage.k8s.io/v1
FIELDS:
   allowVolumeExpansion<boolean>     
   allowedTopologies<[]Object>   
   apiVersion<string>   
   kind<string>     
   metadata<Object>     
   mountOptions<[]string>    #挂载选项
   parameters<map[string]string>  #参数,取决于分配器,可以接受不同的参数。 例如,参数 type 的值 io1 和参数 iopsPerGB 特定于 EBS PV。当参数被省略时,会使用默认值。  
   provisioner<string> -required-  #存储分配器,用来决定使用哪个卷插件分配 PV。该字段必须指定。
   reclaimPolicy<string>   #回收策略,可以是 Delete 或者 Retain。如果 StorageClass 对象被创建时没有指定 reclaimPolicy ,它将默认为 Delete。 
   volumeBindingMode<string>  #卷的绑定模式


StorageClass 中包含 provisioner、parameters 和 reclaimPolicy 字段,当 class 需要动态分配 PersistentVolume 时会使用到。