引子
kubernetes中的概念比较多,对入门或理解kubernetes会造成一定的难度,尤其是有些概念有相近之处,更加让初学者摸不着头脑。
所以对于概念之间的联立比较,区别它们之间的异同,对清晰的理解kubernetes中的有关概念有很大的帮助。
pv、pvc和StorageClass是kubernetes中存储的相关概念,相互联系又有区别,那如何来理解这三个概念呢?
概念简介
PersistentVolume(PV )
PersistentVolume(持久化卷),是对底层的共享存储的一种抽象,PV 由管理员进行创建和配置,它和具体的底层的共享存储技术的实现方式有关,比如 Ceph、GlusterFS、NFS 等,都是通过插件机制完成与共享存储的对接。除了EmptyDir类型的存储卷,PV的生命周期独立于使用它的Pod。
PersistentVolumeClaim(PVC)
PVC是由用户进行存储的请求。它类似于pod。 Pod消耗节点资源,PVC消耗PV资源。Pod可以请求特定级别的资源(CPU和内存)。声明可以请求特定的大小和访问模式(例如,可以一次读/写或多次只读)。
StorageClass
StorageClass为管理员提供了一种描述他们提供的存储的“类”的方法。 不同的类可能映射到服务质量级别,或备份策略,或者由群集管理员确定的任意策略。
PV是运维人员来创建的,开发操作PVC,可是大规模集群中可能会有很多PV,如果这些PV都需要运维手动来处理这也是一件很繁琐的事情,所以就有了动态供给概念,也就是Dynamic Provisioning,动态供给的关键就是StorageClass,它的作用就是创建PV模板。创建StorageClass里面需要定义PV属性比如存储类型、大小等;另外创建这种PV需要用到存储插件。最终效果是,用户提交PVC,里面指定存储类型,如果符合我们定义的StorageClass,则会为其自动创建PV并进行绑定。
pvc和pv的关系
PVC和PV相当于“接口”和“实现”,所以我们需要将PVC和PV绑定起来才可以使用,而PVC和PV绑定的时候需要满足:
- PV 和 PVC 的 spec 字段要匹配,比如PV 的存储(storage)大小,就必须满足 PVC 的要求。
- PV 和 PVC 的 storageClassName 字段必须一样才能进行绑定。storageClassName表示的是StorageClass的name属性。
如果我们想要在Pod中使用这个PVC,那么我们可以这么做:
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc
在Pod中只需要声明PVC的名字,等Pod创建后kubelet 就会把这个 PVC 所对应的 PV,也就是一个 GCE类型的 Volume,挂载在这个 Pod 容器内的目录上。
PersistentVolumeController会不断地查看当前每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC 进行绑定。这个PersistentVolumeController的源码我们下面会进行分析。那么问题来了,k8s为什么要将一个存储卷分成两部分呢?
创建PV
创建一个 hostPath 类型的 PV 资源对象:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-hostpath
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/data/k8s/test/hostpath"
PV资源对象需要设置的关键配置参数如下。
-
存储容量(Capacity):描述存储的容量,目前仅支持对存储空间的设置(storage=xx),未来可能加入IOPS、吞吐率等设置。
-
存储卷模式(Volume Modes)可以设置的选项包括Filesystem(文件系统,默认值)和Block(块设备)。文件系统模式的PV将以目录(Directory)形式挂载到Pod内。如果模式为块设备,但是设备是空的,则Kubernetes会自动在块设备上创建一个文件系统。支持块设备的存储类型会以裸设备(Raw Block Device)的形式挂载到容器内,并且不会创建任何文件系统,适用于需要直接操作裸设备(速度最快)的应用程序。
-
访问模式(Access Modes)
ReadWriteOnce(RWO):读写权限,并且只能被单个Node挂载。
ReadOnlyMany(ROX):只读权限,允许被多个Node挂载。
ReadWriteMany(RWX):读写权限,允许被多个Node挂载。
-
存储类别(Class)PV可以设定其存储的类别,通过storageClassName参数指定一个StorageClass资源对象的名称。具有特定类别的PV只能与请求了该类别的PVC绑定。未设定类别的PV则只能与不请求任何类别的PVC绑定。
-
回收策略(Reclaim Policy)
Retain:保留数据,需要手工处理。
Recycle:简单清除文件的操作(例如运行rm-rf/thevolume/*命令)。
Delete:与PV相连的后端存储完成Volume的删除操作。
-
挂载选项(Mount Options)在将PV挂载到一个Node上时,根据后端存储的特点,可能需要设置额外的挂载选项的参数,这个可以在PV定义中的mountOptions字段进行设置。
创建PVC
PVC作为用户对存储资源的需求申请,主要涉及存储空间请求、访问模式、PV选择条件和存储类别等信息的设置。
PVC的的创建示例:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-hostpath
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
对PVC的关键配置参数说明如下。
- 资源请求(Resources):描述对存储资源的请求,通过resources.requests.storage字段设置需要的存储空间大小。
- 访问模式(Access Modes):PVC也可以设置访问模式,用于描述用户应用对存储资源的访问权限。其三种访问模式的设置与PV的设置相同。
- 存储卷模式(Volume Modes):PVC也可以设置存储卷模式,用于描述希望使用的PV存储卷模式,包括文件系统(Filesystem)和块设备(Block)。PVC设置的存储卷模式应该与PV存储卷模式相同,以实现绑定;如果不同,则可能出现不同的绑定结果。
- PV选择条件(Selector):通过Label Selector的设置,可使PVC对于系统中已存在的各种PV进行筛选。系统将根据标签选出合适的PV与该PVC进行绑定。对选择条件可以使用matchLabels和matchExpressions进行设置,如果两个字段都已设置,则Selector的逻辑将是两组条件同时满足才能完成匹配。
- 存储类别(Class):PVC在定义时可以设定需要的后端存储的类别(通过storageClassName字段指定),以减少对后端存储特性的详细信息的依赖。只有设置了该Class的PV才能被系统选出,并与该PVC进行绑定。PVC也可以不设置Class需求,如果storageClassName字段的值被设置为空(storageClassName=""),则表示该PVC不要求特定的Class,系统将只选择未设定Class的PV与之匹配和绑定。
pod使用pvc
在PVC创建成功之后,Pod就可以以存储卷(Volume)的方式使用PVC的存储资源了。PVC受限于命名空间,Pod在使用PVC时必须与PVC处于同一个命名空间。
Kubernetes为Pod挂载PVC的过程如下:系统在Pod所在的命名空间中找到其配置的PVC,然后找到PVC绑定的后端PV,将PV存储挂载到Pod所在Node的目录下,最后将Node的目录挂载到Pod的容器内。
apiVersion: v1
kind: Pod
metadata:
name: pv-hostpath-pod
spec:
volumes:
- name: pv-hostpath
persistentVolumeClaim:
claimName: pvc-hostpath
nodeSelector:
kubernetes.io/hostname: ydzs-node1
containers:
- name: task-pv-container
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: pv-hostpath
PV和PVC之间的相互作用的生命周期
Provisioning ——-> Binding ——–>Using——>Releasing——>Recycling
- 供应准备Provisioning---通过集群外的存储系统或者云平台来提供存储持久化支持。
-
- 静态提供Static:集群管理员创建多个PV。 它们携带可供集群用户使用的真实存储的详细信息。 它们存在于Kubernetes API中,可用于消费
-
- 动态提供Dynamic:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试为PVC动态配置卷。 此配置基于StorageClasses:PVC必须请求一个类,并且管理员必须已创建并配置该类才能进行动态配置。 要求该类的声明有效地为自己禁用动态配置。
-
- 绑定Binding---用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。
- 使用Using---用户可在pod中像volume一样使用pvc。
- 释放Releasing---用户删除pvc来回收存储资源,pv将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc使用。
- 回收Recycling---pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。
-
- 保留策略:允许人工处理保留的数据。
-
- 删除策略:将删除pv和外部关联的存储资源,需要插件支持。
-
- 回收策略:将执行清除操作,之后可以被新的pvc使用,需要插件支持。
-
StorageClass的Dynamic Provisioning
在上面我们说的PV和PVC绑定的过程称为Static Provisioning,需要手动的创建PV,我们在研发中可能有这样的情况,就是管理员没有及时给我们创建对应的PV,难道一直等着吗?所以这个时候就需要用到StorageClass了,StorageClass提供了Dynamic Provisioning机制,可以根据模板创建PV。
StorageClass 对象会定义如下两个部分内容:
- PV 的属性。比如,存储类型、Volume 的大小等等。
- 创建这种 PV 需要用到的存储插件。比如,Ceph 等等。
这样k8s就能够根据用户提交的 PVC,找到一个对应的 StorageClass ,然后调用该 StorageClass 声明的存储插件,创建出需要的 PV。
例如声明如下StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-service
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
这里定义了名叫 block-service 的 StorageClass,provisioner 字段的值是:kubernetes.io/gce-pd,这是k8s内置的存储插件,type字段也是跟着provisioner定义的,官方默认支持 Dynamic Provisioning 的内置存储插件:https://kubernetes.io/docs/concepts/storage/storage-classes/。
然后就可以在PVC中声明storageClassName为block-service,当创建好PVC 对象之后,k8s就会调用相应的存储插件API创建一个PV对象。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claim1
spec:
accessModes:
- ReadWriteOnce
storageClassName: block-service
resources:
requests:
storage: 30Gi
这种自动创建PV的机制就是Dynamic Provisioning,Kubernetes 就能够根据用户提交的 PVC,找到一个对应的 StorageClass ,然后会调用StorageClass 声明的存储插件,创建出需要的 PV。
需要注意的是,如果没有声明StorageClassName在PVC中,PVC 的 storageClassName 的值就是"",这也意味着它只能够跟 storageClassName 也是""的 PV 进行绑定。
总结
简单来讲,为了将数据长久保存在容器外,需要用到PV,应用通过PVC来连接对应的PV,从而消费存储。为了避免创建的PVC没有对应的PV,引入StorageClass来自动创建PV。
评论区