概述
容器持久化,相比小伙伴都不陌生。通过 Docker 的 volume,我们可以非常方便的实现容器数据的持久化存储。但 volume 之下的文件系统,相比许多小伙伴并不是非常清楚。因而本文以 Docker 为例,重点讲述 Docker 底层所支持的三种文件系统。
首先在说清楚 Docker 文件系统的具体功能之前,我们需要先了解一下,什么叫做联合文件系统。
联合文件系统(Union File System,Unionfs)是一种分层的轻量级文件系统,它可以把多个目录内容联合挂载到同一目录下,从而形成一个单一的文件系统,这种特性可以让使用者像是使用一个目录一样使用联合文件系统。
对于 Docker 来说,联合文件系统可以说是其镜像和容器的基础。联文件系统可以使得 Docker 把镜像做成分层结构,从而使得镜像的每一层都可以被共享。从而节省大量的存储空间。
联合文件系统更多的是一种概念或者标准,真正实现联合文件系统才是关键,当前 Docker 中常见的联合文件系统有三种:AUDFS、Devicemapper 和 OverlayFS。
AUFS
AUFS 是如何存储文件的?
AUFS 是联合文件系统,意味着它在主机上使用多层目录存储,每一个目录在 AUFS 中都叫作分支,而在 Docker 中则称之为层(layer),但最终呈现给用户的则是一个普通单层的文件系统,我们把多层以单一层的方式呈现出来的过程叫作联合挂载。
每一个镜像层和容器层都是 /var/lib/docker 下的一个子目录,镜像层和容器层都在 aufs/diff 目录下,每一层的目录名称是镜像或容器的 ID 值,联合挂载点在 aufs/mnt 目录下,mnt 目录是真正的容器工作目录。
创建整个容器过程中,aufs 文件夹的变化:
当一个镜像未生成容器时:
- diff 文件夹:存储镜像内容,每一层都存储在镜像层 ID 命名的子文件夹中。
- layers 文件夹:存储镜像层关系的元数据,在 diff 文件夹下的每一个镜像层在这里都会有一个文件,文件的内容为该层镜像的父级镜像的 ID
- mnt 文件夹:联合挂载点目录,未生成容器时,该目录为空
当一个镜像生成容器后,AUFS 存储结构会发生如下变化:
- diff 文件夹:当容器运行时会在 difff 文件夹下生成容器层
- layers 文件夹:增加容器相关的元数据
- mnt 文件夹:容器的联合挂载点,这和容器中看到的文件内容一致
AUFS 如何工作?
- 读取文件:
- 文件在容器层中存在时:当文件存在于容器层时,直接从容器层读取。
- 当文件在容器层中不存在时:当容器运行时需要读取某个文件,如果容器层中不存在时,则从镜像层查找该文件,然后读取文件内容。
- 文件既存在于镜像层,又存在于容器层:当我们读取的文件既存在于镜像层,又存在于容器层时,将会从容器层读取该文件。
- 修改文件或者目录
- 第一次修改文件:当我们第一次在容器中修改某个文件时,AUFS 会触发写时复制操作,AUFS 首先从镜像层复制文件到容器层,然后再执行对应的修改操作。
- 删除文件或目录:当文件或目录被删除时,AUFS 并不会真正从镜像中删除它,因为镜像层是只读的,AUFS 会创建一个特殊的文件或文件夹,这种特殊的文件或文件夹会阻止容器的访
Devicemapper
什么是 Devicemapper ?
Devicemapper 是 Linux 内核提供的框架,从 Linux 内核 2.6.9 版本开始引入,Devicemapper 与 AUFS 不同,AUFS 是一种文件系统,而 Devicemapper 是一种映射块设备的技术框架。
Devicemapper 的工作机制主要围绕三个核心概念。
- 映射设备(mapped device):即对外提供的逻辑设备,它是由 Devicemapper 模拟的一个虚拟设备,并不是真正存在于宿主机上的物理设备。
- 目标设备(target device):目标设备是映射设备对应的物理设备或者物理设备的某一个逻辑分段,是真正存在于物理机上的设备。
- 映射表(map table):映射表记录了映射设备到目标设备的映射关系,它记录了映射设备在目标设备的起始地址、范围和目标设备的类型等变量。
映射设备通过映射表关联到具体的物理目标设备。事实上,映射设备不仅可以通过映射表关联到物理目标设备,也可以关联到虚拟目标设备,然后虚拟目标设备再通过映射表关联到物理目标设备。
Devicemapper 如何实现镜像分层与共享?
Devicemapper 使用专用的块设备实现镜像的存储,并且像 AUFS 一样使用了写时复制的技术来保障最大程度节省存储空间,所以 Devicemapper 的镜像分层也是依赖快照来是实现的。
Devicemapper 的每一镜像层都是其下一层的快照,最底层的镜像层是我们的瘦供给池,通过这种方式实现镜像分层有以下优点:
- 相同的镜像层,仅在磁盘上存储一次。例如,我有 10 个运行中的 busybox 容器,底层都使用了 busybox 镜像,那么 busybox 镜像只需要在磁盘上存储一次即可。
- 快照是写时复制策略的实现,也就是说,当我们需要对文件进行修改时,文件才会被复制到读写层。
- 相比对文件系统加锁的机制,Devicemapper 工作在块级别,因此可以实现同时修改和读写层中的多个块设备,比文件系统效率更高。
当我们需要读取数据时,如果数据存在底层快照中,则向底层快照查询数据并读取。当我们需要写数据时,则向瘦供给池动态申请存储空间生成读写层,然后把数据复制到读写层进行修改。Devicemapper 默认每次申请的大小是 64K 或者 64K 的倍数,因此每次新生成的读写层的大小都是 64K 或者 64K 的倍数。
OverlayFS
OverlayFS 的发展分为两个阶段。2014 年,OverlayFS 第一个版本被合并到 Linux 内核 3.18 版本中,此时的 OverlayFS 在 Docker 中被称为 overlay 文件驱动。由于第一版的 overlay 文件系统存在很多弊端(例如运行一段时间后 Docker 会报 "too many links problem" 的错误), Linux 内核在 4.0 版本对 overlay 做了很多必要的改进,此时的 OverlayFS 被称之为 overlay2。
overlay2 工作原理
overlay2 和 AUFS 类似,它将所有目录称之为层(layer),overlay2 的目录是镜像和容器分层的基础,而把这些层统一展现到同一的目录下的过程称为联合挂载(union mount)。overlay2 把目录的下一层叫作 lowerdir,上一层叫作 upperdir,联合挂载后的结果叫作 merged。
overlay2 如何读取、修改文件?
读取文件:
容器内进程读取文件分为以下三种情况。
- 文件在容器层中存在:当文件存在于容器层并且不存在于镜像层时,直接从容器层读取文件;
- 当文件在容器层中不存在:当容器中的进程需要读取某个文件时,如果容器层中不存在该文件,则从镜像层查找该文件,然后读取文件内容;
- 文件既存在于镜像层,又存在于容器层:当我们读取的文件既存在于镜像层,又存在于容器层时,将会从容器层读取该文件。
修改文件或目录:
overlay2 对文件的修改采用的是写时复制的工作机制,这种工作机制可以最大程度节省存储空间。具体的文件操作机制如下。
- 第一次修改文件:当我们第一次在容器中修改某个文件时,overlay2 会触发写时复制操作,overlay2 首先从镜像层复制文件到容器层,然后在容器层执行对应的文件修改操作。
- 删除文件或目录:当文件或目录被删除时,overlay2 并不会真正从镜像中删除它,因为镜像层是只读的,overlay2 会创建一个特殊的文件或目录,这种特殊的文件或目录会阻止容器的访问。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于