一、理解存储卷
Volume(存储卷)是Pod中能够被多个容器访问的共享目录。解决了容器崩溃时文件丢失(容器中的文件默认情况下在磁盘是临时存放)和多容器共享文件的问题。
<img src="https://confluence.mysre.cn/download/attachments/53852724/image2021-12-14_9-41-23.png?version=1&modificationDate=1639446083199&api=v2" alt="volume" width=680 />
与 docker 存储卷的区别
docker volume:定义在容器上,生命周期是与容器的生命周期相同;
k8s volume:定义在 pod 上,与 pod 的生命周期相同,容器终止或重启,卷的数据是不会丢失的。
k8s存储卷类型
常用的存储卷类型有:emptyDir、hostPash、NFS、secret、configMap、persistentVolumeClaim。
更多请参考官方链接。
emptyDir
在 Pod 分配到 Node 时创建,初始内容为空,并且无须指定宿主机上对应的目录文件,因为这是 Kubernetes 自动分配的一个目录,当Pod 从 Node 上移除时,emptyDir 中的数据也会被永久删除。
emptyDir 的一些用途如下:
1、临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。例如用于基于磁盘的归并排序。
2、长时间任务的中间过程的临时保存目录。检查点用于从崩溃中恢复的长时间计算。
3、一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。保存内容管理器容器获取的文件,而网络服务器容器提供数据。
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: {}
volumes: 指定卷的名称和类型等卷配置;
volumeMounts: 指定挂载那个卷及挂载目录等。
使用数据卷流程:镜像里程序写的目录 -> mountPath 挂载到写的目录 -> 引用的volumes对应的name -> 相关卷的配置
persistentVolumeClaim(重要)
持久卷声明类型。通过这个声明,可以在不知道存储卷类型的情况下进行申请持久化存储。需要和PV一起搭配。
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs:
server: xxx.xxx.xx.xx
path: "/"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs
spec:
accessModes:
- ReadWriteMany
storageClassName: manual
resources:
requests:
storage: 1Gi
---
...(省略)
spec:
containers:
- name: nginx-demo
image: nginx
imagePullPolicy: Always
volumeMounts:
- name: nfs-pvc-vol
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-pvc-vol
persistentVolumeClaim:
claimName: nfs
pv/pvc完成绑定的条件:
- 第一个条件,检查 PV 和 PVC 的 spec 字段。比如,PV 的存储(storage)大小,就必须满足 PVC 的要求。
- 第二个条件,则是 PV 和 PVC 的 storageClassName 字段必须一样。
hostPath
hostPath 为在 Pod 上挂载宿主机上的文件或目录,它通常可以用于以下几方面:
1、容器应用程序生成的日志文件需要永久保存时,可以使用宿主机的高速文件系统进行存储。
2、 需要访问宿主机上Docker引擎内部数据结构的容器应用时,可以通过定义hostPath为宿主机/var/lib/docker目录,使容器内部应用可以直接访问Docker的文件系统。
注意: 在不同的Node上具有相同配置的Pod,可能会因为宿主机上的目录和文件不同而导致对Volume上目录和文件的访问结果不一致。
官方地址-ctrl+f搜索关键字
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:
# 宿主上目录位置
path: /data
# 此字段为可选
type: Directory
type取值 | 行为 |
---|---|
空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。 | |
DirectoryOrCreate | 如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。 |
Directory | 在给定路径上必须存在的目录。 |
FileOrCreate | 如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。 |
File | 在给定路径上必须存在的文件。 |
Socket | 在给定路径上必须存在的 UNIX 套接字。 |
CharDevice | 在给定路径上必须存在的字符设备。 |
BlockDevice | 在给定路径上必须存在的块设备 |
NFS
不像 emptyDir 那样会在删除 Pod 的同时也会被删除,nfs 卷的内容在删除 Pod 时会被保存,卷只是被卸载。 这意味着 nfs 卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
volumes:
- name: nfs-volume
nfs:
server: xxx.xx.xxx.xx # 服务地址
path: /path # 存放路径
secret
secret 卷用来给 Pod 传递敏感信息,例如密码、OAuth 令牌和 SSH 密钥,它们不会被写到持久化的存储器里面,是以文件的形式挂在到 Pod 中,且无需直接与 Kubernetes 耦合。
- 作为挂载到一个或多个容器上的 卷 中的文件
- 以环境变量的形式使用 Secrets
- 由 kubelet 在为 Pod 拉取镜像时使用
secret type类型
- Service Account:Kubernetes 自动创建包含访问 API 凭据的 secret,并自动修改 pod 以使用此类型的 secret。
- Opaque:使用base64编码存储信息,可以通过base64 --decode解码获得原始数据,因此安全性弱。
- kubernetes.io/dockerconfigjson:用于存储docker registry的认证信息。
configMap
提供了向 Pod 注入配置数据的方法,用来将非机密性的数据保存到键值对中,比如存储卷中的配置文件。 ConfigMap 对象中存储的数据可以被 configMap 类型的卷引用,然后被 Pod 中运行的 容器化应用使用(以key-value的形式)。
示例实践:configMap引用
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.how: very
---
apiVersion: v1
kind: ConfigMap
metadata:
name: log-config
namespace: default
data:
log_level: INFO
---
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
namespace: default
data:
log_level: INFO
---
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
<img src="https://confluence.mysre.cn/download/attachments/53852724/image2021-12-15_11-41-47.png?version=1&modificationDate=1639539707531&api=v2" alt="configMap" width=680 />
实践一:【挂载NFS卷】
- step1:在master节点执行 NFS 工具包安装命令:
yum install -y nfs-utils
systemctl enable rpcbind
systemctl enable nfs
systemctl start rpcbind
systemctl start nfs
- step2: 创建 NFS 目录并授权:
mkdir -p /data/nfs/pv-1 && mkdir -p /data/nfs/pv-2 && mkdir -p statefulSet-1
chmod -R 755 /data/nfs
- step3:配置NFS目录权限(这里我们直接设置成可读写):
vim /etc/exports
加入下面两行配置
/data/nfs/pv-1 *(rw,sync,no_root_squash,no_all_squash)
/data/nfs/pv-2 *(rw,sync,no_root_squash,no_all_squash)
/data/nfs/statefulSet-1 *(rw,sync,no_root_squash,no_all_squash)
# 立即生效
exportfs -rv
step3参数exports说明:
rw 读写
ro 只读
sync 数据直接写入磁盘
async 数据先写入内存
no_root_squash 对root用户不压制,在服务端都映射为服务端的root用户,即仅使用普通用户授权。
root_squash 如果客户端是用户root操作,会被压制成nobody用户,即可以使用 root 授权。
all_squash 不管客户端的使用nfs的用户是谁,都会压制成nobody用户
nonuid=uid: 指定uid的值
anongid=gid:指定gid的值
- step4: 查看NFS服务的挂载卷列表
[root@master configMap]# showmount -e
Export list for master:
/data/nfs/pv-2 *
/data/nfs/pv-1 *
在使用的node节点上分别安装nfs工具包
yum install nfs-utils
在node节点上检查master节点的nfs servcie服务是否可用
[root@node2 config]# showmount -e 192.168.23.10
Export list for 192.168.23.10:
/data/nfs/pv-2 *
/data/nfs/pv-1 *
- step5:创建一个pod应用来引用上面的nfs进行挂载
nfs-kdemo.yaml
kind: Pod
apiVersion: v1
metadata:
name: nfs-pod-test
spec:
volumes:
- name: nfs-volume
nfs:
server: 192.168.23.10
path: /data/nfs/pv-1
containers:
- name: nfs-kdemo-test
image: registry.cn-hangzhou.aliyuncs.com/liwb/kdemo:1.9
volumeMounts:
- name: nfs-volume
mountPath: /var/nfs/pv-1
command: ["/bin/sh"]
args: ["-c", "while true; do date >> /var/nfs/pv-1/dates.txt; sleep 5; done"]
不需要进入pod,我们就可以发现在外面的挂载目录下就能看到同步了pod中输出的数据
[root@master nfs]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-pod-test 1/1 Running 0 4s
[root@master nfs]# tail -f /data/nfs/pv-1/dates.txt
Wed Dec 15 08:42:28 UTC 2021
Wed Dec 15 08:42:33 UTC 2021
Wed Dec 15 08:42:38 UTC 2021
Wed Dec 15 08:42:43 UTC 2021
Wed Dec 15 08:42:48 UTC 2021
Wed Dec 15 08:42:53 UTC 2021
Wed Dec 15 08:42:58 UTC 2021
Wed Dec 15 08:43:03 UTC 2021
Wed Dec 15 08:43:08 UTC 2021
Wed Dec 15 08:43:13 UTC 2021
Wed Dec 15 08:43:18 UTC 2021
Wed Dec 15 08:43:23 UTC 2021
^C
二、【pv/pvc/storageClass】挂载实践
pv/pvc/storageClass概念
从yaml文件来解释,如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs:
server: xxx.xxx.xx.xx
path: "/"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pv
spec:
accessModes:
- ReadWriteMany
storageClassName: manual
resources:
requests:
storage: 1Gi
PVC 描述的:是 Pod 想要使用的持久化存储的属性,比如存储的大小、读写权限等。
PV 描述的:则是一个具体的 Volume 的属性,比如 Volume 的类型、挂载目录、远程存储服务器地址等。
PVC 和 PV ,是Kubernetes对于有状态的容器应用或者对数据需要持久化的应用场景设计的。
PV是对底层网络共享存储的抽象,将共享存储定义为一种“资源”,PVC则是用户对存储资源的一个“申请”。
其实跟“面向对象”的思想完全一致。PVC 可以理解为持久化存储的“接口”,它提供了对某种持久化存储的描述,但不提供具体的实现;而这个持久化存储的“实现“部分则由 PV 负责完成。
pv常用配置
存储能力(Capacity):
描述存储设备具备的能力,目前仅支持对存储空间的设置(storage=xx),未来可能加入IOPS、吞吐率等指标的设置。
访问模式(Access Modes):
对PV进行访问模式的设置,用于描述用户的应用对存储资源的访问权限。访问模式如下()里的缩写,是在CLI中的访问模式缩写。
- ◎ ReadWriteOnce(RWO):读写权限,并且只能被单个Node挂载。
- ◎ ReadOnlyMany(ROX):只读权限,允许被多个Node挂载。
- ◎ ReadWriteMany(RWX):读写权限,允许被多个Node挂载。
- ◎ ReadWriteOncePod(RWOP):读写权限,允许单个pod挂载,这仅支持 CSI 卷和 Kubernetes 1.22+ 版。如果您想确保整个集群中只有一个 Pod 可以读取该 PVC 或写入该 PVC,可使用 ReadWriteOncePod 访问模式。
存储类别(Class):
用于绑定PVC.具有特定类别的PV只能与请求了该类别的PVC进行绑定。未设定类别的PV则只能与不请求任何类别的PVC进行绑定。其他还有存储卷模式(Volume Mode)、回收策略(Reclaim Policy)、挂载参数(Mount Options)等。
StorageClass
StorageClass作为对存储资源的抽象定义,对用户设置的PVC申请屏蔽后端存储的细节,
一方面减少了用户对于存储资源细节的关注;另一方面减轻了管理员手工管理PV的工作,由系统自动完成PV的创建和绑定(动态资源供应模式),实现了动态的资源供应。
关键配置参数:
提供者(Provisioner):
描述存储资源的提供者,也可以看作后端存储驱动。目前Kubernetes支持的Provisioner都以 kubernetes.io/ 为开头,用户也可以使用自定义的后端存储提供者。为了符合StorageClass的用法,自定义Provisioner需要符合存储卷的开发规范。我们用的是阿里云的 alicloud/nas
<img src="https://confluence.mysre.cn/download/attachments/53852724/image2021-12-16_20-2-4.png?version=1&modificationDate=1639656125101&api=v2" alt="sc" width=680 />
Reclaim Policy的三种状态
- 保持(Retain):删除PV后后端存储上的数据仍然存在,如需彻底删除则需要手动删除后端存储volume
- 删除(Delete):删除被PVC释放的PV和后端存储volume
- 回收(Recycle):保留PV,但清空PV上的数据(已废弃)
pod启动的时候pv/pvc做了什么?
1、根据pod声明的pvc找是否存在符合条件的pv,完成绑定(PV 和 PVC 的 spec 字段相同、PV 和 PVC 的 storageClassName 字段必须一样);
2、为虚拟机挂载远程磁盘到宿主机(attach);
3、格式化这个磁盘设备,然后将它挂载到宿主机指定的挂载点上。
<img src="https://confluence.mysre.cn/download/attachments/53852724/image2021-12-16_20-57-47.png?version=1&modificationDate=1639659467583&api=v2" alt="pv-pv-pvc-start-doing" width=680 />
PV生命周期
PV生命周期的各个阶段某个PV在生命周期中可能处于以下4个阶段(Phaes)之一。
Available:可用状态,还未与某个PVC绑定。
Bound:已与某个PVC绑定。
Released:绑定的PVC已经删除,资源已释放,但没有被集群回收。
Failed:自动资源回收失败。
<img src="https://confluence.mysre.cn/download/attachments/53852724/image2021-12-16_20-56-52.png?version=1&modificationDate=1639659412553&api=v2" alt="生命周期" width=680 />
回收策略
当用户对存储资源使用完毕后,用户可以删除PVC,与该PVC绑定的PV将会被标记为“已释放”,但还不能立刻与其他PVC进行绑定。通过之前PVC写入的数据可能还被留在存储设备上,只有在清除之后该PV才能再次使用。
在启用动态供应模式的情况下,一旦用户删除了PVC,与之绑定的PV也将根据其默认的回收策略“Delete”被删除。如果需要保留PV(用户数据),则在动态绑定成功后,用户需要将系统自动生成PV的回收策略从“Delete”改成“Retain”。
三、pv/pvc实践
1、声明一个 pvc,执行kubectl apply -f nfs-pvc.yaml
,内容为:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: "rwx-nfs-pvc"
resources:
requests:
storage: 20Mi
2、查看 pvc 状态:
kubectl describe pvc nfs
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal FailedBinding 1s (x4 over 42s) persistentvolume-controller no persistent volumes available for this claim and no storage class is set
3、创建一个 statefulSet,执行kubectl apply -f nfs-deploy.yaml
,内容为:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx-demo
template:
metadata:
labels:
app: nginx-demo
spec:
containers:
- name: nginx-demo
image: nginx
imagePullPolicy: Always
volumeMounts:
- name: nfs-pvc-vol
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-pvc-vol
persistentVolumeClaim:
claimName: nfs-pvc
4、观察 pod 的状态,可以看到: pvc 在没有绑定pv的情况会导致pod一直处于pending;
$ kubectl describe pod <pod-name>
5、创建一个 pv,执行kubectl apply -f pv.yaml
,内容为:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 20Mi
accessModes:
- ReadWriteMany
storageClassName: "rwx-nfs-pvc"
nfs:
server: 192.168.23.10
path: "/data/nfs/pv-2"
6、观察pod状态,可以看到,容器正常运行。
这里有个PersistentVolumeController的控制器, 会不断地查看当前每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC 进行绑定。
7、进入容器,执行:
cd /usr/share/nginx/html ** echo "hello nfs!" > index.html
8、退出容器,执行以下命令暴露服务:
kubectl expose deployment nginx-demo --port=80 --type=NodePort --target-port=80 --name=nginx-demo
9、浏览器查看页面。
10、模拟容器挂掉,重启pod后数据是否会丢失。
11、尝试删除pv、pvc,看是否成功。
kubectl delete pvc nfs
kubectl delete pv nfs
12、删除deploy后再尝试删除pvc/pv;
四、使用StatefulSet创建有状态的应用
StatefulSet
有以下几个特点
- 稳定,并且具有唯一的网络标识。
- 稳定,并且可以持久存储。
- 有序,可优雅的部署和伸缩。
- 有序,可优雅的删除和停止。
- 有序,可自动滚动更新。
即,我们的应用如有以上的任何一个特点,我们就可以使用stateFulSet控制器来完成应用的部署。
<img src="https://confluence.mysre.cn/download/attachments/53852399/image2021-12-9_1-48-48.png?version=1&modificationDate=1638985728830&api=v2" alt="statefulSet" width=840 />
statefulSet的一些概念
1.网络标识
- Pod 名:唯一且不会发生变化,由$(stateFulSet name)-(0-N)组成,N为一个无限大的正整数,从0开始。
- 容器主机名:与Pod名称一致且不会发生变化。
- A记录:每一个副本都拥有唯一且不变的一条A记录制定自己,格式为:(service name).$(namespace name).svc
- Pod标签,通过StateFulSet创建的每个Pod都拥有一个唯一的标签,格式为:stateFulSet.kubernetes.io/pod-name=$(pod name),
通常可以将新的service单独关联到此标签,来解决某个Pod的问题等。
2.存储(pvc)
每个Pod都对应一个PVC,PVC的名称格式:(pod name)-(0-N),N为一个无限大的正整数,从0开始。
当Pod被删除的时候不会自动删除对应的PVC,需要手动删除。
每次Pod升级,重启,删除重新创建后都可以保证使用的是首次使用的PVC,保证数据的唯一性与持久化。
3.有序
- 当部署有N个副本的StatefulSet的时候,严格按照从0到N的顺序创建,并且下一个Pod创建的前提是上一个Pod已经running状态。
- 当删除有N个副本的stateFulSet的时候,严格按照从N到0的顺序删除,并且下一个Pod删除的前提是上一个Pod已经完全Delete。
- 当扩容StateFulSet副本的时候,每增加一个Pod的前提是上一个Pod已经running状态。
- 当减少stateFulSet副本的时候,每删除一个Pod的前提是上一个Pod已经完全delete。
- 当升级statefulSet的时候,严格按照从N到1的顺序升级,并且下一个Pod升级的前提是上一个Pod已经running状态。
4.无状态服务(Stateless Service)
该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的,比如kdemo,我们可以同时启动多个实例,但是我们访问任意一个实例得到的结果都是一样的,因为他唯一需要持久化的数据是存储在MySQL数据库中的,所以我们可以说kdemo这个应用是无状态服务,但是MySQL数据库就不是了,因为他需要把数据持久化到本地。
5.有状态服务(Stateful Service)
就和上面的概念是对立的了,该无法运行的实例需要在本地存储持久化数据,比如上面的MySQL数据库,你现在运行在节点Node-1,那么他的数据就存储在Node-1上面的,如果这个时候你把该服务迁移到节点Node-2去的话,那么久没有之前的数据了,因为他需要去对应的数据目录里面恢复数据,而此时没有任何数据。
6.无头服务(Headless Service)
Headless Service
在定义上和普通的 Service 几乎一致, 只是他的 clusterIP: None
,所以,这个 Service 被创建后并不会被分配一个 cluster IP,而是会以 DNS 记录的方式暴露出它所代理的 Pod,而且还有一个非常重要的特性,对于 Headless Service
所代理的所有 Pod 的 IP 地址都会绑定一个如下所示的 DNS 记录:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
7.volumeClaimTemplates
用于给StatefulSet的Pod申请PVC,称为卷申请模板,它会为每个Pod生成不同的PVC,关绑定到PV,从而实现Pod的专有存储。
实践四:使用pv/pvc部署statefulSet类型的Deployment
1.准备一个pvc用于绑定pv
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kdemo-statefulset-pv
spec:
accessModes:
- ReadWriteMany
storageClassName: "rwx-statefulset-pvc"
resources:
requests:
storage: 20Mi
2.准备用于挂载的存储卷pvpv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: kdemo-statefulset-pv
spec:
capacity:
storage: 20Mi
accessModes:
- ReadWriteMany
storageClassName: "rwx-statefulset-pvc"
nfs:
server: 192.168.23.10
path: "/data/nfs/statefulSet-1"
3.准备用于statefulset绑定的无头服务headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: kdemo-statefulset
namespace: default
labels:
app: kdemo-statefulset
spec:
ports:
- name: http
port: 80
targetPort: 80
clusterIP: None
selector:
app: kdemo-statefulset
4.statefulset使用的deployment文件statefulSet.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kdemo-statefulset
namespace: default
spec:
serviceName: "nginx-demo"
replicas: 1
selector:
matchLabels:
app: kdemo-statefulset
template:
metadata:
labels:
app: kdemo-statefulset
spec:
containers:
- name: kdemo-statefulset
image: registry.cn-hangzhou.aliyuncs.com/liwb/kdemo:1.9
imagePullPolicy: Always
volumeMounts:
- name: nfs-statefulset-vol
mountPath: /webser/www/kdemo/web
volumes:
- name: nfs-statefulset-vol
persistentVolumeClaim:
claimName: kdemo-statefulset-pv
依次执行部署
kubectl apply -f pvc.yaml
kubectl apply -f pv.yaml
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv-2 1Gi RWO Recycle Available slow 8s
# 可以看到成功创建了一个 PV 对象,状态是:`Available`。
kubectl apply -f headless-svc.yaml
kubectl get svc
kubectl apply -f statefulSet.yaml
kubectl get pvc
通过 Headless Service
,StatefulSet 就保证了 Pod 网络标识的唯一稳定性,由于 Pod IP 并不是固定的,所以我们访问有状态应用
实例的时候,就必须使用 DNS 记录的方式来访问
最后我们可以通过删除 StatefulSet 对象来删除所有的 Pod,Pod是按照倒序的方式进行删除的
kubectl delete statefulsets kdemo-statefulset
kubectl get pods -w
五、思考
k8s明明已经有了多种存储卷方式实现持久化存储了。为什么还要增加pv/pvc的方式?
从上面的流程看通过pv/pvc的方式其实挺复杂的,这种方式是否过度设计?
六、作业
使用StorageClass的特性实现动态创建pv。