一、PV和PVC详解
当前,存储的方式和种类有很多,并且各种存储的参数也需要非常专业的技术人员才能够了解。在Kubernetes集群中,放了方便我们的使用和管理,Kubernetes提出了PV和PVC的概念,这样Kubernetes集群的管理人员就可以将注意力集中到Kubernetes集群中来,而无需操心后端的存储设备。
pv : 相当于磁盘分区
pvc: 相当于磁盘请求
PersistentVolumeClaim(PVC)是用户存储的请求
PVC的使用逻辑:在pod中定义一个存储卷(该存储卷类型为PVC),
定义的时候直接指定大小,pvc必须与对应的pv建立关系,
pvc会根据定义去pv申请,而pv是由存储空间创建出来的。
pv和pvc是kubernetes抽象出来的一种存储资源。
- PV : 持久化卷的意思,是对底层的共享存储的一种抽象
- PVC(Persistent Volume Claim)是持久卷请求于存储需求的一种声明(PVC其实就是用户向kubernetes系统发出的一种资源需求申请。)
从上图可以看出,底层的存储可以使各种类型,包括NFS、Ceph、CIFS等等,而Kubernetes会把这些存储统一抽象为PV。PV,即Persistent Volume,是集群中配置的存储资源。PVC,即Persistent Volume Claim,是用户存储的请求,通常我们在一个Pod中定义一个存储卷,定义的时候会指定该存储卷的相关信息,比如空间大小、可读可写等属性。但是PVC并不是真正的存储空间,Pod的PVC和PV之间必须建立某种联系,这样才能使得Pod可以调用实际存储空间。
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2
spec:
nfs: # 存储类型,与底层真正存储对应
capacity: # 存储能力,目前只支持存储空间的设置
storage: 2Gi
accessModes: # 访问模式
storageClassName: # 存储类别
persistentVolumeReclaimPolicy: # 回收策略
使用了PV和PVC之后,工作可以得到进一步的细分:
存储:存储工程师维护
PV: kubernetes管理员维护
PVC:kubernetes用户维护
二、PV和PVC生命周期
实际上,不管是PV,还是PVC,都遵循以下生命周期:
Provisioning(配置)---> Binding(绑定)---> Using(使用)---> Releasing(释放) ---> Recycling(回收)
2.1 Provisioning 配置
Provisioning,即配置阶段。一般而言,PV的提供方式有两种——静态和动态。
所谓静态提供,就是Kubernetes管理员创建多个PV,这些PV的存储空间等属性已经确定,并且已经和真实的存储设备进行了关联。Pod中的PVC可以根据需要请求这些PV。
所谓动态提供,需要依托与StorageClass的支持,这时Kubernetes会尝试为PVC来动态的创建PV。这样做的好处是避免出现这种情况:部分PVC被分配给了远远超出其资源需求的PV、或者说系统存在很多资源较少的PV,但是一个资源需求很高的PVC缺无法被满足的情况。
2.2 Binding 结合
在动态配置的情况下,用户创建或者已经创建了具有特定数量的PVC后,PVC与PV绑定的过程。
如果没有满足PVC请求需求的PV,那么PVC将无法被创建,因此造成的结果就是相应的Pod也不会被创建。
2.3 Using 使用
即PVC与PC绑定后,Pod对存储空间的使用过程。
2.4 Releasing 释放
当Pod被删除或者对该PV的资源使用结束后,Kubernetes就会删除该PVC对象,相应的也会回收PV资源,这时的PV就会处于这种状态。但是此时的PV还需要处理完毕之前的Pod在该存储卷上存储信息后才能够被使用。
2.5 Reclaiming 处理中
PV的回收策略对被释放的PV的处理过程。
2.6 Recycling 循环
根据配置,有时PV会被执行擦除操作,删除掉该存储空间上的所有信息,并且该存储资源也可以被再次使用。
三、访问模式
3.1 PV的访问模式(accessmodes)
模式 | 翻译 |
ReadWriteOnce (RWO) | 可读可写,但只支持被单个节点挂载。 |
ReadOnlyMany (ROX) | 只读,可以被多个节点挂载。 |
ReadWriteMany (RWX) | 多路可读可写。这种存储可以以读写的方式被多个节点共享。不是每一种存储都支持这三种方式,像共享方式,目前支持的还比较少,比较常用的是 NFS。在PVC绑定PV时通常根据两个条件来绑定,一个是存储的大小,另一个就是访问模式。 |
3.2 PV的回收策略(persistentVolumeReclaimPolicy)
策略 | 解释 |
retain | 不清理,保留Volume (需要手动清理) |
Recycle | 删除数据,即rm -rf /thevolumel*(只有NFS和HostPath支持) |
Delete | 删除存储资源,比如删除AWS EBS卷(只有AWS EBS,GCE PD,Azure Disk和Cinder支持) |
3.3 pv的状态
状态 | 解释 |
Available | 可用 |
Bound | 已经分配给PVC |
Released | PVC解绑但还未执行回收策略 |
Failed | 发生错误 |
四、实验验证
4.1 安装nfs
# 1、创建目录
[root@k8s ~]# mkdir /root/data/{pv1,pv2,pv3} -pv
chmod 777 /data/volumes
# 2、暴露服务
[root@k8s ~]# vim /etc/exports
/root/data/pv1 192.168.223.0/24(rw,sync,no_root_squash)
/root/data/pv2 192.168.223.0/24(rw,sync,no_root_squash)
/root/data/pv3 192.168.223.0/24(rw,sync,no_root_squash)
# 3、重启服务
[root@k8s ~]# systemctl restart nfs
//master节点操作
vim pod-nfs-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-nfs
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
nfs:
path: /data/volumes
server: stor01(主机名)
4.2 验证nfs持久化存储
//在nfs服务器上创建index.html
cd /data/volumes
vim index.html
<h1> nfs stor01</h1>//master节点操作
curl 10.244.2.38
<h1> nfs stor01</h1>kubectl delete -f pod-nfs-vol.yaml #删除nfs相关pod,再重新创建,可以得到数据的持久化存储
kubectl apply -f pod-nfs-vol.yaml
nas gfs ceph san
可以实现持久化存储,使用nfs将存储设别空间挂载到容器中,pod可以跨node节点共享数据
4.3 NFS使用PV和PVC
PVC是资源的申请,用来声明对存储空间、访问模式、存储类别需求信息。下面是资源清单文件
4.3.1、配置nfs存储
//NFS使用PV和PVC
1、配置nfs存储
mkdir v{1,2,3,4,5}vim /etc/exports
/data/volumes/v1 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.10.0/24(rw,no_root_squash)exportfs -arv
showmount -e
4.3.2 定义PV
//这里定义5个PV,并且定义挂载的路径以及访问模式,还有PV划分的大小。
vim pv-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: stor01
accessModes: ["ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv004
labels:
name: pv004
spec:
nfs:
path: /data/volumes/v4
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv005
labels:
name: pv005
spec:
nfs:
path: /data/volumes/v5
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 5Gikubectl apply -f pv-demo.yaml
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 7s
pv002 2Gi RWO Retain Available 7s
pv003 2Gi RWO,RWX Retain Available 7s
pv004 4Gi RWO,RWX Retain Available 7s
pv005 5Gi RWO,RWX Retain Available 7s
4.3.3 定义PVC
//这里定义了pvc的访问模式为多路读写,该访问模式必须在前面pv定义的访问模式之中。定义PVC申请的大小为2Gi,此时PVC会自动去匹配多路读写且大小为2Gi的PV,匹配成功获取PVC的状态即为Bound
vim pod-vol-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-pvc
namespace: default
spec:
containers:
- name: myapp
image: soscscs/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: mypvc
kubectl apply -f pod-vol-pvc.yamlkubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 19m
pv002 2Gi RWO Retain Available 19m
pv003 2Gi RWO,RWX Retain Bound default/mypvc 19m
pv004 4Gi RWO,RWX Retain Available 19m
pv005 5Gi RWO,RWX Retain Available 19mkubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound pv003 2Gi RWO,RWX 22s
4.3.4 测试访问
//在存储服务器上创建index.html,并写入数据,通过访问Pod进行查看,可以获取到相应的页面。
cd /data/volumes/v3/
echo "welcome to use pv3" > index.htmlkubectl get pods -o wide
pod-vol-pvc 1/1 Running 0 3m 10.244.2.39 k8s-node02curl 10.244.2.39
welcome to use pv3
4.4 搭建 StorageClass + NFS,实现 NFS 的动态 PV 创建
4.4.1 在stor01节点上安装nfs,并配置nfs服务
关闭防火墙
mkdir /opt/k8s
chmod 777 /opt/k8s/vim /etc/exports
/opt/k8s 192.168.10.0/24(rw,no_root_squash,sync)systemctl restart nfs
4.4.2 创建 Service Account,用来管理 NFS Provisioner 在 k8s 集群中运行的权限,设置 nfs-client 对 PV,PVC,StorageClass 等的规则
vim nfs-client-rbac.yaml
#创建 Service Account 账户,用来管理 NFS Provisioner 在 k8s 集群中运行的权限
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
---
#创建集群角色
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-client-provisioner-clusterrole
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: ["list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
---
#集群角色绑定
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nfs-client-provisioner-clusterrolebinding
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-clusterrole
apiGroup: rbac.authorization.k8s.io
kubectl apply -f nfs-client-rbac.yaml
4.4.3 3、使用 Deployment 来创建 NFS Provisioner
NFS Provisione(即 nfs-client),有两个功能:一个是在 NFS 共享目录下创建挂载点(volume),另一个则是将 PV 与 NFS 的挂载点建立关联。
#由于 1.20 版本启用了 selfLink,所以 k8s 1.20+ 版本通过 nfs provisioner 动态生成pv会报错,解决方法如下:
vim /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- command:
- kube-apiserver
- --feature-gates=RemoveSelfLink=false #添加这一行
- --advertise-address=192.168.10.19
......kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
kubectl delete pods kube-apiserver -n kube-system
kubectl get pods -n kube-system | grep apiserver#创建 NFS Provisioner
vim nfs-client-provisioner.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner #指定Service Account账户
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: nfs-storage #配置provisioner的Name,确保该名称与StorageClass资源中的provisioner名称保持一致
- name: NFS_SERVER
value: stor01 #配置绑定的nfs服务器
- name: NFS_PATH
value: /opt/k8s #配置绑定的nfs服务器目录
volumes: #申明nfs数据卷
- name: nfs-client-root
nfs:
server: stor01
path: /opt/k8s
kubectl apply -f nfs-client-provisioner.yamlkubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-cd6ff67-sp8qd 1/1 Running 0 14s
4.4.4 创建 StorageClass,负责建立 PVC 并调用 NFS provisioner 进行预定的工作,并让 PV 与 PVC 建立关联
vim nfs-client-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client-storageclass
provisioner: nfs-storage #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
parameters:
archiveOnDelete: "false" #false表示在删除PVC时不会对数据进行存档,即删除数据
kubectl apply -f nfs-client-storageclass.yamlkubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client-storageclass nfs-storage Delete Immediate false 43s
4.4.5 创建 PVC 和 Pod 测试
vim test-pvc-pod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-nfs-pvc-1
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-client-storageclass #关联StorageClass对象
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: test-storageclass-pod
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
- "-c"
args:
- "sleep 3600"
volumeMounts:
- name: nfs-pvc
mountPath: /mnt
restartPolicy: Never
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-nfs-pvc-1 #与PVC名称保持一致
4.4.6 验证结果
kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
myapp01 1/1 Running 0 24h 10.244.1.6 node01 <none>
myapp04 1/1 Running 0 24h 10.244.2.4 node02 <none>
nfs-client-provisioner-d9dcdf4c4-f57d4 1/1 Running 0 144m 10.244.2.6 node02 <none>
nginx-6799fc88d8-9vhpk 1/1 Running 0 24h 10.244.1.7 node01 <none>
pod-vol-nfs 1/1 Running 0 19h 10.244.2.5 node02 <none>
pod-vol-pvc 1/1 Running 1 18h 10.244.1.8 node01 <none>
test-storageclass-pod 1/1 Running 0 78s 10.244.1.9 node01 <none>
kubectl exec -it test-storageclass-pod sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead./ #
/ # cd /mnt/
/mnt # ls
/mnt #
/mnt # echo "wang shi kang sb" > index.html
/mnt #
五、总结
5.1 Volume
①emptyDir:可以实现Pod中的容器之间共享数据,但是存储卷不能持久化数据,且会随着Pod生命周期结束而一起删除
②hostpath:可以实现持久化存储,使用node节点的目录或文件挂载到容器,但是存储空间会收到node节点单机限制,node节点故障数据会丢失,Pod会跨节点不能共享数据
③NFS:可以实现持久化存储,使用NFS将存储设备空间挂载到容器中,Pod可以跨Node节点共享数据
5.2 PV和PVC
①PV 持久卷(Persistent Volumes)是集群中的一块存储资源,由管理员事先创建或由动态供应机制(如StorageClass)自动创建。
②PVC持久卷声明(Persistent Volume Claims)是用户对存储的请求或声明,Pod通过挂载PVC来使用PV。这种方式提供了存储的抽象和解耦,使得应用无需关心具体的存储细节。
③当Pod销毁或重建时,与之关联的PVC仍然保留,保证了数据的持久性。
5.3 StorageClass——动态存储
①存储类定义了存储的“类”,用于动态供应PV。管理员可以根据不同的需求(如性能、成本)定义多个存储类。
②用户在创建PVC时可以选择一个存储类,Kubernetes会自动匹配并创建合适的PV。
③对于存储资源,虽然不像CPU和内存那样频繁地设置请求和限制,但在使用PV/PVC时,可以通过设置存储容量的大小来间接限制使用量。
④在Pod的YAML定义中,可以通过volumeMounts来指定容器如何挂载卷,以及是否需要设置读写权限等。
⑤Pod中的存储卷可以配置访问模式(如只读、读写)来确保数据的安全性。
对于敏感数据,推荐使用Secrets或ConfigMaps,并注意权限控制。
六、存储卷
容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet 会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。其次,在Pod中同时运行多个容器时,这些容器之间通常需要共享文件。Kubernetes 中的Volume抽象就很好的解决了这些问题。Pod中的容器通过Pause容器共享Volume。
6.1 emptyDir存储卷
当Pod被分配给节点时,首先创建emptyDir卷,并且只要该Pod在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。Pod 中的容器可以读取和写入emptyDir卷中的相同文件,尽管该卷可以挂载到每个容器中的相同或不同路径上。当出于任何原因从节点中删除 Pod 时,emptyDir中的数据将被永久删除。
mkdir /opt/volumes
cd /opt/volumes
vim pod-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-emptydir
namespace: default
labels:
app: myapp
tier: frontend
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
#定义容器挂载内容
volumeMounts:
#使用的存储卷名称,如果跟下面volume字段name值相同,则表示使用volume的这个存储卷
- name: html
#挂载至容器中哪个目录
mountPath: /usr/share/nginx/html/
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: html
#在容器内定义挂载存储名称和挂载路径
mountPath: /data/
command: ['/bin/sh','-c','while true;do echo $(date) >> /data/index.html;sleep 2;done']
#定义存储卷
volumes:
#定义存储卷名称
- name: html
#定义存储卷类型
emptyDir: {}
[root@master01 volumes]]#kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-797d747cf6-6mkc9 1/1 Running 0 12d 10.244.1.12 node02 <none> <none>
nginx-deployment-797d747cf6-hzn8m 1/1 Running 0 12d 10.244.2.13 node01 <none> <none>
nginx-deployment-797d747cf6-rk8hr 1/1 Running 0 12d 10.244.2.12 node01 <none> <none>
pod-emptydir 2/2 Running 0 96s 10.244.2.14 node01 <none> <none>
在上面定义了2个容器,其中一个容器是输入日期到index.html中,然后验证访问nginx的html是否可以获取日期。以验证两个容器之间挂载的emptyDir实现共享。
6.2 hastPath存储卷
hostPath卷将 node 节点的文件系统中的文件或目录挂载到集群中。
hostPath可以实现持久存储,但是在node节点故障时,也会导致数据的丢失。
//在 node01 节点上创建挂载目录
mkdir -p /data/pod/volume1
echo 'node01.kgc.com' > /data/pod/volume1/index.html
//在 node02 节点上创建挂载目录
mkdir -p /data/pod/volume1
echo 'node02.kgc.com' > /data/pod/volume1/index.html
//创建 Pod 资源
vim pod-hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-hostpath
namespace: default
spec:
containers:
- name: myapp
image: soscscs/myapp:v1
#定义容器挂载内容
volumeMounts:
#使用的存储卷名称,如果跟下面volume字段name值相同,则表示使用volume的这个存储卷
- name: html
#挂载至容器中哪个目录
mountPath: /usr/share/nginx/html
#读写挂载方式,默认为读写模式false
readOnly: false
#volumes字段定义了paues容器关联的宿主机或分布式文件系统存储卷
volumes:
#存储卷名称
- name: html
#路径,为宿主机存储路径
hostPath:
#在宿主机上目录的路径
path: /data/pod/volume1
#定义类型,这表示如果宿主机没有此目录则会自动创建
type: DirectoryOrCreate
kubectl apply -f pod-hostpath.yaml
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-797d747cf6-6mkc9 1/1 Running 0 12d 10.244.1.12 node02 <none> <none>
nginx-deployment-797d747cf6-hzn8m 1/1 Running 0 12d 10.244.2.13 node01 <none> <none>
nginx-deployment-797d747cf6-rk8hr 1/1 Running 0 12d 10.244.2.12 node01 <none> <none>
pod-emptydir 2/2 Running 0 30m 10.244.2.14 node01 <none> <none>
pod-hostpath 1/1 Running 0 2m3s 10.244.1.13 node02 <none> <none>
[root@master01 volumes]]#curl 10.244.1.13
node02.rmh.com