⼗、k8s⼊门系列----PV、PVC、StorageClass
容器的设计理念就是⼀次性,也就是容器销毁后容器⾥的所有数据都会销毁,所以需要将容器⾥⾯需要保留的数据挂载到持久性存储中,这⾥就涉及到三个概念:PV、PVC、StorageClass 。
HostPath
当使⽤docker创建container的时候,⼀般都是加参数 -v 挂载宿主机的⽬录到container⾥⾯,k8s 也可以实现该功能,先讲解⼀下挂载到宿主机⽬录的⽅法。
创建⼀个deployment资源配置⽂件,挂载到宿主机⽬录:
[root@ylrver10686071 ~]# l
apiVersion: apps/v1
kind: Deployment我在学外语
metadata:
name: volumes001
namespace: prod
spec:
replicas: 1
lector:
matchLabels:
k8s-app: volumes001
template:
metadata:
labels:
k8s-app: volumes001
spec:
containers:
- name: nginx
image: nginx:1.21
淘宝网网页版volumeMounts:
- mountPath: /usr/share/nginx/html/
name: html
volumes:
- name: html
hostPath:
path: /data/nginx/html/
type: DirectoryOrCreate
volumeMounts 挂载到container 指定⽬录的相关配置,根据name来匹配volumes对象的下的name,据此来找到挂载对象
volumes 声明挂载对象
hostPath 存储类型
type: DirectoryOrCreate 如果宿主机路径不存在则创造该路径,当值为Directory 是,宿主机必须有该⽬录,否则会导致pod创建失败
创建deployment资源,查看pod在哪台Node上运⾏:
[root@ylrver10686071 ~]# kubectl apply -l
deployment.apps/volumes001 created
[root@ylrver10686071 ~]# kubectl get pods -n prod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
volumes001-66767f866f-rc5qk 1/1 Running 0 21s 10.233.72.59 ylrver10686073 <none> <none>
[root@ylrver10686071 ~]#
在ylrver10686071 Node上查看⽬录是否创建,然后到Pod运⾏的节点上查看⽬录是否创建:
[root@ylrver10686071 ~]# ll /data/nginx/html/
ls: cannot access /data/nginx/html/: No such file or directory
[root@ylrver10686071 ~]#
[root@ylrver10686073 ~]# ll /data/nginx/html/
total 0
[root@ylrver10686073 ~]#
给挂载⽬录创建⽂件,验证是否挂载到Pod⾥⾯:
[root@ylrver10686073 ~]# echo "Hello K8S" > /data/nginx/html/index.html
[root@ylrver10686073 ~]# curl 10.233.72.59/index.html
Hello K8S
[root@ylrver10686073 ~]#
PV
上⾯的实验中宿主机挂载的⽬录只有在 Pod 运⾏的 Node 上才会创建,换⾔之,Pod要挂载的⽬录必须跟Node做绑定,这会增加运维的难度,也失去
k8s的故障转移特性。
针对这个问题,可以使⽤存储券解决,这⾥就要引⼊⼀个概念:PV。
PV全称叫做Persistent Volume,持久化存储卷。它是⽤来描述或者说⽤来定义⼀个存储卷的,这个通常都是有运维或者数据存储⼯程师来定义。本节使⽤NFS来作为存储端,NFS搭建这⾥不做讲解。
先创建⼀个PV资源配置⽂件:
[root@ylrver10686071 ~]# l
apiVersion: v1
kind: PersistentVolume ###PV资源不属于任何命名空间,属于集群级别的
###kubectl api-resources --namespaced=true 命令可以查看哪些资源属于命名空间
metadata:
name: pv001
labels: ###Label可以不定义
name: pv001
storetype: nfs
spec: ###定义PV资源规格
storageClassName: normal
文明委
accessModes: ###设置访问模型
-
ReadWriteMany
- ReadWriteOnce
- ReadOnlyMany
capacity: ###设置存储空间⼤⼩
storage: 500Mi
persistentVolumeReclaimPolicy: Retain ###回收策略
nfs:
path: /data/nfs/k8s/
rver: 10.68.60.193
[root@ylrver10686071 ~]#
accessModes 有3种属性值:
ReadWriteMany 多路读写,卷能被集群多个节点挂载并读写
ReadWriteOnce 单路读写,卷只能被单⼀集群节点挂载读写
ReadOnlyMany 多路只读,卷能被多个集群节点挂载且只能读
persistentVolumeReclaimPolicy 回收策略也有3种属性值:
Retain
当删除与之绑定的PVC时候,这个PV被标记为relead(PVC与PV解绑但还没有执⾏回收策略)且之前的数据依然保存在该PV上,但是该PV不可⽤,需要⼿动来处理这些数据并删除该PV
这种⽅式是最常⽤的,可以避免误删pvc或者pv⽽造成数据的丢失
Delete 删除存储资源,AWS EBS, GCE PD, Azure Disk, and Cinder volumes⽀持这种⽅式
Recycle 这个在1.14版本中以及被废弃,取⽽代之的是推荐使⽤动态存储供给策略,它的功能是当删除与该PV关联的PVC时,⾃动删除该PV中的所有数据
创建完PV后,PV会有⼏种状态:
滕红Available(可⽤)块空闲资源还没有被任何声明绑定
Bound(已绑定)卷已经被声明绑定
Relead(已释放)声明被删除,但是资源还未被集群重新声明
Failed(失败)该卷的⾃动回收失败
创建PV资源,并查看PV信息:
[root@ylrver10686071 ~]# kubectl apply -l
persistentvolume/pv001 created
[root@ylrver10686071 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 500Mi RWO,ROX,RWX Retain Available normal 6s
PVC
PV只是定义了⼀个存储卷实体,还需要⼀层抽象的接⼝使其与POD关联,这层抽象的接⼝就是PVC,全称 Persistent Volume Claim,也就是持久化存储声明。开发⼈员使⽤这个来描述该容器需要⼀个什么存储。
创建⼀个PVC资源配置⽂件:
[root@ylrver10686071 ~]# l
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc001
namespace: prod ###PVC资源属于命名空间级别
labels: ###Label可以不定义
name: pvc001
storetype: nfs
capacity: 500Mi
spec:
storageClassName: normal
accessModes: ###PVC也需要定义访问模式,不过它的模式⼀定是和现有PV相同或者是它的⼦集,否则匹配不到PV
- ReadWriteMany
resources: ###定义资源要求PV满⾜这个PVC的要求才会被匹配到
requests:
storage: 500Mi # 定义要求有多⼤空间
创建PVC资源,查看PVC资源和PV资源绑定情况,可以看到PV和PVC已经实现绑定:
[root@ylrver10686071 ~]# kubectl apply -l
persistentvolumeclaim/pvc001 created
[root@ylrver10686071 ~]# kubectl get pvc -n prod
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc001 Bound pv001 500Mi RWO,ROX,RWX normal 15s
[root@ylrver10686071 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 500Mi RWO,ROX,RWX Retain Bound prod/pvc001 normal 62m
[root@ylrver10686071 ~]#
PVC是如何跟PVC绑定的呢,有以下⼏个原则:
PV和PVC中的spec关键字段要匹配,⽐如存储(storage)⼤⼩
PVC的访问模式⼀定是和现有PV相同或者是它的⼦集,否则匹配不到PV
PV和PVC中的 StorageClass Name字段必须⼀致,StorageClass后⾯会讲到
Label 标签在这⾥只做描述作⽤,跟 PV 和 PVC 的绑定没有任何关系
看到这⾥,回想总结⼀下就会发现 k8s ⾥⾯会通过定义⼀层抽象概念来管理实体或者连接实体,类似于 Pod 和 Container , PVC 和 PV ;对象和对象的匹配设计原理也是⼀直,例如 deployment匹配replicat通过matchLabels ,PV 和 PVC通过 StorageClass Name以及 resources等,即对象与对象之间通过匹配关系进⾏绑定。
更新上⾯的deployment资源配置⽂件,使其使⽤创建好的PVC资源:
[root@ylrver10686071 ~]# l
apiVersion: apps/v1
kind: Deployment
metadata:
name: volumes001
namespace: prod ###要和指定的PVC同⼀个命名空间
spec:
replicas: 1
lector:
matchLabels:
k8s-app: volumes001
template:
metadata:
labels:
k8s-app: volumes001
spec:
containers:
- name: nginx
image: nginx:1.21
volumeMounts: ###container的挂载声明没有改变
- mountPath: /usr/share/nginx/html/
name: html
volumes: ###依然使⽤volumes声明挂载卷
- name: html
persistentVolumeClaim: ###指定PVC
claimName: pvc001
更新资源deployment资源配置⽂件,查看pod关于volumes相关信息:
[root@ylrver10686071 ~]# kubectl apply -l
deployment.apps/volumes001 configured
[root@ylrver10686071 ~]# kubectl describe pod volumes001 -n prod|grep -5 Volumes
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
html:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: pvc001
ReadOnly: fal
default-token-lx75g:
[root@ylrver10686071 ~]#
写⼀个⽂件到NFS挂载⽬录中,测试⼀下效果:
[root@ylrver106860193 ~]# echo "K8S PV" > /data/nfs/k8s/index.html
[root@ylrver10686071 ~]# kubectl get pods -n prod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
volumes001-55f5bb9585-nx9xd 1/1 Running 0 11m 10.233.72.60 ylrver10686073 <none> <none>
[root@ylrver10686071 ~]# curl 10.233.72.60
K8S PV
[root@ylrver10686071 ~]#
给原来的Node打上Taint,重启deployment资源,查看Pod在其他Node上运⾏时,原来的挂载⽂件是否还存在:
[root@ylrver10686071 ~]# kubectl taint node ylrver10686073 web=nginx:NoSchedule
node/ylrver10686073 tainted
[root@ylrver10686071 ~]# kubectl rollout restart deployment volumes001 -n prod
deployment.apps/volumes001 restarted
[root@ylrver10686071 ~]# kubectl get pods -n prod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
volumes001-7fcd68c5b8-hcwcf 1/1 Running 0 30s 10.233.67.54 ylrver10686072 <none
> <none>
[root@ylrver10686071 ~]#
验证⼀下,可以看到挂载的⽂件依然存在:
[root@ylrver10686071 ~]# curl 10.233.67.54
K8S PV
[root@ylrver10686071 ~]#
PV回收时需要删除PVC,删除PVC需要先删除关联的Pod,验证⼀下:
[root@ylrver10686071 ~]# kubectl delete deployments volumes001 -n prod
deployment.apps "volumes001" deleted
[root@ylrver10686071 ~]# kubectl delete pvc pvc001 -n prod
persistentvolumeclaim "pvc001" deleted
[root@ylrver10686071 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 500Mi RWO,ROX,RWX Retain Relead prod/pvc001 normal 18m
[root@ylrver10686071 ~]#
可以看到删除pvc后,pv处于Relead状态,此时pv只能删除重新创建才能继续使⽤,验证⼀下:
[root@ylrver10686071 ~]# kubectl apply -l
persistentvolumeclaim/pvc001 created
[root@ylrver10686071 ~]# kubectl get pvc -n prod
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc001 Pending normal 17s
[root@ylrver10686071 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 500Mi RWO,ROX,RWX Retain Relead prod/pvc001 normal 19m
删除PV,因为回收策略是Retain,所以NFS端的数据依然存在,验证⼀下:
[root@ylrver10686071 ~]# kubectl delete pv pv001
persistentvolume "pv001" deleted
[root@ylrver106860193 ~]# cat /data/nfs/k8s/index.html
K8S PV
[root@ylrver106860193 ~]#
重新创建PV,PVC就可以继续绑定:
[root@ylrver10686071 ~]# kubectl apply -l
persistentvolume/pv001 created
[root@ylrver10686071 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 500Mi RWO,ROX,RWX Retain Bound prod/pvc001 normal 18s
[root@ylrver10686071 ~]# kubectl get pvc -n prod
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc001 Bound pv001 500Mi RWO,ROX,RWX normal 4m11s
[root@ylrver10686071 ~]#
StorageClass
PV是运维⼈员创建的,开发操作PVC,⼀个PV只能被⼀个PVC绑定,如果这些PV都需要运维⼿动来处理将会是⼀件⼗分繁琐的事情,所以就有了动态供给概念,也就是Dynamic Provisioning。⽽我们上⾯的创建的PV都是静态供给⽅式,也就是Static Provisioning。⽽动态供给的关键就是StorageClass,它的作⽤就是创建PV模板。
创建StorageClass⾥⾯需要定义PV属性⽐如存储类型、⼤⼩等;另外创建这种PV需要⽤到存储插件。最终效果是,⽤户提交PVC,⾥⾯指定存储类型,如果符合我们定义的StorageClass,则会为其⾃动创建PV并进⾏绑定。横加
Kubernetes本⾝⽀持的动态PV创建不包括nfs,所以需要使⽤额外插件实现。
下载⽂件后,解压到 /opt ⽬录下:
tar -zxvf -C /opt/
使⽤helm部署 nfs-client插件:
[root@ylrver10686071 ~]# cd /opt/nfs-subdir-external-provisioner/
[root@ylrver10686071 nfs-subdir-external-provisioner]# helm install nfs-subdir-external-provisioner --namespace kube-system . --t nfs.rver=10.68.60.193 --t nfs.path=/data/nfs/k8s/ NAME: nfs-subdir-external-provisioner
LAST DEPLOYED: Fri Jul 30 20:36:05 2021
NAMESPACE: prod
轮胎充氮气STATUS: deployed
REVISION: 1
TEST SUITE: None
[root@ylrver10686071 nfs-subdir-external-provisioner]#
查看已经创建的 StorageClass,StorageClass属于集群级别:
[root@ylrver10686071 nfs-subdir-external-provisioner]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client cluster.local/nfs-subdir-external-provisioner Delete Immediate true 48s
[root@ylrver10686071 nfs-subdir-external-provisioner]#
创建PVC资源配置⽂件,使⽤nfs-client StorageClass:
[root@ylrver10686071 ~]# l
apiVersion: v1
kind: PersistentVolumeClaim
硫磺的作用与功效metadata:
name: pvc002
namespace: default
labels:
name: pvc002
storetype: nfs
capacity: 300Mi
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 300Mi
[root@ylrver10686071 ~]#
创建PVC资源,可以看到PV已经⾃动创建:
[root@ylrver10686071 ~]# kubectl apply -f l
persistentvolumeclaim/pvc002 created
[root@ylrver10686071 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc002 Bound pvc-13f05a23-0fce-429f-9803-db4ec3dd6465 300Mi RWX nfs-client 5m
[root@ylrver10686071 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-13f05a23-0fce-429f-9803-db4ec3dd6465 300Mi RWX Delete Bound default/pvc002 nfs-client 5m3s
[root@ylrver10686071 ~]#
NFS服务端可以看到创建的PV对应存储⽬录:
[root@ylrver106860193 ~]# ll /data/nfs/k8s/
total 4
drwxrwxrwx 2 root root 6 Jul 31 03:17 default-pvc002-pvc-13f05a23-0fce-429f-9803-db4ec3dd6465
-rw-r--r-- 1 root root 7 Jul 30 03:32 index.html
[root@ylrver106860193 ~]#
现在往PV对应的⽬录写⼊⽂件,然后删除PVC,看NFS端PV对应的存储⽬录是否存储:
tolstoy
[root@ylrver106860193 ~]# echo "StorageClass" > /data/nfs/k8s/default-pvc002-pvc-13f05a23-0fce-429f-9803-db4ec3dd6465/index.html
[root@ylrver106860193 ~]#
开始删除PVC,PV也会⾃动删除
[root@ylrver10686071 ~]# kubectl delete pvc pvc002
persistentvolumeclaim "pvc002" deleted
[root@ylrver10686071 ~]# kubectl get pvc
No resources found in default namespace.
[root@ylrver10686071 ~]# kubectl get pv
No resources found
[root@ylrver10686071 ~]#
查看NFS服务端⽬录,可以看到⽂件依然保留,⽬录在原有名称上添加了archived-:
[root@ylrver106860193 ~]# cat /data/nfs/k8s/archived-default-pvc002-pvc-13f05a23-0fce-429f-9803-db4ec3dd6465/index.html
StorageClass
[root@ylrver106860193 ~]#
其实跟StorageClass回收策略,涉及两个参数,⽂件 /opt/nfs-subdir-external-provisioner/values.yaml⾥可以修改,然后helm update 即可:archiveOnDelete: true
reclaimPolicy: Delete
archiveOnDelete 当设置为 true 时,在删除PVC后,会对 PV 对应的存储⽬录进⾏备份
reclaimPolicy 回收策略,上⾯有讲解过,Delete策略就是删除PVC时⾃动删除绑定的PV
StatefulSet StorageClass
当使⽤ StatefulSet 控制器创建Pod的时候,可以在 StatefulSet 配置⽂件⾥⾯直接声明 PV的创建,创建⼀个StatefulSet 资源配置⽂件:[root@ylrver10686071 ~]# l
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: volumes002
namespace: prod
spec:
rviceName: volumes002-svc
lector:
matchLabels:
k8s-app: volumes002
replicas: 3
template:
metadata:
labels:
k8s-app: volumes002
spec:
containers: