六、存储
现在来了解容器是如何访问外部磁盘存储的,以及如何在它们之间共享存储空间。容器中的文件在磁盘上是临时存放的,这给容器中运行的较重要的应用程序带来一些问题:
- 当容器崩溃时文件丢失。kubelet会重新启动容器,但容器会以干净的状态重启。当容器被重建时,我们可能希望新的容器可以在之前容器结束的位置继续运行,比如在物理机上重启进程。可能不需要(或者不想要)整个文件系统被持久化, 但又希望能保存实际数据的目录。
- 同一Pod中运行多个容器时,我们可能希望容器之间能够共享文件,但容器本身是彼此隔离的。
Kubernetes 卷(Volume)这一抽象概念能够解决这两个问题。它们不像pod这样的*资源,而是被定义为pod的一部分,并和pod共享相同的生命周期(临时卷)。这意味着在pod启动时创建卷,并在删除pod时销毁卷。因此,在容器重新启动期间,卷的内容将保持不变,在重新启动容器之后,新容器可以识别前一个容器写入卷的所有文件。针对第二个问题,如果一个pod包含多个容器,那这个卷可以同时被所有的容器使用。
1 卷介绍
Kubernetes的卷是pod的一个组成部分,因此像容器一样在pod的规范中就定义了。它们不是独立的Kubernetes对象,也不能单独创建或删除。pod中的所有容器都可以使用卷,但必须先将它挂载在每个需要访问它的容器中,在每个容器中,都可以在其文件系统的任意位置挂载卷。
有多种卷类型可供选择。其中一些是通用的, 而另一些则相对于当前常用的存储技术有较大差别。
- awsElasticBlockStore
- azureDisk
- azureFile
- cephfs
- cinder
- configMap
- downwardAPI
- emptyDir
- fc (光纤通道)
- flocker (已弃用)
- gcePersistentDisk
- gitRepo (已弃用)
- glusterfs
- hostPath
- iscsi
- local
- nfs
- persistentVolumeClaim
- portworxVolume
- projected
- quobyte (已弃用)
- rbd
- secret
- storageOS (已弃用)
- vsphereVolume
这些卷类型有各种用途,它们的使用也大同小异,因此这里主要介绍一些常用的卷。
2 常用的卷
2.1 emptyDir
最简单的卷类型是emptyDir
卷。当 Pod 分派到某个 Node 上时,emptyDir
卷会被创建,并且 Pod 在该节点上运行期间,卷一直存在。 就像其名称表示的那样,卷最初是空的。 尽管 Pod 中的容器挂载 emptyDir
卷的路径可能相同也可能不同,这些容器都可以读写 emptyDir
卷中相同的文件。 当 Pod 因为某些原因被从节点上删除时,emptyDir
卷中的数据也会被永久删除。
容器崩溃并不会导致 Pod 被从节点上移除,因此容器崩溃期间
emptyDir
卷中的数据是安全的。
下面是一个pod中使用emptyDir
卷的资源清单:
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
通过volumes
参数,创建一个名为cache-volume
的空卷,挂载到容器的/cache
目录下。
下面通过实例演示一下,我们在pod中创建两个容器,一个容器使用Nginx作为Web服务器代理HTML页面,另一个容器使用脚本来生成HTML内容。
首先创建一个名为fortune-pod.yaml
的资源清单:
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- name: html-generator # 第一个容器名为html-generator
image: luksa/fortune # 运行luksa/fortune镜像
volumeMounts:
- mountPath: /var/htdocs # 挂载到容器的该路径下
name: html # 名为html的卷
- name: web-server # 第二个容器名为web-server
image: nginx:alpine # 运行nginx:alpine镜像
volumeMounts:
- mountPath: /usr/share/nginx/html # 挂载到容器的该路径下
name: html # 名为html的卷
readOnly: true # 只读
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {}
pod包含两个容器和一个挂载在两个容器中的共用的卷,但在不同的路径上。当 html-generator
容器启动时,它每10秒启动一次fortune
命令输出到/var/htdocs/index.html
文件,index.html
文件被写入卷中。一旦web-server
容器启动,它就开始为/usr/share/nginx/html
目录中的任意HTML文件提供服务(这是Nginx服务的默认服务文件目录)。最终的效果是,一个客户端向pod上的80端口发送一个HTTP请求,将接收当前的fortune消息作为响应。
现在创建该pod
kubectl apply -f fortune-pod.yaml
然后查看pod的运行状态
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
fortune 2/2 Running 0 6m48s 10.244.1.2 k8s-node-1 <none> <none>
可以看到运行成功,该pod运行在node-1节点上,且IP为10.244.1.2
,我们尝试访问它:
curl 10.244.1.2:80
You teach best what you most need to learn.
curl 10.244.1.2:80
You can do very well in speculation where land or anything to do with dirt is concerned.
每隔几秒发送一个请求,会接收到不同的信息。说明两个容器之间通过卷共享了index.html
文件。
同样,也可以进入到nginx容器内部查看这个html文件:
kubectl exec -it fortune -c web-server -- /bin/sh
进入到/usr/share/nginx/html
目录下:
cd /usr/share/nginx/html
cat index.html
同样可以看到该文件的变化:
并且该文件是只读的:
emptyDir
的一些应用场景:
- 缓存空间,例如基于磁盘的归并排序。
- 为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。
- 在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。
2.2 hostPath
大多数pod应该忽略它们的主机节点,因此它们不应该访问节点文件系统上的任何文件。但是某些系统级别的pod( 这些通常由DaemonSet 管理)确实需要读取节点的文件或使用节点文件系统来访问节点设备。Kubernetes通过hostPath卷实现了这一点。
警告:
HostPath 卷存在许多安全风险,最佳做法是尽可能避免使用 HostPath。 当必须使用 HostPath 卷时,它的范围应仅限于所需的文件或目录,并以只读方式挂载。
如果通过 AdmissionPolicy 限制 HostPath 对特定目录的访问, 则必须要求
volumeMounts
使用readOnly
挂载以使策略生效。
hostPath卷指向节点文件系统上的特定文件或目录,同一个节点上运行并在其hostPath卷中使用相同路径的pod可以看到相同的文件。
通过hostPath,可以运行一个需要访问Docker内部的容器;使用 hostPath
挂载 /var/lib/docker
路径。
hostPath也是我们介绍的第一种类型的持久性存储,因为emptyDir卷的内容会在pod被删除时被删除,而hostPath卷的内容则不会被删除。如果删除了一个pod,并且下一个pod使用了指向主机上相同路径的hostPath卷,则新pod将会发现上一个pod留下的数据,但前提是必须将其调度到与第一个pod相同的节点上。
除了必需的 path
属性之外,用户可以选择性地为 hostPath
卷指定 type
。
支持的 type
值如下:
取值 | 行为 |
---|---|
空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。 | |
DirectoryOrCreate |
如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。 |
Directory |
在给定路径上必须存在的目录。 |
FileOrCreate |
如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。 |
File |
在给定路径上必须存在的文件。 |
Socket |
在给定路径上必须存在的 UNIX 套接字。 |
CharDevice |
在给定路径上必须存在的字符设备。 |
BlockDevice |
在给定路径上必须存在的块设备。 |
当使用这种类型的卷时要小心,因为:
- HostPath 卷可能会暴露特权系统凭据(例如 Kubelet)或特权 API(例如容器运行时套接字), 可用于容器逃逸或攻击集群的其他部分。
- 具有相同配置(例如基于同一 PodTemplate 创建)的多个 Pod 会由于节点上文件的不同而在不同节点上有不同的行为。
- 下层主机上创建的文件或目录只能由 root 用户写入。你需要在 特权容器 中以 root 身份运行进程,或者修改主机上的文件权限以便容器能够写入
hostPath
卷。
下面是一个hostPath的资源清单配置:
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:
# 宿主上目录位置
path: /data
# 此字段为可选
type: Directory
hostPath的特点在于,只要节点可以挂载任何存储,那么节点上的pod就可以使用该存储,因此它具有很强的灵活性,可以与很多远程存储对接。
3 持久卷
当运行在一个pod中的应用程序需要将数据保存到磁盘上,并且即使该pod重新调度到另一个节点时也要求具有相同的数据可用。这就不能使用到目前为止我们提到的任何卷类型,由于这些数据需要可以从任何集群节点访问,因此必须将其存储在某种类型的网络存储(NAS) 中。
持久卷(PersistentVolume,PV)是集群中的一块存储,由管理员事先供应,或者 使用存储类(Storage Class)来动态供应。持久卷是集群资源,就像节点也是集群资源一样。
持久卷声明(PersistentVolumeClaim,PVC)表达的是用户对存储的请求。概念上与Pod类似。Pod会耗用节点资源,而PVC申领会耗用PV资源。Pod可以请求特定数量的资源(CPU和内存);同样PVC申领也可以请求特定的大小和访问模式 (例如,可以要求PV卷能够以ReadWriteOnce、ReadOnlyMany或ReadWriteMany模式之一来挂载。
举个例子:首先由集群管理员设置底层存储,然后通过Kubernetes API服务器创建持久卷并注册。在创建持久卷时,管理员可以指定其大小和所支持的访问模式。当集群用户需要在其pod中使用持久化存储时,他们首先创建持久卷声明清单,指定所需要的最低容量要求和访问模式,然后用户将持久卷声明清单提交给Kubernetes API服务器,Kubernetes将找到可匹配的待久卷并将其绑定到持久卷声明。整个流程如图所示:
一旦绑定关系建立,则PVC绑定就是排他性的,无论该PVC是如何与PV建立的绑定关系。PVC与PV之间的绑定是一种一对一的映射。
注意,由于持久卷是集群资源,持久卷不属于任何命名空间。如下图所示:
3.1 持久卷的类型
PV 持久卷是用插件的形式来实现的。Kubernetes 目前支持以下插件(参考官方文档):
-
awsElasticBlockStore
- AWS 弹性块存储(EBS) -
azureDisk
- Azure Disk -
azureFile
- Azure File -
cephfs
- CephFS volume -
csi
- 容器存储接口 (CSI) -
fc
- Fibre Channel (FC) 存储 -
flexVolume
- FlexVolume -
gcePersistentDisk
- GCE 持久化盘 -
glusterfs
- Glusterfs 卷 -
hostPath
- HostPath 卷 (仅供单节点测试使用;不适用于多节点集群; 请尝试使用local
卷作为替代) -
iscsi
- iSCSI (SCSI over IP) 存储 -
local
- 节点上挂载的本地存储设备 -
nfs
- 网络文件系统 (NFS) 存储 -
portworxVolume
- Portworx 卷 -
rbd
- Rados 块设备 (RBD) 卷 -
vsphereVolume
- vSphere VMDK 卷
以下的持久卷已被弃用。这意味着当前仍是支持的,但是 Kubernetes 将来的发行版会将其移除。
-
cinder
- Cinder(OpenStack 块存储)(于 v1.18 弃用) -
flocker
- Flocker 存储(于 v1.22 弃用) -
quobyte
- Quobyte 卷 (于 v1.22 弃用) -
storageos
- StorageOS 卷(于 v1.22 弃用)
旧版本的 Kubernetes 仍支持这些“树内(In-Tree)”持久卷类型:
-
photonPersistentDisk
- Photon 控制器持久化盘。(v1.15 之后 不可用) -
scaleIO
- ScaleIO 卷(v1.21 之后 不可用)
3.2 持久卷的创建
下面是一个是NFS类型的PV资源清单示例:
apiVersion: v1
kind: PersistentVolume # 类型为PV资源
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi # 容量为5G
volumeMode: Filesystem
accessModes: # 访问模式
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle # 回收策略
storageClassName: slow # 存储类
mountOptions:
- hard
- nfsvers=4.1
nfs: # nfs服务
path: /tmp
server: 172.17.0.2
3.3 访问模式
通过accessModes
参数设置访问模式。PV可以用资源提供者所支持的任何方式挂载到宿主系统上。 如下表所示,每个不同类型的卷插件所支持的模式不同。
卷插件 | ReadWriteOnce | ReadOnlyMany | ReadWriteMany | ReadWriteOncePod |
---|---|---|---|---|
AWSElasticBlockStore | ✓ | - | - | - |
AzureFile | ✓ | ✓ | ✓ | - |
AzureDisk | ✓ | - | - | - |
CephFS | ✓ | ✓ | ✓ | - |
Cinder | ✓ | - | - | - |
CSI | 取决于驱动 | 取决于驱动 | 取决于驱动 | 取决于驱动 |
FC | ✓ | ✓ | - | - |
FlexVolume | ✓ | ✓ | 取决于驱动 | - |
Flocker | ✓ | - | - | - |
GCEPersistentDisk | ✓ | ✓ | - | - |
Glusterfs | ✓ | ✓ | ✓ | - |
HostPath | ✓ | - | - | - |
iSCSI | ✓ | ✓ | - | - |
Quobyte | ✓ | ✓ | ✓ | - |
NFS | ✓ | ✓ | ✓ | - |
RBD | ✓ | ✓ | - | - |
VsphereVolume | ✓ | - | - (Pod 运行于同一节点上时可行) | - |
PortworxVolume | ✓ | - | ✓ | - |
StorageOS | ✓ | - | - | - |
访问模式有:
-
ReadWriteOnce
卷可以被一个节点以读写方式挂载。 ReadWriteOnce 访问模式也允许运行在同一节点上的多个 Pod 访问卷。
-
ReadOnlyMany
卷可以被多个节点以只读方式挂载。
-
ReadWriteMany
卷可以被多个节点以读写方式挂载。
-
ReadWriteOncePod
卷可以被单个 Pod 以读写方式挂载。 如果你想确保整个集群中只有一个 Pod 可以读取或写入该 PVC, 请使用ReadWriteOncePod 访问模式。这只支持 CSI 卷以及需要 Kubernetes 1.22 以上版本。
3.4 回收策略
通过persistentVolumeReclaimPolicy
参数设置回收策略。目前的回收策略有:
- Retain -- 手动回收
- Recycle -- 基本擦除 (
rm -rf /thevolume/*
) - Delete -- 诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除
目前,仅 NFS 和 HostPath 支持回收(Recycle)。 AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除(Delete)。
3.5 阶段
每个卷会处于以下阶段(Phase)之一:
- Available(可用)-- 卷是一个空闲资源,尚未绑定到任何声明上去;
- Bound(已绑定)-- 该卷已经绑定到某声明;
- Released(已释放)-- 所绑定的声明已被删除,但是资源尚未被集群回收;
- Failed(失败)-- 卷的自动回收操作失败。
命令行能够显示绑定到某 PV 卷的 PVC 对象名称。