存储管理3:理解StorageClass

在前面的文章中,我们通过手动方式创建出了所需要的PV。接下来,我们来看一种自动创建PV的方式。

1.1、Static Provisioning & Dynamic Provisioning

在一个大规模的Kubernetes集群中,集群用户可能会创建出成千上万个PVC,这时,集群管理员就需要不停地创建出满足一定条件的PV,否则新Pod就会因为PVC无法绑定PV而运行失败。显然,在实际操作中,完全依靠人工是不实现的。

Kubernetes 为我们提供了一套可以自动创建 PV 的机制,即:Dynamic Provisioning。与之对应的,人工创建 PV 的方式就叫作 Static Provisioning。而Dynamic Provisioning的核心,就在于一个名叫StorageClass的API对象。

1.2、理解StorageClass对象

StorageClass对象是一个用来自动创建PV的模板。具体来说,一个StorageClass对象通常会定义如下两部分内容:

  • 创建PV需要用到的存储插件。比如,Ceph等

  • PV的属性。比如,Volume大小等

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

下面我们通过一个示例来实践StorageClass对象的使用。

1.2.1、创建StorageClass对象

在这里,我们通过部署在本地的Kubernetes集群以及Rook存储服务来创建StorageClass对象。定义的内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
name: replicapool
namespace: rook-ceph
spec:
failureDomain: host
replicated:
size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block
# Change "rook-ceph" provisioner prefix to match the operator namespace if needed
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
# clusterID is the namespace where the rook cluster is running
clusterID: rook-ceph
# Ceph pool into which the RBD image shall be created
pool: replicapool

# (optional) mapOptions is a comma-separated list of map options.
# For krbd options refer
# https://docs.ceph.com/docs/master/man/8/rbd/#kernel-rbd-krbd-options
# For nbd options refer
# https://docs.ceph.com/docs/master/man/8/rbd-nbd/#options
# mapOptions: lock_on_read,queue_depth=1024

# (optional) unmapOptions is a comma-separated list of unmap options.
# For krbd options refer
# https://docs.ceph.com/docs/master/man/8/rbd/#kernel-rbd-krbd-options
# For nbd options refer
# https://docs.ceph.com/docs/master/man/8/rbd-nbd/#options
# unmapOptions: force

# RBD image format. Defaults to "2".
imageFormat: "2"

# RBD image features. Available for imageFormat: "2". CSI RBD currently supports only `layering` feature.
imageFeatures: layering

# The secrets contain Ceph admin credentials.
csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph

# Specify the filesystem type of the volume. If not specified, csi-provisioner
# will set default as `ext4`. Note that `xfs` is not recommended due to potential deadlock
# in hyperconverged settings where the volume is mounted on the same node as the osds.
csi.storage.k8s.io/fstype: ext4

# Delete the rbd volume when a PVC is deleted
reclaimPolicy: Delete

# Optional, if you want to add dynamic resize for PVC. Works for Kubernetes 1.14+
# For now only ext3, ext4, xfs resize support provided, like in Kubernetes itself.
allowVolumeExpansion: true

在这个 YAML 文件中,我们定义了一个名叫 block-service 的 StorageClass。这个 StorageClass 的 provisioner 字段的值是:ceph.rook.io/block,即我们使用的存储插件是Rook。有关定义StorageClass对象的更多信息可参考kubernetes官方文档

1.2.2、创建PVC

创建好StorageClass对象之后,我们只需要在PVC中指定要使用的StorageClass的名字即可,如下所示:

1
2
3
4
5
6
7
8
9
10
11
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: claim1
spec:
resources:
requests:
storage: 30Gi
accessModes:
- ReadWriteOnce
storageClassName: rook-ceph-block

可以看到,在这个 PVC 对象中添加了一个叫作 storageClassName 的字段,它指定了该 PVC 所要使用的 StorageClass 是:block-service。当我们创建好这个PVC后,它就会和一个由kubernetes自动创建的PV相绑定,如下所示:

1
2
3
4
5
6
7
8
$ kubectl describe pvc claim1 
Name: claim1
Namespace: default
StorageClass: rook-ceph-block
Status: Bound
Volume: pvc-312466a1-ad30-4d28-9d4a-088085fc5115
Labels: <none>
...

1.2.3、查看PV

通过查看这个自动创建出来的 PV 的属性,我们就可以看到它跟我们在 PVC 里声明的存储的属性是一致的,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 kubectl describe pv pvc-312466a1-ad30-4d28-9d4a-088085fc5115
Name: pvc-312466a1-ad30-4d28-9d4a-088085fc5115
Labels: <none>
Annotations: pv.kubernetes.io/provisioned-by: rook-ceph.rbd.csi.ceph.com
Finalizers: [kubernetes.io/pv-protection]
StorageClass: rook-ceph-block
Status: Bound
Claim: default/claim1
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 30Gi
Node Affinity: <none>
Message:
Source:
...

此外,我们还可以看到,这个自动创建出来的 PV 的 StorageClass 字段的值,也是 block-service。这是因为,Kubernetes 只会将 StorageClass 相同的 PVC 和 PV 绑定起来。

1.2.4、小结

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

在 Kubernetes 的官方文档里已经列出了默认所支持的 Dynamic Provisioning 存储插件。对于不在文档里的插件,比如 NFS,或者其他非内置存储插件,我们其实可以通过kubernetes-incubator/external-storage这个库来自己编写一个外部插件完成这个工作。就如同 Rook一样,因为它内置了 external-storage 的实现,所以 Rook 是完全支持 Dynamic Provisioning 特性的。

需要注意的是,StorageClass 对象并不是专门为了 Dynamic Provisioning 而设计的。比如,在前面的文章中,我们在 PV 和 PVC 里都声明了 storageClassName=manual。而我们的集群里,实际上并没有一个名叫 manual 的 StorageClass 对象。但这完全没有问题,此时 Kubernetes 进行的是 Static Provisioning,但在做绑定决策的时候,它依然会考虑 PV 和 PVC 的 StorageClass 定义。这么做的好处也很明显:这个 PVC 和 PV 的绑定关系,完全在我们自己的掌控之中。

此外,如果集群开启了一个名叫 DefaultStorageClass 的 Admission Plugin,那么它就会为 PVC 和 PV 自动添加一个默认的 StorageClass;否则,PVC 的 storageClassName 的值就是“”,这也意味着它只能够跟 storageClassName 也是“”的 PV 进行绑定。

1.3、小结

根据持久化存储的创建方式,Kubernetes将存储管理抽象成静态供应(Static Provisioning)和动态供应(Dynamic Provisioning)两种类型。其中:

  • Static Provisioning是一种手动管理方式,创建PVC的同时需要手动创建相应的PV,适用于较小规模的集群;
  • Dynamic Provisioning则是一种动态管理方式,其核心是Storage Class对象。一个Storage Class对象就是一个事先创建好的PV模板,后续一旦有PVC提交,kubernetes就会通过相应Storage Class对象所声明的存储插件创建出满足条件的PV,适用于大规模集群。

1.4、参考

https://time.geekbang.org/column/intro/100015201

https://kubernetes.io/zh/docs/concepts/storage/storage-classes/


存储管理3:理解StorageClass
https://kuberxy.github.io/2020/10/05/存储管理3:理解StorageClass/
作者
Mr.x
发布于
2020年10月5日
许可协议