存储卷
k8s存储卷(Volume)和docker存储卷功能类似,都是用来提供数据永久存储或数据共享的。不过docker存储卷是挂载在容器上,可以伴随着容器的生命周期,而k8s存储卷是挂载在pod上,可以伴随着pod的生命周期,因此pod中的容器重启不影响卷,但pod被重建时卷可能会被影响。
存储卷类型
k8s支持很多种卷,这里仅介绍一些简单的存储卷:emptyDir,hostpath,nfs, pvc、pv,configmap和secret。分布式存储卷或云存储不做介绍。
# 指定挂载卷的类型 kubectl explain pod.spec.volumes # 指定每个容器的挂载点 kubectl explain pod.spec.containers.volumeMounts
卷使用注意:
- 卷不能挂载到其他卷上,也不能具有到其他卷的硬链接。
- Pod中的每个容器必须独立指定每个卷的安装位置。
emptyDir
当Pod分配到Node上时,将会创建emptyDir,并且自动分配对应Node的某个空目录。只要Node上的Pod一直运行,emptyDir就会一直存在。本文中只有emptyDir是伴随Pod生命周期的,当Pod(不管任何原因)从Node上被删除时,emptyDir也同时会删除,存储的数据也将永久删除。注:删除Pod中容器不影响emptyDir。
emptyNode使用场景:
1)程序临时目录
2)memory:在以内存文件系统(tmps)挂载到系统中时,如果node重启,emptyDir会自动清空;同时写入emptyDir的任何文件都将计入Container的内存使用限制。
# cat emptyDir-demo.yaml apiVersion: v1 kind: Pod metadata: name: emptydir-demo namespace: default labels: demo: emptydir spec: containers: - name: nginx image: nginx:1.17.5-alpine imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 volumeMounts: - name: html mountPath: /usr/share/nginx/html/ - name: busybox image: busybox:latest imagePullPolicy: IfNotPresent command: - "/bin/sh" - "-c" - "while true; do date >> /www/index.html ;sleep 10;done" volumeMounts: - name: html mountPath: /www volumes: - name: html emptyDir: {}
创建访问测试,可以看到emptydir是共享的
# curl 10.244.1.62 Thu Dec 05 04:16:03 UTC 2019 # kubectl exec -it emptydir-demo -c nginx cat /usr/share/nginx/html/index.html Thu Dec 05 04:16:03 UTC 2019 Thu Dec 05 04:16:13 UTC 2019 Thu Dec 05 04:16:23 UTC 2019 # kubectl exec -it emptydir-demo -c busybox cat /www/index.html Thu Dec 05 04:16:03 UTC 2019 Thu Dec 05 04:16:13 UTC 2019 Thu Dec 05 04:16:23 UTC 2019
删除重建pod看下,emptydir也被删除重建了
# kubectl replace -f emptyDir-demo.yaml --force # curl 10.244.1.63 Thu Dec 05 04:24:56 UTC 2019 # kubectl exec -it emptydir-demo -c busybox tail /www/index.html Thu Dec 05 04:24:56 UTC 2019 Thu Dec 05 04:25:06 UTC 2019
hostPath
类似emptyDir,hostpath可以将本地node已存在的文件或目录挂载到pod中,且不随pod周期,实现持久化存储或共享。但是如果pod发生跨主机重建,容器hostPath文件的内容可能变动。因此hostPath一般和DaemonSet搭配使用,如本节点容器日志收集。
DirectoryOrCreate: 如果给定路径上不存在任何内容,则将根据需要在其中创建一个空目录,并将权限设置为0755。该目录与Kubelet具有相同的组和所有权,即进程必须用root权限写入目录,或手动修改挂载目录权限。
kubectl explain pod.spec.volumes.hostPath
# 因为之前测试创建那个容器时间是UTC,所以这里顺便也用hostPath修改下 # cat hostPath-demo.yaml apiVersion: v1 kind: Pod metadata: name: pod-hostpath-demo namespace: default labels: app: mynginx tier: frontend annotations: bubble/createby: "cluster admin" spec: containers: - name: nginx image: nginx:1.17.5-alpine imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 volumeMounts: - name: html mountPath: /var/log/nginx - name: localtime mountPath: /etc/localtime volumes: - name: html hostPath: path: /data/pod/hostPath type: DirectoryOrCreate - name: localtime hostPath: path: /etc/localtime type: File
访问测试,可以看到pod重建后hostpath内容没变
# kubectl exec -it pod-hostpath-demo tail /var/log/nginx/access.log 10.244.0.0 - - [05/Dec/2019:16:57:00 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "-" # kubectl replace -f pod-hostpath-demo.yaml --force pod "pod-hostpath-demo" deleted pod/pod-hostpath-demo replaced # kubectl exec -it pod-hostpath-demo tail /var/log/nginx/access.log 10.244.0.0 - - [05/Dec/2019:16:57:00 +0800] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.29.0" "-"
nfs
nfs是网络存储,可以直接作为存储卷使用,实现pod间数据共享。如果pod被删仅会自动卸载nfs,nfs中内容仍保留。
# cat nfs-demo.yaml apiVersion: v1 kind: Pod metadata: name: nfs-demo namespace: default labels: app: nfs spec: containers: - name: nginx image: nginx:1.17.5-alpine imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 volumeMounts: - name: html mountPath: /usr/share/nginx/html/ volumes: - name: html nfs: path: /data/pod/nfs server: node2.xlbubble.xyz
访问测试
# curl 10.244.1.66 nfs on node2.xlbubble.xyz # pod启动检查:确保各node能访问nfs目录
PV 和PVC
pv(PersistentVolum)是集群中的存储资源,基于物理存储。其生命周期和pod无关。
pvc(PersistentVolumClaim)是基于pv抽象的存储请求。Pod通过PVC请求物理存储。
pv卷插件支持的物理存储(Kubernetes v1.13+):本地存储,iscsi,云硬盘,Vsphere存储等,具体看官网。
创建pv
先创建类型为nfs的持久化存储卷,用于为pvc提供存储卷。
# 确认node2.xlbubble.xyz的nfs正常 [root@node2 ~]# exportfs -arv exporting *:/data/pod/v4 exporting *:/data/pod/v3 exporting *:/data/pod/v2 exporting *:/data/pod/v1 exporting *:/data/pod/nfs
可以创建多个pv,并提供各种不同的功能,包括卷大小和IO方式,IO性能等。
# cat pv-demo.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv001 labels: name: pv001 spec: nfs: # 创建nfs类型存储卷 path: /data/pod/pv1 # nfs目录 server: node2.xlbubble.xyz # nfs server accessModes: ["ReadWriteOnce"] # 仅单节点读写 capacity: # pv大小 storage: 2Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv002 labels: name: pv002 spec: nfs: path: /data/pod/pv2 server: node2.xlbubble.xyz accessModes: ["ReadWriteMany","ReadWriteOnce"] capacity: storage: 5Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv003 labels: name: pv003 spec: nfs: path: /data/pod/pv3 server: node2.xlbubble.xyz accessModes: ["ReadWriteMany","ReadWriteOnce"] capacity: storage: 10Gi --- apiVersion: v1 kind: PersistentVolume metadata: name: pv004 labels: name: pv004 spec: nfs: path: /data/pod/pv4 server: node2.xlbubble.xyz accessModes: ["ReadWriteMany","ReadWriteOnce"] capacity: storage: 20Gi
# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 2Gi RWO Retain Available 33s pv002 5Gi RWO,RWX Retain Available 33s pv003 10Gi RWO,RWX Retain Available 33s pv004 20Gi RWO,RWX Retain Available 33s
创建pvc,再创建pod并指定pvc
# cat pvc-demo.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mypvc namespace: default spec: accessModes: ["ReadWriteMany"] resources: requests: storage: 4Gi # 创建4G的pvc # cat pvc-pod-demo.yaml apiVersion: v1 kind: Pod metadata: name: pvc-demo namespace: default labels: app: mynginx tier: frontend annotations: bubble/createby: "cluster admin" spec: containers: - name: nginx image: nginx:alpine imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 volumeMounts: - name: html mountPath: /usr/share/nginx/html/ volumes: - name: html persistentVolumeClaim: claimName: mypvc
目前pvc使用绑定了pv002号存储卷,这里是自动寻找并绑定符合要求的静态pv,如果没有符合要求的pv,pvc会一直处于pending状态,直到出现符合要求的pv才创建。
静态pv: 如上。
动态pv:如果没有符合要求的pv,会将多个pv捆绑在一块,提供存储。当pv集群仍未符合要求,则pvc会一直pending,直到pv集群符合要求。动态pv基于StorageClasses,需要有支持StorageClasses的卷插件。
# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE mypvc Bound pv002 5Gi RWO,RWX 11s # kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 2Gi RWO Retain Available 22s pv002 5Gi RWO,RWX Retain Bound default/mypvc 22s pv003 10Gi RWO,RWX Retain Available 22s pv004 20Gi RWO,RWX Retain Available 22s
访问测试
# curl 10.244.2.72 pv2 on node2.xlbubble.xyz
注意! 本次测试的IO模式:nfs(rw) --> pv(rw) --> pvc(rw)。在配置过程中,要确保每次配置的存储权限都符合要求。
卷的删除
使用中的存储对象保护机制
删除pvc前k8s会先确认pvc是否有绑定pod,如果有绑定则不会立即删除,会一直等待pod那边主动解除绑定。同理,删除pv前也先确认是否有绑定pvc,如果有绑定则不会立即删除,会一直等待pvc那边主动解除绑定。Finalizers: [kubernetes.io/pvc-protection]
# kubectl describe pvc mypvc Name: mypvc Namespace: default StorageClass: Status: Bound Volume: pv003 Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"mypvc","namespace":"default"},"spec":{"accessModes"... pv.kubernetes.io/bind-completed: yes pv.kubernetes.io/bound-by-controller: yes Finalizers: [kubernetes.io/pvc-protection]
删除后的回收策略
回收策略包括:保留,删除和回收(已弃用)。
静态pv默认使用保留策略:删除pvc或pv后,数据还是会保存在原来的物理存储上,如确认无需数据,可以去手动清理。
在删除pvc后pv处于Released状态,pv仍是有pvc声明的 default/mypvc (pvc和pv一对一映射),无法在重新绑定pvc。可以手动删除重建pv或修改pv声明。
kubectl delete -f pvc-demo.yaml # kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 2Gi RWO Retain Available 93m pv002 5Gi RWO,RWX Retain Released default/mypvc 93m pv003 10Gi RWO,RWX Retain Available 93m pv004 20Gi RWO,RWX Retain Available 93m # kubectl edit pv ... claimRef: apiVersion: v1 kind: PersistentVolumeClaim name: mypvc namespace: default resourceVersion: "3498958" uid: 2a110677-d6d6-495e-8b95-5f7cbb49e104 ...
动态pv默认使用删除策略:删除pvc或pv后,原来物理存储上的数据也会被删除。
官方建议通过kubectl patch pv YOURPVNAME -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'修改pv的回收策略。
configmap和secret
To Be Continued
参考与扩展
k8s-storage