文章目录
- 1 为什么需要卷(Volume)
- 2 卷的挂载
- 2.1 k8s集群中可以直接使用
- 2.2 需要额外的存储组件
- 2.3 公有云
- 2 PV(Persistent Volume)
- 3 SC(Storage Class) 和 PVC(Persistent Volume Claim)
- 4 总结
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: nginximage: nginxvolumeMounts:- name: my-configmountPath: /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-webservername: test-containervolumeMounts:- mountPath: /my-nfs-dataname: test-volumevolumes:- name: test-volumenfs:server: my-nfs-server.example.compath: /my-nfs-volumereadOnly: 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: 5GiaccessModes:- ReadWriteOncenfs:path: /tmpserver: 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:- ReadWriteOnceresources:requests:storage: 2GistorageClassName: nfs
而在SC中:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: nfsannotations: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文件的一些字段进行了解释。