[TOC]
0x00 前言简述 描述: 我们知道在Docker中可以通过Volume将宿主机文件(配置文件、数据库等
等)映射到Container内部供其容器内的应用程序使用。在Kubernrtes中我们可以采用ConfigMap控制器
创建共享应用配置,亦可采用Kubernetes中的volume
(卷)在一个Pod内多个Container之间进行文件共享;
Q: K8s 与 Docker 两者之间卷的区别?
1) K8S 的 Volume (卷) 定义在Pod之上被同一个Pod内的多个容器挂载到具体文件之下便于文件的共享;
2) K8S 的 Volume (卷) 与 Pod 的生命周期相同(持久卷除外),即Pod销毁后Volume也会被销毁,但Pod中容器重启或者终止并不会导致Volumes中数据的丢失;
3) K8S 的 VOlume (卷) 支持多种分布式文件系统,例如GlusterFS、NFS、Ceph、
Volume 使用流程
1) 声明一个Volume(可以是多种类型)
2) 在容器内引用该Volume并Mount到Pod容器里的某个指定文件目录之中;
k8s持久化存储方案 描述: 共享存储为分布式系统非常重要的一部分,存储一般要求稳定、可用、性能、可靠。
1.从用户角度看 存储就是一块盘或者一个目录,用户不关心盘或者目录如何实现,用户要求非常“简单”,就是稳定,性能好。为了能够提供稳定可靠的存储产品,各个厂家推出了各种各样的存储技术和概念。为了能够让大家有一个整体认识,本文先介绍存储中的这些概念。
2.从存储介质角度 存储介质分为机械硬盘和固态硬盘(SSD)。机械硬盘泛指采用磁头寻址的磁盘设备,包括SATA硬盘和SAS硬盘。由于采用磁头寻址,机械硬盘性能一般,随机IOPS一般在200左右,顺序带宽在150MB/s左右。固态硬盘是指采用Flash/DRAM芯片+控制器组成的设备,根据协议的不同,又分为SATA SSD,SAS SSD,PCIe SSD和NVMe SSD。
3.从产品定义角度 存储分为本地存储(DAS),网络存储(NAS),存储局域网(SAN)和软件定义存储(SDS)四大类
DAS就是本地盘,直接插到服务器上
NAS是指提供NFS协议的NAS设备,通常采用磁盘阵列+协议网关的方式
SAN跟NAS类似,提供SCSI/iSCSI协议,后端是磁盘阵列
SDS是一种泛指,包括分布式NAS(并行文件系统),ServerSAN等
4.从应用场景角度 存储分为文件存储(Posix/MPI),块存储(iSCSI/Qemu)和对象存储(S3/Swift)三大类。
Kubernetes是如何给存储定义和分类呢?
1.Kubernetes中跟存储相关的概念有PersistentVolume (PV)和PersistentVolumeClaim(PVC),PV又分为静态PV和动态PV。静态PV方式如下:
PV-PVC
2.动态PV需要引入StorageClass的概念,使用方式如下:
StorageClass
Kubernetes 常见类型以及支持的卷
configMap (常用)
secret (常用)
emptyDir (常用)
hostPath (常用)
nfs (常用)
persistentVolumeClaim (常用)
cephfs (常用)
awsElasticBlockStore azureDisk azureFile csi downwardAPI
fc flocker gcePersistentDisk gitRepo glusterfs iscsi local
projected portworxVolume quobyte rbd scaleI0
storageos vsphereVolume
0x01 Storage - 存储 描述: 以下都是 statefulSet 控制器
涉及有状态服务必须拥有的一些资源对象
1) configMap : 在k8s中专门用来存储配置文件。
2) Secret : 有一些需要加密的信息,例如密钥、用户名密码信息在Secret中可以被加密,是k8s中加密的解决方案【base64】
。
3) Volume : 用于赋予k8s中pod共享存储卷的能力,例如可以通过nfs共享,本地磁盘目录共享等等。
4) Persistent Volume : 简称PV【持久卷】,还包含一个PVC,通过服务进行持久卷的构建。
5) StorageClass : 存储类可以动态的绑定PV(持久卷)和创建PVC(持久卷要求)。
6) Nfs / Cephfs : 常用的分布式共享存储解决方案。
1.ConfigMap 描述: 在部署应用时常常需要给应用程序提供一些配置信息,比如Database的IP地址和开放端口以及用户密码等;
常用的简单方法有如下几种:
1) 通过构建镜像时(Build)将应用配置文件打入Docker Images : 不灵活且以明文不安全,并且修改配置还需重新构建打包Image;
2) 通过environment(环境环境)传参 : 不灵活且明文传入,而且更新环境变量时资源对象将需要重新部署;
3) 配置管理中心 : 例如 百度 Disconf 、360 的qconf配置管理,在大规模的分布式系统之中将分布式应用所需的配置信息与应用程序进行分离及其重要,它可以极大的简化应用配置管理工作;
4) 通过K8s提供的 ConfigMap 资源对象进行实现;
Tips : DisConf 将 X/Y/Z 多个业务平台进行配置统一管理, 其主要实现目标是对于同一个上线包,无需改动配置,即可在多个环境(DEV/TEST/STAGE/PRODUCTION
)上线,简单的说其实现了配置信息与应用程序进行解耦操作
;1 2 3 - 1) 动态化更改配置部署 : 当更改配置后无需重新打包或重启,即可实时生效; - 2) 配置统一管理 : 提供Web平台统一管理多个配置的应用环境(`DEV/TEST/STAGE/PRODUCTION`),以及多个产品的所有配置; - 3) 核心目标 : 一个 Jar 包到处运行;
configMap - 介绍 描述: ConfigMap 功能在 Kubernetes 1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量
中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制(即配置的一种统一管理方法
,使得应用程序与配置信息进行解耦),ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON 二进制大对象。
简单的说: 通过ConfigMap能够把配置以Key-Value
对的形式供其它资源对象消费;
Q: configMap 配置文件注册中心
A: 在集群中有一个配置文件注册中心,集群中的节点向配置中心索要属于自身的配置,配置中心根据各节点的主机名或ip地址进行分发配置。 作用: k8s创建configMap,pod去引用,来达到类似于配置文件注册中心的效果。
Tips : 不同集群获取到的信息是不一样的需要使用者自己配置;
configMap - 创建 描述: configMap的几种创建方式进行实践演示基本创建格式:kubectl create configmap [map-name] [data-source]
0) 通过--from-file
参数进行目录批量&单一文件configMap创建实践流程:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 cat > ~/K8s/Day8/demo1/dir1.properties <<'EOF' dir.number=1 dir.name=demo1 dir.path=~/K8s/Day8/demo1 EOF cat > ~/K8s/Day8/demo1/dir2.properties <<'EOF' dir.number=2 dir.name=demo2 dir.path=~/K8s/Day8/demo2 EOF cat > ~/K8s/Day8/demo2/file.properties <<'EOF' file.number=2 file.name=file.properties file.path=~/K8s/Day8/demo2/file.properties file.html=configMap Test - WeiyiGeek EOF ~/K8s/Day8/demo1$ ls ~/K8s/Day8/demo1$ kubectl create configmap dir-config --from-file=/home/weiyigeek/K8s/Day8/demo1/ ~/K8s/Day8/demo1$ kubectl create configmap file-config --from-file=/home/weiyigeek/K8s/Day8/demo2/file.properties ~/K8s/Day8/demo1$ kubectl create configmap file-config-aliase --from-file file-key=~/K8s/Day8/demo2/file.properties ~/K8s/Day8/demo2$ kubectl get cm file-config-aliase ~/K8s/Day8/demo2$ kubectl get cm file-config-aliase -o yaml
Tips : 如需要通过多个文件创建configMap可以分别通过多个 –from-file 参数进行制定对应的配置文件;
1) 通过 --from-env-file
参数进行创建configMap与上面的方式不同,此种形式对env-file的内容格式有则严格的要求,并且如果您提供了多个env-file文件
仅最后一个文件的内容生效;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 cat > ~/K8s/Day8/demo2/env-file.properties <<'END' name=weiyigeek age=88 allowed="True" END kubectl create configmap env-file --from-env-file env-file.properties ~/K8s/Day8/demo2$ kubectl get cm env-file -o yaml
2) 字面值创建简单示例:1 2 3 ~/K8s/Day8/demo2 $ kubectl create configmap text-config --from-literal=my.name=weiyigeek --from-literal=my.age=18 --from-literal=my.site=http://www.weiyigeek.top
Tips :通过字面值创建的configMap不便于配置管理的记录
所以不推荐使用;
3) 以资源清单创建1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mkdir -pv ~/K8s/Day8/demo3/ cat > ~/K8s/Day8/demo3/configmap.yaml <<'EOF' apiVersion: v1 kind: ConfigMap metadata: name: configmap-demo namespace: default data: special.name: weiyigeek special.site: www.weiyigeek.top EOF ~/K8s/Day8/demo3$ kubectl create -f configmap.yaml configmap/configmap-demo created
configMap - 操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ~/K8s/Day8/demo3$ kubectl api-resources | grep "configmap" ~/K8s/Day8/demo3$ kubectl get cm ~/K8s/Day8/demo3$ kubectl describe cm file-config ~/K8s/Day8/demo3$ kubectl delete cm flie-config
configMap - 使用
(1) 创建Pod资源控制器并以ConfigMap作为环境变量&命令行参数(非常值得学习)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 cat > configmap-use-1.yaml<<'EOF' # Pod Controller apiVersion: v1 kind: Pod metadata: name: configmap-env-demo spec: containers: - name: configmap-env-demo image: harbor.weiyigeek.top/test/busbox:latest imagePullPolicy: IfNotPresent command: [ "/bin/sh" , "-c" , "env; echo Name: ${USERNAME_KEY}, Site: ${SITE_KEY}" ] env: - name: USERNAME_KEY valueFrom: configMapKeyRef: name: configmap-demo key: special.name - name: SITE_KEY valueFrom: configMapKeyRef: name: configmap-demo key: special.site envFrom: - configMapRef: name: configmap-demo restartPolicy: Never EOF
configMap使用操作流程:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ~/K8s/Day8/demo3$ docker tag busybox:latest harbor.weiyigeek.top/test /busbox:latest ~/K8s/Day8/demo3$ docker push harbor.weiyigeek.top/test /busbox:latest ~$ kubectl create -f configmap-use-1.yaml ~$ kubectl get pod $ kubectl logs configmap-env-demo
(2) 创建Pod资源控制器并通过数据卷(Volume)插件使用ConfigMap对象&热更新1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 cat > configmap-use-3.yaml<<'EOF' apiVersion: apps/v1 kind: Deployment metadata: name: configmap-update-demo spec: replicas: 1 selector: matchLabels: app: configmap-update-demo template: metadata: labels: app: configmap-update-demo spec: containers: - name: configmap-update-demo image: harbor.weiyigeek.top/test /nginx:v3.0 imagePullPolicy: IfNotPresent volumeMounts: - name: config-volume mountPath: /usr/share/nginx/html/ volumes: - name: config-volume configMap: name: file-config EOF $ kubectl create -f configmap-use-3.yaml ~/K8s/Day8/demo4$ kubectl get pod -o wide --show-labels ~/K8s/Day8/demo4$ kubectl describe cm file-config ~/K8s/Day8/demo4$ kubectl exec `kubectl get pods -l app=configmap-update-demo -o name` -it -- sh -c "ls /usr/share/nginx/html" file.properties ~/K8s/Day8/demo4$ curl http://10.244.1.131/file.properties ~/K8s/Day8/demo4$ kubectl edit cm file-config ~/K8s/Day8/demo4$ curl http://10.244.1.131/file.properties ~/K8s/Day8/demo4$ kubectl exec `kubectl get pods -l app=configmap-update-demo -o name` -it -- sh -c "cat /usr/share/nginx/html/file.properties" ~/K8s/Day8/demo4$ kubectl logs configmap-update-demo
Tips : configMap 资源对象消费常用于Pod环境变量消费、Pod command 中消费、Volume 中消费
;
Tips : 非常注意更新 ConfigMap 后数据同步会有一定延迟
1.使用该 ConfigMap 挂载的Env不会同步更新
2.使用该 ConfigMap 挂载的Volume中的数据需要一段时间(实测大概10秒)才能同步更新
2.Secret 描述: 前面我们说过在k8s中利用ConfigMap
控制器可以去保存配置文件以及一些数据, 这些数据可以被导入到Pod内部
成为环境变量或者文件,从而可以达到热更新的目的, 带来便利的同时却有一定的安全问题(保存的配置以明文的形式保存的),所以密码文件、密钥文件类型的文件通过ConfigMap
去保存就不是很合适,在k8s还有一种保存机制即Secret
。
简单的说: Secret 主要用于保存需要进行加密访问的的配置,解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec
中。
在k8s资源清单中1 2 3 $ kubectl api-resources | grep "secret" NAME NAMESPACED KIND secrets true Secret
Q: Secret的应用场景
答: Secret 可以Volume卷或者环境变量
的方式进行消费使用。
Secret 的三种类型
(1) Service Account
: 由 Kubernetes 自动创建用来访问Kubernetes API (所以其访问安全至关重要常规的应用一般不涉及配置它
),并且会自动挂载到Pod的 /run/secrets/kubernetes.io/serviceaccount 目录中
(2) Opaque
: Base64编码格式的Secret 用来存储密码、密钥等,注意加密程度并不高。
(3) Kubernetes.io/dockerconfigjson
: 将私有仓库的登陆认证信息进行存储;
Service Account 描述: 由Kubernetes 自动创建, 它用于访问我们的 Kubernetes API
并且会自动挂载到Pod的 /run/secrets/kubernetes.io/serviceaccount
目录中注意其并不需要我们手动去管理和创建
。
实践演示:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ kubectl get secret $ kubectl get pod configmap-update-demo-597f67f4df-v82j6 $ kubectl exec configmap-update-demo-597f67f4df-v82j6 -it -- bash root@configmap-update-demo-597f67f4df-v82j6:/$ ls /run/secrets/kubernetes.io/serviceaccount/ & cd $_ root@configmap-update-demo-597f67f4df-v82j6:$ /run/secrets/kubernetes.io/serviceaccount
Opaque Secret 描述: Opaque 英 /əʊˈpeɪk/
Secret 类型的数据是一个 map 类型,并且要求 Value 是 Base64 编码格式;
资源清单示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 cat > Opaque-Secret.yaml<<'EOF' apiVersion: v1 kind: Secret metadata: name: secret-test type: Opaque data: name: dXNlcm5hbWU= pass: d2VpeWlnZWVr EOF ~$ echo -n "username" | base64 dXNlcm5hbWU= ~$ echo -n "weiyigeek" | base64 d2VpeWlnZWVr # - 输出指定字符的base64解密形式 echo -n "d2VpeWlnZWVr" | base64 -d # weiyigeek
Opaque Secret 创建与查看: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ~/K8s/Day8/demo5$ kubectl create -f Opaque-Secret.yaml ~/K8s/Day8/demo5$ kubectl get secret ~/K8s/Day8/demo5$ kubectl describe secret secret-test
下面演示 Opaque Secret 在实际环境中的两种使用方式;
1) 将Secret挂载到Volume
中将会生成文件
2) 将Secret导出到环境变量(env)
中以供脚本使用
资源清单示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 cat > opaque-Secret-1.yaml<<'EOF' # 1) Volume 挂载使用 apiVersion: v1 kind: Pod metadata: name: seret-test-1 labels: name: seret-test-1 spec: volumes: - name: secrets secret: secretName: secret-test containers: - name: seret-test-1 image: harbor.weiyigeek.top/test/busbox:latest command: [ "/bin/sh" , "-c" , "ls /etc/secrets; sleep 700" ] imagePullPolicy: IfNotPresent volumeMounts: - name: secrets mountPath: "/etc/secrets" readOnly: true --- apiVersion: apps/v1 kind: Deployment metadata: name: seret-test-2 spec: replicas: 2 selector: matchLabels: app: seret-test-2 template: metadata: labels: app: seret-test-2 spec: containers: - name: seret-test-2 image: harbor.weiyigeek.top/test/busbox:latest imagePullPolicy: IfNotPresent command: [ "/bin/sh" , "-c" , "env; echo Name: ${USERNAME}, Pass: ${PASSWORD}; sleep 700" ] env: - name: USERNAME valueFrom: secretKeyRef: name: secret-test key: name - name: PASSWORD valueFrom: secretKeyRef: name: secret-test key: pass EOF
操作流程:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 ~/K8s/Day8/demo5$ kubectl create -f opaque-Secret-1.yaml ~/K8s/Day8/demo5$ kubectl get pod -o wide --show-labels ~/K8s/Day8/demo5$ kubectl logs seret-test-1 ~/K8s/Day8/demo5$ kubectl exec seret-test-1 -- sh -c "cat /etc/secrets/pass" ~/K8s/Day8/demo5$ kubectl exec -it seret-test-1 -- sh ~/K8s/Day8/demo5$ kubectl logs seret-test-2-5bffbffc7d-444db ~/K8s/Day8/demo5$ kubectl logs seret-test-2-5bffbffc7d-c5mqv | egrep "username|weiyigeek"
kubernetes.io/dockerconfigjson 描述: 它可以设置我们私有镜像仓库信息帮助我们在没有进行docker login
的Node节点上也可以拉取私有镜像;
演示示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 kuberctl create secret docker-registry private-registry-key \ - -docker-server=harbor.weiyigeel.top \ - -docker-username=weiyigeek \ - -docker-password=Harbor12345 \ - -docker-email=master@weiyigeek.top apiVersion: v1 kind: Pod metadata: name: docker-config-json spec: containers: - name: busybox image: harbor.weiyigeel.top/private/busybox:v1.0 imagePullSecrets: - name: private-registry-key
3.Volume 描述: 由于容器磁盘上文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。
首先当容器崩溃时kubelet会重启它,但是容器中的文件将丢失,容器以干净的状态(镜像最初的状态)重新启动。其次,在Pod 中同时运行多个容器时,这些容器之间通常需要共享文件。
Kubernetes 中的 Volume 抽象就很好的解决了这些问题。
Volume 生命周期 : Kubernetes中的卷有明确的寿命与封装它的Pod相同, 所以卷的生命比 Pod 中的所有容器都长
Volume 作用 : 当这个容器重启时数据仍然得以保存, 注意当 Pod不再存在时卷也将不复存在
, 也许更重要的是Kubernetes支持多种类型的卷,Pod 可以同时使用任意数量的卷。
PS : 在 Docker 中如果 restartPolicy
设置为always时容器因docker崩溃重启时将会保留数据,但是在K8s中并不会这样所以我们需要用到持久卷保证容器中指定数据的留存
;
下面实践中讲解一些经常使用以及后续遇到的一些卷配置使用:
emptyDir - 空卷 描述: 正如卷的名字所述它最初是空的,其作用是可以在不同的容器中相同或者不同路径进行文件共享,当 Pod 被分配给节点时,首先创建 emptyDir 卷,并且只要该 Pod 在该节点上运行该卷就会存在,Pod中的容器可以读取和写入 emptyDir 卷中的相同文件,但是当出于任何原因从节点中删除Pod时,emptyDir 中的数据也将被永久删除
;
emptyDir 使用场景:
1.Pod 内多容器共享目录,例如一个Pod内的多个Container共享一个目录(生产者 <-> 消费者
)
2.暂存空间(临时目录), 例如用于基于磁盘的合并排序,用作长时间计算崩溃恢复时的检查点(即该目录无需持久化保留
)
3.Web服务器容器提供数据时,保存内容管理器容器提取的文件
PS : 容器崩溃不会从节点中移除 pod,因此 emptyDir
卷中的数据在容器崩溃时是安全的;
资源清单示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 cat > emptydir-demo-1.yaml<<'EOF' # 1) Volume 挂载使用 apiVersion: v1 kind: Pod metadata: name: emptydir-test-1 labels: name: emptydir-test-1 spec: volumes: - name: cachedir emptyDir: {} containers: - name: emptydir-pod-1 image: harbor.weiyigeek.top/test/busbox:latest command: [ "/bin/sh" , "-c" ,"sleep 700 " ] imagePullPolicy: IfNotPresent volumeMounts: - name: cachedir # 引用卷配置名称 mountPath: " /cache/pod1" - name: emptydir-pod-2 image: harbor.weiyigeek.top/test/busbox:latest command: [ "/bin/sh" , "-c" ,"sleep 700 " ] imagePullPolicy: IfNotPresent volumeMounts: - name: cachedir mountPath: " /cache/pod2" restartPolicy: Never EOF
操作流程:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ~/K8s/Day8/demo6$ kubectl create -f emptydir-demo-1.yaml ~/K8s/Day8/demo6$ kubectl get pod -o wide --show-labels weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-1 -it -- sh / / / emptydir-test-1-Wed Nov 18 03:43:48 UTC 2020 / weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-2 -it -- sh -c "echo $HOSTNAME -$(date) >> /cache/pod2/time.log" weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-1 -it -- sh -c "cat /cache/pod1/time.log" weiyigeek@ubuntu:~$ kubectl exec emptydir-test-1 -c emptydir-pod-2 -it -- sh -c "cat /cache/pod2/time.log" $ kubectl delete pod emptydir-test-1 pod "emptydir-test-1" deleted
hostPath - 主机路径卷 描述:与emptyDir方式直接将目录或者文件写在Container内部不同,hostPath 卷将主机节点(宿主机)的文件系统(FileSystem
)中的文件或目录挂载到集群(Cluster)中
类似于docker中使用的 -v 宿主机目录:容器挂载目录
, 使用其的好处是可以与任何存储进行对接使用;
hostPath 与 emptyDir 比较
(1) emptyDir 直接将目录或者文件写在Container内部, 而 HostPath 可以使用宿主机的高速文件系统
以及更好的持久化;
(2) emptyDir 被限制在一个Pod共享文件或目录中, 而 HostPath 可以实现跨Pod共享;
hostPath 使用场景:
1.运行需要访问 Docker 内部的容器(即容器内部使用容器
), 例如使用 /var/lib/docker 以及 /var/run/docker.sock
的 hostPath以及依赖挂载的方式;
2.在容器中运行 cAdvisor, 使用 /dev/cgroups 的 hostPath;
3.同一个宿主机跨Pod共享文件或者;
Tips: 允许 pod 指定给定的 hostPath 是否应该在 pod 运行之前存在,是否应该创建以及它应该以什么形式存在(除了所需的 path 属性之外,用户还可以为 hostPath 卷指定 type);
hostPath 卷指定 type 类型说明:
空字符串 : (默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。
DirectoryOrCreate : 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为0755,与Kubelet 具有相同的组和所有权。
Directory : 给定的路径下必须存在目录
FileOrCreate : 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为0644,与Kubelet具有相同的组和所有权。
File : 给定的路径下必须存在文件
Socket : 给定的路径下必须存在UNIX套接字
CharDevic : 给定的路径下必须存在字符设备
BlockDevice : 给定的路径下必须存在块设备
注意事项:
1.由于每个节点上的文件都不同,具有相同配置(例如从 podTemplate 创建的
)的pod在不同节点上的行为可能会导致访问的结果不一致
;
2.当Kubernetes 按照计划添加资源感知调度时,将无法考虑 hostPath 使用的资源;
3.在底层主机上创建的文件或目录只能由 root 写入或者其他可读可写用户。您需要在特权容器中以 root 身份运行进程或修改主机上的文件权限以便写入 hostPath 卷;
4.hostPath使用宿主机Qutoa不会纳入或记录到k8s的资源管理配额管理中;
资源清单示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 cat > hostPath-demo.yaml<<'EOF' apiVersion: apps/v1 kind: Deployment metadata: name: hostpath-test-1 spec: replicas: 2 selector: matchLabels: app: hostpath-test-1 template: metadata: labels: app: hostpath-test-1 spec: volumes: - name: test-volume hostPath: type: DirectoryOrCreate path: /data containers: - name: hostpath-test image: harbor.weiyigeek.top/test/busbox:latest imagePullPolicy: IfNotPresent command: [ "/bin/sh" , "-c" , "sleep 700" ] volumeMounts: - name: test-volume mountPath: /disk/hostpath/ EOF
Tips : Deployment 不支持 restartPolicy: Never 其参数选项必须是 Always
操作流程: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 $ kubectl create -f hostPath-demo.yaml ~/K8s/Day8/demo6$ kubectl get pod -o wide --show-labels ~/K8s/Day8/demo6$ kubectl describe pod hostpath-test-1-785b9544b9-sv2wh $ kubectl exec -it hostpath-test-1-785b9544b9-n5vcz -- sh / $ echo $(id)-$(date)-$(pwd ) > /disk/hostpath/hostpath.log / $ cat /disk/hostpath/hostpath.log $ kubectl exec -it hostpath-test-1-785b9544b9-sv2wh -- sh / $ echo $HOSTNAME -$(date)-$(pwd ) >> /disk/hostpath/hostpath.log / $ cat /disk/hostpath/hostpath.log kubectl exec pod/hostpath-test-1-785b9544b9-n5vcz -it -- sh -c "cat /disk/hostpath/hostpath.log" kubectl exec pod/hostpath-test-1-785b9544b9-sv2wh -it -- sh -c "cat /disk/hostpath/hostpath.log" weiyigeek@Ubuntu-PC:~$ ansible k8s-node-4 -a "cat /data/hostpath.log" weiyigeek@Ubuntu-PC:~$ ansible k8s-node-5 -a "cat /data/hostpath.log" $ kubectl delete pod --all $ kubectl get pod -o wide $ kubectl get pod -o name kubectl exec pod/hostpath-test-1-785b9544b9-h5dbw -it -- sh -c "cat /disk/hostpath/hostpath.log" 0 kubectl exec pod/hostpath-test-1-785b9544b9-hnngk -it -- sh -c "cat /disk/hostpath/hostpath.log" $ kubectl delete deploy hostpath-test-1
Tips : Deployment 中只有在副本数为1的运行时候此种方式持久化是没有没有问题的因为一把情况下它会运行在初次运行的节点之上,但是如果多个副本数则需要下面的PVC持久化卷才能进行数据的持久化与一致性;
Tips : 通过控制器创建的Pod共享了宿主机上的目录,做到了Node层面的数据持久化,但需要注意删除Pod后宿主机上的Volume仍然存在需要按需清理;
Tips : 通过 docker 的 voulme 命令选择查看与删除未使用的voulme或者通过docker volume rm -force 强制删除一个护或者多个Volume;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 $ docker volume ls $ docker volume inspect 1b39579ff10934e81a10652ba201635141db80aea377e8a7c322533673f64a80 ~$ docker volume prune
入坑计:
问题1.配置Volume时各node节点可能没有/data目录此时需要type指定为DirectoryOrCreate,即目录不存在时创建目录(巨坑);1 2 3 4 5 6 7 8 9 10 ~/K8s/Day8/demo6$ kubectl get pod ~/K8s/Day8/demo6$ kubectl describe pod hostpath-test-1-565b986876-qgfhk
nfs - 分布式共享存储卷 描述: 我们可以采用nfs直接给我们的Pod定义一个NFS类型的卷Volume示例如下;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 cat > nfs-volume-demo.yaml<<'EOF' apiVersion: apps/v1 kind: Deployment metadata: name: nfs-test-1 spec: replicas: 1 selector: matchLabels: app: nfs-test-1 template: metadata: labels: app: nfs-test-1 spec: volumes: - name: test -volume nfs: server: 192.168.1.253 path: '/data' containers: - name: nfs-test image: harbor.weiyigeek.top/test /busbox:latest imagePullPolicy: IfNotPresent command : [ "/bin/sh" , "-c" , "sleep 700" ] volumeMounts: - name: test -volume mountPath: /app/tools/ EOF
Tips : 此处针对于无状态服务创建,如果是有状态服务请采用statefulset
控制创建;
4.PersistentVolume & PersistentVolumeClaim 描述: 默认情况下对于运行的容器,其对文件系统的写入都发生在其分层文件系统的可写层的(Copy-On-Write), 当迁移应用程序从开发到生产环境时,开发与运维面临着挑战,为了防止容器挂掉、崩溃或者运行结束时,任何与之相关的数据都将会丢失,为了解决这个问题引发的数据丢失,所以我们需要将数据存储持久化也就是本章主题(Persistent Volume [Claim]);
基础概念 Q: 什么是PersistentVolume(PV)?
答: PV是Volume之类的卷插件,但具有独立于使用PV的Pod的生命周期、不支持命名空间划分。它是由管理员设置的存储并且是属于群集的一部分(资源) 此API对象包含存储实现的细节,即NFS、iSCSl或特定于云供应商的存储系统。
Q: 什么是PersistentVolumeClaim (PVC) ?
答: PVC 是用户存储的请求它与Pod比较相似,支持命名空间的划分
,例如 Pod 消耗节点资源,而PVC消耗PV资源 又例如Pod可以请求特定级别的资源(CPU和内存),而PVC 声明可以请求特定的大小和访问模式(例如,可以以读/写一次或只读多次模式挂载)
PV/PVC生命周期流程 答: 供应准备 -> 绑定 -> 使用 -> 释放 -> 回收(Reclaiming)
持久卷(PV)插件类型 描述: PersistentVolume类型以插件形式实现下面是K8s目前支持的一些插件类型;
·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 Scalelo Volumes StorageOS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 AWSElasticBlockStore ✓ - - AzureFile ✓ ✓ ✓ AzureDisk ✓ - - CephFS ✓ ✓ ✓ Cinder ✓ - - CSI 取决于驱动 取决于驱动 取决于驱动 FC ✓ ✓ - FlexVolume ✓ ✓ 取决于驱动 Flocker ✓ - - GCEPersistentDisk ✓ ✓ - Glusterfs ✓ ✓ ✓ HostPath ✓ - - iSCSI ✓ ✓ - Quobyte ✓ ✓ ✓ NFS ✓ ✓ ✓ RBD ✓ ✓ - VsphereVolume ✓ - - (Pod 运行于同一节点上时可行) PortworxVolume ✓ - ✓ ScaleIO ✓ ✓ - StorageOS ✓ - -
参考地址: https://kubernetes.io/zh/docs/concepts/storage/persistent-volumes/#access-modes
PV 分类 描述: 供应准备通过集群外的存储系统或者云平台来提供存储持久化支持;
1) 静态pv : 由集群管理员创建一些PV。它们带有可供群集用户使用的实际存储的细节。它们存在于KubernetesAPl中可用于消费。
2) 动态pv : 当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim
时,集群可能会尝试动态地为PVC创建卷。此配置基于StorageClasse:PVC必须请求[存储类]
,并且管理员必须创建并配置该类才能进行动态创建。声明该类为空可以有效地禁用其动态配置;要启用基于存储级别的动态存储配置,集群管理员需要启用API server上的DefaultStorageClass[准入控制器]
。
例如,通过确保 DefaultStorageClass位于API server 组件的–admission-control标志,使用逗号分隔的有序值列表中,可以完成此操作。
PV 绑定 Q: 什么是绑定PV以及为啥需要绑定?
答: 由于 PVC 需要对接绑定一个PV才能够正常使用, 由于 master 中的控制环路监视新的PVC,寻找匹配的PV(如果可能),并将它们绑定在一起。 如果为新的PVC动态调配PV,则该环路将始终将该PV绑定到PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。一旦PV和PVC绑定后 PersistentVolumeClaim 绑定是排他性的
不管它们是如何绑定的。简单的说PVC跟PV绑定是一对一的映射一旦PV绑定后便不可与其它PVC绑定。
持久卷(PV)状态 描述: PV卷可以处于以下的某种状态;
·Available(可用) - 空闲资源还没有被任何声明绑定
·Bound(已绑定) - 卷已经被声明绑定
·Released(已释放)- 声明被删除,但是资源还未被集群重新声明
·Failed(失败)- 该卷的自动回收失败
PV 使用 描述: 用户可在Pod中像Volume一样使用PVC;
持久卷(PV)声明的保护 描述: PVC保护的目的是确保由pod正在使用的PVC不会从系统中移除,因为如果被移除的话可能会导致数据丢失当启用PVC保护 alpha功能时
,如果用户删除了一个pod 正在使用的PVC,则该PVC不会被立即删除。PVC的删除将被推迟,直到PVC不再被任何 pod 使用;
持久卷(PV)访问模式 描述: PersistentVolume可以资源提供者支持的任何方式挂载到主机上, 如下表所示供应商具有不同的功能,每个PV的访问模式都将被设置为该卷支持的特定模式。 例如,NFS可以支持多个读/写客户端
,但特定的NFSPV可能以只读方式导出到服务器上。每个PV都有一套自己的用来描述特定功能的访问模式;
·RWO (Cli 格式简写
)-ReadWriteOnce 该卷可以被单个节点以读/写模式挂载
·ROX-ReadOnlyMany - 该卷可以被多个节点以只读模式挂载
·RWX-ReadWriteMany - 该卷可以被多个节点以读/写模式挂载
PS : 每个卷只能同一时刻只能以一种访问模式挂载,即使该卷能够支持 多种访问模式。例如GCEPersistentDisk
可以由单个节点作为ReadWriteOnce
模式挂载,或由多个节点以ReadOnlyMany
模式挂载,但不能同时挂载 ;
PV 释放 描述: 用户删除PVC来回收存储资源此时PV将变成Released状态,由于其还保留着之前的数据,该数据需要根据不同的策略来处理,否则这些存储资源无法被其它PVC使用;
PV 回收 描述: 持久卷(PV) 回收的几种策略
·Retain(保留)– 允许人工处理保留的数据;
·Recycle (deprecated) – 基本擦除将删除PV与外部关联的存储资源 (rm -rf /thevolume/*),注意:需要插件支持
·Delete(删除)– 关联的存储资产(例如AWS EBS、GCE PD、Azure Disk 和OpenStack Cinder卷)将被删除, 注意:需要插件支持
PS :当前只有 NFS 和 HostPath 支持回收策略基本不用(Recycle已被废弃)。AWS EBS、GCE PD、Azure Disk 和Cinder 卷支持删除策略;
PV/PVC 举例 Volume API 资源清单:1 2 3 ~/K8s/Day8/demo6$ kubectl api-resources | grep "volume"
PV(持久卷)NFS 类型 与 HostPath 类型资源清单:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 apiVersion: v1 kind: PersistentVolume metadata: name: nfs-test spec: capacity: storage: 5 Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: slow mountOptions: - hard - nfsvers=4.1 nfs: path: /tmp server: 172.17 .0 .2 --- apiVersion: v1 kind: PersistentVolume metadata: name: hostpath-test spec: capacity: storage: 2 Gi accessModes: - ReadWriteOnce hostPath: path: /tmp/local
PS : storageClassName 表示所属卷的 StorageClass 的名称,空值意味着此卷不属于任何存储类, 他是PVC绑定到PV卷的重要指标。
PVC(持久卷)NFS 类型 与 HostPath 类型资源清单:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-test-claim spec: accessModes: ["ReadWriteOnce"] storageClassName: slow volumeMode: Filesystem resources: requests: storage: 5 Gi selector: matchLabels: release: "stable" matchExpressions: - {key: environment, operator: In, values: [dev]} --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: hostpath-test-claim spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 1 Gi
Pod 消费 PV 卷资源清单示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion: v1 kind: Pod metadata: name: pv-consumer spec: containers: - name: myfrontend image: nginx:latest imagePullPolicy: ifNotPresent ports: - containerPort: 80 name: web volumeMounts: - name: pv-claim mountPath: /usr/share/nginx/html volumes: - name: pv-claim persistentVolumeClaim: claimName: nfs-test-claim
PV/PVC 实例 NFS 持久化卷配置演示
Step 1.NFS 服务端与客户端配置1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 > ansible admin -a "sudo apt install -y nfs-kernel-server rpcbind" sudo mkdir -pv /nfs/data{1..4} /nfs/datamkdir sudo chmod -R 777 /nfs sudo chown -R nobody /nfs sudo tee /etc/exports <<'EOF' /nfs/data *(rw,no_root_squash,no_all_squash,sync) /nfs/data1 *(rw,no_root_squash,no_all_squash,sync) /nfs/data2 *(rw,no_root_squash,no_all_squash,sync) /nfs/data3 *(rw,no_root_squash,no_all_squash,sync) /nfs/data4 *(rw,no_root_squash,no_all_squash,sync) EOF sudo systemctl restart rpcbind sudo systemctl restart nfs-server.service sudo exportfs -r && sudo exportfs -av > ansible k8s-node-4 -a "sudo apt install -y nfs-common rpcbind" > ansible k8s-node-5 -a "sudo apt install -y nfs-common rpcbind" showmount -e 10.10.107.202 mount -t nfs 10.10.107.202:/nfsdata /test / cd /test / && lsumount /test /
Step 2.PV 创建1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 cat > pv-test-demo-1.yaml<<'EOF' # 1.访问模式为 ReadWriteOnce - pv-test-1 apiVersion: v1 kind: PersistentVolume metadata: name: pv-test-1 spec: capacity: storage: 1 Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /nfs/data1 server: 10.10 .107 .202 --- apiVersion: v1 kind: PersistentVolume metadata: name: pv-test-2 spec: capacity: storage: 1 Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /nfs/data2 server: 10.10 .107 .202 --- apiVersion: v1 kind: PersistentVolume metadata: name: pv-test-4 spec: capacity: storage: 2 Gi accessModes: - ReadOnlyMany persistentVolumeReclaimPolicy: Retain storageClassName: nfs-retain nfs: path: /nfs/data4 server: 10.10 .107 .202 --- apiVersion: v1 kind: PersistentVolume metadata: name: pv-test-5 spec: capacity: storage: 3 Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Delete storageClassName: nfs-delete nfs: path: /nfs/data server: 10.10 .107 .202 EOF
Step 3.操作流程&查看1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ~/K8s/Day8/demo7$ kubectl create -f pv-test-demo-1.yaml ~/K8s/Day8/demo7$ kubectl get pv -o wide ~/K8s/Day8/demo7$ kubectl describe pv pv-test-1
Step 4.Service 和 PV 绑定 与 PVC 创建1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 cat > pvc-demo1.yaml<<'EOF' apiVersion: v1 kind: Service metadata: name: nginx-pvc-demo labels: app: nginx spec: clusterIP: None selector: app: nginx ports: - port: 80 name: web --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web-pvc-demo spec: serviceName: nginx-pvc-demo replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: harbor.weiyigeek.top/test/nginx:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: web volumeMounts: - name: diskpv mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: diskpv spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "nfs" resources: requests: storage: 1 Gi EOF
Step 5.部署PVC与查看1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 ~/K8s/Day8/demo7$ kubectl create -f pvc-demo1.yaml ~/K8s/Day8/demo7$ kubectl get svc -o wide --show-labels | grep "nginx-pvc-demo" ~/K8s/Day8/demo7$ kubectl get statefulset -o wide ~/K8s/Day8/demo7$ kubectl get pod -o wide cat > web-pvc-demo-2.yaml<<'EOF' apiVersion: v1 kind: PersistentVolume metadata: name: pv-test-3 spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: nfs nfs: path: /nfs/data3 server: 10.10.107.202 EOF ~/K8s/Day8/demo7$ kubectl create -f web-pvc-demo-2.yaml ~/K8s/Day8/demo7$ kubectl get pvc -o wide ~/K8s/Day8/demo7$ kubectl get pv -o wide
6.PVC卷使用验证1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 pv-test-1 1Gi RWO Retain Bound default/diskpv-web-pvc-demo-1 nfs 73m Filesystem pv-test-2 1Gi RWO Retain Bound default/diskpv-web-pvc-demo-0 nfs 73m Filesystem pv-test-3 1Gi RWO Delete Bound default/diskpv-web-pvc-demo-2 nfs 11s Filesystem --- NAME IP NODE web-pvc-demo-0 10.244.2.15 k8s-node-5 /nfs/data2 web-pvc-demo-1 10.244.1.140 k8s-node-4 /nfs/data1 web-pvc-demo-2 10.244.2.16 k8s-node-5 /nfs/data3 cd /nfs/data1 && echo "web-pvc-demo-1 - $(pwd) -- $(date) " > /nfs/data1/index.htmlcd /nfs/data2 && echo "web-pvc-demo-0 - $(pwd) -- $(date) " > /nfs/data2/index.htmlcd /nfs/data3 && echo "web-pvc-demo-2 - $(pwd) -- $(date) " > /nfs/data3/index.html/nfs/data2$ curl http://10.244.2.15 /nfs/data1$ curl http://10.244.1.140 $ kubectl get pod -n kube-system -o wide | grep "coredns" $ cat /etc/resolv.conf $ dig -t A nginx-pvc-demo.default.svc.cluster.local. @10.244.0.8 /nfs/data3$ curl http://nginx-pvc-demo.default.svc.cluster.local web-pvc-demo-2 - /nfs/data3 -- Thu 19 Nov 2020 02:53:48 PM CST
7.PV 与 PVC 删除 & 回收策略查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 ~/K8s/Day8/demo7$ kubectl delete statefulset --all ~/K8s/Day8/demo7$ kubectl get pod -w $ kubectl delete pvc diskpv-web-pvc-demo-2 pv-test-3 1Gi RWO Delete Failed default/diskpv-web-pvc-demo-2 nfs 34m $ kubectl delete pvc --all $ kubectl get pv ~/K8s/Day8/demo7$ kubectl edit pv pv-test-1 $ kubectl edit pv pv-test-2 $ kubectl edit pv pv-test-3 $ kubectl get pv ~/K8s/Day8/demo7$ kubectl delete pv pv-test-3
Step 8.至此持久化数据卷演示结束;
5.StorageClass - 动态分配持久卷 描述: StorageClass 被用于描述存储分类(存储类),不同的StorageClass
会被关系的服务质量层面或者备份策略;
基础概念 描述: 前面我们说过 Persistent Volume
简称PV是一个K8S资源对象,所以我们可以单独创建一个PV。它不和Pod直接发生关系,而是通过 Persistent Volume Claim
,简称PVC来实现动态绑定。Pod定义里指定的PVC绑定关键字,然后PVC会根据Pod的要求去自动绑定合适的PV给Pod使用
。
Tips : 比如一个配置了许多 50Gi PV 的集群不会匹配到一个要求 100Gi 的PVC。只有在 100Gi PV被加到集群之后,该PVC才可以被绑定。
Q: 什么是 storageClass?
答: 前面我们说过创建PV有静态(即手动创建一堆PV组成一个PV池,供PVC来绑定
)和动态两种方式, 而动态是指在现有PV不满足PVC的请求时,即通过一个叫StorageClass(存储类)
的对象由存储系统根据PVC的要求自动创建来完成的。动态卷供给能力让管理员不必进行预先创建存储卷,而是随用户需求进行创建。 所以说 storageclass 是一个存储类,k8s集群管理员通过创建storageclass
可以动态生成一个存储卷
供k8s用户使用。这是一种新的存储供应方式。
Q: 使用StorageClass有什么好处呢?
答: 除了由存储系统动态创建,节省了管理员的时间,还有一个好处是可以封装不同类型的存储供PVC选用
。在StorageClass出现以前,PVC绑定一个PV只能根据两个条件,一个是存储的大小,另一个是访问模式
。在StorageClass出现后,等于增加了一个绑定维度即StorageClassName
参数。
资源定义 Q: 什么是 storageClass 的资源定义?
答: 每一个StorageClass都包含了Provisioner、Parameters、ReclaimPolicy
等几个字段,当需要动态配置属于该类的PersistentVolume
时使用这些字段。 描述具体过程为: PV先创建分类,PVC请求绑定某个已创建的类(StorageClass)
的资源,同时由 Controller控制器
创建的资源绑定到PVC之中,这样就达到动态配置的效果。
Tips : StorageClass对象的名称很重要,是用户可以请求特定类的方式。管理员在首次创建StorageClass
对象时设置类的名称和其他参数,注意在创建对象后无法更新这些对象
。
Tips : 管理员可以为不请求任何特定类绑定的PVC指定默认的StorageClass
;
yaml文件示例说明:1 2 3 4 5 6 7 8 9 10 11 apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: kubernetes.io/aws-ebs parameters: type: gp2 reclaimPolicy: Retain mountOptions: - debug volumeBindingMode: Immediate
Provisoner - 供应商 Q: 什么是供应商 (Provisoner)? 描述: storageclass需要有一个供应者,用来确定我们使用什么样的存储方式来创建pv;
Tips : provisioner 供应者它既可以是内部供应程序,也可以由外部供应商提供; 如果是外部供应商可以参考 https://github.com/kubernetes-incubator/external-storage/ 下提供的方法创建storageclass的provisioner;
例如,NFS不提供内部配置程序,但可以使用外部配置程序。
常见的provisioner供应者列表:
weiyigeek.top-provisioner
Tips : nfs 的 provisioner 部署资源清单文件示例 https://github.com/kubernetes-incubator/external-storage/tree/master/nfs/deploy/kubernetes
【2022年6月15日 20:27:10】补充: 注意 nfs-client-provisioner 已经不在更新维护建议使用 nfs-subdir-external-provisioner (https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner )
Parameters - 参数 描述: 其功能与标签类似,但其是为了给外部插件提供某些自定义参数;
Reclaim Policy - 回收策略 描述: 由一个存储类动态创建持久化卷(Persistent Volumes
-PV), 可以通过ReclaimPolicy指定回收策略而该策略可以是删除Delete(默认)
或者保持Retain
;
Mount Options - 挂载选项 描述: 如果Volume Plugin
不支持这个挂载选项,但是指定了就会使provisioner
创建StorageClass失败
Volume Binding Mode - 卷绑定模式 描述: 该字段用来说明什么时候进行卷绑定和动态配置,默认情况下立即模式表示一旦创建了PersistentVolumeClaim
,就会发生卷绑定和动态配置。 对于受拓扑约束且无法从群集中的所有节点全局访问的存储后端
,将在不知道Pod的调度要求的情况下绑定或配置PersistentVolumes, 这可能导致不可调度的Pod。
集群管理员可以通过指定WaitForFirstConsumer
模式来解决此问题,该模式将延迟绑定和配置PersistentVolume
,直到创建使用PersistentVolumeClaim的Pod。将根据Pod的调度约束指定的拓扑选择或配置PersistentVolumes。这些包括但不限于资源需求,节点选择器,pod亲和力和反亲和力,以及污点和容忍度
。
基础实例 环境构建流程
Step 1.设置查看当NFS目录以及定义nfs驱动进行编排以下是nfs-client-provisioner
部署文件;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 showmount -e cat > nfs-client-provisioner.yaml <<'EOF' apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner namespace: default spec: replicas: 1 selector: matchLabels: app: nfs-client-provisioner strategy: type: Recreate template: metadata: labels: app: nfs-client-provisioner spec: serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner volumeMounts: - name: timezone mountPath: /etc/localtime - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: fuseim.pri/ifs - name: NFS_SERVER value: 10.10 .107 .202 - name: NFS_PATH value: /nfs/data volumes: - name: timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai - name: nfs-client-root nfs: server: 10.10 .107 .202 path: /nfs/data EOF
Step 2.Storageclass 部署文件1 2 3 4 5 6 7 8 9 10 11 cat > nfs-client-storageclass.yaml <<'EOF' apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: fuseim.pri/ifs parameters: archiveOnDelete: "false" EOF
Step 3.rbac授权文件1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 cat > nfs-client-rbac.yaml<<'EOF' kind: ServiceAccount apiVersion: v1 metadata: name: nfs-client-provisioner --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-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: ["create", "update" , "patch" ] - apiGroups: [""] resources: ["services", "endpoints" ] verbs: ["get"] - apiGroups: ["extensions"] resources: ["podsecuritypolicies"] resourceNames: ["nfs-provisioner"] verbs: ["use"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: default roleRef: - kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "list" , "watch" , "create" , "update" , "patch" ] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner namespace: default roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io EOF
Step 4.准备好以上三个文件后,使用kubectl apply命令应用即可完成nfs-client-provisioner的部署。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /nfs/data~/K8s/Day7/demo3$ ls /nfs/data~/K8s/Day7/demo3$ kubectl create -f . ~/K8s/Day7/demo3$ kubectl get pod,deployment,sc -o wide
动态存储类验证
Step 5.创建的 nfs-client-provisioner(StorageClass动态存储卷)已经正常运行, 我们现在部署几个PVC来测试验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 cat > logstash-pvc.yaml<<'EOF' apiVersion: v1 kind: PersistentVolumeClaim metadata: name: log-01-pvc annotations: volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" spec: accessModes: ["ReadWriteMany"] resources: requests: storage: 1 Gi --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: log-02-pvc spec: accessModes: ["ReadWriteMany"] resources: requests: storage: 1 Gi EOF
部署与查看PVC持久卷1 2 3 4 5 6 7 8 9 10 11 12 ~/K8s/Day7/demo3$ kubectl create -f logstash-pvc.yaml ~/K8s/Day7/demo3$ kubectl get pv,pvc -o wide
Step 6.pvc已经创建成功,并自动创建了一个关联的pv资源对象,我们再查看后端存储目录里面是否生成了对应命名格式的 pv;1 2 3 4 /nfs/data$ ls -alh | grep "pvc" drwxrwxrwx 2 root root 4.0K Dec 9 20:57 default-log-01-pvc-pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b drwxrwxrwx 2 root root 4.0K Dec 9 20:57 default-log-02-pvc-pvc-e76d332e-52bf-470a-a362-48742c03ab5f
动态存储类使用
Step 7.在实际工作中,使用 StorageClass 更多的是 StatefulSet 类型的服务,StatefulSet 类型的服务我们也可以通过一个 volumeClaimTemplates 属性
来直接使用 StorageClass;
实战利用StatefulSet控制器部署Nginx以及StorageClass动态PV的使用示例如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 cat > nginx-use-storageClass.yaml <<'EOF' apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: type : NodePort ports: - name: web port: 80 targetPort: 80 nodePort: 30001 selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web labels: app: nginx spec: serviceName: "nginx" replicas: 8 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: harbor.weiyigeek.top/test /nginx:v3.0 imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: web volumeMounts: - name: webroot mountPath: /usr/share/nginx/html - name: weblog mountPath: /var/log /nginx - name: timezone mountPath: /etc/localtime volumes: - name: timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai volumeClaimTemplates: - metadata: name: webroot spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi - metadata: name: weblog spec: accessModes: [ "ReadWriteMany" ] storageClassName: managed-nfs-storage resources: requests: storage: 1Gi EOF
动态存储类扩容缩与删除
Step 10.查看StatefulSet扩容缩对及删除资源时候对StorageClass的影响1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 /nfs/data$ kubectl scale statefulset --replicas=3 web /nfs/data$ kubectl get pod,pv,pvc /nfs/data$ kubectl delete sts web /nfs/data$ kubectl get pod /nfs/data$ kubectl get pvc ~/K8s/Day7/demo3$ kubectl delete pv,pvc --all persistentvolume "pvc-102f5ada-5599-4152-bae4-968670a0d9a8" deleted persistentvolume "pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83" deleted persistentvolume "pvc-5c8f0e23-4718-4ce4-aa60-883c95bc7352" deleted persistentvolume "pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9" deleted persistentvolume "pvc-9b482936-5252-43b2-b3a0-22bfee032dfb" deleted persistentvolume "pvc-a7d6783b-2d91-4839-9a5b-d84436018f84" deleted persistentvolume "pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190" deleted persistentvolume "pvc-bf4734e3-a010-4336-a190-6a13527d9672" deleted persistentvolume "pvc-c0c41528-85c0-4881-b05b-8e236bc77a47" deleted persistentvolume "pvc-c7baf155-3514-4dce-83ed-6d33362864b4" deleted persistentvolume "pvc-cb930d15-558b-4ede-a087-4f0ee001826a" deleted persistentvolume "pvc-d02d6fcd-20e4-47a1-bdb9-3e42e8a6cf54" deleted persistentvolume "pvc-d8685582-3122-4eb7-997f-b3d9c729918d" deleted persistentvolume "pvc-e77bbe07-bf82-4be8-9bc2-c3721c2db4ae" deleted persistentvolume "pvc-f709d766-1061-4703-9c18-c2a814d450bf" deleted persistentvolume "pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09" deleted persistentvolumeclaim "weblog-web-0" deleted persistentvolumeclaim "weblog-web-1" deleted persistentvolumeclaim "weblog-web-2" deleted persistentvolumeclaim "weblog-web-3" deleted persistentvolumeclaim "weblog-web-4" deleted persistentvolumeclaim "weblog-web-5" deleted persistentvolumeclaim "weblog-web-6" deleted persistentvolumeclaim "weblog-web-7" deleted persistentvolumeclaim "webroot-web-0" deleted persistentvolumeclaim "webroot-web-1" deleted persistentvolumeclaim "webroot-web-2" deleted persistentvolumeclaim "webroot-web-3" deleted persistentvolumeclaim "webroot-web-4" deleted persistentvolumeclaim "webroot-web-5" deleted persistentvolumeclaim "webroot-web-6" deleted persistentvolumeclaim "webroot-web-7" deleted ~/K8s/Day7/demo3$ ls /nfs/data data/ data1/ data2/ data3/ data4/ ~/K8s/Day7/demo3$ ls /nfs/data/ archived-default-elasticsearch-master-elasticsearch-master-0-pvc-337180d4-e12a-4933-bbe7-bc9eea8d068a default-weblog-web-6-pvc-cb930d15-558b-4ede-a087-4f0ee001826a archived-default-elasticsearch-master-elasticsearch-master-0-pvc-6c445381-1a64-40e4-a412-493e691d8a22 default-weblog-web-7-pvc-94d2d4a2-fbc7-455a-a554-31e171dd19c9 archived-default-elasticsearch-master-elasticsearch-master-0-pvc-ad986a1f-f36e-4223-af17-e4100ca2a587 default-webroot-web-0-pvc-f709d766-1061-4703-9c18-c2a814d450bf archived-default-log-01-pvc-pvc-f003301b-d22b-4a9f-beb3-b6fe81a6398b default-webroot-web-1-pvc-c7baf155-3514-4dce-83ed-6d33362864b4 archived-default-log-02-pvc-pvc-e76d332e-52bf-470a-a362-48742c03ab5f default-webroot-web-2-pvc-d8685582-3122-4eb7-997f-b3d9c729918d archived-default-weblog-web-0-pvc-b24af2f5-e6ad-4487-9f7b-d87d38fd9190 default-webroot-web-3-pvc-5c8f0e23-4718-4ce4-aa60-883c95bc7352 archived-default-weblog-web-1-pvc-102f5ada-5599-4152-bae4-968670a0d9a8 default-webroot-web-4-pvc-c0c41528-85c0-4881-b05b-8e236bc77a47 archived-default-weblog-web-2-pvc-bf4734e3-a010-4336-a190-6a13527d9672 default-webroot-web-5-pvc-e77bbe07-bf82-4be8-9bc2-c3721c2db4ae archived-default-weblog-web-3-pvc-d02d6fcd-20e4-47a1-bdb9-3e42e8a6cf54 default-webroot-web-6-pvc-9b482936-5252-43b2-b3a0-22bfee032dfb archived-default-weblog-web-4-pvc-2196b531-445f-48d5-bf2c-1c3ee4ee2a83 default-webroot-web-7-pvc-a7d6783b-2d91-4839-9a5b-d84436018f84 archived-default-weblog-web-5-pvc-fbd240f3-eaaa-46cc-af77-b8ea81f7be09
weiyigeek.top-Nginx网站与日志数据依然存储
Step 11.以上即为k8s持久化存储之storageclass实践。 以上就是使用storageclass实现动态pv的具体步骤,内容较为全面而且我也相信有相当的一些工具可能是我们日常工作可能会见到或用到的。
实际案例 MySQL数据库使用StorageClass对数据的持久存储 描述: 接下来我们部署一个mysql应用,测试下 StorageClass 方式声明的 PVC 对象
Step 1.MySQL在K8s集群中部署的资源清单1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 cat mysql-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: mysql-config data: custom.cnf: | [mysqld] default_storage_engine=innodb skip_external_locking skip_host_cache skip_name_resolve default_authentication_plugin=mysql_native_password cat mysql-secret.yaml apiVersion: v1 kind: Secret metadata: name: mysql-user-pwd data: mysql-root-pwd: cGFzc3dvcmQ= cat mysql-deploy.yaml apiVersion: v1 kind: Service metadata: name: mysql spec: type: NodePort ports: - port: 3306 nodePort: 30006 protocol: TCP targetPort: 3306 selector: app: mysql cat mysql-pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pvc annotations: volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" spec: accessModes: ["ReadWriteMany"] storageClassName: managed-nfs-storage resources: requests: storage: 2 Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: mysql spec: replicas: 1 selector: matchLabels: app: mysql strategy: type: Recreate template: metadata: labels: app: mysql spec: containers: - image: mysql name: mysql imagePullPolicy: IfNotPresent env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-user-pwd key: mysql-root-pwd ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-config mountPath: /etc/mysql/conf.d/ - name: mysql-persistent-storage mountPath: /var/lib/mysql - name: timezone mountPath: /etc/localtime volumes: - name: mysql-config configMap: name: mysql-config - name: timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai - name: mysql-persistent-storage persistentVolumeClaim: claimName: mysql-pvc
Step 2.按照资源清单进行部署构建响应的Pod以及Services
1 2 3 4 5 $ kubectl apply -f . configmap/mysql-config created service/mysql created deployment.apps/mysql created secret/mysql-user-pwd created
Step 3.查看创建的资源1 2 3 4 5 6 7 8 $ kubectl get pod,svc NAME READY STATUS RESTARTS AGE pod/mysql-7c5b5df54c-vrnr8 1/1 Running 0 83s pod/nfs-client-provisioner-c676947d-pfpms 1/1 Running 0 30m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 93d service/mysql NodePort 10.0.0.19 <none> 3306:30006/TCP 83s
Step 4.可以看到mysql应用已经正常运行,我们通过任意一个node节点的ip和30006端口连接mysql数据库测试1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [root@localhost ~] Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 10 Server version: 8.0.19 MySQL Community Server - GPL Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help . Type '\c' to clear the current input statement. MySQL [(none)]> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.01 sec) MySQL [(none)]>
Step 5.可以看到mysql数据库连接正常,此时查看nfs存储mysql数据库数据已经持久化到nfs服务器 /data/nfs/default-mysql-pvc-pvc-eef853e1-f8d8-4ab9-bfd3-05c2a58fd9dc
目录中1 2 3 4 5 6 7 8 9 $ du -sh * 177M default-mysql-pvc-pvc-eef853e1-f8d8-4ab9-bfd3-05c2a58fd9dc $ cd default-mysql-pvc-pvc-eef853e1-f8d8-4ab9-bfd3-05c2a58fd9dc/ $ ls auto.cnf binlog.index client-cert.pem ibdata1 ibtmp1 mysql.ibd public_key.pem sys binlog.000001 ca-key.pem client-key.pem ib_logfile0 innodb_temp performance_schema server-cert.pem undo_001 binlog.000002 ca.pem ib_buffer_pool ib_logfile1 mysql private_key.pem server-key.pem undo_002
关于 StatefulSet 补充使用PV/PVC或者StorageClass的补充
1) StatefulSet 为每个Pod副本创建了一个DNS 域名,这个域名的格式为:(podname).(headless servername)
,也就意味着服务间是通过Pod域名来通信而非PodIP,因为当Pod所在Node发生故障时,Pod会被飘移到其它 Node 上 PodIP 会发生变化但是 Pod域名不会有变化;1 2 3 4 ping web-pvc-demo-0.nginx-pvc-demo ping web-pvc-demo-1.nginx-pvc-demo ping web-pvc-demo-2.nginx-pvc-demo
2) StatefulSet 配 Pod name (网络标识) 的模式为:(statefulset名称−序号
),比如上面的示例:web-pvc-demo-0, web-pvc-demo-1,web-pvc-demo-2
3) StatefulSet 使用 Headless 服务来控制 Pod 的域名,这个域名的FQDN为:(servicename).$(namespace).svc.cluster.local
,其中"cluster.local"
指的是集群的域名1 dig -t A nginx-pvc-demo.default.svc.cluster.local. @10.244.0.8
4) 根据volumeClaimTemplates为每个Pod 创建一个PVC,其的命名规则匹配模式:(volumeClaim Templates.name-pod_name)
, 比如上面的volumeMounts.name=diskpv
因此创建出来的PVC是1 2 3 diskpv-web-pvc-demo-1 diskpv-web-pvc-demo-2 diskpv-web-pvc-demo-3
5) 删除 Pod 不会删除其pvc,需要手动删除 pvc 将自动释放 pv,查看上面的步骤6.PV 与 PVC 删除 & 回收策略查看