1. Volume简介
Container 中的文件在磁盘上是临时存放的,这会带来两个问题:1)当容器崩溃时文件会丢失;2)同一Pod中运行多个容器并共享文件时,容器重启时状态丢失。Kubernetes 卷(Volume) 这一抽象概念能够解决这两个问题。
Docker卷的本质是目录,存在与一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过“联合文件系统”提供一些用于持续存储或共享数据的特性。
K8S卷的本质也是目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。
Kubernetes中的卷分为两类:临时卷和持久卷。临时卷会遵从 Pod 的生命周期,和Pod一起创建和删除;持久卷的生命周期独立于pod的生命周期。
Kubernetes中常用的卷类型包括:
- configMap:临时卷/投射卷,常用来存储配置数据
- secret:临时卷/投射卷,常用来存储配置数据,使用base64加密数据
- downwardAPI:临时卷/投射卷,让 Pod 里的容器能够直接获取到这个 Pod API 对象本身的信息。
- emptyDir:临时卷,存储pod中的数据,pod删除时数据会被删除
- hostPath:持久卷,将主机节点上的文件或目录挂载到pod中(存在许多安全风险,尽量避免使用)
- local:持久卷,创建pv时指定pv所在节点,使用pvc的pod会被调度到指定节点,将主机节点上的文件或目录挂载到pod中
- nfs:持久卷,将 NFS (网络文件系统) 挂载到的pod中。
- persistentVolumeClaim:持久卷申领,不需要知道持久卷细节。
- projected:投射卷能将多个卷来源映射到同一目录上。
持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class)来动态制备。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。
持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参见访问模式)。
PVC与PV是一一对应关系,不能一个PVC挂载多个PV,也不能一个PV挂载多个PVC。
参考文档:
- K8S官方文档 - 存储
- K8S官方文档 - 卷
- K8S官方文档 - 持久卷
- K8S官方文档 - 临时卷
- K8S官方文档 - 投射卷
- K8S官方文档 - Downward API
- 阿里云ACK - 存储基础知识
- 《K8S中安装配置StorageClass》
2. ConfigMap/Secret为什么是临时卷?
临时卷会遵从 Pod 的生命周期,和Pod一起创建和删除。
ConfigMap本身是一个API对象,用来将非机密性的数据保存到键值对中。使用时,Pods可以将其用作环境变量、命令行参数或者存储卷中的配置文件。
当容器挂载 ConfigMap 时,ConfigMap的信息会被节点本地临时存储,然后挂载到容器的文件系统中。当Pod删除时,容器的文件系统没了,本地临时存储中的ConfigMap信息也没了,因此ConfigMap是临时卷。
Secret同理。
其实,如果说ConfigMap/Secret是持久卷,也没有什么问题,因为ConfigMap/Secret不会随着Pod删除而删除。
不用太纠结于这种概念性的东西,了解即可。
3. 使用configmap
我们有四种方式来使用 ConfigMap 配置 Pod 中的容器:
- 容器的环境变量
- 在容器命令和参数内
- 在只读卷里面添加一个文件,让应用来读取
- 编写代码在 Pod 中运行,使用 Kubernetes API 来读取 ConfigMap
参考文档:
3.1. 生成configmap
3.1.1. 基于文字生成configmap
1 | kubectl create cm game-demo \ |
3.1.2. 基于环境变量配置文件生成configmap
1、准备环境变量配置文件 env.sh
1 | sleep_seconds=3600 |
2、生成config文件
1 | kubectl create cm game-demo \ |
3.1.3. 基于文件生成configmap
1、准备配置文件 game.properties 和 user-interface.properties
1 | enemy.types=aliens,monsters |
1 | color.good=purple |
2、生成configmap文件
1 | kubectl create cm game-demo \ |
3.2. pod中使用configmap简单示例
1、configmap.yaml
1 | apiVersion: v1 |
2、pod.yaml
1 | apiVersion: v1 |
ConfigMap 不会区分单行属性值和多行类似文件的值,重要的是 Pods 和其他对象如何使用这些值。
上面的例子定义了一个卷并将它作为 /config 文件夹挂载到 demo 容器内, 创建两个文件,/config/game.properties 和 /config/user-interface.properties, 尽管 ConfigMap 中包含了四个键。 这是因为 Pod 定义中在 volumes 节指定了一个 items 数组。 如果你完全忽略 items 数组,则 ConfigMap 中的每个键都会变成一个与该键同名的文件, 因此你会得到四个文件。
PS:使用 envFrom 可以将 ConfigMap 的所有数据定义为容器的环境变量。
4. 使用secret
Pod 可以用三种方式之一来使用 Secret:
- 作为容器的环境变量。
- 作为挂载到一个或多个容器上的卷 中的文件。
- 由 kubelet 在为 Pod 拉取镜像时使用。
创建 Secret 时,我们可以使用 Secret 资源的 type 字段,或者与其等价的 kubectl 命令行参数(如果有的话)为其设置类型。 Secret 类型有助于对 Secret 数据进行编程处理。
Kubernetes 提供若干种内置的类型,用于一些常见的使用场景。 针对这些类型,Kubernetes 所执行的合法性检查操作以及对其所实施的限制各不相同。
如果 type 值为空字符串,则被视为 Opaque 类型(用户定义的任意数据)。
Secret使用base64加密,可以使用echo "YWRtaW4=" | base64 -d
命令进行解密。
参考文档:
4.1. 生成secret
4.1.1. 基于文字生成secret
1 | kubectl create secret generic mysecret \ |
4.1.2. 基于环境变量配置文件生成secret
1、准备环境变量配置文件 env.sh
1 | username=admin |
2、生成secret
1 | kubectl create secret generic mysecret \ |
4.1.3. 基于文件生成secret
1 | kubectl create secret generic mysecret \ |
4.2. pod中使用secret简单示例
1、secret.yaml
1 | apiVersion: v1 |
2、pod.yaml
1 | apiVersion: v1 |
容器中看到的环境变量,和文件中的内容,都是明文。
PS:使用 envFrom 可以将 Secret 的所有数据定义为容器的环境变量。
4.3. imagePullSecrets
参考文档 《K8S配置使用imagePullSecrets》
5. 使用emptyDir
参考文档K8S官方文档 - 卷 - emptyDir configuration example
1 | apiVersion: v1 |
6. 使用hostPath
参考文档K8S官方文档 - 卷 - hostPath configuration example
1 | apiVersion: v1 |
7. 使用nfs
7.1. 直接使用nfs
1 | apiVersion: v1 |
7.2. nfs作为pv
参考文档:
1、pv.yaml
1 | apiVersion: v1 |
需要注意的是,persistentVolumeReclaimPolicy使用的Retain,当pvc删除后,真实存储设备上的数据依然存在,pv不能被其他pvc申领;当pv删除后,真实存储设备上的数据依然存在。
如果使用Delete,那么当pvc删除后,pv也会被删除,真实存储设备上的数据也会被删除。pv自动删除可能会删除失败,需要手动删除,手动删除pv后,真实存储设备上的数据依然存在。
如果使用Recycle,那么当pvc删除后,真实存储设备上的数据会被删除(rm -rf /thevolume/*
),pv可以被新的pvc申领;如果只删除pv,真实存储设备上的数据不会被删除。
回收策略Recycle 已被废弃。取而代之的建议方案是使用动态制备。
2、pvc.yaml
1 | apiVersion: v1 |
3、pod.yaml
1 | apiVersion: v1 |
8. 使用storageclass
9. subPath说明
有时,在单个 Pod 中共享卷以供多方使用是很有用的。 volumeMounts.subPath 属性可用于指定所引用的卷内的子路径,而不是其根路径。
例如:
1 | apiVersion: v1 |
上面的例子中,Pod 使用同一共享卷。其中 PHP 应用的代码和相关数据映射到卷的 html 文件夹,MySQL 数据库存储在卷的 mysql 文件夹。
使用 subPathExpr 字段可以基于 downward API 环境变量来构造 subPath 目录名。 subPath 和 subPathExpr 属性是互斥的。
例如:
1 | apiVersion: v1 |
在这个示例中,Pod 使用 subPathExpr 在 hostPath 卷 /var/log/pods 中创建目录 pod1。 hostPath 卷采用来自 downwardAPI 的 Pod 名称生成目录名。 宿主目录 /var/log/pods/pod1 被挂载到容器的 /logs 中。
如果我们想要挂在一个configmap或者secret配置文件到pod,但是不想覆盖pod中的文件所在目录,这时也需要使用subPath。
1 | spec: |
参数说明:
- volumes.configMap.items.key 表示configmap中的data key
- volumes.configMap.items.path 表示要将key映射到的文件的相对路径(相对于volume)
- containers.volumeMounts.mountPath 表示volume在容器中的挂载路径
- containers.volumeMounts.subPath 表示volume的子路径
本例中,configmap作为volume,可以认为是根目录/
,key nginx.conf 被映射为volume的相对路径nginx.conf
,绝对路径就是 /nginx.conf
。
容器中挂载volume,使用volume的子路径nginx.conf
(绝对路径/nginx.conf
),挂载到容器中的/etc/nginx/nginx.conf
。
10. Mount Propagation
Mount Propagation(挂载卷的传播)允许将容器安装的卷共享到同一 Pod 中的其他容器,甚至共享到同一节点上的其他 Pod。
卷的挂载传播特性由 containers[*].volumeMounts
中的 mountPropagation
字段控制。 它的值包括:
- None - 此卷挂载将不会感知到主机后续在此卷或其任何子目录上执行的挂载变化。这是默认模式。该模式等同于 mount(8) 中描述的 rprivate 挂载传播选项。
- HostToContainer - 此卷挂载将会感知到主机后续针对此卷或其任何子目录的挂载操作。换句话说,如果主机在此挂载卷中挂载任何内容,容器将能看到它被挂载在那里。该模式等同于 mount(8) 中描述的 rslave 挂载传播选项。
- Bidirectional - 这种卷挂载和 HostToContainer 挂载表现相同。另外,容器创建的卷挂载将被传播回至主机和使用同一卷的所有 Pod 的所有容器。该模式等同于 mount(8) 中描述的 rshared 挂载传播选项。
参考文档:Kubernetes Volumes - Mount propagation
Mount Propagation很重要的一个应用是Pod挂载点自动恢复。
例如,我们使用了JuiceFS CSI作为持久化驱动,当修改了JuiceFS的挂载配置后,需要重启CSI Pod和Mount Pod。而重启Mount Pod后,应用Pod的挂载点会失效,需要重启应用Pod。而如果配置了 mountPropagation: HostToContainer
,应用Pod的挂载点就能自动恢复,不需要重启。