环境:centos7.9 docker-ce-20.10.9 kubernetes-version v1.22.6
先来回顾deployment,deployment创建了replicaset,由replicaset创建的pod都是无状态的,pod的名字都是replicaset名字+随机字符构成的,pod的启动顺序也是无所谓的,而且当一个pod消亡了,重新创建的pod是和消亡的pod没有任何关系的,所有pod副本都是使用同一个持久卷。这样的pod应用称之为无状态应用。
StatefulSet 有状态应用是指:
1、由StatefulSet创建的每个pod实例都是不可替代的个体,都拥有稳定的名字和状态;
2、由StatefulSet创建的每个pod都有一个从0开始的顺序索引,这个体现在pod的名字和主机名上,同时还会体现在pod对应的固定存储上;
3、当一个由StatefulSet创建的pod实例消失后,新创建的pod会拥有与之前消失的pod完全一致的名称;
4、由StatefulSet创建的每个pod实例都有自己的专属存储,这一点与replicaset创建的pod副本都是使用同一个持久卷存储不同;
5、扩容一个由StatefulSet创建的pod实例,会使用下一个还没使用到的顺序索引值来创建一个新的pod实例;
6、缩容一个StatefulSet将会最先删除最高索引值的pod实例;
7、当一个pod实例消失之后,其pvc和pv会被保留下来,新创建的pod实例将继续使用之前pvc和pv。
replicaset或replicacontroller管理的pod都是无状态的,任何时候它们都可以被一个全新的pod替换,而有状态的pod不一样,当有状态的pod挂掉之后,这个pod实例需要重建时,新的pod实例必须与被替换的pod实例具有相同的名字、网络标识和状态,这就是statefulset管理的pod。
StatefulSet保证了pod在重新调度后保留它们的标识和状态,与replicaset类似,StatefulSet也会指定期望的副本数,pod也是依据模板创建的,不过与replicaset不同的是,StatefulSet创建的pod副本并不是完全一样的,每个pod都可以有独立的数据卷,而且pod的名称也是有索引规律的,不是像replicaset创建的pod那样名称无规律。
1、headless server 用于定义网络标识DNS(所以需要创建一个headless server);
2、StatefulSet 控制器,用于创建具体的pod应用;
3、volumeClaimTemplates 持久卷声明模板(用于创建pvc),创建StatefulSet后就会依据这个模板为每一个pod都创建一个持久卷声明,有了持久卷声明之后,持久卷声明就会根据模式绑定到对应的持久卷;
#首先在nfs服务器上创建3个目录pv-0、pv-1、pv-2
[root@mysql k8s_data]# pwd
/home/k8s_data
[root@mysql k8s_data]# ll
total 0
drwxr-xr-x. 2 nfsnobody nfsnobody 23 May 3 22:39 pv-0
drwxr-xr-x. 2 nfsnobody nfsnobody 23 May 3 22:39 pv-1
drwxr-xr-x. 2 nfsnobody nfsnobody 23 May 3 22:39 pv-2
#现在开始创建pv,下面这个只是pv-0的资源清单,pv-1和pv-2的也照着做,不过注意修改name和path字段值
[root@master pv-nfs]# vim pv-nfs-0.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-0 #按照这个模板创建3个pv,名称分别是 pv-0、 pv-1、 pv-2
labels:
app: pv-nfs-0
spec:
capacity:
storage: 100M
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.118.128
path: /home/k8s_data/pv-0/ #同理,这里的目录也要修改为对应的目录
readOnly: false
[root@master statefulset]# kubectl get pv #现在3个pv已经创建好了,status是可用状态
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-0 100M RWO Retain Available 4s
pv-1 100M RWO Retain Available 4s
pv-2 100M RWO Retain Available 4s
[root@master statefulset]# vim statefulset.yaml
apiVersion: v1
kind: Service #先创建一个无头service
metadata:
labels: #service本身的标签
app: svc-statefulset-headless
name: svc-statefulset-headless #service的名称,下面创建的StatefulSet就要引用这个service名称
spec:
ports:
- port: 80 #service本身的端口
protocol: TCP
targetPort: 80 #目标端口80,即目标容器的80端口
selector:
app: nginx-statefulset #标签选择器要与下面创建的pod的标签一样
type: ClusterIP
clusterIP: None #clusterIP为None表示创建的service为无头service
---
apiVersion: apps/v1
kind: StatefulSet #创建StatefulSet资源
metadata:
labels: #StatefulSet本身的标签
app: statefulset
name: statefulset #资源名称
namespace: default #资源所属命名空间
spec:
selector: #标签选择器,要与下面pod模板定义的pod标签保持一致
matchLabels:
app: nginx-statefulset
replicas: 3 #副本数为3个
serviceName: svc-statefulset-headless #指定使用service为上面我们创建的无头service的名称
template:
metadata:
labels: #pod的标签,上面的无头service的标签选择器和StatefulSet标签选择器都要与这个相同
app: nginx-statefulset
spec:
containers:
- name: nginx-container #容器名称
image: nginx:1.7.9 #镜像
imagePullPolicy: IfNotPresent #镜像拉取策略
ports: #定义容器端口
- name: http #为端口取个名称为http
containerPort: 80 #容器端口
volumeMounts: #挂载点
- name: statefulset-data #指定使用的卷名称,这里使用的是下面定义的pvc模板的名称
mountPath: /var/data/ #挂载点
volumeClaimTemplates: #定义创建pvc的模板
- metadata:
name: statefulset-data #模板名称
spec:
resources: #资源请求
requests:
storage: 100M #需要100M的存储空间
accessModes:
- ReadWriteOnce #访问模式为RWO
storageClassName: "" #指定使用的存储类
注意:storageClassName字段设为空字符串表示不自动创建pv,如果不定义该storageClassName字段并且存在系统存在默认的存储类,则会根据volumeClaimTemplates模板要求自动创建pv,这里为了演示使用我们上面手动创建的pv,所以将storageClassName赋予一个空字符串。
#查看创建好的pod
#注意:statefulset为了确保创建的pod都是有索引顺序的,所以必须是前一个pod创建完成后处于就绪状态才开始创建下一个索引顺序的pod,依次类推
[root@master statefulset]# kubectl get pods
NAME READY STATUS RESTARTS AGE
statefulset-0 1/1 Running 0 16m
statefulset-1 1/1 Running 0 16m
statefulset-2 1/1 Running 0 16m
[root@master statefulset]#
#查看pvc,发现创建了3个以模板名称+pod名字命名的pvc,并且绑定到了我们手动创建的pv上面
[root@master statefulset]# kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pv-0 100M RWO Retain Bound default/statefulset-data-statefulset-0 17m
persistentvolume/pv-1 100M RWO Retain Bound default/statefulset-data-statefulset-1 17m
persistentvolume/pv-2 100M RWO Retain Bound default/statefulset-data-statefulset-2 17m
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/statefulset-data-statefulset-0 Bound pv-0 100M RWO 17m
persistentvolumeclaim/statefulset-data-statefulset-1 Bound pv-1 100M RWO 17m
persistentvolumeclaim/statefulset-data-statefulset-2 Bound pv-2 100M RWO 16m
#对每个pod实例写入数据
[root@master pv-nfs]# kubectl exec -it statefulset-0 -- touch /var/data/file0.txt
[root@master pv-nfs]# kubectl exec -it statefulset-1 -- touch /var/data/file1.txt
[root@master pv-nfs]# kubectl exec -it statefulset-2 -- touch /var/data/file2.txt
#查看nfs服务器,发现每个pod都能实现自己的存储数据了
[root@mysql /]# ll /home/k8s_data/pv-0/
total 0
-rw-r--r--. 1 nfsnobody nfsnobody 0 May 3 22:39 file0.txt
[root@mysql /]# ll /home/k8s_data/pv-1/
total 0
-rw-r--r--. 1 nfsnobody nfsnobody 0 May 3 22:39 file1.txt
[root@mysql /]# ll /home/k8s_data/pv-2/
total 0
-rw-r--r--. 1 nfsnobody nfsnobody 0 May 3 22:39 file2.txt
下面将继续演示,不手动创建pv,让StatefulSet动态分配pv,其原理就是在statefulset定义中指定使用的存储类,这样就会根据模板的要求自动创建pv,如下:
[root@master pv-nfs]# kubectl get sc #查集群中所有的存储类,只有一个储存类并且是默认存储类
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-storageclass (default) nfs-client-provisioner Retain Immediate false 41d
[root@master pv-nfs]#
[root@master statefulset]# vim statefulset-sc.yaml #创建一个动态配置pv的statefulset资源
apiVersion: v1
kind: Service #先创建一个无头的service
metadata:
labels: #service本身的标签
app: svc-statefulset-sc-headless
name: svc-statefulset-sc-headless #无头service的名称
spec:
ports:
- port: 80 #service的端口
protocol: TCP
targetPort: 80 #service的目标端口,一般是对应容器端口
selector:
app: nginx-statefulset-sc #标签选择器,与pod的标签应该一致
type: ClusterIP #service类型
clusterIP: None #clusterIP为None就表示创建的service是headlessservice
---
apiVersion: apps/v1 #创建statefulset资源
kind: StatefulSet
metadata:
labels:
app: statefulset-sc #statefulset本身的标签
name: statefulset-sc #statefulset的名称
namespace: default #statefulset所属命名空间
spec:
selector: #标签选择器,要与下面的pod的标签一致
matchLabels:
app: nginx-statefulset-sc
replicas: 3 #副本数为3个
serviceName: svc-statefulset-sc-headless #指定对应的service为上面创建的headless service
template:
metadata:
labels:
app: nginx-statefulset-sc #pod的标签,与上面的statefulset标签选择器一致
spec:
containers:
- name: nginx-container #容器名称
image: nginx:1.7.9 #镜像名称及版本
imagePullPolicy: IfNotPresent #镜像拉取策略
ports:
- name: http #端口名称,这个端口名称可以被service的targetPort直接引用
containerPort: 80 #容器端口
volumeMounts:
- name: statefulset-data #引用自下面创建的持久卷声明模板的name相同
mountPath: /var/data/ #挂载点
volumeClaimTemplates: #定义一个pvc的模板
- metadata:
name: statefulset-data #模板的名称
spec:
resources: #资源定义
requests:
storage: 100M #容量100M
accessModes:
- ReadWriteOnce #访问模式RWO
storageClassName: nfs-storageclass #指定使用的存储类
[root@master statefulset]#
#上面在volumeClaimTemplates中使用了storageClassName指定了存储类,这样创建pvc时就会动态pv,并与pvc进行绑定,不用在手动创建pv。
#注意:即使不写storageClassName属性,但是集群中有默认的存储类,此时仍会动态创建pv,如果不想要动态创建pv,想要pvc绑定主机你手动创建的pv,那么必须指定storageClassName: "",即给定storageClassName一个空字符串告诉它不用自动创建pv,这样创建statefulset时的pvc就会寻找你手动创建的pv进行匹配绑定而不是自动创建pv了。
#查看pvc和pv,发现已经自动创建了pv,并且与pvc进行了绑定,这就是由于存储类起的动态分配作用
[root@master statefulset]# kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/statefulset-data-statefulset-sc-0 Bound pvc-4f7e48b6-c623-4c0b-a6e1-c9a5e5fe5332 100M RWO nfs-storageclass 6s
persistentvolumeclaim/statefulset-data-statefulset-sc-1 Bound pvc-e705f80b-9ffc-4d0b-a8b4-b63d190023ef 100M RWO nfs-storageclass 3s
persistentvolumeclaim/statefulset-data-statefulset-sc-2 Bound pvc-4bb89035-e419-47b5-acd1-25f2b529ef93 100M RWO nfs-storageclass 0s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-4bb89035-e419-47b5-acd1-25f2b529ef93 100M RWO Retain Bound default/statefulset-data-statefulset-sc-2 nfs-storageclass 0s
persistentvolume/pvc-4f7e48b6-c623-4c0b-a6e1-c9a5e5fe5332 100M RWO Retain Bound default/statefulset-data-statefulset-sc-0 nfs-storageclass 6s
persistentvolume/pvc-e705f80b-9ffc-4d0b-a8b4-b63d190023ef 100M RWO Retain Bound default/statefulset-data-statefulset-sc-1 nfs-storageclass 3s
#查看nfs服务器上,动态创建了3个目录,对应着3个pv
[root@mysql /]# ll /home/k8s_data/
total 0
drwxrwxrwx. 2 nfsnobody nfsnobody 6 May 3 22:58 default-statefulset-data-statefulset-sc-0-pvc-4f7e48b6-c623-4c0b-a6e1-c9a5e5fe5332
drwxrwxrwx. 2 nfsnobody nfsnobody 6 May 3 22:58 default-statefulset-data-statefulset-sc-1-pvc-e705f80b-9ffc-4d0b-a8b4-b63d190023ef
drwxrwxrwx. 2 nfsnobody nfsnobody 6 May 3 22:58 default-statefulset-data-statefulset-sc-2-pvc-4bb89035-e419-47b5-acd1-25f2b529ef93
#对每个pod实例挂载点存入数据
[root@master statefulset]# kubectl exec -it statefulset-sc-0 -- touch /var/data/file0
[root@master statefulset]# kubectl exec -it statefulset-sc-1 -- touch /var/data/file1
[root@master statefulset]# kubectl exec -it statefulset-sc-2 -- touch /var/data/file2
#查看nfs服务器上是否真正存储数据,发现每个pod对应的目录已经存入了数据
[root@mysql /]# ll /home/k8s_data/default-statefulset-data-statefulset-sc-0-pvc-4f7e48b6-c623-4c0b-a6e1-c9a5e5fe5332
total 0
-rw-r--r--. 1 nfsnobody nfsnobody 0 May 3 23:20 file0
[root@mysql /]# ll /home/k8s_data/default-statefulset-data-statefulset-sc-1-pvc-e705f80b-9ffc-4d0b-a8b4-b63d190023ef
total 0
-rw-r--r--. 1 nfsnobody nfsnobody 0 May 3 23:20 file1
[root@mysql /]# ll /home/k8s_data/default-statefulset-data-statefulset-sc-2-pvc-4bb89035-e419-47b5-acd1-25f2b529ef93
total 0
-rw-r--r--. 1 nfsnobody nfsnobody 0 May 3 23:21 file2
[root@mysql /]#
演示pod异常挂掉,看看结果如何:
[root@master statefulset]# kubectl get pod statefulset-sc-1 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
statefulset-sc-1 1/1 Running 0 30m 10.244.1.210 node1 <none> <none>
[root@master statefulset]# kubectl delete pod statefulset-sc-1 #故意删除statefulset-sc-1
pod "statefulset-sc-1" deleted
[root@master statefulset]# kubectl get pod statefulset-sc-1 -o wide #稍等一会之后,statefulset又创建了一个与删除掉的pod同名的pod
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
statefulset-sc-1 1/1 Running 0 4s 10.244.1.211 node1 <none> <none>
[root@master statefulset]# kubectl exec -it statefulset-sc-1 -- ls /var/data/ #查看数据,仍是之前的数据
file1
#以上说明,由statefulset创建pod挂掉之后,statefulset又会创建一个同名的pod,而且继承了之前的存储,但是pod的ip和pod所在的节点可能与之
# 前的pod不同,因为我们之前说过,statefulset创建的pod都是有索引顺序的,而pod的ip是随机分配的,pod所在的节点也是由调度器决定的
statefulset缩容会依次删除最高索引的pod,但是即使pod删除了,其对应的pvc和pv仍保留着,需要手动删除,这里不在举例演示;
statefulset扩容也是依次从最高索引的下一个索引开始创建pod ,如果是动态分配pv,则创建对应的pv,这里也不在举例演示;
statefulset滚动更新和deployment一样,没有什么好讲的,这里不在陈诉。
1、什么是无状态应用?先来回顾deployment,deployment创建了replicaset,由replicaset创建的pod都是无状态的,pod的名字都是replicaset
名字+随机字符构成的,pod的启动顺序也是无所谓的,而且当一个pod消亡了,重新创建的pod是和消亡的pod没有任何关系的,所有pod副本都是使用同一
个持久卷。这样的pod应用称之为无状态应用。
2、什么是有状态应用?使用StatefulSet创建有状态应用,由StatefulSet创建的每个pod实例都是不可替代的个体,都拥有稳定的名字和状态;每个
pod都有一个从0开始的顺序索引,每个pod都有自己的持久卷存储;当一个由StatefulSet创建的pod实例消失后,新创建的pod会拥有与之前消失的pod
完全一致的名称;扩容一个由StatefulSet创建的pod实例,会使用下一个还没使用到的顺序索引值来创建一个新的pod实例;缩容一个StatefulSet将会
最先删除最高索引值的pod实例;当一个pod实例消失之后,其pvc和pv会被保留下来,新创建的pod实例将与消失的pod名称一模一样并且继续继承其pvc
和pv。
3、statefulset资源必须要有一个headless service。
4、可以手动创建pv,然后在创建statefulset资源,如下:
#首先在nfs服务器上创建3个目录pv-0、pv-1、pv-2
[root@mysql k8s_data]# pwd
/home/k8s_data
[root@mysql k8s_data]# ll
total 0
drwxr-xr-x. 2 nfsnobody nfsnobody 23 May 3 22:39 pv-0
drwxr-xr-x. 2 nfsnobody nfsnobody 23 May 3 22:39 pv-1
drwxr-xr-x. 2 nfsnobody nfsnobody 23 May 3 22:39 pv-2
#现在开始创建pv,下面这个只是pv-0的资源清单,pv-1和pv-2的也照着做,不过注意修改name和path字段值
[root@master pv-nfs]# vim pv-nfs-0.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-0 #按照这个模板创建3个pv,名称分别是 pv-0、 pv-1、 pv-2
labels:
app: pv-nfs-0
spec:
capacity:
storage: 100M
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.118.128
path: /home/k8s_data/pv-0/ #同理,这里的目录也要修改为对应的目录
readOnly: false
[root@master statefulset]# kubectl get pv #现在3个pv已经创建好了,status是可用状态
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-0 100M RWO Retain Available 4s
pv-1 100M RWO Retain Available 4s
pv-2 100M RWO Retain Available 4s
[root@master statefulset]# vim statefulset.yaml #创建statefulset资源清单
apiVersion: v1
kind: Service #先创建一个无头service
metadata:
labels: #service本身的标签
app: svc-statefulset-headless
name: svc-statefulset-headless #service的名称,下面创建的StatefulSet就要引用这个service名称
spec:
ports:
- port: 80 #service本身的端口
protocol: TCP
targetPort: 80 #目标端口80,即目标容器的80端口
selector:
app: nginx-statefulset #标签选择器要与下面创建的pod的标签一样
type: ClusterIP
clusterIP: None #clusterIP为None表示创建的service为无头service
---
apiVersion: apps/v1
kind: StatefulSet #创建StatefulSet资源
metadata:
labels: #StatefulSet本身的标签
app: statefulset
name: statefulset #资源名称
namespace: default #资源所属命名空间
spec:
selector: #标签选择器,要与下面pod模板定义的pod标签保持一致
matchLabels:
app: nginx-statefulset
replicas: 3 #副本数为3个
serviceName: svc-statefulset-headless #指定使用service为上面我们创建的无头service的名称
template:
metadata:
labels: #pod的标签,上面的无头service的标签选择器和StatefulSet标签选择器都要与这个相同
app: nginx-statefulset
spec:
containers:
- name: nginx-container #容器名称
image: nginx:1.7.9 #镜像
imagePullPolicy: IfNotPresent #镜像拉取策略
ports: #定义容器端口
- name: http #为端口取个名称为http
containerPort: 80 #容器端口
volumeMounts: #挂载点
- name: statefulset-data #指定使用的卷名称,这里使用的是下面定义的pvc模板的名称
mountPath: /var/data/ #挂载点
volumeClaimTemplates: #定义创建pvc的模板
- metadata:
name: statefulset-data #模板名称
spec:
resources: #资源请求
requests:
storage: 100M #需要100M的存储空间
accessModes:
- ReadWriteOnce #访问模式为RWO
storageClassName: "" #指定使用的存储类
注意:storageClassName字段设为空字符串表示不自动创建pv,如果不定义该storageClassName字段并且存在系统存在默认的存储类,则会根据
volumeClaimTemplates模板要求自动创建pv,这里为了演示使用我们上面手动创建的pv,所以将storageClassName赋予一个空字符串。
5、使用存储类自动分配pv,如下:
[root@master pv-nfs]# kubectl get sc #查集群中所有的存储类,只有一个储存类并且是默认存储类
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-storageclass (default) nfs-client-provisioner Retain Immediate false 41d
[root@master pv-nfs]#
[root@master statefulset]# vim statefulset-sc.yaml #创建一个动态配置pv的statefulset资源
apiVersion: v1
kind: Service #先创建一个无头的service
metadata:
labels: #service本身的标签
app: svc-statefulset-sc-headless
name: svc-statefulset-sc-headless #无头service的名称
spec:
ports:
- port: 80 #service的端口
protocol: TCP
targetPort: 80 #service的目标端口,一般是对应容器端口
selector:
app: nginx-statefulset-sc #标签选择器,与pod的标签应该一致
type: ClusterIP #service类型
clusterIP: None #clusterIP为None就表示创建的service是headlessservice
---
apiVersion: apps/v1 #创建statefulset资源
kind: StatefulSet
metadata:
labels:
app: statefulset-sc #statefulset本身的标签
name: statefulset-sc #statefulset的名称
namespace: default #statefulset所属命名空间
spec:
selector: #标签选择器,要与下面的pod的标签一致
matchLabels:
app: nginx-statefulset-sc
replicas: 3 #副本数为3个
serviceName: svc-statefulset-sc-headless #指定对应的service为上面创建的headless service
template:
metadata:
labels:
app: nginx-statefulset-sc #pod的标签,与上面的statefulset标签选择器一致
spec:
containers:
- name: nginx-container #容器名称
image: nginx:1.7.9 #镜像名称及版本
imagePullPolicy: IfNotPresent #镜像拉取策略
ports:
- name: http #端口名称,这个端口名称可以被service的targetPort直接引用
containerPort: 80 #容器端口
volumeMounts:
- name: statefulset-data #引用自下面创建的持久卷声明模板的name相同
mountPath: /var/data/ #挂载点
volumeClaimTemplates: #定义一个pvc的模板
- metadata:
name: statefulset-data #模板的名称
spec:
resources: #资源定义
requests:
storage: 100M #容量100M
accessModes:
- ReadWriteOnce #访问模式RWO
storageClassName: nfs-storageclass #指定使用的存储类
[root@master statefulset]#
#上面在volumeClaimTemplates中使用了storageClassName指定了存储类,这样创建pvc时就会动态pv,并与pvc进行绑定,不用在手动创建pv。
注意:即使不写storageClassName属性,但是集群中有默认的存储类,此时仍会动态创建pv,如果不想要动态创建pv,想要pvc绑定主机你手动创建的
pv,那么必须指定storageClassName: "",即给定storageClassName一个空字符串告诉它不用自动创建pv,这样创建statefulset时的pvc就会寻找
你手动创建的pv进行匹配绑定而不是自动创建pv了。
再次强调,不管了手动分配pv还是使用存储类动态分配pv,由statefulset创建的每个pod实例都有自己对应的pvc,而pvc需要绑定到一个匹配的pv,也就是说每个pod实例都有自己的专属持久卷存储。