• 官方文档:https://kubernetes.io/zh/docs/concepts/storage/volumes/
  • 应用程序在处理请求时,可根据其对当前请求的处理是否受影响于此前的请求,将应用划分为有状态应用和无状态应用两种。微幅体系中,各种应用均被拆分成了众多微服务或更小的应用模块,因此往往存在为数不少的有状态应用,当然,也会存在数量可观的无状态应用。而对于有状态应用来说,数据持久化几乎是必然之需。
  • K8S系统提供的存储卷属于Pod资源级别,共享于Pod内的所有容器,可用于在容器的文件系统之外存储应用程序的相关数据,甚至还可以独立于Pod的生命周期之外实现数据持久化

存储卷概述

  • 容器中的文件在磁盘上是临时存放的,因此如果容器中运行着一些需要持久化数据的应用(如数据库服务或其他服务等)就会带来一些问题,因为如果容器崩溃了,kubelet会重建,容器中的文件将会丢失,因为容器必须以干净的状态重建。其次,如果一个Pod中同时运行多个容器,那么久需要在这些容器直接共享文件。K8S抽象出volumes对象来解决这两个问题
  • Pod本身具有生命周期,故其内部运行的容器及其相关数据自身均无法持久存在。只要Pod里面的容器出现问题,kubelet就会将其重建,容器内的数据会随着Pod消亡而自动消失。Docker支持配置容器使用存储卷将数据持久存储于容器自身文件系统之外的存储空间中,它们可以是节点文件系统或网络文件系统之上的存储空间。相应的,K8S也支持类似的存储功能,不过,其存储卷是与Pod资源绑定并非与容器绑定。简单来说,存储卷是定义在Pod资源之上,可被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久能力则取决于存储卷自身是否支持持久化机制。Pod、容器与存储卷的关系图如下
    存储卷与数据持久化(一)之存储卷概述 - 图1

K8S支持的存储卷类型

  • K8S支持非常丰富的存储卷类型,包括本地存储(节点)和网络存储系统中的诸多存储机制,甚至还支持Secret和ConfigMap这样的特殊存储资源。对于Pod来说,卷类型主要是为关联先关的存储系统时提供相关的配置参数,例如,关联节点本地的存储目录与关联GlusterFS存储系统所需要的配置参数差异巨大,因此指定存储卷类型时也就限定了其关联到的后端存储设备
  • 目前,K8S支持的存储类型包含以下这些类型(也可以使用deployment.spec.template.spec.volumes)查看

  • awsElasticBlockStore

  • azureDisk
  • azureFile
  • cephfs
  • cinder
  • configMap
  • csi
  • downwardAPI
  • emptyDir
  • fc
  • flexVolume
  • flocker
  • gcePersistentDisk
  • gitRepo
  • glusterfs
  • hostPath
  • iscsi
  • nfs
  • persistentVolumeClaim
  • photonPersistentDisk
  • portworxVolume
  • projected
  • quobyte
  • rbd
  • scaleIO
  • secret
  • storageos
  • vsphereVolume
  • local

  • 上述类型中,emptyDir和hostPath属于节点级别的卷类型,emptyDir的生命周期与Pod资源相同,而使用了hostPath卷的Pod一旦被重新调度至其他节点,那么它将无法再使用此前的数据。因此,这两种类型都不具有持久性。要想使用持久类型的存储卷,就得使用网络存储系统,如NFS、Ceph、GlusterFS等,或者云端存储,如gcePersistentDisk、awsElasticBlockStore等

  • 然而,网络存储系统通常都不太容易使用,有的甚至很复杂,以至于对大多数用户来说它是一个难以逾越的障碍。K8S为此专门设计了一种集群级别的资源PersistentVolume(简称PV),它借助由管理员配置存储系统,而后由用户通过”persistentVolumeClaim”(简称PVC)存储卷直接申请使用的机制大大简化了终端存储用户的配置过程,有效降低了使用难度
  • 再者,Secret和ConfigMap算得上是两种特殊的卷类型

  • Secret用于向Pod传递敏感信息,如密码、秘钥、证书文件等,这些信息如果直接定义在镜像中很容易导致泄露,有了Secret资源,可以将这些信息保存于集群总而后由Pod进行挂载,从而实现将敏感数据与系统解耦

  • ConfigMap资源则用于向Pod注入非敏感数据,使用时,用户将数据直接存储于ConfigMap对象中,而后直接在Pod中使用ConfigMap卷引用即可,它可以帮助实现容器配置文件集中化定义和管理

  • 另外,K8S从1.9版本开始对存储的支持进一步增强了,引入了容器存储接口(Container Storge Interface,CSI)的一套实现版本,其能够将插件的安装流程简化至与创建Pod相当,并允许第三方存储供应商在无需修改K8S代码库的前提下提供自己的解决方案。因此,K8S系统支持的存储卷机制必将进一步增强

存储卷的使用方式

  • 在Pod中定义使用存储卷的配合由两部分组成

  • 通过.spec.volumes字段定义在Pod之上的存储卷列表和存储卷的类型,其支持使用多种不同类型的存储卷且配置参数差异特别大

  • 通过.spec.containers.volumeMounts字段在容器上定义的存储卷挂载列表,它只能挂载当前Pod资源中定义的具体存储卷,Pod 中的每个容器必须独立地指定每个卷的挂载位置。当然,也可以不挂载任何存储卷。

  • 在Pod级别定义存储卷时,.spec.volumes字段的值是对象列表格式,每个对象为一个存储卷的定义,它由存储卷名称(.spec.volumes.name <string>)或存储卷对象(.spec.volumes.VOL_TYPE <object>)组成,其中VOL_TYPE是使用的存储卷类型名称,它的内嵌字段随类型的不同而不同。下面的资源清单片段定义了两个存储卷组成的卷列表,一个是emtyDir类型,一个是gitRepo类型

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: tomcat-deploy
  5. namespace: testing
  6. labels:
  7. app: tomcat
  8. spec:
  9. replicas: 2
  10. selector:
  11. matchLabels:
  12. app: tomcat
  13. template:
  14. metadata:
  15. labels:
  16. app: tomcat
  17. spec:
  18. containers:
  19. - image: tomcat
  20. imagePullPolicy: IfNotPresent
  21. name: tomcat
  22. ports:
  23. - containerPort: 8080
  24. name: httpport
  25. - containerPort: 8009
  26. name: ajpport
  27. lifecycle:
  28. postStart:
  29. exec:
  30. command: ["/bin/sh","-c","echo '健哥说非要echo一个页面,不然就是搞学问不严谨,哈哈哈哈' > /usr/local/tomcat/webapps/a.txt"]
  31. volumes:
  32. - name: logdata
  33. emptyDir: {}
  34. - name: example
  35. gitRepo:
  36. directory: /usr/local/tomcat/webapps/aaa/
  37. repository: https://gitee.com/juory/blog-hexo.git
  38. revision: master
  • 定义好的存储卷可以由当前Pod资源内的各容器进行挂载。事实上,也只有多个容器挂载同一个存储卷时,”共享”才有了具体的意义。当Pod中只有一个容器时,使用存储卷的目的通常在于数据持久化。.spec.containers.volumesMounts字段的值也是对象列表格式,由一到多个存储卷挂载定义组成。无论何种类型的存储卷,它们的挂载格式基本上都是相同的,下面的代码段是在容器中定义挂载卷时的通用语法格式
  1. spec:
  2. containers:
  3. ...
  4. ...
  5. volumeMounts:
  6. - name: <string> -required
  7. mountPath: <string> -required
  8. readOnly: <boolean>
  9. subPath: <string>
  10. mountPropagation: <string>
  11. subPathExpr: <string>
  • 其中各字段的意义及使用要求具体如下

  • name : 指定要挂载的存储的名称

  • mountPath :指定挂载点路径,容器文件系统上的路径
  • readOnly : 是否挂载为只读卷
  • subPath : 挂在存储载时使用的子路径,即在mountPath指定的路径下使用一个子路径作为挂载点
  • mountPropagation : 挂载命名空间的传播

  • 下面是一个挂载示例,容器将tomcat将logdata存储卷挂载挂载于/usr/local/tomcat/logs/,将example挂载到/usr/local/tomcat/webapps/aaa/目录下

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: tomcat-deploy
  5. namespace: testing
  6. labels:
  7. app: tomcat
  8. spec:
  9. replicas: 2
  10. selector:
  11. matchLabels:
  12. app: tomcat
  13. template:
  14. metadata:
  15. labels:
  16. app: tomcat
  17. spec:
  18. containers:
  19. - image: tomcat
  20. imagePullPolicy: IfNotPresent
  21. name: tomcat
  22. ports:
  23. - containerPort: 8080
  24. name: httpport
  25. - containerPort: 8009
  26. name: ajpport
  27. lifecycle:
  28. postStart:
  29. exec:
  30. command: ["/bin/sh","-c","echo '健哥说非要echo一个页面,不然就是搞学问不严谨,哈哈哈哈' > /usr/local/tomcat/webapps/a.txt"]
  31. volumeMounts:
  32. - name: logdata
  33. mountPath: /usr/local/tomcat/logs/
  34. readOnly: false
  35. - name: example
  36. mountPath: /usr/local/tomcat/webapps/aaa/
  37. volumes:
  38. - name: logdata
  39. emptyDir: {}
  40. - name: example
  41. gitRepo:
  42. directory: .
  43. repository: https://gitee.com/juory/blog-hexo.git
  44. revision: master
  • 存储卷的定义基本相似,不过在新版本的K8S系统中girRepo类型的存储卷已经被废弃,如果要在容器中使用git仓库,可以将一个EmptyDir类型的卷挂载到initContainer中,使用git命令完成仓库的克隆操作,然后将EmptyDit卷挂载到Pod的容器中
  • emptyDir类型的存储卷磁盘类型是由kubelet根目录的文件系统的磁盘类型确定,并且emptyDir卷或者hostPath卷可以消耗的空间没有限制,容器直接或Pod直接也没用隔离