k8s (十二) --- Kubernetes 存储 之 PV 持久卷

一、persistent volumes简介

简介

管理存储和管理计算有着明显的不同。PersistentVolume给用户和管理员提供了一套API,抽象出存储是如何提供和消耗的细节。在这里,我们介绍两种新的API资源:PersistentVolume(简称PV)和PersistentVolumeClaim(简称PVC)。

  • PersistentVolume(持久卷,简称PV) 是集群内,由管理员提供的网络存储的一部分。就像集群中的节点一样,PV也是集群中的一种资源。它也像Volume一样,是一种volume插件,但是它的生命周期却是和使用它的Pod相互独立的。PV这个API对象,捕获了诸如NFS、ISCSI、或其他云存储系统的实现细节。

  • PersistentVolumeClaim(持久卷声明,简称PVC) 是用户的一种存储请求。它和Pod类似,Pod消耗Node资源,而PVC消耗PV资源。Pod能够请求特定的资源(如CPU和内存)。PVC能够请求指定的大小和访问的模式(可以被映射为一次读写或者多次只读)。

PVC允许用户消耗抽象的存储资源,用户也经常需要各种属性(如性能)的PV。集群管理员需要提供各种各样、不同大小、不同访问模式的PV,而不用向用户暴露这些volume如何实现的细节。因为这种需求,就催生出一种StorageClass资源。

StorageClass提供了一种方式,使得管理员能够描述他提供的存储的等级。集群管理员可以将不同的等级映射到不同的服务等级、不同的后端策略。

pv和pvc的区别

PersistentVolume(持久卷)和PersistentVolumeClaim(持久卷申请)是k8s提供的两种API资源,用于抽象存储细节。管理员关注于如何通过pv提供存储功能而无需关注用户如何使用,同样的用户只需要挂载pvc到容器中而不需要关注存储卷采用何种技术实现。

pvc和pv的关系与pod和node关系类似,前者消耗后者的资源。pvc可以向pv申请指定大小的存储资源并设置访问模式,这就可以通过Provision -> Claim 的方式,来对存储资源进行控制。

二、volume和claim的生命周期

PV是集群中的资源,PVC是对这些资源的请求,同时也是这些资源的“提取证”。PV和PVC的交互遵循以下生命周期:

供给

有两种PV提供的方式:静态和动态。

静态

集群管理员创建多个PV,它们携带着真实存储的详细信息,这些存储对于集群用户是可用的。它们存在于Kubernetes API中,并可用于存储使用。

动态

当管理员创建的静态PV都不匹配用户的PVC时,集群可能会尝试专门地供给volume给PVC。这种供给基于StorageClass:PVC必须请求这样一个等级,而管理员必须已经创建和配置过这样一个等级,以备发生这种动态供给的情况。请求等级配置为“ ”的PVC,有效地禁用了它自身的动态供给功能。

绑定

用户创建一个PVC(或者之前就已经就为动态供给创建了),指定要求存储的大小和访问模式。master中有一个控制回路用于监控新的PVC,查找匹配的PV(如果有),并把PVC和PV绑定在一起。如果一个PV曾经动态供给到了一个新的PVC,那么这个回路会一直绑定这个PV和PVC。另外,用户总是至少能得到它们所要求的存储,但是volume可能超过它们的请求。一旦绑定了,PVC绑定就是专属的,无论它们的绑定模式是什么。

如果没找到匹配的PV,那么PVC会无限期得处于unbound未绑定状态,一旦PV可用了,PVC就会又变成绑定状态。比如,如果一个供给了很多50G的PV集群,不会匹配要求100G的PVC。直到100G的PV添加到该集群时,PVC才会被绑定。

使用

Pod使用PVC就像使用volume一样。集群检查PVC,查找绑定的PV,并映射PV给Pod。对于支持多种访问模式的PV,用户可以指定想用的模式。一旦用户拥有了一个PVC,并且PVC被绑定,那么只要用户还需要,PV就一直属于这个用户。用户调度Pod,通过在Pod的volume块中包含PVC来访问PV。

释放

当用户使用PV完毕后,他们可以通过API来删除PVC对象。当PVC被删除后,对应的PV就被认为是已经是“released”了,但还不能再给另外一个PVC使用。前一个PVC的属于还存在于该PV中,必须根据策略来处理掉。

回收

PV的回收策略告诉集群,在PV被释放之后集群应该如何处理该PV。当前,PV可以被Retained(保留)、 Recycled(再利用)或者Deleted(删除)。保留允许手动地再次声明资源。对于支持删除操作的PV卷,删除操作会从Kubernetes中移除PV对象,还有对应的外部存储(如AWS EBS,GCE PD,Azure Disk,或者Cinder volume)。动态供给的卷总是会被删除。

三、PV详解

每个PV都包含一个spec和状态,即说明书和PV卷的状态,如以下示例:

[root@server1 volumes]# vim pv1.yaml 
[root@server1 volumes]# cat pv1.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1
spec:
  capacity:
    storage: 500Mi					#设置pv大小
  volumeMode: Filesystem			#卷类型
  accessModes:
    - ReadWriteOnce							#访问模式
  persistentVolumeReclaimPolicy: Recycle		#回收策略
  storageClassName: slow			#存储类别名称
  nfs:
    server: 172.25.63.1			#设置nfs挂载路径和服务器地址
    path: /nfs

创建pv:

[root@server1 volumes]# kubectl create -f pv1.yaml 
persistentvolume/pv1 created

查看pv:

[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Available           slow                    11s

删除:

[root@server1 volumes]# kubectl delete -f pv1.yaml 
persistentvolume "pv1" deleted

Capacity(容量)

一般来说,PV会指定存储的容量,使用PV的capacity属性来设置。查看Kubernetes的Resource Model来了解capacity。
  当前,存储大小是唯一能被设置或请求的资源。未来可能包含IOPS,吞吐率等属性。

访问模式

PV可以使用存储资源提供商支持的任何方法来映射到host中。如下的表格中所示,提供商有着不同的功能,每个PV的访问模式被设置为卷支持的指定模式。比如,NFS可以支持多个读/写的客户端,但可以在服务器上指定一个只读的NFS PV。每个PV有它自己的访问模式。

访问模式包括:
   ▷ ReadWriteOnce —— 该volume只能被单个节点以读写的方式映射
   ▷ ReadOnlyMany —— 该volume可以被多个节点以只读方式映射
   ▷ ReadWriteMany —— 该volume只能被多个节点以读写的方式映射
 
在CLI中,访问模式可以简写为:
   ▷ RWO - ReadWriteOnce
   ▷ ROX - ReadOnlyMany
   ▷ RWX - ReadWriteMany
   
注意:即使volume支持很多种访问模式,但它同时只能使用一种方式来映射。比如,GCEPersistentDisk可以被单个节点映射为ReadWriteOnce,或者多个节点映射为ReadOnlyMany,但不能同时使用这两种方式来映射。

在这里插入图片描述

Class

一个PV可以有一种class,通过设置storageClassName属性来选择指定的StorageClass。有指定class的PV只能绑定给请求该class的PVC。没有设置storageClassName属性的PV只能绑定给未请求class的PVC。

过去,使用volume.beta.kubernetes.io/storage-class注解,而不是storageClassName属性。该注解现在依然可以工作,但在Kubernetes的未来版本中已经被完全弃用了。

回收策略

当前的回收策略有:
   ▷ Retain:手动回收
   ▷ Recycle:需要擦除后才能再使用
   ▷ Delete:相关联的存储资产,如AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷都会被删除

当前,只有NFS和HostPath支持回收利用,AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷支持删除操作。

阶段

一个volume卷处于以下几个阶段之一:
   ▷ Available:空闲的资源,未绑定给PVC
   ▷ Bound:绑定给了某个PVC
   ▷ Released:PVC已经删除了,但是PV还没有被集群回收
   ▷ Failed:PV在自动回收中失败了

CLI可以显示PV绑定的PVC名称。

四、PersistentVolumeClaims(PVC)详解

每个PVC都包含一个spec和status,即该PVC的规则说明和状态。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment, operator: In, values: [dev]}

访问模式

当请求指定访问模式的存储时,PVC使用的规则和PV相同。

资源

PVC,就像pod一样,可以请求指定数量的资源。请求资源时,PV和PVC都使用相同的资源样式。

选择器(Selector)

PVC可以指定标签选择器进行更深度的过滤PV,只有匹配了选择器标签的PV才能绑定给PVC。选择器包含两个字段:
   ● matchLabels(匹配标签) - PV必须有一个包含该值得标签
   ● matchExpressions(匹配表达式) - 一个请求列表,包含指定的键、值的列表、关联键和值的操作符。合法的操作符包含In,NotIn,Exists,和DoesNotExist。
  所有来自matchLabels和matchExpressions的请求,都是逻辑与关系的,它们必须全部满足才能匹配上。

等级(Class)

PVC可以使用属性storageClassName来指定StorageClass的名称,从而请求指定的等级。只有满足请求等级的PV,即那些包含了和PVC相同storageClassName的PV,才能与PVC绑定。

PVC并非必须要请求一个等级。设置storageClassName为“”的PVC总是被理解为请求一个无等级的PV,因此它只能被绑定到无等级的PV(未设置对应的标注,或者设置为“”)。未设置storageClassName的PVC不太相同,DefaultStorageClass的权限插件打开与否,集群也会区别处理PVC。
   • 如果权限插件被打开,管理员可能会指定一个默认的StorageClass。所有没有指定StorageClassName的PVC只能被绑定到默认等级的PV。要指定默认的StorageClass,需要在StorageClass对象中将标注storageclass.kubernetes.io/is-default-class设置为“true”。如果管理员没有指定这个默认值,集群对PVC创建请求的回应就和权限插件被关闭时一样。如果指定了多个默认等级,那么权限插件禁止PVC创建请求。
   
   • 如果权限插件被关闭,那么久没有默认StorageClass的概念。所有没有设置StorageClassName的PVC都只能绑定到没有等级的PV。因此,没有设置StorageClassName的PVC就如同设置StorageClassName为“”的PVC一样被对待。

根据安装方法的不同,默认的StorageClass可能会在安装过程中被插件管理默认的部署在Kubernetes集群中。

当PVC指定selector来请求StorageClass时,所有请求都是与操作的。只有满足了指定等级和标签的PV才可能绑定给PVC。当前,一个非空selector的PVC不能使用PV动态供给。

过去,使用volume.beta.kubernetes.io/storage-class注解,而不是storageClassName属性。该注解现在依然可以工作,但在Kubernetes的未来版本中已经被完全弃用了。

使用PVC

Pod通过使用PVC(使用方式和volume一样)来访问存储。PVC必须和使用它的pod在同一个命名空间,集群发现pod命名空间的PVC,根据PVC得到其后端的PV,然后PV被映射到host中,再提供给pod。

五、nfs pv示例

1.首先搭建nfs服务器,这里不做演示。

2.创建NFS PV 卷

[root@server1 volumes]# vim pv2.yaml
[root@server1 volumes]# cat pv2.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1
spec:
  capacity:
    storage: 500Mi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    server: 172.25.63.1
    path: /nfs

创建并查看状态:

[root@server1 volumes]# kubectl create -f pv2.yaml 
persistentvolume/pv1 created
[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Available           nfs                     21s

可以看到状态是Available

3.创建PVC

[root@server1 volumes]# vim pvc2.yaml 
[root@server1 volumes]# cat pvc2.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi

注意我们在上述文件中只是声明我们要求的PV是什么样子的,我们这里的要求与上面创建的PV相同。

同时需要注意的是只有pv满足storageClassName,accessModes,resources的所有条件才会被绑定。

创建并查看:

[root@server1 volumes]# kubectl create -f pvc2.yaml 
persistentvolumeclaim/pvc1 created
[root@server1 volumes]# kubectl get pvc
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc1   Bound    pv1      500Mi      RWO            nfs            7s

可以看到pvc1的状态已经是Bound绑定状态了,且绑定的是pv1,然后我们现在查看pv状态:

[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM          STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Bound    default/pvc1   nfs                     4m55s

同样显示与pvc1是绑定状态,default表示默认的namespace,此时删除pvc1再查看pv状态:

[root@server1 volumes]# kubectl delete -f pvc2.yaml 
persistentvolumeclaim "pvc1" deleted

[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Available           nfs                     5m53s

可以看出删除pvc后,由于pv的回收策略是Recycle,因此pv现在又处于Available可用状态。

4.再创建一个pv

[root@server1 volumes]# vim pv3.yaml 
[root@server1 volumes]# cat pv3.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv2
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Delete
  storageClassName: nfs
  nfs:
    server: 172.25.63.1
    path: /nfs
[root@server1 volumes]# kubectl create -f pv3.yaml 
persistentvolume/pv2 created
[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Available           nfs                     9m44s
pv2    1Gi        RWX            Delete           Available           nfs                     45s

注意我们设置的回收策略设置的是删除,即删除pvc时pv也会被删除,大小设置的是1G.

5.再创建一个pvc

[root@server1 volumes]# vim pvc2.yaml 
[root@server1 volumes]# cat pvc2.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
[root@server1 volumes]# kubectl create -f pvc2.yaml 
persistentvolumeclaim/pvc1 created
[root@server1 volumes]# 
[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM          STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Available                  nfs                     10m
pv2    1Gi        RWX            Delete           Bound       default/pvc1   nfs                     88s
[root@server1 volumes]# kubectl get pvc
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc1   Bound    pv2      1Gi        RWX            nfs            12s

可以看出pvc1的要求 pv2满足,因此创建后就和pv2绑定。

然后现在删除pvc1后查看pv2的状态:

[root@server1 volumes]# kubectl delete -f pvc2.yaml 
persistentvolumeclaim "pvc1" deleted
[root@server1 volumes]# kubectl get pvc
No resources found in default namespace.
[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM          STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Available                  nfs                     11m
pv2    1Gi        RWX            Delete           Failed      default/pvc1   nfs                     2m13s

可以看出pv2的状态是faild,因为nfs不支持删除,因此显示删除失败。

实验完成后将pv2删除:

[root@server1 volumes]# kubectl delete -f pv3.yaml 
persistentvolume "pv2" deleted

六、pod使用pvc示例

首先创建pv和pvc:

[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Available           nfs                     11m

[root@server1 volumes]# vim pvc2.yaml 
[root@server1 volumes]# cat pvc2.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi
[root@server1 volumes]# kubectl create -f pvc2.yaml 
persistentvolumeclaim/pvc1 created
[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM          STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Bound    default/pvc1   nfs                     13m
[root@server1 volumes]# kubectl get pvc
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc1   Bound    pv1      500Mi      RWO            nfs            8s

之后创建pod:

[root@server1 volumes]# vim pod3.yaml
[root@server1 volumes]# cat pod3.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: testpd
spec:
  containers:
  - image: nginx
    name: test-container
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: storage1
  volumes:
  - name: storage1
    nfs:
    persistentVolumeClaim:
      claimName: pvc1

上述部署文件表示将pvc1挂载到容器内的nginx发布目录,创建pod:

[root@server1 volumes]# kubectl create -f pod3.yaml 
pod/testpd created
[root@server1 volumes]# kubectl get pod
NAME      READY   STATUS        RESTARTS   AGE
testpd    1/1     Running       0          5s
[root@server1 volumes]# kubectl describe pod testpd 

在这里插入图片描述
现在我们进入容器内部写入测试文件:

[root@server1 volumes]# kubectl exec -it testpd -- bash
root@testpd:/# cd /usr/share/nginx/html/
root@testpd:/usr/share/nginx/html# ls
root@testpd:/usr/share/nginx/html# echo redhat > index.html
root@testpd:/usr/share/nginx/html# cat index.html 
redhat
root@testpd:/usr/share/nginx/html# exit

然后测试访问:

[root@server1 volumes]# kubectl get pod -o wide
NAME      READY   STATUS        RESTARTS   AGE    IP            NODE      NOMINATED NODE   READINESS GATES
testpd    1/1     Running       0          119s   10.244.2.67   server3   <none>           <none>
[root@server1 volumes]# curl 10.244.2.67
redhat

可以成功访问,需要注意的是,在容器内写入的数据会直接写入到nfs服务器中:

[root@server1 volumes]# cat /nfs/index.html 
redhat

此时我们将pod删除后再查看pvc的状态:

[root@server1 volumes]# kubectl delete -f pod3.yaml 
pod "testpd" deleted
[root@server1 volumes]# kubectl get pvc
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc1   Bound    pv1      500Mi      RWO            nfs            7m52s

可以看出pvc依然存在,这个pvc也可以被其他pod挂载:

[root@server1 volumes]# vim pod3.yaml
[root@server1 volumes]# cat pod3.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - image: nginx
    name: test-container
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: storage1
  volumes:
  - name: storage1
    nfs:
    persistentVolumeClaim:
      claimName: pvc1
[root@server1 volumes]# kubectl create -f pod3.yaml 
pod/mypod created

[root@server1 volumes]# kubectl get pod -o wide
NAME      READY   STATUS        RESTARTS   AGE   IP            NODE      NOMINATED NODE   READINESS GATES
mypod     1/1     Running       0          27s   10.244.2.68   server3   <none>           <none>

挂载后直接访问其虚拟地址:

[root@server1 volumes]# curl 10.244.2.68
redhat

依然可以访问到之前写入的文件,这就是持久卷pv的作用。

但是当我们把pod 和 pvc全部删除后,nfs服务器中的文件也就删除了:

[root@server1 volumes]# kubectl delete -f pvc2.yaml 
persistentvolumeclaim "pvc1" deleted
[root@server1 volumes]# kubectl delete -f pod3.yaml 
pod "mypod" deleted
[root@server1 volumes]# ls /nfs/
[root@server1 volumes]# 

此时pv恢复成了Available可用状态:

[root@server1 volumes]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    500Mi      RWO            Recycle          Available           nfs                     24m
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章