第16章 持久卷PV和PVC
1.PV和PVC介绍
https://kubernetes.io/zh/docs/concepts/storage/persistent-volumes/
PV是对底层网络共享存储的抽象,将存储定义为一种“资源”。PV由管理员创建和配置PVC则是用户对存储资源的一个“申请”。就像Pod消费Node的资源一样,PVC能够“消费”PV资源PVC可以申请特定的存储空间和访问模式
2.PV和PVC生命周期
在PV的整个生命周期中,可能会处于4种不同的阶段:
Avaliable(可用):表示可用状态,还未被任何PVC绑定
Bound(绑定):表示PV已经被PVC绑定
Released(已释放):PVC被删除,但是资源还未被集群重新声明
Failed(失败):表示该PV的自动回收失败
创建PVC之后,k8s就会去查找满足我们声明要求的PV,比如storageClassName,accessModes以及容量这些是否满足要求,如果满足要求就将PV和PVC绑定在一起。
需要注意的是目前PV和PVC之间是一对一绑定的关系,也就是说一个PV只能被一个PVC绑定。
3.PV支持的类型
PV支持的类型主要分为动态和静态两种,这里我们只列举常用的存储类型:
静态PV:
- 宿主机目录:hostPath
- 本地存储设备:LocalPV
动态PV:
- 共享存储:Ceph,GlusterFS,NFS
4.创建hostPath类型PV和PVC
4.1 创建PV
创建数据目录:因为hostPath使用的是宿主机目录,所以这种类型的PV需要POD绑定到固定的Node节点上才能保证POD删除后再创建数据还能继续使用,这样就大大降低了POD的灵活性,不过我们还是先在node1上创建数据目录及文件来测试。这里选择在node1节点上操作。
mkdir /data/k8s/hostpath -p
echo "hostpath node1 ok" >> /data/k8s/hostpath/index.html
资源配置清单:
cat > pv-hostpath.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: hostpath
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: "/data/k8s/hostpath"
EOF
kubectl apply -f pv-hostpath.yaml
配置详解:
capacity: PV存储的容量
------------------------------------------------------
accessModes: 访问模式,k8s支持的访问模式如下
- ReadWriteOnce(RWO): 读写权限,并且只能被单个Node挂载
- ReadOnlyMany(ROX): 只读权限,允许被多个Node挂载
- ReadWriteMany(RWX): 读写权限,允许被多个Node挂载
------------------------------------------------------
persistentVolumeReclaimPolicy: 回收策略
- Retain: 保留数据,需要手工处理
- Recycle: 简单清除文件的操作(例如运行rm -rf /dada/* 命令)
- Delete: 与PV相连的后端存储完成Volume的删除操作
目前只有NFS和HostPath两种类型的PV支持Recycle策略。
------------------------------------------------------
storageClassName: 存储类别
具有特定类别的PV只能与请求了该类别的PVC绑定。未指定类型的PV则只能对与不请求任何类别的PVC绑定。
------------------------------------------------------
hostPath: #存储类型是hostpath,即会在宿主机目录上创建相应的路径
path: "/data/k8s/hostpath" #宿主机创建的目录
查看创建的PV:
kubectl get pv
kubectl describe pv pv-hostpath
PV状态解释:
Avaliable(可用):可用状态,还未被任何PVC绑定
Bound(可绑定):PV已经被PVC绑定
Released(已释放):PVC被删除,但是资源未被回收,不能被其他PVC使用
Failed(失败):该PV的自动回收失败
4.2 创建PVC
资源配置清单:
cat > pvc-hostpath.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-hostpath
spec:
storageClassName: hostpath
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
EOF
kubectl apply -f pvc-hostpath.yaml
资源配置详解:
accessModes: 访问模式,与PV定义的一样
------------------------------------------------------
resources: 描述对存储资源的请求,设置需要的存储空间大小
------------------------------------------------------
storageClassName: 存储类别,与PV的存储类型相匹配
查看创建的PVC:
[root@master-10 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-hostpath Bound pv-hostpath 10Gi RWO hostpath 56s
PVC状态解释:
通过查看pvc状态可以看到STATUS字段显示的状态为Bound,这里表示PVC已经与PV进行了绑定
查看PVC绑定状态:
[root@master-10 ~]# kubectl describe pvc pvc-hostpath
Name: pvc-hostpath
Namespace: default
StorageClass: hostpath #PV的StorageClass名称
Status: Bound #PV已经与PVC绑定
Volume: pv-hostpath #绑定的PV
Labels: <none>
Annotations: pv.kubernetes.io/bind-completed: yes
pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pvc-protection]
Capacity: 10Gi #PV生命的容量
Access Modes: RWO #访问模式
VolumeMode: Filesystem
Mounted By: <none>
Events: <none>
4.3 创建POD
pv和pvc已经创建好了,接下来我们就可以创建POD来使用PVC了
资源配置清单:注意,因为我们hostpath目录只在node1上创建了,所以这里我们需要手动将POD调度到Node1节点上才能使用这个PV。
cat > pod-pvc-hostpath.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pod-pvc-hostpath
spec:
volumes:
- name: pvc-hostpath
persistentVolumeClaim:
claimName: pvc-hostpath
nodeSelector:
kubernetes.io/hostname: node1
containers:
- name: pod-pvc-hostpath
image: nginx:1.14.0
ports:
- containerPort: 80
volumeMounts:
- name: pvc-hostpath
mountPath: "/usr/share/nginx/html"
EOF
kubectl apply -f pod-pvc-hostpath.yaml
查看POD创建的情况:
[root@master-10 yaml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-pvc-hostpath 1/1 Running 0 42s
[root@master-10 yaml]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-pvc-hostpath 1/1 Running 0 42s 10.2.1.14 node-20 <none> <none>
测试访问:
[root@master-10 yaml]# curl 10.2.1.14
hostpath node1 ok
删除POD然后再重新创建之后测试:
# kubectl delete pod pod-pvc-hostpath
pod "pod-pvc-hostpath" deleted
# kubectl apply -f pod-pvc-hostpath.yaml
pod/pod-pvc-hostpath created
# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-pvc-hostpath 1/1 Running 0 2s
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-pvc-hostpath 1/1 Running 0 4s 10.2.1.15 node-20 <none> <none>
# curl 10.2.1.15
hostpath node1 ok
5.创建NFS类型PV和PVC
5.1 nfs配置:
注意:Node节点也需要安装nfs客户端
yum install nfs-utils -y
cat > /etc/exports <<EOF
/data/k8s/nfs 10.0.0.0/8(rw,sync,no_root_squash)
EOF
systemctl restart nfs rpcbind
mkdir -p /data/k8s/nfs
chown -R nfsnobody:nfsnobody /data/k8s/nfs/
echo "nfs master ok" >> /data/k8s/nfs/index.html
5.2 创建PV
资源配置清单:
cat > pv-nfs.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs
labels:
type: nfs
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
server: 10.0.0.10
path: /data/k8s/nfs
EOF
kubectl apply -f pv-nfs.yaml
配置详解:
nfs: #存储类型是NFS
server: 10.0.0.10 #NFS服务的IP地址
path: /data/nfs-volume/mysql #NFS的共享目录路径
查看pv信息:
# kubectl get pv pv-nfs
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-nfs 5Gi RWO Retain Available nfs 38m
5.3 创建PVC
资源配置清单:
cat > pvc-nfs.yaml <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs
spec:
storageClassName: nfs
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
EOF
kubectl apply -f pvc-nfs.yaml
查看创建结果:
# kubectl get pvc pvc-nfs
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-nfs Bound pv-nfs 5Gi RWO nfs 17s
创建POD使用PVC:
cat > pod-pvc-nfs.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pod-pvc-nfs
spec:
volumes:
- name: pvc-nfs
persistentVolumeClaim:
claimName: pvc-nfs
containers:
- name: pod-pvc-nfs
image: nginx:1.14.0
ports:
- containerPort: 80
volumeMounts:
- name: pvc-nfs
mountPath: "/usr/share/nginx/html"
EOF
kubectl apply -f pod-pvc-nfs.yaml
查看创建的POD:
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-pvc-hostpath 1/1 Running 0 17m 10.2.1.15 node1 <none> <none>
pod-pvc-nfs 1/1 Running 0 2s 10.2.1.16 node1 <none> <none>
测试访问:
# curl 10.2.1.16
nfs master ok
6.创建LocalPV
我们刚才已经学习了hostPath配型的PV,hostPath使用的是宿主机的目录,并且我们的POD不能随意漂移到其他节点上,所以我们使用hostPath类型的PV时,都会使nodeSelector将POD固定在指定的node节点上。这样做的好处是因为使用的是本地磁盘,所以速度快,但是缺点是如果宿主机宕机了不太好恢复数据,而且如果使用宿主机目录,很容易造成磁盘占完导致宿主机的系统崩溃。
k8s在hostPath的基础上,实现了一个新特性叫LocalPV,他其实和hostPath加上nodeSelector实现的效果类似,区别在对于普通pv来说,k8s是先调度POD到节点上,然后再持久化节点上的数据目录与POD挂载。而LocalPV则是需要提前在节点准备好,但不一定所有节点都具备这个类型的磁盘,所以k8s调度的时候会先知道所有节点与LocalPV指定的磁盘的关系,也就是说,先确定哪些节点可以调度,再去调度POD。另外一个区别则是LocalPV应该是一块独立的磁盘挂载在宿主机上,这样做的好处一个是磁盘IO独享,另外加入宿主机故障了,只需要将磁盘插到其他节点上就能继续使用上面的数据。
普通PV:调度POD到某个节点 --> 持久化数据目录 --> 数据目录绑定POD
LocalPV: 筛选符合LocalPV调度的节点 --> 调度POD到LocalPV节点 --> 持久化数据目录 --> 数据目录绑定POD
6.1 数据目录准备
这里我们给虚拟机新增加一块独立的磁盘,然后格式化磁盘并挂载到node1节点的宿主机目录上。但是由于我们的虚拟机都是链接克隆的,而VM对链接克隆的虚拟机不支持添加新硬盘。所以我们这里就直接创建目录来模拟独立挂载的磁盘,接下来我们在node2上创建数据目录。
mkdir /data/k8s/localpv -p
6.2 创建Local PV
资源配置:
cat > pv-local.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /data/k8s/localpv
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1
EOF
配置解释:
这里的配置和hostpath类似,只是多了节点亲和性,这样调度器在调度pod的时候,能够知道pv和节点之间的关系。
查看状态:
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWO Retain Available local-storage 2m24s
6.3 创建Local PVC
cat > pvc-local.yaml << 'EOF'
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-local
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-storage
EOF
查看创建结果:
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-local Bound pv-local 5Gi RWO local-storage 20s
但是这时候有个问题,那就是如果我node1和node2都有Local类型的PV,storageClassName都叫ocal-storage,这个时候创建PVC的时候直接绑定到了node1上,但是我的POD调度的时候指定要在Node2上,这样就变成POD在node2,但是却绑定了Node1上的LocalPV,这肯定是不符合要求的,所以会调度失败。下面我们来演示一下:
创建Node2的PV,storageClassName也是local-storage
cat > pv-local-node2.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local-node2
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /data/k8s/localpv
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node2
EOF
查看执行结果:
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWO Retain Bound default/pvc-local local-storage 23m
pv-local-node2 5Gi RWO Retain Available local-storage 15m
这个时候会发现pvc绑定的是node1的pv,那么假如这时候我创建一个POD,但是指定运行在node2上,会发生什么事情呢
cat > pod-pvc-local.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pod-pvc-local
spec:
volumes:
- name: pvc-local
persistentVolumeClaim:
claimName: pvc-local
containers:
- name: pod-pvc-local
image: nginx:1.14.0
ports:
- containerPort: 80
volumeMounts:
- name: pvc-local
mountPath: "/usr/share/nginx/html"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node2
EOF
kubectl apply -f pod-pvc-local.yaml
查看创建结果:我们会发现状态时Pending,因为我们绑定的节点在Node2,但是PVC帮的PV在node1
# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-pvc-local 0/1 Pending 0 22s
查看详细信息:
# kubectl describe pod pod-pvc-local
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 12s (x5 over 2m5s) default-scheduler 0/3 nodes are available: 1 node(s) had volume node affinity conflict, 2 node(s) didn't match node selector.
为什么会导致这种现象?那是因为正常流程是创建PVC的时候立刻就绑定了PV,没有考虑到POD调度的情况,所以我们需要延迟PVC绑定PV的操作,直到第一个声明使用这个PVC的POD被创建出来的时候PVC才会和PV进行绑定。
原来:PVC-->PV-->POD调度
延迟:POD调度-->PVC-->PV
那么如何实现这个延迟操作呢?这就需要使用一个新的资源类型叫StorageClass,我们需要利用StorageClass里的延迟绑定特性。
资源配置:
cat > local-storageclass.yaml << 'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
EOF
kubectl apply -f local-storageclass.yaml
配置解释:
apiVersion: storage.k8s.io/v1
kind: StorageClass #资源类型
metadata:
name: local-storage #和PV里定义的名字一样
provisioner:kubernetes.io/no-provisioner #不自动创建PV,因为LocalPV不支持自动创建PV,我们是手动创建
volumeBindingMode:WaitForFirstConsumer #延迟PVC绑定PV的操作,等到POD被创建时才绑定
首先我们删除之前创建的POD和PVC,恢复成只有两个PV和没有POD没有PVC被创建的状态
# kubectl get pod
No resources found in default namespace.
# kubectl get pvc
No resources found in default namespace.
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWO Retain Available local-storage 43m
pv-local-node2 5Gi RWO Retain Available local-storage 35m
然后我们创建PVC,因为我们设置了绑定延迟,所以这时候PVC不会直接和PV绑定。
# kubectl apply -f pvc-local.yaml
persistentvolumeclaim/pvc-local created
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-local Pending local-storage 2s
查看PVC详情:
# kubectl describe pvc pvc-local
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal WaitForFirstConsumer 5s (x3 over 29s) persistentvolume-controller waiting for first consumer to be created before binding
此时PVC在等待我们创建的POD,那么现在我们创建运行在node2的pod,看看PVC会不会绑定到node2的PV
# kubectl apply -f pod-pvc-local.yaml
pod/pod-pvc-local created
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-local Bound pv-local-node2 5Gi RWO local-storage 2m13s
可以发现PVC绑定到了正确的PV上,POD也顺利创建出来了。
# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-pvc-local 1/1 Running 0 75s
6.x pvc删除后显示Released状态
Released是因为我们的PVC配置的模式是Retain,所以PVC删除后并不会清空数据,此时pv会是已释放状态,但是资源还没有被认领,我们在确定数据备份了的前提下可以通过修改PV来将pv恢复到Available状态
官方解释:
https://kubernetes.io/zh/docs/concepts/storage/persistent-volumes/#reclaiming
未编辑前:
[root@master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWO Retain Released default/pvc-local local-storage 21m
pv-local-node2 5Gi RWO Retain Released default/pvc-local local-storage 13m
编辑PV,然后删除claimRef字段
kubectl edit pv pv-local
----------------------------------------------
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: pvc-local
namespace: default
resourceVersion: "200673"
uid: e28ba817-636a-4776-99f9-a3522706bc8c
删除后再次查看:
[root@master ~/k8s_yml/PVPVC]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWO Retain Available local-storage 21m
pv-local-node2 5Gi RWO Retain Released default/pvc-local local-storage 13m
7.StorageClass介绍
当前PV和PVC存在的问题
- 可能会出现POD需要的PVC无法正确匹配到适合的PV而被挂起
- 能够匹配到PV的PVC可能存在资源使用率不佳的情况,比如一个PVC声明需要5G的存储空间,但是却绑定到了20G的PV上。
而存储类StorageClass则是通过创建不同的PV模版,在PV模版中定义好所依赖的底层存储资源,然后创建PVC时用户只需要声明自己需要的存储类StorageClass,接着StorageClass就会动态的去创建PV来去绑定这个PVC。
简单来说,就是StorageClass可以帮我们根据PVC的需求动态去创建合适的PV。
但是这个看似很美好的自动创建PV的功能是需要后端存储资源支持才行的,支持动态创建PV的存储资源一般都是分布式存储。由于我们还没有学习过分布式存储,所以这里只做简单介绍。
8.PV注意
PV资源配置里的容量其实只是用于PVC查找时匹配的描述,并不代表只有这个容量可以使用,如果是hostpath类型的pv其实POD可以使用所有宿主机上的容量空间。
一个PV只能绑定一个PVC
更新: 2024-09-10 08:44:17