【kubernetes】带你了解k8s中PV和PVC的由来

1 为什么需要卷(Volume)

Pod是由一个或者多个容器组成的,在启动Pod中的容器之前,会先创建一个pause容器,再将其他容器加入到pause容器的网络命名空间,因此,多个容器的网络是共享的,pause容器就像是一个宿主机,多个容器就像是宿主机上面的多个进程,既然多个容器共享网络命名空间,那么多个容器肯定就不能监听同一个端口。多个容器由于都有自己的文件系统,因此,多个容器的文件系统肯定是相互隔离的,如果多个容器之间需要共享数据呢?Pod在重建时,Pod中的所有容器都会基于镜像重建,那么,原来的Pod中的容器中的数据都会丢失,但是很多场景下都需要对数据进行持久化保存,例如,mysql的Pod在重建时可不希望保存的数据没有了。

为了解决以上两个问题,k8s提供了数据卷的机制,可以将同一个数据卷挂载到Pod的多个容器,实现多个容器的数据共享;也可以将数据卷对接外部的存储,实现数据的持久化,使得Pod重建或者被删除后,数据不丢失。

2 卷的挂载

一个Pod可以在pod.spec.volumes中声明,使用命令kubectl explain pod.spec.volumes可以发现里面各种类型的卷,看这些卷的类型会发现,有些卷是可以直接在k8s集群中使用的,有些是需要安装额外组件的,还有一些是对接共有云的,因此,每种类型的卷中的参数是不一样的。

2.1 k8s集群中可以直接使用
卷的类型 特点 特性状态
configMap 对接ConfigMap,可以将ConfigMap中的多个KV挂载为容器中的多个文件
downwardAPI 对接DownwardAPI,可以将集群的信息挂载为容器中的文件
emptyDir 用于多个容器之间的数据共享,相当于将一个空目录分别挂载到Pod的多个容器,它的生命周期与Pod相同,只要Pod在运行,卷就不会被删除
gitRepo 从git上clone仓库代码,然后挂载到容器 已废弃,建议在initContainer中在emptyDir下载代码
hostPath 将宿主机的路径挂载到容器中,这种方式有安全风险,通常不建议使用
persistentVolumeClaim 用于将PV挂载到容器中
projected 投射卷,可以将多个卷同时挂载到容器中的一个目录,源卷只能是secret、downwardAPI、configMap、serviceAccountToken
sercet 将Secret挂载为容器中的文件
2.2 需要额外的存储组件
卷的类型 特点 特性状态
cephfs 对接CephFS 1.28版本中废弃,建议使用Ceph CSI第三方存储驱动
cinder 对接Openstack Cinder 1.11版本中废弃,1.26版本中移除,1.27版本开始可以使用Openstack Cinder第三方驱动程序
fc 对接FiberChannel
glusterfs 对接GlusterFS 1.25版本中废弃,1.26版本中移除
iscsi 对接iSCSI
nfs 对接NFS
portworxVolume 对接Portworx 1.25版本中废弃
rdb 对接CephFS的rdb 1.28版本中废弃
2.3 公有云
卷的类型 特点 特性状态
awsElasticBlockStore 对接AWS EBS 1.19版本中废弃,1.28版本中移除,1.28版本开始可以使用AWS EBS第三方存储驱动
azureDisk 对接Azure Disk 1.19版本中废弃,1.28版本中移除,1.28版本开始可以使用Azure Disk第三方存储驱动
azureFile 对接Azure File 1.21版本中废弃,1.26版本开始可以使用Azure File第三方存储驱动
gcePersistentDisk 对接GCE PersistentDisk 1.17版本中废弃
vsphereVolume 对接VMWare vsphere VMDK 1.28版本可以使用vSphere CSI第三方存储驱动

当在pod.spec.volumes配置使用的卷后,就可以在pod.spec.containers.volumeMounts将卷挂载到容器中,以下以configMap类型的卷为例,将app-config这个ConfigMap挂载到nginx容器的/opt目录:

apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: my-config
      mountPath: /opt/
  volumes:
  - name: my-config 
    configMap:
      name: app-config

2 PV(Persistent Volume)

将pod.spec.volumes中配置的卷挂载到容器中,这时候volume是附属于Pod的,volume和Pod就构成了一种静态绑定关系,这对于hostPath、configMap、secret等类型的volume来说是可以的,因为hostPath是对本地目录的挂载,configMap和secret是对ConfigMap和Secret的挂载,这时候ConfigMap/Secret和Pod是同一个人创建的。

当开发人员用这种方式将NFS挂载到容器中:

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: registry.k8s.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /my-nfs-data
      name: test-volume
  volumes:
  - name: test-volume
    nfs:
      server: my-nfs-server.example.com
      path: /my-nfs-volume
      readOnly: true

此处将my-nfs-server.example.com的/my-nfs-volume目录挂载到test-container容器的/my-nfs-data路径,这里的问题是:当运维部门的同事部署完NFS后,难道还需要将NFS的域名/IP和路径告诉开发人员?但是开发人员实际上并不关心这些,那有没有一种方式可以让开发人员只是使用NFS而不需要关心NFS的具体配置?这就需要使用到PV(持久卷)。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-nfs
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /tmp
    server: 172.17.0.2

上述yaml会创建一个my-nfs的PV,需要注意的是:PV是集群级别的资源。这里用的pv.spec.nfs字段跟pod.spec.volumes.nfs相同,只是多了外层的capacity和accessModes,它们分别表示PV的容量和访问模式。当运维人员部署完成NFS文件系统,就可以直接创建好PV,然后开发人员就可以直接引用该PV。

这样虽然可以解决volume创建和volume使用的问题,如果集群中只有少量的PV时是没有问题的,运维人员是可以手动创建的。如果PV的数量很多呢?当运维人员创建了很多PV后,开发人员难道需要自己从中挑选PV使用吗?那管理的复杂度会非常大,因此,k8s提供了SC和PVC。

3 SC(Storage Class) 和 PVC(Persistent Volume Claim)

独立的PV资源分离了卷的创建和使用,而PVC资源则进一步将卷的使用分为卷的申请和绑定:当用户创建一个PVC,表明要申请一个持久卷,控制器就会去找到合适的PV,然后将PVC的pvc.spec.volumeName设置为PV的名称,同时将PVC的信息写入PV的pv.spec.claimRef,将PV和PVC进行双向关联,这成为动态绑定

那什么才是合适的PV呢?或者说PV满足哪些条件才能被某个PVC进行绑定呢?

在这里插入图片描述

  • 容量:在PVC中是request,表明需要多大容量的PV,在PV中是capacity,表明当前PV的容量
  • 访问模式:accessModes,不同的存储系统有不同的访问特点,有的卷可以被多个Pod同时读写,有的只能以只读方式挂载
  • 存储类:storageClassName,创建PV的存储类型

当根据PVC去查找PV时,就会根据上述三个条件去找到对应的PV完成绑定操作。PVC使得用户选择不用自己去选择PV,而是让控制器帮我们选择合适的PV。

还剩下最后一个问题:PV应该是谁创建的?肯定有一个类似控制器的组件负责创建PV,该控制器能够接收创建PV的请求,根据请求中的参数创建对应规格的持久卷。

k8s可以对接许多不同的外部存储系统,为了区分这些存储系统,k8s提供了StorageClass资源,每个StorageClass代表一个存储系统,为了支持这个存储系统,在k8s中还会有一个provisoner的组件,它通过CSI接口与外部存储系统进行通信,负责PV的管理。

在这里插入图片描述

下面数理下整体的工作流程:

  • 用户创建PVC,说明要申请一个PV,此时的PVC的状态是Pending
  • provisioner监听到PVC的创建时,会根据PVC的参数调用CSI接口创建PV,此时PV的状态是Available,表明未被绑定
  • 控制器监听到PV的创建,并发现未被绑定的PVC,就会将二者进行绑定
  • 将PV挂载到容器中使用

下面的PVC申请2G大小的nfs存储卷:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
  storageClassName: nfs

而在SC中:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"   ## 是否设置为默认的storageclass
provisioner: nfs.csi.k8s.io
parameters:
  archiveOnDelete: "true"                                  ## 设置为"false"时删除PVC不会保留数据,"true"则保留数据
mountOptions: 
  - hard                                                   ## 指定为硬挂载方式
  - nfsvers=4 

上面的PVC指定storageClassName为nfs,表明要向nfs申请卷,下面的SC由于有is-default-class的annotations,它就是默认的SC,当PVC未指定storageClassName时,就会向nfs申请卷。SC中有个参数是provisioner,是provisioner的标识字符串,通常是csidriver驱动程序的名称,可以通过kubectl get csidrivers命令查询。

4 总结

本文介绍了k8s中的PV、PVC、SC等资源以及它们的出现分别解决了什么问题,并对资源的yaml文件的一些字段进行了解释。

猜你喜欢

转载自blog.csdn.net/ILOVEYOUXIAOWANGZI/article/details/133721394