06.存储

K8s存储

Kubernetes集群中,虽然无状态的服务非常常见,但是在实际的生产中仍然会需要在集群中部署一些有状态的节点,比如一些存储中间件、消息队列等等。在K8s中通过Pod的概念将一组具有超亲密关系的容器组合到了一起形成了一个服务实例,为了保证一个Pod中某一个容器异常退出,被kubelet重建拉起旧容器产生的重要数据不丢,以及同一个Pod的多个容器可以共享数据,K8sPod层面定义了存储卷,不仅能够解决Container中文件的临时性问题,也能够让同一个Pod中的多个Container共享文件。

Docker Volume是针对容器层面的存储抽象,其Volume的生命周期是通过Docker Engine来维护的。K8s Volume则是应用层面的存储抽象,通过CRI接口解耦和Docker Engine的耦合关系,所以K8sVolume的生命周期理所当然由K8s来管理,因此K8s本身也有自己的volume plugin扩展机制。K8s中的Volume主要分为以下几类:

  • 本地存储:emptydir/hostpath等,主要使用Pod运行的node上的本地存储

  • 网络存储:in-tree(内置): awsElasticBlockStore/gcePersistentDisk/nfs等,存储插件的实现代码是放在K8s代码仓库中的;out-of-tree(外置): flexvolume/CSI等网络存储inline volume plugins,存储插件单独实现,特别是CSIVolume扩展机制的核心发展方向。

  • Projected Volume: Secret/ConfigMap/downwardAPI/serviceAccountToken,将K8s集群中的一些配置信息以volume的方式挂载到Pod的容器中,也即应用可以通过POSIX接口来访问这些对象中的数据。

  • PersistentVolumeClaimPersistentVolume体系,K8s中将存储资源与计算资源分开管理的设计体系。

卷(Volume)

卷(Volume)其实是一个比较特定的概念,它并不是一个持久化存储,可能会随着Pod的删除而删除,常见的卷就包括EmptyDir、HostPath、ConfigMapSecret,这些卷与所属的Pod具有相同的生命周期,它们可以通过如下的方式挂载到Pod下面的某一个目录中:

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      volumeMounts:
        - name: cache-volume
          mountPath: /cache
        - name: test-volume
          mountPath: /hostpath
        - name: config-volume
          mountPath: /data/configmap
        - name: special-volume
          mountPath: /data/secret
  volumes:
    - name: cache-volume
      emptyDir: {}
    - name: hostpath-volume
      hostPath:
        path: /data/hostpath
        type: Directory
    - name: config-volume
      configMap:
        name: special-config
    - name: secret-volume
      secret:
        secretName: secret-config

需要注意的是,当我们将ConfigMap或者Secret包装成卷并挂载到某个目录时,我们其实创建了一些新的Volume,这些Volume并不是Kubernetes中的对象,它们只存在于当前Pod中,随着Pod的删除而删除,但是需要注意的是这些临时卷的删除并不会导致相关ConfigMap或者Secret对象的删除。

从上面我们其实可以看出Volume没有办法脱离Pod而生存,它与Pod拥有完全相同的生命周期,而且它们也不是Kubernetes对象,所以Volume的主要作用还是用于跨节点或者容器对数据进行同步和共享。

持久卷

如果我们希望将数据进行持久化存储,可以引入PersistentVolume(PV),以将Pod和卷的生命周期分离。我们可以将PersistentVolume理解为集群中资源的一种,它与集群中的节点Node有些相似,PVKubernete集群提供了一个如何提供并且使用存储的抽象,与它一起被引入的另一个对象就是PersistentVolumeClaim(PVC),这两个对象之间的关系与节点和Pod之间的关系差不多。

因为PVC允许用户消耗抽象的存储资源,所以用户需要不同类型、属性和性能的PV就是一个比较常见的需求了,在这时我们可以通过StorageClass来提供不同种类的PV资源,上层用户就可以直接使用系统管理员提供好的存储类型。

访问模式

Kubernetes中的PV提供三种不同的访问模式,分别是 ReadWriteOnceReadOnlyManyReadWriteMany,这三种模式的含义和用法我们可以通过它们的名字推测出来:

  • ReadWriteOnce 表示当前卷可以被一个节点使用读写模式挂载;
  • ReadOnlyMany 表示当前卷可以被多个节点使用只读模式挂载;
  • ReadWriteMany 表示当前卷可以被多个节点使用读写模式挂载;

不同的卷插件对于访问模式其实有着不同的支持,AWS上的 AWSElasticBlockStoreGCP上的 GCEPersistentDisk 就只支持 ReadWriteOnce 方式的挂载,不能同时挂载到多个节点上,但是 CephFS 就同时支持这三种访问模式。

回收策略

当某个服务使用完某一个卷之后,它们会从apiserver中删除PVC对象,这时Kubernetes就需要对卷进行回收(Reclaim,持久卷也同样包含三种不同的回收策略,这三种回收策略会指导Kubernetes选择不同的方式对使用过的卷进行处理。

  • 第一种回收策略就是保留(Retain)PV中的数据,如果希望PV能够被重新使用,系统管理员需要删除被使用的PersistentVolume对象并手动清除存储和相关存储上的数据。

  • 另一种常见的回收策略就是删除(Delete,当PVC被使用者删除之后,如果当前卷支持删除的回收策略,那么PV和相关的存储会被自动删除,如果当前PV上的数据确实不再需要,那么将回收策略设置成Delete能够节省手动处理的时间并快速释放无用的资源。

存储供应

Kubernetes集群中包含了很多的PV资源,而PV资源有两种供应的方式,一种是静态的,另一种是动态的,静态存储供应要求集群的管理员预先创建一定数量的PV,然后使用者通过PVC的方式对PV资源的使用进行声明和申请;但是当系统管理员创建的PV对象不能满足使用者的需求时,就会进入动态存储供应的逻辑,供应的方式是基于集群中的StorageClass对象,当然这种动态供应的方式也可以通过配置进行关闭。

Links