一. 简介
PVC 和 PV 的设计,其实跟“面向对象”的思想完全一致。PVC 可以理解为持久化存储的“接口”,它提供了对某种持久化存储的描述,但不提供具体的实现,而 PV 负责完成持久化存储的实现。而StorageClass 对象的作用,其实就是创建 PV 的模板,减少手动操作的重复工作以及错误情况。
关于本文的项目的代码,都放于链接:GitHub资源
二. PV
2.1 Volume
容器的 Volume,其实就是将一个宿主机上的目录,跟一个容器里的目录绑定挂载在了一起。
而所谓的“持久化 Volume”,指的就是这个宿主机上的目录,具备“持久性”。即:这个目录里面的内容,既不会因为容器的删除而被清理掉,也不会跟当前的宿主机绑定。
而我们常见的hostPath
和emptyDir
类型的 Volume 并不具备这个特征:它们既有可能被 kubelet 清理掉,也不能被“迁移”到其他节点上。这也是我们需要使用PV与PVC的原因。
2.2 PV持久化
PV的持久化可以分为2阶段流程:
Attach阶段
这一步为虚拟机挂载远程磁盘的操作。Kubernetes 提供的可用参数是nodeName
,即宿主机的名字。在 Kubernetes 中,我们把这个阶段称为Attach
。Mount
这个将磁盘设备格式化并挂载到Volume
宿主机目录。Kubernetes 提供的可用参数是dir
,即Volume
的宿主机目录。在 Kubernetes 中,我们把这个阶段称为Mount
。
经过了“两阶段处理”,我们就得到了一个“持久化”的Volume
宿主机目录。kubelet 只要把这个Volume
目录通过CRI
里的Mounts
参数,传递给Docker
,然后就可以为 Pod 里的容器挂载这个“持久化”的 Volume 了。
2.3 PV与PVC绑定控制器
PersistentVolumeController
会不断地查看当前每一个 PVC,是不是已经处于 Bound(
已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单独”的 PVC 进行绑定。这样,Kubernetes 就可以保证用户提交的每一个 PVC,只要有合适的 PV 出现,它就能够很快进入绑定状态。
而所谓将一个 PV 与 PVC 进行“绑定”,其实就是将这个 PV 对象的名字,填在了 PVC 对象的spec.volumeName
字段上。所以,接下来 Kubernetes 只要获取到这个 PVC 对象,就一定能够找到它所绑定的 PV。
2.4 小结
关于 PV 的“两阶段处理”流程,是靠独立于 kubelet 主控制循环(Kubelet Sync Loop)之外的两个控制循环来实现的。
第一阶段的
Attach
(以及 Dettach)操作,是由Volume Controller
负责维护的,这个控制循环的名字叫作:AttachDetachController
。而它的作用,就是不断地检查每一个 Pod 对应的 PV,和这个 Pod 所在宿主机之间挂载情况。从而决定,是否需要对这个PV
进行Attach
(或者 Dettach)操作。第二阶段的
Mount
(以及Unmount
)操作,必须发生在 Pod 对应的宿主机上,所以它必须是 kubelet 组件的一部分。这个控制循环的名字,叫作:VolumeManagerReconciler
,它运行起来之后,是一个独立于 kubelet 主循环的Goroutine
。
通过这样将 Volume 的处理同 kubelet 的主循环解耦,Kubernetes 就避免了这些耗时的远程挂载操作拖慢 kubelet 的主控制循环,进而导致 Pod 的创建效率大幅下降的问题。
三. StorageClass
StorageClass 对象的作用,其实就是创建 PV 的模板。
3.1 PV创建方式
Static Provisioning
人工管理 PV 的方式就叫作 Static Provisioning,通过手动创建PV方式具有极高的重复性。Dynamic Provisioning
Dynamic Provisioning 机制工作的核心,在于一个名叫 StorageClass 的 API 对象。
3.2 StorageClass范围
StorageClass 对象会定义如下两个部分内容:
PV 的属性。比如,存储类型、Volume 的大小等等。
创建这种 PV 需要用到的存储插件。比如,Ceph 等等。
有了这样两个信息之后,Kubernetes 就能够根据用户提交的 PVC,找到一个对应的StorageClass
了。然后,Kubernetes 就会调用该 StorageClass
声明的存储插件,创建出需要的 PV。
3.3 案例
3.3.1 StorageClass定义
“demo-storageclass.yaml”文件内容如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: demo-storageclass
provisioner: docker.io/hostpath
parameters:
type: pd-ssd
在这个 YAML 文件里,我们定义了一个名叫 demo-storageclass 的 StorageClass。这个 StorageClass 的 provisioner 字段的值是:docker.io/hostpath,因为我使用的是mac docker desktop 。而这个 StorageClass 的 parameters 字段,就是 PV 的参数。比如:上面例子里的 type=pd-ssd。
3.3.2 PVC定义
demo-pvc.yaml
的定义如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: demo-storageclass
resources:
requests:
storage: 1Gi
可以看到,我们在这个 PVC 里添加了一个叫作 storageClassName
的字段,用于指定该 PVC 所要使用的StorageClass
的名字是:demo-storageclass
。
3.3.3 验证
通过按序创建StorageClass与PVC
,指令如下:
kubectl apply -f demo-storageclass.yaml
kubectl apply -f demo-pvc.yaml
我们再通过如下的指令查看volume
情况:
kubectl describe pvc demo-pvc
# result
Name: demo-pvc
Namespace: default
StorageClass: demo-storageclass
Status: Bound
Volume: pvc-cd36fd9f-d15a-49f7-836b-9b6605a75f87
Labels: <none>
Annotations: pv.kubernetes.io/bind-completed: yes
pv.kubernetes.io/bound-by-controller: yes
volume.beta.kubernetes.io/storage-provisioner: docker.io/hostpath
Finalizers: [kubernetes.io/pvc-protection]
Capacity: 1Gi
Access Modes: RWO
VolumeMode: Filesystem
Mounted By: <none>
Events:
执行如下指令,再查看对应的PV内容:
kubectl describe pv pvc-cd36fd9f-d15a-49f7-836b-9b6605a75f87
# result
Name: pvc-cd36fd9f-d15a-49f7-836b-9b6605a75f87
Labels: <none>
Annotations: docker.io/hostpath: /var/lib/k8s-pvs/demo-pvc/pvc-cd36fd9f-d15a-49f7-836b-9b6605a75f87
pv.kubernetes.io/provisioned-by: docker.io/hostpath
Finalizers: [kubernetes.io/pv-protection]
StorageClass: demo-storageclass
Status: Bound
Claim: default/demo-pvc
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 1Gi
Node Affinity: <none>
Message:
这个自动创建出来的 PV 的 StorageClass
字段的值,也是demo-storageclass
,Kubernetes 只会将StorageClass
相同的 PVC 和 PV 绑定起来。
实操结果如下图:
3.4. 小结
有了 Dynamic Provisioning
机制,运维人员只需要在 Kubernetes 集群里创建出数量有限的StorageClass
对象。当开发人员提交了包含 StorageClass
字段的 PVC 之后,Kubernetes 就会根据这个StorageClass
创建出对应的 PV。
四. 总结
基于Pod,PVC,PV与StorageClass之间的关系,我总结出如下的架构图:
从图中我们可以总结为:
- PVC 描述了 Pod 想要使用的持久化存储的属性,比如存储的大小、读写权限等。
- PV 描述了一个具体的 Volume 的属性,比如 Volume 的类型、挂载目录、远程存储服务器地址等。
- StorageClass 的作用,则是充当 PV 的模板。并且,只有同属于一个
StorageClass
的 PV 和 PVC,才可以绑定在一起。同时,可以指定 PV 的 Provisioner(存储插件),当存储插件支持Dynamic Provisioning
的话,Kubernetes 就可以自动创建 PV 了。