本文对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:
-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-config
ConfigMap is mounted as a volume, and all contents stored in itslog_level
entry 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。类型如下。
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