本文对Kubernetes中的storage进行研究学习,参见文档Storage。

Volume

Container中On-disk files是临时的,会导致一些问题:1.Container重启之后,file会丢失; 2. Pod中的多个container之间需要share files。K8S Volume这个抽象概念用来解决这些问题。

K8s volume的生命周期跟使用它的pod的生命周期相同,这样,container重启后,volume还存在,而且pod中所有container可以共享volume。 Volume就是一个directory,能够被pod中的container访问,至于volume背后的介质是通过volume type来决定的。

K8S支持以下Volume type:




k8s configmap 配置redis k8s修改configmap 重启pod_Pod


-ConfigMap

configMap用来将配置信息添加进pod中,存放在configMap object中的配置data可以被pod中的container以configMap type使用。使用之前需要先创建ConfigMap object。


# data-source可以为文件,目录或字符串
kubectl create configmap <map-name> <data-source>
#示例
kubectl create configmap hadoop-configmap  --namespace=sophon --from-file=hdfs-site.xml --from-file=core-site.xml --from-file=yarn-site.xml
kubectl create configmap game-config --from-file=configure-pod-container/configmap/
kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm


若不想用文件名作为key,可以指定key。


kubectl create configmap game-config-3 --from-file=<my-key-name>=<path-to-file>


同时,也可以从key=value形式的环境变量文件中生成configMap。注意:当指定多个环境变量文件时,只会使用最后一个。


kubectl create configmap game-config-env-file 
       --from-env-file=configure-pod-container/configmap/game-env-file.properties


生成的configMap如下:


apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: 2017-12-27T18:36:28Z
  name: game-config-env-file
  namespace: default
  resourceVersion: "809965"
  uid: d9d1ca5b-eb34-11e7-887b-42010a8002b8
data:
  allowed: '"true"'
  enemies: aliens
  lives: "3"


注意:ConfigMap更新后并不会影响使用它的volume。

Pod可以以环境变量或加载文件的形式使用configmap。

以环境变量形式使用:


apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      env:
        # Define the environment variable
        - name: SPECIAL_LEVEL_KEY
          valueFrom:
            configMapKeyRef:
              # The ConfigMap containing the value you want to assign to SPECIAL_LEVEL_KEY
              name: special-config
              # Specify the key associated with the value
              key: special.how
  restartPolicy: Never


也可以直接使用configmap中的所有环境变量。


apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      envFrom:
      - configMapRef:
          name: special-config
  restartPolicy: Never


以加载文件形式使用:


apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      configMap:
        name: log-config
        items:
          - key: log_level
            path: log_level


Thelog-configConfigMap is mounted as a volume, and all contents stored in itslog_levelentry are mounted into the Pod at path “/etc/config/log_level”.

注意:若之前有文件存放于目录/etc/config,这些文件将会被删除。而且该目录将会变为Read-only file system,从而阻止修改。若想保留原来的文件,可以将configmap挂载到临时目录,然后通过启动脚本中link或cp的方式配置到目标目录,但这种方式不会自动更新pod的配置,需要重启Pod。

更新了configmap,使用该configmap的pod会自动更新,但存在延迟,取决于kubelet sync period (1 minute by default) + ttl of ConfigMaps cache (1 minute by default)。

同时,作为subPath volume使用的configmap将不会被自动更新,如下所示。


volumeMounts:
      - mountPath: /var/lib/mysql
        name: site-data
        subPath: mysql


Pod中使用configmap之前需要先创建configmap(除非设置为optional),若configmap或其中的key不存在,pod会启动失败。当使用configmap创建环境变量,无效的key会被忽略并在event中提示。ConfigMap只能被相同namespace的Pod访问,并且在static pod中不能使用ConfigMap,因为kubelet不支持。

-emptyDir

emptyDir在Pod分配到Node上时被创建并在Pod运行的整个生命周期存在。emptyDir最初是空目录,可被Pod中的所有container访问,但可以被container mount到不同路径下。当Pod从node移除时,emptyDir被删除。

默认情况下,emptyDir使用node使用的存储介质,可能是disk, ssd或网络存储。也可以指定emptyDir.medium来设置,若其为Memory,k8s将mount tmpfs.


apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}


-hostPath

hostPath加载host node的文件系统中的文件或目录到pod。类型如下。


k8s configmap 配置redis k8s修改configmap 重启pod_加载_02


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 volume表示本地加载的存储设备,只能以静态创建的PersistentVolume形式使用。


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


-nfs

nfs允许已经存在的NFS被加载到pod中,可以被多个pod同时加载写入。

-persistentVolumeClaim

persistentVolumeClaim用来加载PersistentVolume到pod中。PersistentVolume是用户生成durable storage的一种方式,不用关系底层云环境的细节。

-projected

projected将多个存在的volume sources映射到相同的目录。目前支持的volume source type为secret, downwardAPI,configMap,serviceAccountToken,但要求与pod在相同namespace。


apiVersion: v1
kind: Pod
metadata:
  name: volume-test
spec:
  containers:
  - name: container-test
    image: busybox
    volumeMounts:
    - name: all-in-one
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: all-in-one
    projected:
      sources:
      - secret:
          name: mysecret
          items:
            - key: username
              path: my-group/my-username
      - downwardAPI:
          items:
            - path: "labels"
              fieldRef:
                fieldPath: metadata.labels
            - path: "cpu_limit"
              resourceFieldRef:
                containerName: container-test
                resource: limits.cpu
      - configMap:
          name: myconfigmap
          items:
            - key: config
              path: my-group/my-config


-Secret

Secret用来存储和管理敏感信息,使得敏感信息更安全,更灵活,详情参见secret。

Pod有2种方式使用Secret,作为file被mount到container中;被kubelet用来pull image。

K8s会自动为Service Account创建secret并修改Pod来使用该secret。自动创建的特性可以被disable或重写。

下面为创建secret的示例,可以使用文件或字符串。字符串中特殊字符,如$,,*, and!需要转义,或直接使用单引号。


kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
kubectl create secret generic dev-db-secret --from-literal=username=devuser --from-literal=password='S!B*d$zDsb'

kubectl get secrets


默认情况下,kubectl get/describe获得的secret内容是编码过的,用来保护敏感信息。

Secret中有data和stringData field,其中data用来存放任意data,使用base64编码,stringData用来存放未编码的数据。当同时使用data和stringData来指定相同属性时,会使用stringData的value。data和stringData的key必须由字母数字以及‘-’, ‘_’ or ‘.’组成。

下面对字符串进行base64编码和解码。


echo -n 'admin' | base64
YWRtaW4=
echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
echo 'MWYyZDFlMmU2N2Rm' | base64 --decode


Secrets可以被作为文件或环境变量被pod中的容器使用。

下面是使用Secret的示例。


apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username


这样mysecret中的username便被加载到容器内的/etc/foo/my-group/my-username中。同时,指定key时则只会加载指定的key。

同时,可以为secret中加载进来的文件指定mode,默认为0644.由于json中不支持八进制,可以使用十进制来表示,下面使用256表示0400。


apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      defaultMode: 256


当然,也可以为每个文件指定mode。


apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username
        mode: 511


需要注意的是将secret加载进容器后,该文件夹变为readonly file system,尽管mode设置为可写,也不允许再往其中拷贝文件。可以通过加载进临时目录,然后从临时目录拷贝文件到目标目录。

注意:当secret更新后,使用该secret的pod中也会自动更新,但是会有一些延迟,延迟时间取决于kubelet sync period + cache propagation delay。

下面是使用secret作为环境变量的示例。


apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
  restartPolicy: Never


在容器内部可以直接使用这些加载进来的环境变量,环境变量的值为解码的值。

限制:1. 必须先创建secret,然后在pod中使用,secret缺失会阻止pod启动(作为环境变量使用,除非设置为optional);2. secret和pod必须在同一个namespace;3.单个secret大小被限制为1MB,主要为约束内存使用;

Use subPath

有时候需要在pod中的volume可能被用于多种用途,这时使用volumeMounts.subPath可以指定volume root path中的子路径来存储。

下面是示例:


apiVersion: v1
kind: Pod
metadata:
  name: my-lamp-site
spec:
    containers:
    - name: mysql
      image: mysql
      env:
      - name: MYSQL_ROOT_PASSWORD
        value: "rootpasswd"
      volumeMounts:
      - mountPath: /var/lib/mysql
        name: site-data
        subPath: mysql
    - name: php
      image: php:7.0-apache
      volumeMounts:
      - mountPath: /var/www/html
        name: site-data
        subPath: html
    volumes:
    - name: site-data
      persistentVolumeClaim:
        claimName: my-lamp-site-data


PersistentVolume

PV为cluster上的storage,可以由admin静态创建,也可以由StorageClass动态创建。PV的生命周期与Pod无关。PV与PVC之间的使用关系包括下面几个阶段。

-Provisioning

PV可以静态或动态的提供。静态的方式为cluster admin创建一系列的PV,这些PV可以被使用。

当静态创建的PV都不符合PVC的需求,cluster会使用StorageClass动态为该PVC创建PV。前提条件是PVC必须request StorageClass,而且cluster admin必须创建了StorageClass。

-Binding

创建PVC时,master节点的control loop会寻找匹配的PV进行绑定(若有)。若动态创建PV来绑定PVC,则该PV始终绑定该PVC。PVC获取的PV至少保证他们申请的空间大小,但也可能超出申请的空间大小。PVC与PV绑定是一对一的,而且是排外的。PVC若没有绑定会一直等待,直到符合条件的PV被创建。

-Using

Pod使用PVC作为volume,cluster根据PVC来找到绑定的volume并将其用于Pod中。同时,对于支持多种access modes的volume,用户可以在pod中指定使用哪种access mode。

-Storage Object in Use Protection

Use Protection的目的是保证被Pod使用的active PVC绑定的PV不会被移除,从而造成数据丢失。当active pvc被删除时,真正的删除操作会等到该PVC不再被任何Pod使用。同样,active PV被删除时,真正的删除操作也会推迟到该PV不被任何PVC使用。

当PV或PVC的status为Terminating且Finalizers中包含http://kubernetes.io/pvc-protection,相关的PV和PVC将被保护。

-Reclaiming

用完volume后,该volume将会被回收。回收策略分为Retained, Recycled, or Deleted。

Retained将会保留volume上的数据并且该PV不会被分配给其他的PVC,但管理员可以人工回收该volume(删除PV,清理storage asset上的数据,删除storage asset或创建另外的PV使用)。

Delete将删除PV以及对应的storage asset。使用StorageClass获取的PV的回收策略默认为Delete。

Recycle策略已经不被使用。

-Expanding Persistent Volumes Claims

目前默认开启PVC的扩展。只有当StorageClass的allowVolumeExpansion为true才能扩展。只需要增大PVC请求的空间大小,便会扩展绑定的PV的尺寸,而不会创建新的PV。

PV的类型作为plugin进行实现,目前k8s支持多种类型的PV。下面为PV的一个示例。


apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp
    server: 172.17.0.2


  • Capacity用来指定申请的空间大小,未来可能会添加IOPS, throughput等资源属性。
  • Volume Mode默认为filesystem来使用文件系统,但也可以指定为block来使用raw block device。
  • Access Modes可以为ReadWriteOnce(被一个node加载为read-write),ReadOnlyMany(被多个nodes加载为read-only),ReadWriteMany(被多个node加载为read-write),命令行分别为RWO,ROX, RWX。即使PV支持多种access mode,但同时只能使用一种。
  • storageClassName
  • persistentVolumeReclaimPolicy
  • Mount Options
  • Node Affinity
  • Phase

PersistentVolumeClaim

PVC是对storage的请求。下面是PVC的示例。


apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 8Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment, operator: In, values: [dev]}


大部分属性跟PV一样,resources指定申请空间大小,selector用来选定PV,目前来说指定selector的PVC不能动态绑定PV。具有相同storageClassName的PV和PVC才可以绑定,""绑定没有指定StorageClass的PV。若PVC没有指定StorageClass,则绑定default StorageClass的PV;此时若没指定default StorageClass,则绑定没指定StorageClass的PV,若指定多个default StorageClass,则PVC将不会被创建。

Pod借助于claim来使用volume,而且Pod与claim必须在同一个namespace。Cluster在pod所在的namespace中寻找claim并获取claim背后的PV,该volume被mount到host及pod中。


apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim


Storage Class

管理员在创建Storage Class时指定name和其他参数并且创建后不能改变。同时,管理员也可以指定default storage class来被PVC使用。

下面是Storage Class的示例。


apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
  - debug
volumeBindingMode: Immediate


  • Provisioner 用来指定提供volume的volume plugin的类型,为必填项。不仅可以指定internal provisioners,也可以指定external provisioners.
  • reclaimPolicy 默认为Delete,但也可指定为Retain
  • volumeBindingMode 指定volume bounding和dynamic privisioning发生的时间。默认情况下,Immediate表示PVC创建时发生。WaitForFirstConsumer表示Pod使用PVC时发生。
  • Parameters 用来描述属于Storage Class的volume,但具体有哪些参数取决于provisioner。

用户可以在PVC中声明StorageClass来动态请求volume。K8s 1.6之前,可以使用annotation volume.beta.kubernetes.io/storage-class来指定,之后使用storageClassName指定。


apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim1
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast
  resources:
    requests:
      storage: 30Gi