PV、PVC、StorageClass

1、PV、PVC

NFS 类型的 PV:

  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4. name: nfs
  5. spec:
  6. storageClassName: manual
  7. capacity:
  8. storage: 1Gi
  9. accessModes:
  10. - ReadWriteMany
  11. nfs:
  12. server: 10.244.1.4
  13. path: "/"

开发人员可以声明一个 1 GiB 大小的 PVC:

  1. apiVersion: v1
  2. kind: PersistentVolumeClaim
  3. metadata:
  4. name: nfs
  5. spec:
  6. accessModes:
  7. - ReadWriteMany
  8. storageClassName: manual
  9. resources:
  10. requests:
  11. storage: 1Gi

PVC 和 PV 进行绑定之后,Pod 就能够像使用 hostPath 等常规类型的 Volume 一样,在自己的 YAML 文件里声明使用这个 PVC 了,如下所示:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: web
  5. labels:
  6. role: web-frontend
  7. spec:
  8. containers:
  9. - name: web
  10. image: nginx
  11. ports:
  12. - name: web
  13. containerPort: 80
  14. volumeMounts:
  15. - name: nfs
  16. mountPath: "/usr/share/nginx/html"
  17. volumes:
  18. - name: nfs
  19. persistentVolumeClaim:
  20. claimName: nfs

Pod 需要做的,就是在 volumes 字段里声明自己要使用的 PVC 名字。接下来,等这个 Pod 创建之后,kubelet 就会把这个 PVC 所对应的 PV,也就是一个 NFS 类型的 Volume,挂载在这个 Pod 容器内的目录上。

那么,如果在创建Pod的时候,系统里并没有合适的 PV 跟它定义的 PVC 绑定,也就是说此时容器想要使用的 Volume 不存在。这时候,Pod 的启动就会报错。那怎么办呢?

在 Kubernetes 中,实际上存在着一个专门处理持久化存储的控制器,叫作 Volume Controller。这个 Volume Controller 维护着多个控制循环,其中有一个循环,扮演的就是撮合 PV 和 PVC 的“红娘”的角色。它的名字叫作 PersistentVolumeController。

PersistentVolumeController 会不断地查看当前每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC 进行绑定。这样,Kubernetes 就可以保证用户提交的每一个 PVC,只要有合适的 PV 出现,它就能够很快进入绑定状态,从而结束“单身”之旅。

而所谓将一个 PV 与 PVC 进行“绑定”,其实就是将这个 PV 对象的名字,填在了 PVC 对象的 spec.volumeName 字段上。所以,接下来 Kubernetes 只要获取到这个 PVC 对象,就一定能够找到它所绑定的 PV。

那么,这个 PV 对象,又是如何变成容器里的一个持久化存储的呢?

容器的 Volume,其实就是将一个宿主机上的目录,跟一个容器里的目录绑定挂载在了一起。

而所谓的“持久化 Volume”,指的就是这个宿主机上的目录,具备“持久性”。即:这个目录里面的内容,既不会因为容器的删除而被清理掉,也不会跟当前的宿主机绑定。这样,当容器被重启或者在其他节点上重建出来之后,它仍然能够通过挂载这个 Volume,访问到这些内容。

显然,我们前面使用的 hostPath 和 emptyDir 类型的 Volume 并不具备这个特征:它们既有可能被 kubelet 清理掉,也不能被“迁移”到其他节点上。

所以,大多数情况下,持久化 Volume 的实现,往往依赖于一个远程存储服务,比如:远程文件存储(比如,NFS、GlusterFS)、远程块存储(比如,公有云提供的远程磁盘)等等。

而 Kubernetes 需要做的工作,就是使用这些存储服务,来为容器准备一个持久化的宿主机目录,以供将来进行绑定挂载时使用。而所谓“持久化”,指的是容器在这个目录里写入的文件,都会保存在远程存储中,从而使得这个目录具备了“持久性”。

2、StorageClass

StorageClass 对象会定义如下两个部分内容:

第一,PV 的属性。比如,存储类型、Volume 的大小等等。

第二,创建这种 PV 需要用到的存储插件。比如,Ceph 等等。

有了这样两个信息之后,Kubernetes 就能够根据用户提交的 PVC,找到一个对应的 StorageClass 了。然后,Kubernetes 就会调用该 StorageClass 声明的存储插件,创建出需要的 PV。

  1. apiVersion: storage.k8s.io/v1
  2. kind: StorageClass
  3. metadata:
  4. name: block-service
  5. provisioner: kubernetes.io/gce-pd
  6. parameters:
  7. type: pd-ssd

定义了一个名叫 block-service 的 StorageClass。

StorageClass 的 provisioner 字段的值是:kubernetes.io/gce-pd,这正是 Kubernetes 内置的 GCE PD 存储插件的名字。

StorageClass 的 parameters 字段,就是 PV 的参数。比如:上面例子里的 type=pd-ssd,指的是这个 PV 的类型是“SSD 格式的 GCE 远程磁盘”

Rook存储服务示例:

  1. apiVersion: ceph.rook.io/v1beta1
  2. kind: Pool
  3. metadata:
  4. name: replicapool
  5. namespace: rook-ceph
  6. spec:
  7. replicated:
  8. size: 3
  9. ---
  10. apiVersion: storage.k8s.io/v1
  11. kind: StorageClass
  12. metadata:
  13. name: block-service
  14. provisioner: ceph.rook.io/block
  15. parameters:
  16. pool: replicapool
  17. #The value of "clusterNamespace" MUST be the same as the one in which your rook cluster exist
  18. clusterNamespace: rook-ceph

申请使用:

  1. apiVersion: v1
  2. kind: PersistentVolumeClaim
  3. metadata:
  4. name: claim1
  5. spec:
  6. accessModes:
  7. - ReadWriteOnce
  8. storageClassName: block-service
  9. resources:
  10. requests:
  11. storage: 30Gi
  1. $ kubectl describe pvc claim1
  2. Name: claim1
  3. Namespace: default
  4. StorageClass: block-service
  5. Status: Bound
  6. Volume: pvc-e5578707-c626-11e6-baf6-08002729a32b
  7. Labels: <none>
  8. Capacity: 30Gi
  9. Access Modes: RWO
  10. No Events.
  1. $ kubectl describe pv pvc-e5578707-c626-11e6-baf6-08002729a32b
  2. Name: pvc-e5578707-c626-11e6-baf6-08002729a32b
  3. Labels: <none>
  4. StorageClass: block-service
  5. Status: Bound
  6. Claim: default/claim1
  7. Reclaim Policy: Delete
  8. Access Modes: RWO
  9. Capacity: 30Gi
  10. ...
  11. No events.

自动创建出来的 PV 的 StorageClass 字段的值,也是 block-service。这是因为,Kubernetes 只会将 StorageClass 相同的 PVC 和 PV 绑定起来。

有了 Dynamic Provisioning 机制,运维人员只需要在 Kubernetes 集群里创建出数量有限的 StorageClass 对象就可以了。这就好比,运维人员在 Kubernetes 集群里创建出了各种各样的 PV 模板。这时候,当开发人员提交了包含 StorageClass 字段的 PVC 之后,Kubernetes 就会根据这个 StorageClass 创建出对应的 PV。

Kubernetes 的官方文档里已经列出了默认支持 Dynamic Provisioning 的内置存储插件。

StorageClass 并不是专门为了 Dynamic Provisioning 而设计的。比如:

在之前示例中,storageClassName=manual。而集群里,实际上并没有一个名叫 manual 的 StorageClass 对象。这完全没有问题,这个时候 Kubernetes 进行的是 Static Provisioning,但在做绑定决策的时候,它依然会考虑 PV 和 PVC 的 StorageClass 定义。

总结:

img

  • PVC 描述的,是 Pod 想要使用的持久化存储的属性,比如存储的大小、读写权限等。
  • PV 描述的,则是一个具体的 Volume 的属性,比如 Volume 的类型、挂载目录、远程存储服务器地址等。
  • 而 StorageClass 的作用,则是充当 PV 的模板。并且,只有同属于一个 StorageClass 的 PV 和 PVC,才可以绑定在一起。StorageClass 的另一个重要作用,是指定 PV 的 Provisioner(存储插件)。这时候,如果你的存储插件支持 Dynamic Provisioning 的话,Kubernetes 就可以自动为你创建 PV 了。
文档更新时间: 2022-11-03 17:16   作者:张尚