一、存储卷概述
Kubernetes提供的存储卷隶属于Pod资源,Pod内的所有容器共享存储卷。存储卷是定义在Pod资源之上、可以被其内部所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久化能力则取决于存储卷本身是否支持持久机制。
Kubernetes支持本地存储(节点)和网路存储,以及Secret和ConfigMap等特殊存储资源。
- 本地存储:包括emptyDir和hostPath,属于节点级别的存储卷。emptyDir的生命周期与Pod一致,使用hostPath卷的Pod一旦被调度至别的节点也将无法再使用之前的数据;所以这两种类型都不具有持久性。
- 网络存储卷:集群级别的存储卷,使用外部存储系统比如NFS、Ceph、GlusterFS等。这种类型都具有持久化。
- Secret和ConfigMap:Secret用于向Pod传递敏感数据,而ConfigMap则用于非敏感数据。
二、临时存储卷emptyDir的使用
emptyDir的生命周期和Pod的生命周期一致,也就是说一旦Pod被回收,该存储卷的数据就会丢失。所以emptyDir一般用于数据缓存或者同一个Pod内的多个容器间文件的共享。
emptyDir示例:定义了一个名为html的存储卷,挂载于容器nginx的/usr/share/nginx/html目录以及容器pagegen的/html目录。容器pagegen每隔30秒向存储卷上的index.html文件中追加一行信息,为nginx容器中的nginx进程则以其为站点主页。
apiVersion: v1
kind: Pod
metadata:
name: vol-emptydir-pod
spec:
nodeSelector:
enough: "true"
volumes:
- name: html
emptyDir: {}
containers:
- name: nginx
image: nginx:1.12-alpine
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
- name: pagegen
image: alpine
env:
- name: hostname
value: test
- name: date
value: 2019-10-28
volumeMounts:
- name: html
mountPath: /html
command: ["/bin/sh", "-c"]
args:
- while true; do
echo ${hostname} ${date} >> /html/index.html;
sleep 30;
done
emptyDir下有两个可配字段:
- medium:可取值为默认的default和Memory。default表示使用节点的默认存储介质。而Memory则使用基于RAM的临时文件系统tmpfs,空间受限于内存,但性能好,适用于为容器中的应用提供缓存空间。
- sizeLimit:当前存储卷的空间限制,默认是nil,即不做限制。但是在medium为Memory时建议务必设置。
三、节点存储卷hostPath的使用
hostPath类型的存储卷是指将工作节点上某文件系统的目录或文件挂载于Pod中。hostPath存储卷虽然能持久保存数据,但对于被调度器按需调度的应用来说并不适用。因为,如果一个Pod因自身重建或者节点崩溃而重新被调度至其他节点后将不能再使用之前节点保存的数据。
hostPath示例:
apiVersion: v1
kind: Pod
metadata:
name: vol-host-path-pod
spec:
volumes:
- name: data
hostPath:
path: /home/test
type: DirectoryOrCreate
containers:
- name: hostpath
image: busybox
command: ["sleep","3600"]
volumeMounts:
- name: data
mountPath: /test
nodeSelector:
enough: "true"
hostPath主要有两个配置字段:
- path:指定工作节点上的目录路径
- type:类型,可取值如下
- DirectoryOrCreate:指定的路径不存在时自动创建权限为0755的空目录
- Directory:必须是存在的目录路径
- FileOrCreate:路径不存在时自动创建权限为0644的空文件,属主和属组都是kubelet
- File:必须是存在的文件路径
- Socket:必须是存在的Socker文件路径
- CharDevice:必须是存在的字符设备文件路径
- BlockDevice:必须是存在的块设备文件路径
四、网络存储卷的使用(以NFS网络文件系统为例)
kubernetes适配众多专用存储系统的网络存储卷,包括传统的NAS或SAN设备(比如NFS)、分布式存储(比如GlusterFS)、云端存储(比如gcePersistentDisk)。下面就给出一个NFS存储卷的例子。
NFS(Network File System)即网络文件系统是一种分布式文件系统协议,其功能旨在允许客户端主机可以像访问本地存储一样通过网络访问服务器端文件。Kubernetes的NFS存储卷用于将事先存在的NFS服务器上导出的存储空间挂载到Pod中以供容器使用。
首先需要搭建一个NFS服务器(为了方便,作者是从k8s集群中选了一台机器IP是10.179.252.77)
(1)修改k8s集群服务的hosts文件,使之能解析nfs服务器
vim /etc/hosts
//添加一行
10.179.252.77 nfs
(2)在10.179.252.77上安装nfs-utils工具
yum -y install nfs-utils
(3)创建文件夹
mkdir /data/volumes -p
(4)创建文件
vim /data/volumes/index.html
(5)导出存储空间
vim /etc/exports
//添加一行
/data/volumes 10.179.252.77/24(rw,no_root_squash)
(6)启动NFS服务
systemctl start nfs
然后编写资源文件:
apiVersion: v1
kind: Pod
metadata:
name: vol-nfs-pod
spec:
nodeSelector:
enough: "true"
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
nfs:
server: nfs
path: /data/volumes
readOnly: false
server:NFS服务器的IP地址或者主机名
path:NFS服务器导出(共享)的文件系统目录
readOnly:是否以只读方式挂载,默认false
五、PersistentVolume以及PersistentVolumeClaim
通过使用具有持久保存数据功能的存储卷比如NFS可知,用户必须要清晰了解所有用到的网络存储系统的配置细节才能完成存储卷相关配置。这与Kubernetes的向用户可开发隐藏底层架构的目标有所背离。为此,Kubernetes利用PersistentVolume在用户和管理员之间添加了一个抽象层,从而将存储系统的使用和管理职能解耦。
- PersistentVolume(后面简称PV)是由集群管理员配置提供的某存储系统上的一段存储空间,它是对底层共享存储的抽象,将共享存储作为一种可由用户申请使用的资源,实现了“存储消费”机制。PV是集群级别的资源,不属于任何namespace。
- PersistentVolumeClaim(后面简称PVC)是供用户提出存储使用申请完成绑定的,PVC是PV的消费者。它向PV申请特定大小的空间以及访问模式(rw或ro),从而创建出PVC存储卷。最后胡再由Pod资源通过PVC存储卷关联使用。
PV、PVC示例:
(1)创建PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1
server: nfs
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: nfs
accessModes: ["ReadWriteOnce"]
capacity:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: nfs
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 20Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv004
labels:
name: pv004
spec:
nfs:
path: /data/volumes/v4
server: nfs
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv005
labels:
name: pv005
spec:
nfs:
path: /data/volumes/v5
server: nfs
accessModes: ["ReadWriteMany","ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
volumeMode: Filesystem
mountOptions:
- hard
- nfsvers=4.1
capacity:
storage: 15Gi
创建了5个PV,配置项说明:
- capacity.storage:当前PV的容量,目前仅支持空间大小设定
- accessModes:访问模式,可用值如下:
- ReadWriteOnce:仅可被单个节点读写挂载RWO
- ReadOnlyMany:可被多个节点同时只读挂载ROX
- ReadWriteMany:可被多个节点同时读写挂载RWX
- persistentVolumeReclaimPolicy:PV空间被释放时的处理机制,可用值如下
- Retain:保持不动,由管理员随后删除
- Recycle:空间回收,即删除存储卷目录下的所有文件(目前仅支持NFS和hostPath)
- Delete:删除存储卷,仅部分云端存储系统支持(比如AWS EBS等)
- volumeMode:卷模型,指定此卷可被用作文件系统还是裸格式的块设备,默认Filesystem
- storageClassName:当前PV所属的StorageClass的名称,默认为空值
- mountOptions:挂载选项组成的列表,如ro、soft、hard等
创建后的PV状态描述:
- Available:可用状态的自由资源,尚未被PVC绑定
- Bound:已经绑定至某PVC
- Released:绑定的PVC已经被删除,但资源尚未被集群回收
- Failed:因自动回收资源失败而处于故障的状态
(2)创建PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 6Gi
可选配置项说明:
- accessModes:访问模式,同PV
- resources:当前PVC存储卷需要占用的资源的的最小值
- selector:标签选择器,绑定时对PV应用的标签选择器或匹配条件表达式
- storageClass:所依赖的存储类的名称
- volumeMode:卷模型,同PV
- volumeName:用于直接指定绑定的PV的名字
上述的PVC会绑定pv004,因为pv004的空间大于PVC需要的空间,且是最接近的。
(3)在Pod中使用PVC
apiVersion: v1
kind: Pod
metadata:
name: vol-pvc
spec:
nodeSelector:
enough: "true"
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: mypvc
直接通过指定persistentVolumeClaim.claimName来使用
(4)补充下存储类(StorageClass)的概念
存储类是由管理员为管理PV和PVC之便而按需创建的类别,例如按照存储系统的性能高低分类、或者根据综合服务质量级别进行分类等等。存储类的好处之一便是支持PV的动态创建。也就是说管理员无需事先手动创建一系列的PV,系统按PVC的需求标准动态创建适配的PV。
第一步:创建ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
第二步:创建ClusterRole
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["nfs-provisioner"]
verbs: ["use"]
第三步:创建ClusterRoleBinding
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
第四步:创建NFS客户端Deployment
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccount: nfs-provisioner
nodeSelector:
enough: "true"
volumes:
- name: nfs-client-root
nfs:
server: nfs
path: /data/volumes
containers:
- name: nfs-client-provisioner
image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: lwh.cn/nfs
- name: NFS_SERVER
value: nfs
- name: NFS_PATH
value: /data/volumes
第五步:创建StorageClass
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
name: standard
provisioner: lwh.cn/nfs
第六步:验证效果—创建PVC(不用创建任何PV)
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
annotations:
volume.beta.kubernetes.io/storage-class: "standard"
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
第七步:验证效果—创建Pod引用PVC(动态创建PV)
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
nodeSelector:
enough: "true"
containers:
- name: test-pod
image: busybox
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim
六、PV和PVC的使命周期
1、存储供给:包括静态供给(事先手动创建一系列PV)和动态供给(不存在PV能匹配满足PVC时,kubernetes会尝试为PVC动态创建)
2、存储绑定:将PV和PVC绑定,若PVC未找到可匹配的PV,则PVC一直处于未绑定状态
- 存储使用
- PVC保护:万一用户删除了仍处于某Pod资源使用中的PVC时,kubernetes不会立即移除,而是推迟到不再被任何Pod资源使用后才执行删除操作
3、存储回收
- 留存:删除PVC之后,kubernetes系统不会自动删除PV,而仅仅为释放状态。这种状态下的PV不能被其他PVC申请绑定。因此之前生成的数据仍然存在,需要由管理员手动决定后续处理方案。这就意味,如果想要再次使用此PV资源,需要做以下几步:
- 删除PV
- 手动清理存储系统上留存的数据
- 手动删除存储系统的存储卷以释放空间,以便再次创建或者直接将其重新创建为PV
- 回收:存储卷上数据被删除并且PV再次变为可申请状态
- 删除:PVC被删除后直接移除PV对象。同时移除的还有PV相关的外部存储系统上的存储资产。动态创建的PV资源的回收策略取决去相关存储类上的定义。存储类默认是Delete
七、downwardAPI存储卷
很多时候,应用程序需要根据其所在的环境信息设定运行特性等,这类环境变量信息包括节点及集群的属性信息等。托管于Kubernetes的Pod对象中的容器化应用有时候也需要获取其所属Pod的IP、主机名、标签、注解、UID请求的CPU及内存资源等信息。容器可以通过环境变量或者downwardAPI存储卷访问此类信息。
第一种:环境变量式元数据注入
apiVersion: v1
kind: Pod
metadata:
name: env-test-pod
labels:
app: env-test-pod
spec:
nodeSelector:
enough: "true"
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_POD_LABEL
valueFrom:
fieldRef:
fieldPath: metadata.labels['app']
- name: MY_CPU_LIMIT
valueFrom:
resourceFieldRef:
resource: limits.cpu
- name: MY_MEMORY_REQUEST
valueFrom:
resourceFieldRef:
resource: requests.memory
divisor: 1Mi
restartPolicy: Never
可通过fieldRef字段引用的信息有:
- spec.nodeName:节点名称
- status.hostIP:节点IP地址
- metadata.namespace:Pod对象隶属的名称空间
- metadata.name:Pod对象的名称
- status.podIP:Pod地址
- spec.serviceAccountName:Pod对象使用的ServiceAccount资源的名称
- metadata.uid:Pod对象的uid
- metadata.labels['key']:标签
- metadata.annotations[''key]:注解
可通过resourceFieldRef字段引用的信息有:
- requests.cpu:cpu请求值
- requests.memory:内存请求值
- limits.cpu:cup最大限制
- limits.request:内存最大限制
最后divisor字段用于单位换算的
第二种:存储卷式元数据注入
apiVersion: v1
kind: Pod
metadata:
name: dapi-vol-pod
labels:
zone: east-china
rack: rack-101
app: dapi-vol-pod
annotations:
annotation: "test-value-1"
spec:
nodeSelector:
enough: "true"
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"
上面这个配置会在容器的/etc/podinfo目录下生成pod_namespace、pod_labels、pod_annotations、cpu_limit、requests.memory等文件用于存储Pod的各类信息。另外,标签和注解信息支持运行时修改,其改动结果会实时映射到downwardAPI生成的文件中。