大家好,我是腾意。

应用程序在处理请求时,可根据其对当前请求的处理是否受影响于此前的请求,将应用划分为有状态应用和无状态应用两种。微服务体系中,各种应用均被拆分成了众多微服务或更小的应用模块,因此往往会存在为数不少的有状态应用,当然,也会存在数量可观的无状态应用。而对于有状态应用来说,数据持久化几乎是必然之需。

Kubernetes提供的存储卷(Volume)属于Pod资源级别,共享于Pod内的所有容器,可用于在容器的文件系统之外存储应用程序的相关数据,甚至还可独立于Pod的生命周期之外实现数据持久化。本篇主要介绍Kubernetes系统之上的主流存储卷类型及其应用。

1.1 存储卷概述

Pod本身具有生命周期,故其内部运行的容器及其相关数据自身均无法持久存在。Docker支持配置容器使用存储卷将数据持久存储于容器自身文件系统之外的存储空间中,它们可以是节点文件系统或网络文件系统之上的存储空间。相应地,Kubernetes也支持类似的存储卷功能,不过,其存储卷是与Pod资源绑定而非容器。简单来说,存储卷是定义在Pod资源之上、可被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久能力则取决于存储卷自身是否支持持久机制。Pod、容器与存储卷的关系如图7-1所示。

img

图7-1 Pod、容器与存储卷

1.1.1 Kubernetes支持的存储卷类型

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

·emptyDir

·hostPath

·nfs

·fc

·iscsi

·flocker

·Glusterfs

·rbd

·cephfs

·cinder

·awsElasticBlockStore

·gcePersistentDisk

·azureDisk

·azureFile

·gitRepo

·downwardAPI

·ConfigMap

·secret

·projected

·name

·persistentVolumeClaim

·downwardAPI

·vsphereVolume

·quobyte

·portworxVolume

·photonPersistentDisk

·scaleIO

·flexVolume

·storageOS

·local

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

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

再者,Secret和ConfigMap算得上是两种特殊的卷类型。

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

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

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

1.1.2 存储卷的使用方式

在Pod中定义使用存储卷的配置由两部分组成:一是通过.spec.volumes字段定义在Pod之上的存储卷列表,其支持使用多种不同类型的存储卷且配置参数差别很大;另一个是通过.spec.containers.volumeMounts字段在容器上定义的存储卷挂载列表,它只能挂载当前Pod资源中定义的具体存储卷,当然,也可以不挂载任何存储卷。如图7-1所示。

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


spec:

volumes:
- name: logdata
  emptyDir: {}
- name: example
   gitRepo:
     repository: https://github.com/iKubernetes/k8s_book.git
     revision: master
     directory: .

定义好的存储卷可由当前Pod资源内的各容器进行挂载。事实上,也只有多个容器挂载同一个存储卷时,“共享”才有了具体的意义。当Pod中只有一个容器时,使用存储卷的目的通常在于数据持久化。.spec.containers.volumeMounts字段的值也是对象列表格式,由一到多个存储卷挂载定义组成。无论何种类型的存储卷,它们的挂载格式基本上都是相同的,下面的代码段是在容器中定义挂载卷时的通用语法形式:


spec:
  
  containers:
  - name: <String>

volumeMounts:
- name <string> -required-
  mountPath  <string> -required-
  readOnly  <boolean>
  subPath  <string>
  mountPropagation  <string>

其中各字段的意义及使用要求具体如下。

·name:指定要挂载的存储的名称,必选字段。

·mountPath:挂载点路径,容器文件系统上的路径,必选字段。

·readOnly:是否挂载为只读卷。

·subPath:挂载存储卷时使用的子路径,即在mountPath指定的路径下使用一个子路径作为其挂载点。

下面是一个挂载示例,容器myapp将logdata存储卷挂载于/var/log/myapp,将example挂载到/webdata/example目录:


spec:
containers:
- name: myapp
image: ikubernetes/myapp:v7
volumeMounts:
- name: logdata
      mountPath: /var/log/myapp/
- name: example
      mountPath: /webdata/example/

存储卷的定义基本相似,因此本篇后面的篇幅将重点放在介绍主流存储卷类型的配置及使用方式,而且出于保持示例简洁及节约篇幅的目的,本篇的示例主要以自主式Pod资源为主。

1.2 临时存储卷

Kubernetes支持存储卷类型中,emptyDir存储卷的生命周期与其所属的Pod对象相同,它无法脱离Pod对象的生命周期提供数据存储功能,因此emptyDir通常仅用于数据缓存或临时存储。不过,基于emptyDir构建的gitRepo存储卷可以在Pod对象的生命周期起始时从相应的Git仓库中复制相应的数据文件到底层的emptyDir中,从而使得它具有了一定意义上的持久性。

1.2.1 emptyDir存储卷

emptyDir存储卷是Pod对象生命周期中的一个临时目录,类似于Docker上的“docker挂载卷”,在Pod对象启动时即被创建,而在Pod对象被移除时会被一并删除。不具有持久能力的emptyDir存储卷只能用于某些特殊场景中,例如,同一Pod内的多个容器间文件的共享,或者作为容器数据的临时存储目录用于数据缓存系统等。

emptyDir存储卷则定义于.spec.volumes.emptyDir嵌套字段中,可用字段主要包含两个,具体如下。

·medium:此目录所在的存储介质的类型,可取值为“default”或“Memory”,默认为default,表示使用节点的默认存储介质;“Memory”表示使用基于RAM的临时文件系统tmpfs,空间受限于内存,但性能非常好,通常用于为容器中的应用提供缓存空间。

·sizeLimit:当前存储卷的空间限额,默认值为nil,表示不限制;不过,在medium字段值为“Memory”时建议务必定义此限额。

下面是一个使用了emptyDir存储卷的简单示例,它保存于vol-emptydir.yaml配置文件中:


apiVersion: v1
kind: Pod
metadata:
  name: vol-emptydir-pod
spec:
  volumes:
  - name: html
    emptyDir: {}
  containers:
  - name: nginx
    image: nginx:1.12-alpine
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  - name: pagegen
    image: alpine
    volumeMounts:
    - name: html
      mountPath: /html
    command: ["/bin/sh", "-c"]
    args:
    - while true; do
        echo $(hostname) $(date) >> /html/index.html;
        sleep 10;
      done

上面的示例中定义的存储卷名称为html,挂载于容器nginx的/usr/share/nginx/html目录,以及容器pagegen的/html目录。容器pagegen每隔10秒向存储卷上的index.html文件中追加一行信息,而容器nginx中的nginx进程则以其为站点主页,如图7-2所示。

img

图7-2 存储卷使用示意图

Pod资源的详细信息中会显示存储卷的相关状态,包括其是否创建成功(在Events字段中输出)、相关的类型及参数(在Volumes字段中输出)以及容器中的挂载状态等信息(在Containers字段中输出)。如下面的命令结果所示:


~]$ kubectl describe pods vol-emptydir-pod
Name:         vol-emptydir-pod
……
Containers:
  nginx:
……
    Mounts:
      /usr/share/nginx/html from html (rw)
  pagegen:
……
    Mounts:
      /html from html (rw)
……
Volumes:
  html:
    Type:    EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
……
Events:
  Type  Reason       Age   From                             Message
  ----    ------        ----  ----                             -------
  Normal  Scheduled  10s  default-scheduler Successfully assigned vol-emptydir 
      to node03.ilinux.io
  Normal  SuccessfulMountVolume  10s  kubelet, node03.ilinux.io  MountVolume.
      SetUp succeeded for volume "html"

而后,可以为其创建Service资源并进行访问测试,或者在集群中直接对Pod的地址发起访问请求,以测试两个容器通过emptyDir卷共享数据的结果状态:


~]$ curl 10.1.3.106
vol-emptydir Wed Mar 7 01:10:56 UTC 2018

作为边车(sidecar)的容器paggen,其每隔10秒生成一行信息追加到存储卷上的index.html文件中,因此,通过主容器nginx的应用访问到的内容也会处于不停的变动中。另外,emptyDir存储卷也可以基于RAM创建tmpfs文件系统的存储卷,常用于为容器的应用提供高性能缓存,下面是一个配置示例:


volumes:
- name: cache
  emptyDir:
    medium: Memory

emptyDir卷简单易用,但仅能用于临时存储。另外还存在一些类型的存储卷建构在emptyDir之上,并额外提供了它所没有的功能,例如,将于1.2.2节介绍的gitRepo存储卷。

1.2.2 gitRepo存储卷

gitRepo存储卷可以看作是emptyDir存储卷的一种实际应用,使用该存储卷的Pod资源可以通过挂载目录访问指定的代码仓库中的数据。使用gitRepo存储卷的Pod资源在创建时,会首先创建一个空目录(emptyDir)并克隆(clone)一份指定的Git仓库中的数据至该目录,而后再创建容器并挂载该存储卷。

定义gitRepo类型的存储卷时,其可嵌套使用的字段具体包含如下三个。

·repository :Git仓库的URL,必选字段。

·directory :目标目录名称,名称中不能包含“..”字符;“.”表示将仓库中的数据直接复制到卷目录中,否则,即为复制到卷目录中以用户指定的字符串为名称的子目录中。

·revision :特定revision的提交哈希码。

注意 使用gitRepo存储卷的Pod资源运行的工作节点上必须安装有Git程序,否则克隆仓库的操作将无从完成。另外,自Kubernetes 1.12起,gitRepo存储卷已经被废弃。

下面示例(vol-gitrepo.yaml配置文件)中的Pod资源在创建时,首先会创建一个空目录,将指定的Git仓库https://github.com/iKubernetes/k8s_book.git 中的数据复制一份直接保存于此目录中,而后将此目录创建为存储卷html,最后由容器Nginx将此存储卷挂载于/usr/share/nginx/html目录上:


apiVersion: v1
kind: Pod
metadata:
  name: vol-gitrepo-pod
spec:
  containers:
  - name: nginx
    image: nginx:1.12-alpine
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
  - name: html
    gitRepo:
      repository: https://github.com/iKubernetes/k8s_book.git
      directory: .
      revision: "master"

访问此Pod资源中的Nginx服务即可看到其来自于Git仓库中的页面资源。不过,gitRepo存储卷在其创建完成后不会再与指定的仓库执行同步操作,这就意味着在Pod资源运行期间,如果仓库中的数据发生了变化,那么gitRepo存储卷不会同步到这些内容。当然,此时可以为Pod资源创建一个专用的边车容器用于执行此类的同步操作,尤其是数据来源于私有仓库时,通过边车容器完成其复制就更为必要。

gitRepo存储卷建构于emptyDir存储卷之上,它的生命周期与隶属的Pod对象相同,因此使用时不建议在此类存储卷中保存由容器生成的重要数据。另外,自Kubernetes 1.12版起,gitRepo存储卷已经被废弃,所以在之后的版本中若要使用它配置Pod对象,建议读者借助初始化容器(InitContainer)将仓库中的数据复制到emptyDir存储卷上,并在主容器中使用此存储卷。

1.3 节点存储卷hostPath

hostPath类型的存储卷是指将工作节点上某文件系统的目录或文件挂载于Pod中的一种存储卷,它可独立于Pod资源的生命周期,因而具有持久性。但它是工作节点本地的存储空间,仅适用于特定情况下的存储卷使用需求,例如,将工作节点上的文件系统关联为Pod的存储卷,从而使得容器访问节点文件系统上的数据。这一点在运行有管理任务的系统级Pod资源需要访问节点上的文件时尤为有用。

配置hostPath存储卷的嵌套字段共有两个:一个是用于指定工作节点上的目录路径的必选字段path;另一个是指定存储卷类型的type,它支持使用的卷类型包含如下几种。

·DirectoryOrCreate:指定的路径不存时自动将其创建为权限是0755的空目录,属主属组均为kubelet。

·Directory:必须存在的目录路径。

·FileOrCreate:指定的路径不存时自动将其创建为权限是0644的空文件,属主和属组同是kubelet。

·File:必须存在的文件路径。

·Socket:必须存在的Socket文件路径。

·CharDevice:必须存在的字符设备文件路径。

·BlockDevice:必须存在的块设备文件路径。

下面是定义在vol-hostpath.yaml配置文件中的Pod资源,它运行着日志收集代理应用filebeat,负责收集工作节点及容器相关的日志信息发往Redis服务器,它使用了三个hostPath类型的存储卷:


apiVersion: v1
kind: Pod
metadata:
  name: vol-hostpath-pod
spec:
  containers:
  - name: filebeat
    image: ikubernetes/filebeat:5.6.7-alpine
    env:
    - name: REDIS_HOST
      value: redis.ilinux.io:6379
    - name: LOG_LEVEL
      value: info
    volumeMounts:
    - name: varlog
      mountPath: /var/log
    - name: socket
      mountPath: /var/run/docker.sock
    - name: varlibdockercontainers
      mountPath: /var/lib/docker/containers
      readOnly: true
  terminationGracePeriodSeconds: 30
  volumes:
  - name: varlog
    hostPath:
      path: /var/log
  - name: varlibdockercontainers
    hostPath:
      path: /var/lib/docker/containers
  - name: socket
    hostPath:
      path: /var/run/docker.sock

这类Pod资源通常受控于daemonset类型的Pod控制器,它运行于集群中的每个工作节点之上,负责收集工作节点上系统级的相关数据,因此使用hostPath存储卷也是理所应当的。读者在创建上述Pod资源时,如果有可用的Redis服务器,则可通过环境变量REDIS_HOST传递给Pod资源,待其Ready之后即可通过Redis服务器查看到由其发送的日志信息。在filebeat的应用架构中,这些日志信息会发往Elasticsearch,并通过Kibana进行展示。

另外,使用hostPath存储卷时需要注意到,不同节点上的文件或许并不完全相同,于是,那些要求事先必须存在的文件或目录的满足状态也可能会有所不同;另外,基于资源可用状态的调度器调度Pod时,hostPath资源的可用性状态不会被考虑在内;再者,在节点中创建的文件或目录默认仅有root可写,若期望容器内的进程拥有写权限,则要么将它运行为特权容器,要么修改节点上目录路径的权限。

那些并非执行系统级管理任务的且不受控于Daemonset控制器的无状态应用在Pod资源被重新调度至其他节点运行时,此前创建的文件或目录大多都不会存在。因此,hostPath存储卷虽然能持久保存数据,但对于被调度器按需调度的应用来说并不适用,这时需要用到的是独立于集群节点的持久性存储卷,即网络存储卷。

1.4 网络存储卷

如前所述,Kubernetes拥有众多类型的用于适配专用存储系统的网络存储卷。这类存储卷包括传统的NAS或SAN设备(如NFS、iSCSI、fc)、分布式存储(如GlusterFS、RBD)、云端存储(如gcePersistentDisk、azureDisk、cinder和awsElasticBlockStore)以及建构在各类存储系统之上的抽象管理层(如flocker、portworxVolume和vsphereVolume)等。

1.4.1 NFS存储卷

NFS即网络文件系统(Network File System),它是一种分布式文件系统协议,最初是由Sun MicroSystems公司开发的类Unix操作系统之上的一款经典的网络存储方案,其功能旨在允许客户端主机可以像访问本地存储一样通过网络访问服务器端文件。作为一种由内核原生支持的网络文件系统,具有Linux系统使用经验的读者大多数都应该对NFS有一定的使用经验。

Kubernetes的NFS存储卷用于将某事先存在的NFS服务器上导出(export)的存储空间挂载到Pod中以供容器使用。与emptyDir不同的是,NFS存储卷在Pod对象终止后仅是被卸载而非删除。另外,NFS是文件系统级共享服务,它支持同时存在的多路挂载请求。定义NFS存储卷时,常用到以下字段。

·server :NFS服务器的IP地址或主机名,必选字段。

·path :NFS服务器导出(共享)的文件系统路径,必选字段。

·readOnly:是否以只读方式挂载,默认为false。

Redis(REmote DIctionary Server)是一个著名的高性能key-value存储系统,应用非常广泛,将其部署运行于Kubernetes系统之上时,需要持久化存储卷的支持。下面是简单使用Redis的一个示例:


apiVersion: v1
kind: Pod
metadata:
  name: vol-nfs-pod
  labels:
    app: redis
spec:
  containers:
  - name: redis
    image: redis:4-alpine
    ports:
    - containerPort: 6379
      name: redisport
    volumeMounts:
    - mountPath: /data
      name: redisdata
  volumes:
    - name: redisdata
      nfs:
        server: nfs.ilinux.io
        path: /data/redis
        readOnly: false

上面的示例定义在资源配置文件vol-nfs.yaml中,其中的Pod资源拥有一个关联至NFS服务器nfs.ilinux.io的存储卷,Redis容器将其挂载于/data目录上,它是运行于容器中的redis-server数据的持久保存位置。

提示 这里应确保事先要存在一个名为nfs.ilinux.io的NFS服务器,其输出了/data/redis目录,并授权给了Kubernetes集群中的节点访问。主机和目录都可以按需要进行调整。

资源创建完成后,可通过其命令客户端redis-cli创建测试数据,并手动触发其同步于存储系统中,下面加粗部分的字体为要执行的Redis命令:


~]$ kubectl exec -it vol-nfs-pod redis-cli
121.0.0.1:6379> set mykey "hello ilinux.io"
OK
121.0.0.1:6379> get mykey
"hello ilinux.io"
121.0.0.1:6379> BGSAVE
Background saving started
121.0.0.1:6379> exit

为了测试其数据持久化效果,下面删除Pod资源vol-nfs-pod,并于再次重建后检测数据是否依然能够访问:


~]$ kubectl delete pods vol-nfs-pod
pod "vol-nfs-pod" deleted
~]$ kubectl apply -f vol-nfs.yaml
pod "vol-nfs-pod" created

待其重建完成后,通过再一次创建的Pod资源的详细信息,我们可以观察到它挂载使用NFS存储卷的相关信息。接下来再次检查redis-server中是否还保存有此前存储的数据:


~]$ kubectl exec -it vol-nfs-pod redis-cli
121.0.0.1:6379> get mykey
"hello ilinux.io"
121.0.0.1:6379>

从上面的命令结果中可以看出,此前创建的键mykey及其数据在Pod资源重建后依然存在,这表明在删除Pod资源时,其关联的外部存储卷并不会被一同删除。如果需要清除此类的数据,需要用户通过存储系统的管理接口手动进行。

1.4.2 RBD存储卷

Ceph是一个专注于分布式的、弹性可扩展的、高可靠的、性能优异的存储系统平台,同时支持提供块设备、文件系统和REST三种存储接口。它是一个高度可配置的系统,并提供了一个命令行界面用于监视和控制其存储集群。Ceph还包含鉴证和授权功能,可兼容多种存储网关接口,如OpenStack Swift和Amazon S3。Kubernetes也支持通过RBD卷类型使用Ceph存储系统为Pod提供存储卷。要配置Pod资源使用RBD存储卷,需要事先满足如下几个前提条件。

·存在某可用的Ceph RBD存储集群,否则就需要创建一个。

·在Ceph RBD集群中创建一个能满足Pod资源数据存储需要的存储映像(image)。

·在Kubernetes集群内的各节点上安装Ceph客户端程序包(ceph-common)。

在配置RBD类型的存储卷时,需要指定要连接的目标服务器和认证信息等,这一点通常使用以下嵌套字段进行定义。

·monitors<[]string>:Ceph存储监视器,逗号分隔的字符串列表;必选字段。

·image:rados image的名称,必选字段。

·pool:rados存储池名称,默认为RBD。

·user:rados用户名,默认为admin。

·keyring:RBD用户认证时使用的keyring文件路径,默认为/etc/ceph/keyring。

·secretRef"Object":RBD用户认证时使用的保存有相应认证信息的Secret对象,会覆盖由keyring字段提供的密钥信息。

·readOnly:是否以只读的方式进行访问。

·fsType:要挂载的存储卷的文件系统类型,至少应该是节点操作系统支持的文件系统,如ext4、xfs、ntfs等,默认为ext4。

下面是一个定义在vol-rbd.yaml配置文件中使用RBD存储卷的Pod资源示例:


apiVersion: v1
kind: Pod
metadata:
  name: vol-rbd-pod
spec:
  containers:
  - name: redis
    image: redis:4-alpine
    ports:
    - containerPort: 6379
      name: redisport
    volumeMounts:
    - mountPath: /data
      name: redis-rbd-vol
  volumes:
    - name: redis-rbd-vol
      rbd:
        monitors:
        - '172.16.0.56:6789'
        - '172.16.0.57:6789'
        - '172.16.0.58:6789'
        pool: kube
        image: redis
        fsType: ext4
        readOnly: false
        user: admin
        secretRef:
           name: ceph-secret

此示例依赖于事先存在的一个Ceph存储集群,这里假设其监视器的地址为172.16.0.56、172.16.0.57和172.16.0.58三个主机IP,并且集群上的存储池kube中存在创建好的映像Redis,此映像拥有ext4文件系统。Ceph客户端访问集群时需要事先完成认证之后才能进行后续的访问操作,此示例上,其认证信息保存于名为ceph-secret的Secret资源对象中。示例所实现的逻辑架构如图7-3所示。

img

图7-3 RBD存储卷

提示 此配置示例依赖于一个可用的Ceph集群,且上述配置示例中的部分参数需要参照实际环境进行修改。

1.4.3 GlusterFS存储卷

GlusterFS(Gluster File System)是一个开源的分布式文件系统,是水平扩展存储解决方案Gluster的核心,具有强大的横向扩展能力,GlusterFS通过扩展能够支持数PB存储容量和处理数千客户端。GlusterFS借助TCP/IP或InfiniBand RDMA网络将物理分布的存储资源聚集在一起,使用单一全局命名空间来管理数据。另外,GlusterFS基于可堆叠的用户空间设计,可为各种不同的数据负载提供优异的性能,是另一种流行的分布式存储解决方案。要配置Pod资源使用GlusterFS存储卷,需要事先满足以下前提条件。

1)存在某可用的GlusterFS存储集群,否则就要创建一个。

2)在GlusterFS集群中创建一个能满足Pod资源数据存储需要的卷。

3)在Kubernetes集群内的各节点上安装GlusterFS客户端程序包(glusterfs和glusterfs-fuse)。

另外,若要基于GlusterFS使用存储卷的动态供给机制(请参考1.5.6节),还需要事先部署heketi,它用于为GlusterFS集群提供RESTful风格的管理接口。Gluster存储集群及heketi的配置示例请参考附录B。

定义Pod资源使用GlusterFS类型的存储卷时,常用的配置字段包含如下几个。

·endpoints:Endpoints资源的名称,此资源需要事先存在,用于提供Gluster集群的部分节点信息作为其访问入口;必选字段。

·path:用到的GlusterFS集群的卷路径,如kube-redis;必选字段。

·readOnly:是否为只读卷。

下面是一个定义在vol-glusterfs.yaml配置文件中的Pod资源示例,它使用了GlusterFS存储卷持久保存应用数据。它通过glusterfs-endpoints资源中定义的GlusterFS集群节点信息接入集群,并以kube-redis卷作为Pod资源的存储卷。glusterfs-endpoints资源需要在Kubernetes集群中事先创建,而kube-redis则需要事先创建于Gluster集群:


apiVersion: v1
kind: Pod
metadata:
  name: vol-glusterfs-pod
  labels:
    app: redis
spec:
  containers:
  - name: redis
    image: redis:alpine
    ports:
    - containerPort: 6379
      name: redisport
    volumeMounts:
    - mountPath: /data
      name: redisdata
  volumes:
    - name: redisdata
      glusterfs:
        endpoints: glusterfs-endpoints
        path: kube-redis
        readOnly: false

用于访问Gluster集群的相关节点信息要事先保存于某特定的Endpoints资源中,例如上面示例中调用的glusterfs-endpoints。此类的Endpoints资源可由用户根据实际需求手动创建,例如,下面的保存于glusterfs-endpoints.yaml文件中的资源示例中定义了三个接入相关的Gluster存储集群的节点gfs01.ilinux.io、gfs02.ilinux.io和gfs03.ilinux.io,其中的端口信息仅为满足Endpoints资源的必选字段要求,因此其值可以随意填写:


apiVersion: v1
kind: Endpoints
metadata:
  name: glusterfs-endpoints
subsets:
  - addresses:
    - ip: gfs01.ilinux.io
    ports:
    - port: 24007
      name: glusterd
  - addresses:
    - ip: gfs02.ilinux.io
    ports:
    - port: 24007
      name: glusterd
  - addresses:
    - ip: gfs03.ilinux.io
    ports:
    - port: 24007
      name: glusterd

首先创建Endpoints资源glusterfs-endpoints,而后再创建Pod资源vol-glusterfs-pod,即可测试其数据持久存储的效果。

1.4.4 Cinder存储卷

Cinder是OpenStack Block Storage的项目名称,用来为虚拟机(VM)实例提供持久块存储。Cinder通过驱动架构支持多种后端(back-end)存储方式,包括LVM、NFS、Ceph和其他诸如EMC、IBM等商业存储产品和方案,其提供了调用度来调度卷创建的请求,能合理优化存储资源的分配,而且还拥有REST API。将Kubernetes集群部署于OpenStack构建的IaaS环境中时,Cinder的块存储功能可为Pod资源提供外部持久存储的有效方式。

在Pod资源上定义使用Cinder存储卷时,其可用的嵌套字段包含如下几个。

·volumeID:用于标识Cinder中的存储卷的卷标识符,必选字段。

·readOnly:是否以只读方式访问。

fsType:要挂载的存储卷的文件系统类型,至少应该是节点操作系统支持的文件系统,如ext4、xfs、ntfs等,默认为“ext4”。

下面的资源清单是定义在vol-cinder.yaml文件中的使用示例,假设在OpenStack环境中有创建好的Cinder卷“e2b8d2f7-wece-90d1-a505-4acf607a90bc”可用:


apiVersion: v1
kind: Pod
metadata:
  name: vol-cinder-pod
spec:
  containers:
   - image: mysql
      name: mysql
      args:
        - "--ignore-db-dir"
        - "lost+found"
      env:
        - name: MYSQL_ROOT_PASSWORD
          value: YOUR_PASS
      ports:
        - containerPort: 3306
          name: mysqlport
      volumeMounts:
        - name: mysqldata
          mountPath: /var/lib/mysql
  volumes:
    - name: mysqldata
      cinder:
        volumeID: e2b8d2f7-wece-90d1-a505-4acf607a90bc
        fsType: ext4

配置可用的系统环境和存储资源时,将其匹配于资源清单文件中即可完成Pod资源创建。另外,Kubernetes所支持的各类持久存储卷其配置使用方式各有不同,鉴于篇幅有限,这里不再一一列举其使用方式。

1.5 持久存储卷

通过前面使用持久存储卷(Persistent Volume)的示例可知,Kubernetes用户必须要清晰了解所用到的网络存储系统的访问细节才能完成存储卷相关的配置任务,例如,NFS存储卷的server和path字段的配置就依赖于服务器地址和共享目录路径。这与Kubernetes的向用户和开发隐藏底层架构的目标有所背离,对存储资源的使用最好也能像使用计算资源一样,用户和开发人员无须了解Pod资源究竟运行于哪个节点,也无须了解存储系统是什么设备以及位于何处。为此,Kubernetes的PersistentVolume子系统在用户与管理员之间添加了一个抽象层,从而使得存储系统的使用和管理职能互相解耦,如图7-4所示。

PersistentVolume(PV)是指由集群管理员配置提供的某存储系统上的一段存储空间,它是对底层共享存储的抽象,将共享存储作为一种可由用户申请使用的资源,实现了“存储消费”机制。通过存储插件机制,PV支持使用多种网络存储系统或云端存储等多种后端存储系统,例如,前面使用的NFS、RBD和Cinder等。PV是集群级别的资源,不属于任何名称空间,用户对PV资源的使用需要通过PersistentVolumeClaim(PVC)提出的使用申请(或称为声明)来完成绑定,是PV资源的消费者,它向PV申请特定大小的空间及访问模式(如rw或ro),从而创建出PVC存储卷,而后再由Pod资源通过PersistentVolumeClaim存储卷关联使用,如图7-4所示。

img

图7-4 Pod存储卷、PVC、PV及存储设备的调用关系

尽管PVC使得用户可以以抽象的方式访问存储资源,但很多时候还是会涉及PV的不少属性,例如,用于不同场景时设置的性能参数等。为此,集群管理员不得不通过多种方式提供多种不同的PV以满足用户不同的使用需求,两者衔接上的偏差必然会导致用户的需求无法全部及时有效地得到满足。Kubernetes自1.4版起引入了一个新的资源对象StorageClass,可用于将存储资源定义为具有显著特性的类别(Class)而不是具体的PV,例如“fast”“slow”或“glod”“silver”“bronze”等。用户通过PVC直接向意向的类别发出申请,匹配由管理员事先创建的PV,或者由其按需为用户动态创建PV,这样做甚至免去了需要事先创建PV的过程。

PV对存储系统的支持可通过其插件来实现,目前,Kubernetes支持如下类型的插件。

·GCEPersistentDisk

·AWSElasticBlockStore

·AzureFile

·AzureDisk

·FC(Fibre Channel)**

·FlexVolume

·Flocker

·NFS

·iSCSI

·RBD(Ceph Block Device)

·CephFS

·Cinder(OpenStack block storage)

·Glusterfs

·VsphereVolume

·Quobyte Volumes

·HostPath

·VMware Photon

·Portworx Volumes

·ScaleIO Volumes

·StorageOS

1.5.1 创建PV

PersistentVolume Spec主要支持以下几个通用字段,用于定义PV的容量、访问模式和回收策略。

1)Capacity:当前PV的容量;目前,Capacity仅支持空间设定,将来应该还可以指定IOPS和throughput。

2)访问模式:尽管在PV层看起来并无差别,但存储设备支持及启用的功能特性却可能不尽相同。例如NFS存储支持多客户端同时挂载及读写操作,但也可能是在共享时仅启用了只读操作,其他存储系统也存在类似的可配置特性。因此,PV底层的设备或许存在其特有的访问模式,用户使用时必须在其特性范围内设定其功能,具体如图7-5所示。

·ReadWriteOnce:仅可被单个节点读写挂载;命令行中简写为RWO。

·ReadOnlyMany:可被多个节点同时只读挂载;命令行中简写为ROX。

·ReadWriteMany:可被多个节点同时读写挂载;命令行中简写为RWX。

3)persistentVolumeReclaimPolicy:PV空间被释放时的处理机制;可用类型仅为Retain(默认)、Recycle或Delete,具体说明如下。

·Retain:保持不动,由管理员随后手动回收。

·Recycle:空间回收,即删除存储卷目录下的所有文件(包括子目录和隐藏文件),目前仅NFS和hostPath支持此操作。

·Delete:删除存储卷,仅部分云端存储系统支持,如AWS EBS、GCE PD、Azure Disk和Cinder。

4)volumeMode:卷模型,用于指定此卷可被用作文件系统还是裸格式的块设备;默认为Filesystem。

5)storageClassName:当前PV所属的StorageClass的名称;默认为空值,即不属于任何StorageClass。

6)mountOptions:挂载选项组成的列表,如ro、soft和hard等。

img

图7-5 各PV支持的访问模式

下面的资源清单配置示例中定义了一个使用NFS存储后端的PV,空间大小为10GB,支持多路的读写操作。待后端存储系统满足需求时,即可进行如下PV资源的创建:


kind: PersistentVolume
metadata:
  name: pv-nfs-0001
  labels:
    release: stable
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path:  "/webdata/htdocs"
    server: nfs.ilinux.io

创建完成后,可以看到其状态为“Available”,即“可用”状态,表示目前尚未被PVC资源所“绑定”:


~]$ kubectl get pv pv-nfs-0001 -o custom-columns=NAME:metadata.name,STATUS:status.
    phase
NAME       STATUS
pv-nfs-0001   Available

下面是另一个PV资源的配置清单,它使用RBD存储后端,空间大小为2GB,仅支持单个客户端的读写访问。将RBD相关属性设定为匹配实际的环境需求,例如在Ceph集群中创建映像pv-rbd-0001,大小为2GB,并在映射后进行映像文件格式化,随后即可创建如下的PV资源:


apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-rbd-0001
spec:
  capacity:
    storage: 2Gi
  accessModes:
    - ReadWriteOnce
  rbd:
    monitors:
      - ceph-mon01.ilinux.io:6789
      - ceph-mon02.ilinux.io:6789
      - ceph-mon03.ilinux.io:6789
    pool: kube
    image: pv-rbd-0001
    user: admin
    secretRef:
      name: ceph-secret
    fsType: ext4
    readOnly: false
  persistentVolumeReclaimPolicy: Retain

使用资源的查看命令可列出PV资源的相关信息。创建完成的PV资源可能处于下列四种状态中的某一种,它们代表着PV资源生命周期中的各个阶段。

·Available:可用状态的自由资源,尚未被PVC绑定。

·Bound:已经绑定至某PVC。

·Released:绑定的PVC已经被删除,但资源尚未被集群回收。

·Failed:因自动回收资源失败而处于的故障状态。

1.5.2 创建PVC

PersistentVolumeClaim是存储卷类型的资源,它通过申请占用某个PersistentVolume而创建,它与PV是一对一的关系,用户无须关心其底层实现细节。申请时,用户只需要指定目标空间的大小、访问模式、PV标签选择器和StorageClass等相关信息即可。PVC的Spec字段的可嵌套字段具体如下。

·accessMode:当前PVC的访问模式,其可用模式与PV相同。

·resources:当前PVC存储卷需要占用的资源量最小值;目前,PVC的资源限定仅指其空间大小。

·selector:绑定时对PV应用的标签选择器(matchLabels)或匹配条件表达式(matchEx-

·pressions),用于挑选要绑定的PV;如果同时指定了两种挑选机制,则必须同时满足两种选择机制的PV才能被选出。

·storageClassName:所依赖的存储类的名称。

·volumeMode:卷模型,用于指定此卷可被用作文件系统还是裸格式的块设备;默认为“Filesystem”。

·volumeName:用于直接指定要绑定的PV的卷名。

下面的配置清单(pvc-nfs-0001.yaml文件)定义了一个PVC资源示例,其选择PV的挑选机制是使用了标签选择器,适配的标签是release:stable,存储类为slow,这会关联到前面创建的PV资源pv-nfs-0001:


apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs-0001
  lables:
    release: stable
spec:
  accessModes:
    - ReadWriteMany
  volumeMode: Filesystem
  resources:
    requests:
      storage: 5Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"

使用资源创建命令完成资源创建,而后即可查看其绑定PV资源的相关信息:


~]$ kubectl get pvc pvc-nfs-0001
NAME          STATUS  VOLUME       CAPACITY  ACCESS MODES  STORAGECLASS  AGE
pvc-nfs-0001  Bound   pv-nfs-0001  5Gi       RWX           slow          6s

如果需要绑定此前创建的PV资源pv-rbd-0001,那么创建类似如下的资源配置即可,它将保存于配置文件pvc-rbd-0001.yaml中:


apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-rbd-0001
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 2Gi
  storageClassName: fast
  selector:
    matchLabels:
      release: "stable"

使用资源创建命令完成资源创建,而后即可查看其绑定PV资源的相关信息:


~]$ kubectl get pvc/pvc-rbd-0001
NAME          STATUS  VOLUME       CAPACITY  ACCESS MODES  STORAGECLASS  AGE
pvc-rbd-0001  Bound   pv-rbd-0001  2Gi       RWO           fast          5s

创建好PVC资源之后,即可在Pod资源中通过persistenVolumeClain存储卷引用它,而后挂载于容器中进行数据持久化。需要注意的是,PV是集群级别的资源,而PVC则隶属于名称空间,因此,PVC在绑定目标PV时不受名称空间的限制,但Pod引用PVC时,则只能是属于同一名称空间中的资源。

1.5.3 在Pod中使用PVC

在Pod资源中调用PVC资源,只需要在定义volumes时使用persistentVolumeClaims字段嵌套指定两个字段即可,具体如下。

·claimName:要调用的PVC存储卷的名称,PVC卷要与Pod在同一名称空间中。

·readOnly:是否将存储卷强制挂载为只读模式,默认为false。

下面的清单定义了一个Pod资源,它是1.4.2节中直接使用RBD存储的Pod资源,此处改为调用了前面刚刚创建的名为pv-rbd-0001的PVC资源:


apiVersion: v1
kind: Pod
metadata:
  name: vol-rbd-pod
spec:
  containers:
  - name: redis
    image: redis:4-alpine
    ports:
    - containerPort: 6379
      name: redisport
    volumeMounts:
    - mountPath: /data
      name: redis-rbd-vol
  volumes:
- name: redis-rbd-vol
persistentVolumeClaim:
    claimName: pv-rbd-0001

资源创建完成后,即可通过类似于此前1.4.1节示例中的方式完成数据持久性测试。

1.5.4 存储类

存储类(storage class)是Kubernetes资源类型的一种,它是由管理员为管理PV之便而按需创建的类别(逻辑组),例如可按存储系统的性能高低分类,或者根据其综合服务质量级别进行分类(如图7-6所示)、依照备份策略分类,甚至直接按管理员自定义的标准进行分类等。不过,Kubernetes自身无法理解“类别”到底意味着什么,它仅仅是将这些当作PV的特性描述。

img

图7-6 基于综合服务质量的存储系统分类

存储类的好处之一便是支持PV的动态创建。用户用到持久性存储时,需要通过创建PVC来绑定匹配的PV,此类操作需求量较大,或者当管理员手动创建的PV无法满足PVC的所有需求时,系统按PVC的需求标准动态创建适配的PV会为存储管理带来极大的灵活性。

存储类对象的名称至关重要,它是用户调用的标识。创建存储类对象时,除了名称之外,还需要为其定义三个关键字段:provisioner、parameter和reclaimPolicy。

1.StorageClass Spec

StorageClass Spec中的字段是定义存储类时最重要的字段,其包含以下五个可用字段。

·provisioner(供给方):即提供了存储资源的存储系统,存储类要依赖Provisioner来判定要使用的存储插件以便适配到目标存储系统。Kubernetes内建有多种供给方(Provisioner),这些供给方的名字都以“kubernetes.io”为前缀。另外,它还支持用户依据Kubernetes规范自定义Provisioner。

·parameters(参数):存储类使用参数描述要关联到的存储卷,不过,不同的Provisioner可用的参数各不相同。

·reclaimPolicy:为当前存储类动态创建的PV指定回收策略,可用值为Delete(默认)和Retain;不过,那些由管理员手工创建的PV的回收策略则取决于它们自身的定义。

·volumeBindingMode:定义如何为PVC完成供给和绑定,默认值为“VolumeBinding Immediate”;此选项仅在启用了存储卷调度功能时才能生效。

·mountOptions:由当前类动态创建的PV的挂载选项列表。

下面是一个定义在glusterfs-storageclass.yaml配置文件中的资源清单,它定义了一个使用Gluster存储系统的存储类glusterfs,并通过annotations字段将其定义为默认的存储类:


kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: glusterfs
provisioner: kubernetes.io/glusterfs
parameters:
  resturl: "http://heketi.ilinux.io:8080"
  restauthenabled: "false"
  restuser: "ik8s"
  restuserkey: "ik8s.io"

这里需要特别提醒读者的是,parameters.resturl字段用于指定Gluster存储系统的RESTful风格的访问接口,本示例中使用的“http://heketi.ilinux.io:8080 ”应替换为读者自己实际环境中的可用地址。Gluster存储系统本身并不支持这种访问方式,管理员需要额外部署heketi配合Gluster以提供此类服务接口。Heketi支持认证访问,不过只有在restauthenabled设置为“true”时,restuser和restuserkey字段才会启用。Heketi的设置及使用方式请参考附录B。

2.动态PV供给

动态PV供给的启用,需要事先由管理员创建至少一个存储类,不同的Provisoner的创建方法各有不同,具体内容如前一节所示。另外,并非所有的存储卷插件都由Kubernetes内建支持PV动态供给功能,具体信息如图7-7所示。

上文中定义glusterfs存储类资源创建完成后,便可以据此使用动态PV供给功能。下面的资源清单定义在pvc-gluserfs-dynamic-0001.yaml配置文件中,它将从glusterfs存储类中申请使用5GB的存储空间:


apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-gluster-dynamic-0001
  annotations:
    volume.beta.kubernetes.io/storage-class: glusterfs
spec:
  # storageClassName: "glusterfs"
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

img

图7-7 各存储插件对动态供给方式的支持状况

目前,在PVC的定义中指定使用的存储类资源的方式共有两种:一种是使用spec.storageClassName字段,另一种是使用“volume.beta.kubernetes.io/storage-class”注解信息,如上面示例中所示。不过,建议仅使用一种方式,以免两者设置为不同的值时会出现配置错误。接下来创建定义的PVC,并检查其绑定状态:


~]$ kubectl create -f pvc-glusterfs-dynamic-0001.yaml
persistentvolumeclaim "pvc-gluster-dynamic-0001" created

通过如下命令输出的PVC资源的描述信息可以看到,PVC存储卷已创建完成且已经完成了PV绑定,绑定的PV资源由persistentvolume-controller控制器动态提供:


~]$ kubectl describe pvc pvc-gluster-dynamic-0001
Name:          pvc-gluster-dynamic-0001
Namespace:     default
StorageClass:  glusterfs
Status:        Bound
Volume:        pvc-5836eb47-6c77-11e8-9bab-000c29be4e28
Labels:        <none>
Annotations:   pv.kubernetes.io/bind-completed=yes
               pv.kubernetes.io/bound-by-controller=yes
               volume.beta.kubernetes.io/storage-provisioner=kubernetes.io/glusterfs
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      5Gi
Access Modes:  RWO
Events:
  Type   Reason           Age   From                       Message
  ----    ------                 ----  ----                         -------
  Normal  ProvisioningSucceeded  55s   persistentvolume-controller  Successfully 
      provisioned volume pvc-5836eb47-6c77-11e8-9bab-000c29be4e28 using kubernetes.
      io/glusterfs

任何支持PV动态供给的存储系统都可以在定义为存储类后由PVC动态申请使用,这对于难以事先预估使用到的存储空间大小及存储卷数量的使用场景尤为有用,例如由StatefulSet控制器管理Pod对象时,存储卷是必备资源,且随着规模的变动,存储卷的数量也会随之变动。

另外,用户也可以使用云端存储提供的PV动态供给机制,如AWS EBS、AzureDisk、Cinder或GCEPersistentDisk等,将Kubernetes部署于IaaS云端时,此种存储方式使用的较多。各类云存储动态供给的具体使用方式请参考相关的使用手册。

1.5.5 PV和PVC的生命周期

PV是Kubernetes集群的存储资源,而PVC则代表着资源需求。创建PVC时对PV发起的使用申请,即为“绑定”。PV和PVC是一一对应的关系,可用于响应PVC申请的PV必须要能够容纳PVC的请求条件,它们二者的交互遵循如下生命周期。

1.存储供给

存储供给(Provisioning)是指为PVC准备可用PV的机制。Kubernetes支持两种PV供给方式:静态供给和动态供给。

(1)静态供给

静态供给是指由集群管理员手动创建一定数量的PV的资源供应方式。这些PV负责处理存储系统的细节,并将其抽象成易用的存储资源供用户使用。静态提供的PV可能属于某存储类(StorageClass),也可能没有存储类,这一点取决于管理员的设定。

(2)动态供给

不存在某静态的PV匹配到用户的PVC申请时,Kubernetes集群会尝试为PVC动态创建符合需求的PV,此即为动态供给。这种方式依赖于存储类的辅助,PVC必须向一个事先存在的存储类发起动态分配PV的请求,没有指定存储类的PVC请求会被禁止使用动态创建PV的方式。

另外,为了支持使用动态供给机制,集群管理员需要为准入控制器(admission controller)启用“DefaultStorageClass”选项,这一点通过“--admission-control”命令行选项为API Server进行设定即可,后文会对准入控制器予以描述。

2.存储绑定

用户基于一系列存储需求和访问模式定义好PVC后,Kubernetes系统的控制器即会为其查找匹配的PV,并于找到之后在此二者之间建立起关联关系,而后它们二者之间的状态即转为“绑定”(Binding)。若PV是为PVC而动态创建的,则该PV专用于其PVC。

若是无法为PVC找到可匹配的PV,则PVC将一直处于未绑定(unbound)状态,直到有符合条件的PV出现并完成绑定方才可用。

(1)存储使用(Using)

Pod资源基于persistenVolumeClaim卷类型的定义,将选定的PVC关联为存储卷,而后即可为内部的容器所使用。对于支持多种访问模式的存储卷来说,用户需要额外指定要使用的模式。一旦完成将存储卷挂载至Pod对象内的容器中,其应用即可使用关联的PV提供的存储空间。

(2)PVC保护(Protection)

为了避免使用中的存储卷被移除而导致数据丢失,Kubernetes自1.9版本起引入了“PVC保护机制”。启用了此特性后,万一有用户删除了仍处于某Pod资源使用中的PVC时,Kubernetes不会立即予以移除,而是推迟到不再被任何Pod资源使用后方才执行删除操作。处于此种阶段的PVC资源的status字段为“Termination”,并且其Finalizers字段中包含“kubernetes.io/pvc-protection”。

3.存储回收(Reclaiming)

完成存储卷的使用目标之后,即可删除PVC对象以便进行资源回收。不过,至于如何操作则取决于PV的回收策略。目前,可用的回收策略有三种:Retained、Recycled和Deleted。

(1)留存(Retain)

留存策略意味着在删除PVC之后,Kubernetes系统不会自动删除PV,而仅仅是将它置于“释放”(released)状态。不过,此种状态的PV尚且不能被其他PVC申请所绑定,因为此前的申请生成的数据仍然存在,需要由管理员手动决定其后续处理方案。这就意味着,如果想要再次使用此类的PV资源,则需要由管理员按下面的步骤手动执行删除操作。

1)删除PV,这之后,此PV的数据依然留存于外部的存储之上。

2)手工清理存储系统上依然留存的数据。

3)手工删除存储系统级的存储卷(例如,RBD存储系统上的image)以释放空间,以便再次创建,或者直接将其重新创建为PV。

(2)回收(Recycle)

如果可被底层存储插件支持,资源回收策略会在存储卷上执行数据删除操作并让PV资源再次变为可被Claim。另外,管理员也可以配置一个自定义的回收器Pod模板,以便执行自定义的回收操作。不过,此种回收策略行将废弃。

(3)删除(Delete)

对于支持Deleted回收策略的存储插件来说,在PVC被删除后会直接移除PV对象,同时移除的还有PV相关的外部存储系统上的存储资产(asset)。支持这种操作的存储系统有AWS EBS、GCE PD、Azure Disk或Cinder。动态创建的PV资源的回收策略取决于相关存储类上的定义,存储类上相关的默认策略为Delete,大多数情况下,管理员都需要按用户期望的处理机制修改此默认策略,以免导致数据非计划内的误删除。

4.扩展PVC

Kubernetes自1.8版本起增加了扩展PV空间的特性,截至目前,它所支持的扩展PVC机制的存储卷共有以下几种。

·gcePersistentDisk

·awsElasticBlockStore

·Cinder

·glusterfs

·rbd

“PersistentVolumeClaimResize”准入插件负责对支持空间大小变动的存储卷执行更多的验证操作,管理员需要事先启用此插件才能使用PVC扩展机制,那些将“allowVolume Expansion”字段的值设置为“true”的存储类即可动态扩展存储卷空间。随后,用户改动Claim请求更大的空间即能触发底层PV空间扩展从而带来PVC存储卷的扩展。

对于包含文件系统的存储卷来说,只有在有新的Pod资源基于读写模式开始使用PVC时才会执行文件系统的大小调整操作。换句话说,如果某被扩展的存储卷已经由Pod资源所使用,则需要重建此Pod对象才能触发文件系统大小的调整操作。支持空间调整的文件系统仅有XFS和EXT3/EXT4。

1.6 downwardAPI存储卷

很多时候,应用程序需要基于其所在的环境信息设定运行特性等,这类环境信息包括节点及集群的部分详细属性信息等,例如,Nginx进程可根据节点的CPU核心数量自动设定要启动的worker进程数,JVM虚拟机可根据节点内存资源自动设定其堆内存大小。类似地,托管运行于Kubernetes的Pod对象中的容器化应用偶尔也需要获取其所属Pod对象的IP、主机名、标签、注解、UID、请求的CPU及内存资源量及其限额,甚至是Pod所在的节点名称等,容器可以通过环境变量或downwardAPI存储卷访问此类信息,不过,标签和注解仅支持通过存储卷暴露给容器。

1.6.1 环境变量式元数据注入

引用downwardAPI元数据信息的常用方式之一是使用容器的环境变量,它通过在valueFrom字段中嵌套fieldRef或resourceFieldRef字段来引用相应的数据源。不过,通常只有常量类的属性才能够通过环境变量注入到容器中,毕竟,在进程启动完成后将无法再向其告知变量值的变动,于是,环境变量也就不支持中途的更新操作。

可通过fieldRef字段引用的信息具体如下。

·spec.nodeName:节点名称。

·status.hostIP:节点IP地址。

·metadata.name:Pod对象的名称。

·metadata.namespace:Pod对象隶属的名称空间。

·status.podIP:Pod对象的IP地址。

·spec.serviceAccountName:Pod对象使用的ServiceAccount资源的名称。

·metadata.uid:Pod对象的UID。

·metadata.labels[''] :Pod对象标签中的指定键的值,例如metadata.labels['mylabel'],仅Kubernetes 1.9及之后的版本才支持。

·metadata.annotations[''] :Pod对象注解信息中的指定键的值,仅Kubernetes 1.9及之后的版本才支持。

另外,可通过resourceFieldRef字段引用的信息是指当前容器的资源请求及资源限额的定义,因此它们包括requests.cpu、limits.cpu、requests.memory和limits.memory四项。

下面的资源配置清单示例(downwardAPI-env.yaml)中定义的Pod对象通过环境变量向容器env-test-container中注入了Pod对象的名称、隶属的名称空间、标签app的值以及容器自身的CPU资源限额和内存资源请求等信息:


apiVersion: v1
kind: Pod
metadata:
  name: env-test-pod
  labels:
    app: env-test-pod
spec:
  containers:
    - name: env-test-container
      image: busybox
      command: [ "/bin/sh", "-c", "env" ]
      resources:
        requests:
          memory: "32Mi"
          cpu: "125m"
        limits:
          memory: "64Mi"
          cpu: "250m"
      env:
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: MY_POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: MY_APP_LABEL
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['app']
        - name: MY_CPU_LIMIT
          valueFrom:
            resourceFieldRef:
              resource: limits.cpu
        - name: MY_MEM_REQUEST
          valueFrom:
            resourceFieldRef:
              resource: requests.memory
              divisor: 1Mi
  restartPolicy: Never

此Pod对象创建完成后,向控制台打印所有的环境变量即可终止运行,它仅用于测试通过环境变量注入信息到容器的使用效果:


~]$ kubectl create -f downwardAPI-env.yaml
pod "env-test-pod" created
$ kubectl get pods -l app=env-test-pod
NAME           READY     STATUS      RESTARTS   AGE
env-test-pod   0/1       Completed   0          1m

而后即可通过控制台日志获取注入的环境变量:


~]$ kubectl logs env-test-pod | grep "^MY_"
MY_POD_NAMESPACE=default
MY_CPU_LIMIT=1
MY_APP_LABEL=env-test-pod
MY_MEM_REQUEST=32
MY_POD_NAME=env-test-pod

如示例中的最后一个环境变量所示,在定义资源请求或资源限制时还可额外指定一个“divisor”字段,用于为引用的值指定一个除数以实现所引用的相关值的单位换算。CPU资源的divisor字段其默认值为1,表示为1个核心,相除的结果不足1个单位时则向上圆整(例如,0.25向上圆整的结果为1),它的另一个可用单位为1m,即表示1个微核心。内存资源的divisor字段其默认值为也是1,不过,它意指1个字节,此时,32Mi的内存资源则要换算为33554432的结果予以输出。其他可用的单位还有1Ki、1Mi、1Gi等,于是,在将divisor字段的值设置为1Mi时,32Mi的内存资源的换算结果即为32。

注意 未为容器定义资源请求及资源限额时,downwardAPI引用的值即默认为节点的可分配CPU及内存资源量。

1.6.2 存储卷式元数据注入

向容器注入元数据信息的另一种方式是使用downwardAPI存储卷,它将配置的字段数据映射为文件并可通过容器中的挂载点进行访问。1.2.5节中能够通过环境变量的方式注入的元数据信息也都可以使用存储卷的方式进行信息暴露,除此之外,还可以在downwardAPI存储卷中使用fieldRef引用如下两个数据源。

·metadata.labels:Pod对象的所有标签信息,每行一个,格式为label-key="escaped-label-value"。

·metadata.annotations:Pod对象的所有注解信息,每行一个,格式为annotation-key="escaped-annotation-value"。

下面的资源配置清单示例(downwardAPI-vol.yaml)中定义的Pod对象通过downwardAPI存储卷向容器volume-test-container中注入了Pod对象隶属的名称空间、标签、注解以及容器自身的CPU资源限额和内存资源请求等信息。存储卷在容器中的挂载点为/etc/podinfo目录,因此,注入的每一项信息均会映射为此路径下的一个文件:


kind: Pod
apiVersion: v1
metadata:
  labels:
    zone: east-china
    rack: rack-101
    app: dapi-vol-pod
  name: dapi-vol-pod
  annotations:
    annotation1: "test-value-1"
spec:
  containers:
    - name: volume-test-container
      image: busybox
      command: ["sh", "-c", "sleep 864000"]
      resources:
        requests:
          memory: "32Mi"
          cpu: "125m"
        limits:
          memory: "64Mi"
          cpu: "250m"
      volumeMounts:
      - name: podinfo
        mountPath: /etc/podinfo
        readOnly: false
  volumes:
  - name: podinfo
    downwardAPI:
      defaultMode: 420
      items:
      - fieldRef:
          fieldPath: metadata.namespace
        path: pod_namespace
      - fieldRef:
          fieldPath: metadata.labels
        path: pod_labels
      - fieldRef:
          fieldPath: metadata.annotations
        path: pod_annotations
      - resourceFieldRef:
          containerName: volume-test-container
          resource: limits.cpu
        path: "cpu_limit"
      - resourceFieldRef:
          containerName: volume-test-container
          resource: requests.memory
          divisor: "1Mi"
        path: "mem_request"

创建资源配置清单中定义的Pod对象后即可测试访问由downwardAPI存储卷映射的文件pod_namespace、pod_labels、pod_annotations、limits_cpu和mem_request等:


~]$ kubectl create -f downwardAPI-vol.yaml
pod "dapi-vol-pod" created

接下来即可测试访问上述的映射文件,例如,查看Pod对象的标签列表:


~]$ kubectl exec dapi-vol-pod -- cat /etc/podinfo/pod_labels
app="dapi-vol-pod"
rack="rack-101"
zone="east-china"

如命令结果所示,Pod对象的标签信息每行一个地映射于自定义的路径/etc/podinfo/pod_labels文件中,类似地,注解信息也以这种方式进行处理。如前面的章节中所述,标签和注解支持运行时修改,其改动的结果也会实时映射进downwardAPI生成的文件中。例如,为dapi-vol-pod添加新的标签:


~]$ kubectl label pods dapi-vol-pod env="test"
pod "dapi-vol-pod" labeled

而后再次查看容器内的pod_labels文件的内容,由如下的命令结果可知新的标签已经能够通过相关的文件获取到:


~]$ kubectl exec dapi-vol-pod -- cat /etc/podinfo/pod_labels
app="dapi-vol-pod"
env="test"
rack="rack-101"
zone="east-china"

downwardAPI存储卷为Kubernetes上运行容器化应用提供了获取外部环境信息的有效途径,这一点对那些非为云原生开发的应用程序在不进行代码重构的前提下,获取环境信息进行自身配置等操作时尤为有用。

1.7 小结

本篇主要讲解了Kubernetes的存储卷及其功用,并通过应用示例给出了部署存储卷类型的使用方法,具体如下。

·临时存储卷emptyDir和gitRepo的生命周期与Pod对象相同,但gitRepo能够通过引用外部Git仓库的数据来实现数据的持久性。

·节点存储卷hostPath提供了节点级别的数据持久能力。

·网络存储卷NFS、GlusterFS和RBD等是企业内部较为常用的独立部署的持久存储系统。

·云存储卷AWS ebs等是托管于云端的Kubernetes系统上较为常用的持久存储系统。

·PV和PVC可将存储管理和存储使用解耦为消费者模型。

·基于StorageClass可以实现PV的动态供给,GlusterFS和Ceph RBD,以及云端存储AWS ebs等都可以实现此类功能。

版权声明:如无特殊说明,文章均为本站原创,版权所有,转载需注明本文链接

本文链接:http://www.bianchengvip.com/article/Kubernetes-Storage-Volumes-and-Data-Persistence/