目录
概念
PersistentVolume (PV)
PersistentVolumeClaim (PVC)
静态 pv
动态PV
绑定PV
持久化卷声明的保护
持久化卷类型
PV访问模式
PV回收策略
PV状态
持久化演示说明 - NFS
Ⅰ、安装 NFS 服务器
Ⅱ、部署 PV
Ⅲ、创建服务并使用 PVC
Ⅳ、释放pv和删除pvc
Ⅴ、总结
关于StatefulSet的总结
在我们整个k8s集群中,外部可能有有一些存储的资源,比如说nfs,mfs,iscsi块存储,这些存储都是由我们的存储工程师去创建的,k8s工程师想要直接去使用他们的话,肯定是很不方便的,因为不同的存储方式不一样。在k8s中,给我们提供了一个新的对象资源,叫做PV,不同的PV会对应到不用的存储资源,这样我们在部署pod的时候直接调用集群内部的pv,即可完成对存储资源的使用,但是呢,直接调用PV的话,有个问题就是,这个pv是否满足我们的需求,因为我们可能需要的是存储能力比较大存储资源,所以这个时候需要一个一个去对比pv,这样很耗费资源,这个时候又引入了我们的pvc。我们在创建pod的时候会附带一个PVC的请求,PVC的请求相当于就是去寻找一个合适的pv,进行绑定,这样我们的pod就会使用到这个pv了。也就是说让我们的pvc去寻找pv,而不是我们的pod资源去寻找。
是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。 PV 是Volume 之类的卷插件,但具有独立于使用 PV 的 Pod 的生命周期(pod被删除了,我们的PV依然会被保留,类似于卷)。此 API 对象包含存储实现的细节,即 NFS、iSCSI 或特定于云供应商的存储系统。
PVC 的全称是PersistentVolumeClaim(持久化卷声明),PVC 是用户存储的一种声明,PVC 和 Pod 比较类似,Pod 消耗的是节点,PVC 消耗的是 PV 资源,Pod 可以请求 CPU 和内存,而 PVC 可以请求特定的存储空间和访问模式,例如,可以以读/写一次或 只读多次模式挂载。对于真正使用存储的用户不需要关心底层的存储实现细节,只需要直接使用 PVC 即可。也就是我们集群中会有一个个的PV,可以被直接挂在到某个pod,也可以被PVC绑定,然后挂载到某个pod。
集群管理员创建一些 PV。它们带有可供群集用户使用的实际存储的细节。它们存在于 Kubernetes API 中,可用于消费。
当管理员创建的静态 PV 都不匹配用户的 PersistentVolumeClaim 时,集群可能会尝试动态地为 PVC 创建卷。此配置基于 StorageClasses :PVC 必须请求 [存储类],并且管理员必须创建并配置该类才能进行动态创建。声明该类为 "" 可以有效地禁用其动态配置。
要启用基于存储级别的动态存储配置,集群管理员需要启用 API server 上的DefaultStorageClass [准入控制器]。例如,通过确保 DefaultStorageClass 位于 API server 组件的 --admission-control 标志,使用逗号分隔的有序值列表中,可以完成此操作。
master 中的控制环路监视新的 PVC,寻找匹配的 PV(如果可能),并将它们绑定在一起。如果为新的 PVC 动态调配 PV,则该环路将始终将该 PV 绑定到 PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。一旦 PV 和 PVC 绑定后, PersistentVolumeClaim 绑定是排他性的,不管它们是如何绑定的,PVC 跟PV 绑定是一对一的映射。
PVC 保护的目的是确保由 pod 正在使用的 PVC 不会从系统中移除,因为如果被移除的话可能会导致数据丢失。意思就是我们的PV被我们的PVC绑定的时候,某一天我们的pod被删除之后,这个PVC依然会存在我们的系统之中,并且这个PVC依然会跟我们的PV有一个绑定关系,主要是为了防止我们的pod出现丢失之后,PVC被删除了,数据就会丢失,这个肯定是合理的。
当启用PVC 保护 alpha 功能时,如果用户删除了一个 pod 正在使用的 PVC,则该 PVC 不会被立即删除。PVC 的删除将被推迟,直到 PVC 不再被任何 pod 使用。
PersistentVolume 类型以插件形式实现。Kubernetes 目前支持以下插件类型:
1. GCEPersistentDisk AWSElasticBlockStore AzureFile AzureDisk FC (Fibre Channel)
2. FlexVolume Flocker NFS iSCSI RBD (Ceph Block Device) CephFS
3. Cinder (OpenStack block storage) Glusterfs VsphereVolume Quobyte Volumes
4. HostPath VMware Photon Portworx Volumes ScaleIO Volumes StorageOS
持久卷演示,这里演示的是一个nfs的方案
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-test
spec:
capacity:
storage: 5Gi #卷的大小
volumeMode: Filesystem #文件类型
accessModes: #访问策略
- ReadWriteOnce #只有一个人能进行读写,两个人同时不允许
persistentVolumeReclaimPolicy: Recycle #回收策略
storageClassName: slow #存储类的名称,相当于的人为定义存储的一个tag
mountOptions:
- hard
- nfsvers=4.1
nfs: #这里演示的是nfs机制
path: /tmp #挂载到哪个目录下,即指定nfs的挂载点
server: 172.17.0.2 #哪一个服务器的某个目录下挂载
分析:上面的就会把nfs服务封装成一个pv-test,并且它的存储类为slow,大小为5G。
扩展:存储类的说明
在我们的后端存储中,有很多种的存储方案,如下,1类,2类,3类存储,我们的PV也会有对应存储的类,当我们的PVC去请求绑定的时候,我们可以去指定它请求绑定的类,比如说,下面的是2类,那么它就会去绑定2类型的资源。。。
PersistentVolume 可以以资源提供者支持的任何方式挂载到主机上。如下表所示,供应商具有不同的功能,每个PV 的访问模式都将被设置为该卷支持的特定模式。例如,NFS 可以支持多个读/写客户端,但特定的 NFS PV 可能以只读方式导出到服务器上。每个 PV 都有一套自己的用来描述特定功能的访问模式。。。
ReadWriteOnce——该卷可以被单个节点以读/写模式挂载
ReadOnlyMany——该卷可以被多个节点以只读模式挂载
ReadWriteMany——该卷可以被多个节点以读/写模式挂载
在命令行中,访问模式缩写为:
RWO - ReadWriteOnce
ROX - ReadOnlyMany
RWX - ReadWriteMany
注意:一个卷一次只能使用一种访问模式挂载,即使它支持很多种访问模式。例如:GCEPersistentDisk可以由单个节点作为ReadWriteOnce模式挂载,或由多个节点以ReadOnlyMany模式挂载,但不能同事挂载。
1. Retain(保留)——手动回收
(也就意味着当有一天,我们的PV不再使用了,那这么PV也不允许被被别人使用,等待我们的管理员手动释放掉)
2. Recycle(回收)——基本擦除(相当于执行删除卷中所有文件的命令,即 rm -rf /thevolume/* )
3. Delete(删除)——关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStackCinder 卷)将被删除
当前,只有 NFS 和 HostPath 支持Recycle回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略。。。
卷可以处于以下的某种状态:
Available(可用)——一块空闲资源还没有被任何声明绑定
Bound(已绑定)——卷已经被声明绑定
Released(已释放)——声明被删除,但是资源还未被集群重新声明
Failed(失败)——该卷的自动回收失败
命令行会显示绑定到 PV 的 PVC 的名称。
下面的是一个示例:nfs组成的一个PV,然后采用PVC的方式,去部署我们的 StatefulSet这个服务。完整的把我们的PV,PVC,StatefulSet结合起来。
环境准备,这里我准备了两台机器,一台master1当做k8s集群,一台master2当做nfs服务器,这台没有加入到k8s集群中。
[root@master1 pv-pvc]# kubectl get node
NAME STATUS ROLES AGE VERSION
master1 Ready master 7d18h v1.15.9
[root@master1 pv-pvc]#
这个可以在我们的第三方主机安装,我们这个测试的主机IP是192.168.64.151,这个作为我们nfs服务器,下面操作没有说明,都在nfs服务器上做。
yum install -y nfs-common nfs-utils rpcbind #所有的节点都需要安装
mkdir /nfsdata #创建一个共享目录
chmod 777 /nfsdata
chown nfsnobody /nfsdata #赋予nfsnobody这样一个身份
cat /etc/exports
/nfsdata *(rw,no_root_squash,no_all_squash,sync)
systemctl start rpcbind
systemctl start nfs
我们创建好nfs服务器之后,然后去另外的一台主机上maser1测试一下这个nfs服务器可不可以用
[root@master1 pv-pvc]# mkdir /test
[root@master1 pv-pvc]# showmount -e 192.168.64.151
Export list for 192.168.64.151:
/nfsdata *
[root@master1 pv-pvc]#
[root@master1 pv-pvc]# mount -t nfs 192.168.64.151:/nfsdata /test/
[root@master1 pv-pvc]#
[root@master1 pv-pvc]# cd /test/
[root@master1 test]# ls
[root@master1 test]# touch aaa
[root@master1 test]# ls
aaa
[root@master1 test]#
[root@master1 test]# cd ..
[root@master1 /]# umount /test
[root@master1 /]# rm -rf /test/
[root@master1 /]#
nfs服务器那边的共享目录也可以同步数据,就算删除共享目录也不影响
[root@master2 nfsdata]# ls
aaa
[root@master2 nfsdata]#
上面的nfs服务器搭建好之后,我们就可以使用这个nfs存储服务器了
在主节点创建 pv.yaml,当然下面的演示都是基于这个模板,创建了三个不用的pv
[root@master1 pv-pvc]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv0
spec:
capacity:
storage: 10Gi #存储大小
accessModes:
- ReadWriteOnce #当前存储模式
persistentVolumeReclaimPolicy: Retain #回收策略
storageClassName: nfs #存储类的名称,也可以是V1,V2什么V3
nfs:
path: /nfsdata #nfs服务器共享目录
server: 192.168.64.151
[root@master1 pv-pvc]# kubectl apply -f pod2.yaml
persistentvolume/nfspv1 created
[root@master1 pv-pvc]#
[root@master1 pv-pvc]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfspv0 10Gi RWO Retain Available nfs 6s
[root@master1 pv-pvc]#
首先创建一个SVC,然后创建StatefulSet,让StatefulSet调用我们的PVC,如果想要建立StatefulSet这个服务的话,必须先建立一个无头服务,因为要提供稳定的网络标志,关于statefulset这个可以参考官方文档Kubernetes(k8s)中文文档 名词解释:StatefulSet_Kubernetes中文社区
为了演示效果,我们在nfs服务器上创建了三个共享目录
[root@master2 nfsdata]# cat /etc/exports
/nfsdata *(rw,no_root_squash,no_all_squash,sync)
/nfsdata1 *(rw,no_root_squash,no_all_squash,sync)
/nfsdata2 *(rw,no_root_squash,no_all_squash,sync)
[root@master2 nfsdata]# ls /nfsdata
nfsdata/ nfsdata1/ nfsdata2/
[root@master2 nfsdata]# chmod 777 /nfsdata*
[root@master2 nfsdata]# chown nfsnobody /nfsdata*
[root@master2 nfsdata]# systemctl restart rpcbind
[root@master2 nfsdata]# systemctl restart nfs
---------------------------------------------------------
测试nfs服务器是否可用
[root@master1 pv-pvc]# showmount -e 192.168.64.151
Export list for 192.168.64.151:
/nfsdata2 *
/nfsdata1 *
/nfsdata *
然后在k8s服务器上对应这三个目录创建三个不同属性的pv,分别绑定nfs服务器的三个共享目录
[root@master1 pv-pvc]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv0
spec:
capacity:
storage: 10Gi #存储大小
accessModes:
- ReadWriteOnce #当前存储模式
persistentVolumeReclaimPolicy: Retain #回收策略
storageClassName: nfs #存储类的名称,也可以是V1,V2什么V3
nfs:
path: /nfsdata #nfs服务器共享目录
server: 192.168.64.151
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv1
spec:
capacity:
storage: 15Gi #存储大小
accessModes:
- ReadOnlyMany #当前存储模式
persistentVolumeReclaimPolicy: Retain #回收策略
storageClassName: slow #存储类的名称,也可以是V1,V2什么V3
nfs:
path: /nfsdata1 #nfs服务器共享目录
server: 192.168.64.151
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv2
spec:
capacity:
storage: 5Gi #存储大小
accessModes:
- ReadWriteMany #当前存储模式
persistentVolumeReclaimPolicy: Retain #回收策略
storageClassName: nfs #存储类的名称,也可以是V1,V2什么V3
nfs:
path: /nfsdata2 #nfs服务器共享目录
server: 192.168.64.151
[root@master1 pv-pvc]#
然后创建我们的pvc
[root@master1 pv-pvc]# cat pvc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "nfs"
resources:
requests:
storage: 1Gi
[root@master1 pv-pvc]#
注意:这里我们需要关注的是volumeClaimTemplates的字段,它会去匹配对应的PV。
查看运行之后的效果
[root@master1 pv-pvc]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 74s 10.244.0.170 master1
web-1 0/1 Pending 0 71s
[root@master1 pv-pvc]# kubectl describe pod web-1
.........................
Warning FailedScheduling 60s (x3 over 2m16s) default-scheduler pod has unbound immediate PersistentVolumeClaims
有一个pod处于pending状态,还有一个没创建,StatefulSet这个控制器管理的pod是按序创建的,第一个创建完成之后,才到第二个,第二个创建完成之后才到第三个,第二个现在没有创建成功,所以第三个就不会显示。
我们看看我们的pod的pvc需求
再看看pv,只有一个pv符合要求,我们之前说过,一个pv只能由一个pvc绑定。
我们现在把不符合要求的pv改成我们pvc能够匹配到的样子,先删除不符合的pv
[root@master1 pv-pvc]# kubectl delete pv nfspv1
persistentvolume "nfspv1" deleted
[root@master1 pv-pvc]# kubectl delete pv nfspv2
persistentvolume "nfspv2" deleted
[root@master1 pv-pvc]#
[root@master1 pv-pvc]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfspv0 10Gi RWO Retain Bound default/www-web-0 nfs 26m
[root@master1 pv-pvc]#
然后把之前的pv.yaml文件修改一下
再重新创建一下
[root@master1 pv-pvc]# kubectl apply -f pv.yaml
persistentvolume/nfspv0 unchanged
persistentvolume/nfspv1 created
persistentvolume/nfspv2 created
[root@master1 pv-pvc]#
[root@master1 pv-pvc]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfspv0 10Gi RWO Retain Bound default/www-web-0 nfs 29m
nfspv1 15Gi RWO Retain Bound default/www-web-2 nfs 41s
nfspv2 5Gi RWO Retain Bound default/www-web-1 nfs 41s
[root@master1 pv-pvc]#
[root@master1 pv-pvc]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 10m
web-1 1/1 Running 0 10m
web-2 1/1 Running 0 36s
[root@master1 pv-pvc]#
[root@master1 pv-pvc]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound nfspv0 10Gi RWO nfs 23m
www-web-1 Bound nfspv2 5Gi RWO nfs 23m
www-web-2 Bound nfspv1 15Gi RWO nfs 74s
绑定成功之后,我们测试一下k8s集群的pod是否与nfs服务器共享目录
[root@master1 pv-pvc]# kubectl exec -it web-0 sh
# cd /usr/share/nginx/html
# ls
# # touch bbb
----------------------------------------------
[root@master2 nfsdata]# ls
bbb
同样我们可以通过ip访问
[root@master2 nfsdata]# echo 11111 > index.html
----------------------------------------------------
[root@master1 pv-pvc]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 8m50s 10.244.0.178 master1
web-1 1/1 Running 0 7m58s 10.244.0.179 master1
web-2 1/1 Running 0 7m56s 10.244.0.180 master1
[root@master1 pv-pvc]# curl 10.244.0.178
11111
[root@master1 pv-pvc]#
这里我们还可以验证一下StatefulSet的一个特点,就是提供稳定的网络标识,这里的网络标识就是pod的name,我们删除一个pod,由于副本数目为3,删除之后马上创建一个pod,此时ip变了,但是pod的名字没有发生改变,且通过新的ip依旧可以访问。
某个pv在生命周期中可能处于以下四个阶段之一
Available:可用状态还未与某个pvc绑定
Bond:已与某个pvc进行绑定
Release:绑定的pvc已经删除,资源已经释放,但没有被集群回收
Failed:自动资源回收失败
当我们不想要这个集群里的数据了,删除如下:
如果我们有创建pod的时候的yaml文件的话,我们可以先delete 该yaml文件
删除完yaml文件,对应的pod会退出,如果有SVC的话,再接着删除SVC,这个只是删除pod的相关信息,我们的pvc还是有的,因为我们的pvc并不会随着pod的删除而删除。
这来我们主要测试如何释放已经绑定的pvc和pv,正确的说应该是删除pvc,释放pv的绑定以供其他服务使用。
[root@master1 pv-pvc]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound nfspv0 10Gi RWO nfs 74m
www-web-1 Bound nfspv2 5Gi RWO nfs 74m
www-web-2 Bound nfspv1 15Gi RWO nfs 52m
[root@master1 pv-pvc]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound nfspv0 10Gi RWO nfs 74m
www-web-1 Bound nfspv2 5Gi RWO nfs 74m
www-web-2 Bound nfspv1 15Gi RWO nfs 52m
[root@master1 pv-pvc]#
[root@master1 pv-pvc]# kubectl delete pvc --all
persistentvolumeclaim "www-web-0" deleted
persistentvolumeclaim "www-web-1" deleted
persistentvolumeclaim "www-web-2" deleted
[root@master1 pv-pvc]# kubectl get pvc
No resources found.
查看一下pv,发现pv处于release状态
[root@master1 pv-pvc]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfspv0 10Gi RWO Retain Released default/www-web-0 nfs 82m
nfspv1 15Gi RWO Retain Released default/www-web-2 nfs 54m
nfspv2 5Gi RWO Retain Released default/www-web-1 nfs 54m
[root@master1 pv-pvc]#
然后我们需要手动去释放对应nfs服务器上进行绑定的目录资源
[root@master2 /]# rm -rf nfsdata*
[root@master2 /]#
这里我们需要手动删除一下对应pv的yaml文件中对应的claimRef的信息
kubectl edit pv nfspv0
kubectl edit pv nfspv1
kubectl edit pv nfspv2
最后查看如下,至此我们所有的资源就被释放了
整个过程如下图
1. 准备好nfs服务器。
2. 准备好pv的yaml文件,并根据nfs服务器共享目录来创建多少个pv.
3. 准备好pod的yaml文件,确定好volumeClaimTemplates各个字段的需求。
关于pod通过volumeClaimTemplates字段来挂载pvc也可参考这个资料K8S有状态服务-StatefulSet使用最佳实践-阿里云开发者社区
当然我们还有其他的控制类型绑定pvc的字段,例如:persistentVolumeClaim,可以参考以下的文章PV、PVC、StorageClass讲解 - 昀溪 - 博客园
关于pv存储的存储原理,可以看https://www.qikqiak.com/k8strain/storage/csi/
下面测试一下上面的总结:
1. 先查看pod与pod之间通过域名的通信方式,这里我们先进去一个pod,然后curl一下
2.关于域名
FQDN: 同时带有主机名和域名的名称
dig:命令主要用来从 DNS 域名服务器查询主机地址信息
[root@master1 pv-pvc]# dig -t A nginx.default.svc.cluster.local. @10.244.0.168
statefulset的启动顺序
StatefulSet使用场景