0%

【Kubernetes】Volume

默认情况下容器的数据都是非持久化的,在容器消亡以后数据也跟着丢失,所以 Docker 提供了 Volume 机制以便将数据持久化存储。类似的,Kubernetes 提供了更强大的 Volume 机制和丰富的插件,解决了容器数据持久化和容器间共享数据的问题。与 Docker 不同,Kubernetes Volume 的生命周期与 Pod 绑定:

  • 容器挂掉后 Kubelet 再次重启容器时,Volume 的数据依然还在
  • 而 Pod 删除时,Volume 才会清理。数据是否丢失取决于具体的 Volume 类型,比如 emptyDir 的数据会丢失,而 PV 的数据则不会丢

卷的类型

k8s 支持众多的 Volume 类型,包括各大云厂商提供的存储服务等,这里主要介绍两种基本的 Volume 类型:

emptyDir

当 Pod 被分配给节点时,首先创建 emptyDir 卷,并且只要该 Pod 在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。Pod 中的容器可以读取和写入 emptyDir 卷中的相同文件,尽管该卷可以挂载到每个容器中的相同或不同路径上。当出于任何原因从节点中删除 Pod 时,emptyDir 中的数据将被永久删除。注意:容器崩溃不会从节点中移除 pod,因此 emptyDir 卷中的数据在容器崩溃时是安全的。

emptyDir 的用法有:

  • 暂存空间,例如用于基于磁盘的合并排序
  • 用作长时间计算崩溃恢复时的检查点
  • Web服务器容器提供数据时,保存内容管理器容器提取的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
spec:
containers:
- image: nginx:1.14.2
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}

emptyDir 卷的存储介质(磁盘、SSD 等)由保存在 kubelet 根目录的文件系统的介质(通常是 /var/lib/kubelet)决定。 emptyDirhostPath 卷可占用多少空间并没有限制,容器之间或 Pod 之间也没有隔离。

在将来,我们预计 emptyDirhostPath 卷将能够使用 resource 规范请求一定的空间,并选择要使用的介质,适用于具有多种媒体类型的集群。

hostPath

hostPath 卷将主机节点的文件系统中的文件或目录挂载到集群中。该功能大多数 Pod 都用不到,但它为某些应用程序提供了一个强大的解决方法。

例如,hostPath 的用途如下:

  • 运行需要访问 Docker 内部的容器;使用 /var/lib/dockerhostPath
  • 在容器中运行 cAdvisor;使用 /dev/cgroupshostPath
  • 允许 pod 指定给定的 hostPath 是否应该在 pod 运行之前存在,是否应该创建,以及它应该以什么形式存在

除了所需的 path 属性之外,用户还可以为 hostPath 卷指定 type

type 字段支持以下值:

行为
空字符串(默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。
DirectoryOrCreate 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权。
Directory 给定的路径下必须存在目录
FileOrCreate 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权。
File 给定的路径下必须存在文件
Socket 给定的路径下必须存在 UNIX 套接字
CharDevice 给定的路径下必须存在字符设备
BlockDevice 给定的路径下必须存在块设备

使用这种卷类型是请注意,因为:

  • 由于每个节点上的文件都不同,具有相同配置(例如从 podTemplate 创建的)的 pod 在不同节点上的行为可能会有所不同
  • 当 Kubernetes 按照计划添加资源感知调度时,将无法考虑 hostPath 使用的资源
  • 在底层主机上创建的文件或目录只能由 root 写入。您需要在特权容器中以 root 身份运行进程,或修改主机上的文件权限以便写入 hostPath
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
# directory location on host
path: /data
# this field is optional
type: Directory

使用 subPath

有时,在单个容器中共享一个卷用于多个用途是有用的。volumeMounts.subPath 属性可用于在引用的卷内而不是其根目录中指定子路径。

下面是一个使用单个共享卷的 LAMP 堆栈(Linux Apache Mysql PHP)的示例。 HTML 内容被映射到它的 html 目录,数据库将被存储在它的 mysql 目录中:

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
apiVersion: v1
kind: Pod
metadata:
name: my-lamp-site
spec:
containers:
- name: mysql
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "rootpasswd"
volumeMounts:
- mountPath: /var/lib/mysql
name: site-data
subPath: mysql
- name: php
image: php:7.0-apache
volumeMounts:
- mountPath: /var/www/html
name: site-data
subPath: html
volumes:
- name: site-data
persistentVolumeClaim:
claimName: my-lamp-site-data

挂载传播

注意:挂载传播是 Kubernetes 1.8 中的一个 alpha 特性,在将来的版本中可能会重新设计甚至删除。

挂载传播允许将由容器挂载的卷共享到同一个 Pod 中的其他容器上,甚至是同一节点上的其他 Pod。

如果禁用 MountPropagation 功能,则不会传播 pod 中的卷挂载。也就是说,容器按照 Linux内核文档中所述的 private 挂载传播运行。

要启用此功能,请在 --feature-gates 命令行选项中指定 MountPropagation = true。启用时,容器的 volumeMounts 字段有一个新的 mountPropagation 子字段。它的值为:

  • HostToContainer:此卷挂载将接收所有后续挂载到此卷或其任何子目录的挂载。这是 MountPropagation 功能启用时的默认模式。

    同样的,如果任何带有 Bidirectional 挂载传播的 pod 挂载到同一个卷上,带有 HostToContainer 挂载传播的容器将会看到它。

    该模式等同于Linux内核文档中描述的 rslave 挂载传播。

  • Bidirectional 卷挂载与 HostToContainer 挂载相同。另外,由容器创建的所有卷挂载将被传播回主机和所有使用相同卷的容器的所有容器。

    此模式的一个典型用例是带有 Flex 卷驱动器或需要使用 HostPath 卷在主机上挂载某些内容的 pod。

    该模式等同于 Linux内核文档中所述的 rshared 挂载传播。

小心:双向挂载传播可能是危险的。它可能会损坏主机操作系统,因此只能在特权容器中使用。强烈建议熟悉 Linux 内核行为。另外,容器在 Pod 中创建的任何卷挂载必须在容器终止时销毁(卸载)

持久化存储

临时的卷没有办法解决数据持久存储的问题,想要让数据能够持久化,首先就需要将 Pod 和 Volume 的生命周期分离,这也就是引入持久卷 PersistentVolume(PV) 的原因。我们可以将 PersistentVolume 理解为集群中资源的一种,它与集群中的节点 Node 有些相似,PV 为 Kubernete 集群提供了一个如何提供并且使用存储的抽象,与它一起被引入的另一个对象就是 PersistentVolumeClaim(PVC),这两个对象之间的关系与节点和 Pod 之间的关系差不多:

PersistentVolume 是集群中的一种被管理员分配的存储资源,而 PersistentVolumeClaim 表示用户对存储资源的申请,它与 Pod 非常相似,PVC 消耗了持久卷资源,而 Pod 消耗了节点上的 CPU 和内存等物理资源。因为 PVC 允许用户消耗抽象的存储资源,所以用户需要不同类型、属性和性能的 PV 就是一个比较常见的需求了,在这时我们可以通过 StorageClass 来提供不同种类的 PV 资源,上层用户就可以直接使用系统管理员提供好的存储类型。

PersistentVolume 类型以插件形式实现,在 这里 可以看到 Kubernetes 目前内置支持的插件类型。

StorageClass

StorageClass 中包含 provisionerparametersreclaimPolicy 字段,当 class 需要动态分配 PersistentVolume 时会使用到。StorageClass 对象的名称很重要,用户使用该类来请求一个特定的方法。 当创建 StorageClass 对象时,管理员设置名称和其他参数,一旦创建了对象就不能再对其更新。

管理员可以为没有申请绑定到特定 class 的 PVC 指定一个默认的 StorageClass ,例如在腾讯云 TKE 上默认创建了提供块存储类型的 StorageClass,通过 StorageClass 配合 PersistentVolumeClaim 可以动态创建所需要的存储资源。

1
2
3
4
5
6
7
8
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: cbs
parameters:
type: cbs
provisioner: cloud.tencent.com/qcloud-cbs
reclaimPolicy: Delete

Provisioner

StorageClass 有一个 provisioner 字段,用来决定使用哪个卷插件分配 PV,在 这里 你可以看到 k8s 内置了一些存储服务提供商的 provisioner 字段,包括 AWS 的 EBS、Azure 的文件和块存储服务、CephFS 和 Cepf RBD 等,下面是创建 AWS EBS 的 StorageClass 的一个例子:

1
2
3
4
5
6
7
8
9
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/aws-ebs
parameters:
type: io1
iopsPerGB: "10"
fsType: ext4

不同的 provisioner 有自己的参数,用于定义创建该存储服务需要提供的参数,比如下面是 GlusterFS 的参数设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://127.0.0.1:8081"
clusterid: "630372ccdc720a92c681fb928f27b53f"
restauthenabled: "true"
restuser: "admin"
secretNamespace: "default"
secretName: "heketi-secret"
gidMin: "40000"
gidMax: "50000"
volumetype: "replicate:3"

对于腾讯云 CBS,对应的 provisioner 就是 cloud.tencent.com/qcloud-cbs,这是由腾讯自己实现的 provisioner 。

ReclaimPolicy

由 storage class 动态创建的 Persistent Volume 会在的 reclaimPolicy 字段中指定回收策略,可以是 Delete 或者 Retain。如果 StorageClass 对象被创建时没有指定 reclaimPolicy ,它将默认为 Delete

通过 storage class 手动创建并管理的 Persistent Volume 会使用它们被创建时指定的回收政策。

MountOption

由 storage class 动态创建的 Persistent Volume 将使用 class 中 mountOptions 字段指定的挂载选项。如果卷插件不支持挂载选项,却指定了该选项,则分配操作失败。 安装选项在 class 和 PV 上都不会做验证,所以如果挂载选项无效,那么这个 PV 就会失败。

PersistentVolume

每个 PV 配置中都包含一个 sepc 规格字段和一个 status 卷状态字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /tmp
server: 172.17.0.2

Capacity

一般而言 PV 将具有特定的存储容量,这是使用 PV 的 capacity 属性设置的。目前,存储大小是可以设置或请求的唯一资源。未来的属性可能包括 IOPS、吞吐量等。

VolumeMode

针对 PV 持久卷,Kuberneretes 支持两种 volumeModes, 如果该参数被省略,默认的卷模式是 Filesystem

  • Filesystem :Volume 以文件系统的形式被 Pod 挂载到某个目录,如果该 Volume 来自块设备,则 k8s 会在第一次挂载前在其上创建文件系统
  • Block:Volume 以块设备的形式交给 Pod 使用,其上没有任何文件系统

AccessModes

不同存储服务提供商提供的持久化存储具有不同的访问模式,总共有以下三种,一个卷一次只能使用一种访问模式挂载,即使它支持很多访问模式。例如,GCEPersistentDisk 可以由单个节点作为 ReadWriteOnce 模式挂载,或由多个节点以 ReadOnlyMany 模式挂载,但不能同时挂载。

  • ReadWriteOnce :该卷可以被单个节点以读/写模式挂载,RWO
  • ReadOnlyMany:该卷可以被多个节点以只读模式挂载,ROX
  • ReadWriteMany:该卷可以被多个节点以读/写模式挂载,RWX

StorageClass

PV 可以具有一个类,通过将 storageClassName 属性设置为 StorageClass 的名称来指定该类。一个特定类别的 PV 只能绑定到请求该类别的 PVC。没有 storageClassName 的 PV 就没有类,它只能绑定到不需要特定类的 PVC。

ReclaimPolicy

当某个服务使用完某一个卷之后,它们会从 apiserver 中删除 PVC 对象,这时 Kubernetes 就需要对 PV 进行回收(Reclaim),持久卷也同样包含三种不同的回收策略,这三种回收策略会指导 Kubernetes 选择不同的方式对使用过的卷进行处理。

  • Retain:保留 PV 中的数据,如果希望 PV 能够被重新使用,系统管理员需要删除被使用的 PersistentVolume 对象并手动清除存储和相关存储上的数据。
  • Recycle:volume上执行基本擦除(rm -rf / thevolume / *),该 PV 可被再次 PVC 使用,回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态供应。
  • Delete:当 PVC 被使用者删除之后,如果当前卷支持删除的回收策略,那么 PV 和相关的存储会被自动删除,如果当前 PV 上的数据确实不再需要,那么将回收策略设置成 Delete 能够节省手动处理的时间并快速释放无用的资源。

MountOption

Kubernetes 管理员可以指定在节点上为挂载持久卷指定挂载选项,不是所有 PV 都支持挂载选项

PersistentVolumeClaim

每个 PVC 中都包含一个 spec 规格字段和一个 status 声明状态字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 8Gi
storageClassName: slow
selector:
matchLabels:
release: "stable"
matchExpressions:
- {key: environment, operator: In, values: [dev]}
  • Access Mode:在请求具有特定访问模式的存储时,PVC 使用与 PV 相同的约定。
  • Volume Mode:指示将卷作为文件系统或块设备使用,PVC 使用与 PV 相同的约定。
  • Resources:像 Pod 一样,PVC 可以请求特定数量的资源
  • Selector:声明可以指定一个标签选择器来进一步过滤该组卷,只有标签与选择器匹配的 PV 可以绑定到声明。选择器由两个字段组成:
    • matchLabels:volume 必须有具有该值的标签
    • matchExpressions:这是一个要求列表,通过指定关键字,值列表以及与关键字和值相关的运算符组成。有效的运算符包括 In、NotIn、Exists 和 DoesNotExist。
    • 所有来自 matchLabelsmatchExpressions 的要求都被 AND 在一起——它们必须全部满足才能匹配。
  • StorageClass:PVC 可以通过使用属性 storageClassName 指定 StorageClass 的名称来请求特定的类。只有所请求的类与 PVC 具有相同 storageClassName 的 PV 才能绑定到 PVC。PVC 不一定要请求类。其 storageClassName 设置为 "" 的 PVC 始终被解释为没有请求类的 PV,因此只能绑定到没有类的 PV(没有注解或 "")。没有 storageClassName 的 PVC 根据是否打开DefaultStorageClass 准入控制插件,集群对其进行不同处理
    • 如果打开了准入控制插件,管理员可以指定一个默认的 StorageClass。所有没有 StorageClassName 的 PVC 将被绑定到该默认的 PV。通过在 StorageClass 对象中将注解 storageclass.kubernetes.io/is-default-class 设置为 “true” 来指定默认的 StorageClass。如果管理员没有指定缺省值,那么集群会响应 PVC 创建,就好像关闭了准入控制插件一样。如果指定了多个默认值,则准入控制插件将禁止所有 PVC 创建。
    • 如果准入控制插件被关闭,则没有默认 StorageClass 的概念。所有没有 storageClassName 的 PVC 只能绑定到没有类的 PV。在这种情况下,没有 storageClassName 的 PVC 的处理方式与 storageClassName 设置为 "" 的 PVC 的处理方式相同。
    • 根据安装方法的不同,默认的 StorageClass 可以在安装过程中通过插件管理器部署到 Kubernetes 集群。

生命周期

卷可以处于以下的某种状态:

  • Available(可用)——一块空闲资源还没有被任何声明绑定
  • Bound(已绑定)——卷已经被声明绑定
  • Released(已释放)——声明被删除,但是资源还未被集群重新声明
  • Failed(失败)——该卷的自动回收失败

PV 属于集群中的资源,PVC 是对这些资源的请求,也作为对资源的请求的检查。 PV 和 PVC 之间的相互作用遵循这样的生命周期:

Provisioning

Kubernetes 集群中包含了很多的 PV 资源,而 PV 资源有两种供应的方式,一种是静态的,另一种是动态的:

  • 静态:静态存储供应要求集群的管理员预先创建一定数量的 PV,然后使用者通过 PVC 的方式对 PV 资源的使用进行声明和申请
  • 动态:根据 StorageClasses,当管理员创建的静态 PV 都不匹配用户的 PersistentVolumeClaim 时,集群可能会尝试动态地为 PVC 创建卷

这里我们演示在腾讯云 TKE 上动态创建 PV 的方式,创建 PVC 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pv
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: cbs
volumeMode: Filesystem

Binding

master 中的 PV controller 会一直监听新的 PVC,用户创建具有特定存储量的 PersistentVolumeClaim 以及某些访问模式后,controller 会寻找匹配的 PV(如果可能),并将它们绑定在一起。如果没有匹配的卷,PVC 将无限期地保持未绑定状态。随着匹配卷的可用,PVC 将被绑定。例如,配置了许多 50Gi PV的集群将不会匹配请求 100Gi 的PVC。将100Gi PV 添加到群集时,可以绑定 PVC。

在动态创建的场景下,将会为新的 PVC 动态创建 PV,并将始终将该 PV 绑定到 PVC。一旦 PV 和 PVC 绑定后,PersistentVolumeClaim 绑定是排他性的,不管它们是如何绑定的。 PVC 跟 PV 绑定是一对一的映射。

在 TKE 创建上述 PVC 之后,会同步创建出对应的 PV :

1
2
3
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-3faef531-1def-4630-8f3e-c40d89ab7009 100Gi RWO Delete Bound default/test-pv cbs 119s

查看该 PV,可以看到 claimRef 字段表明其源自 PVC test-pv

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
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
Provisioner_Id: ""
kubernetes.io/createdby: qcloud-cbs-dynamic-provisioner
kubernetes.io/disk-delete-with-cluster-deletion: "true"
pv.kubernetes.io/provisioned-by: cloud.tencent.com/qcloud-cbs
creationTimestamp: "2021-03-07T11:28:14Z"
finalizers:
- kubernetes.io/pv-protection
labels:
failure-domain.beta.kubernetes.io/region: bj
failure-domain.beta.kubernetes.io/zone: "800003"
manager: kube-controller-manager
operation: Update
time: "2021-03-07T11:28:14Z"
name: pvc-3faef531-1def-4630-8f3e-c40d89ab7009
resourceVersion: "458141437"
selfLink: /api/v1/persistentvolumes/pvc-3faef531-1def-4630-8f3e-c40d89ab7009
uid: ff6d4d44-e436-49bf-8898-c54e0a5a37a9
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 100Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: test-pv
namespace: default
resourceVersion: "458140618"
uid: 3faef531-1def-4630-8f3e-c40d89ab7009
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: failure-domain.beta.kubernetes.io/region
operator: In
values:
- bj
- key: failure-domain.beta.kubernetes.io/zone
operator: In
values:
- "800003"
persistentVolumeReclaimPolicy: Delete
qcloudCbs:
cbsDiskId: disk-mkrqzttv
storageClassName: cbs
volumeMode: Filesystem
status:
phase: Bound

Using

Pod 使用 PVC 作为卷,集群检查 PVC 以查找绑定的卷并为集群挂载该卷。对于支持多种访问模式的卷,用户指定在使用 PVC 作为容器中的卷时所需的模式。用户申请了 PVC ,并且该 PVC 是绑定的,则只要用户需要,绑定的 PV 就属于该用户。用户通过在 Pod 的 volume 配置中包含 persistentVolumeClaim 来调度 Pod 并访问用户声明的 PV。

PVC 必须与使用 PVC 的 Pod 存在于相同的命名空间中,集群在 Pod 的命名空间中查找 PVC,并使用它来获取支持声明的 PersistentVolume,该卷然后被挂载到主机的 Pod 上,下面的示例中 Pod 和 PVC 同处在 default 的命名空间中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kind: Pod
apiVersion: v1
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
volumeMounts:
- mountPath: "/var/www/html"
name: mypvc
volumes:
- name: mypvc
persistentVolumeClaim:
claimName: test-pv

Reclaiming

用户用完 volume 后,可以从允许回收资源的 API 中删除 PVC 对象。PersistentVolume 的回收策略告诉集群在存储卷声明释放后应如何处理该卷。目前,volume 的处理策略有保留、回收或删除。