Union File System(联合文件系统,UnionFS)是一种轻量级的高性能分层文件系统,它支持将文件系统中的修改信息作为一次提交,并层层叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,应用看到的是挂载的最终结果。联合文件系统将多个文件系统层以一种有效的方式组合在一起,形成了一个单一的可读写的文件系统。联合文件系统是实现Docker镜像的技术基础。
联合文件系统概述
联合文件系统是一种轻量级的高性能分层文件系统,它能够将多个文件系统联合到一个联合挂载点。联合文件系统的主要特点包括:
分层管理:Docker镜像是一种分层结构,这样每一层构建在其他层之上,从而实现增量增加内容的功能。当用户改变了一个Docker镜像(如升级程序到新的版本),则会创建一个新的层(layer)。用户不用替换整个原镜像或者重新建立,只需要添加新层即可。
只读与读写层分离:在联合文件系统中,通常存在一个或多个只读层和一个读写层。只读层保存了基础镜像或文件系统,而读写层则用于保存容器运行时的修改。
写时复制:联合文件系统采用写时复制策略,当对联合文件系统进行修改时,会先复制只读层中的相应文件到读写层,然后在读写层上进行修改。这样,只读层始终保持不变,而读写层则记录了所有修改。
联合挂载:联合文件系统能够将多个文件系统联合到一个挂载点,使得这些文件系统看起来像一个整体。
在Docker中,联合文件系统是实现容器镜像和容器之间共享与隔离机制的关键。Docker镜像是只读模板,用于创建容器。每个镜像由多个只读层组成,这些层通过联合文件系统联合在一起,使得容器可以在镜像的基础上运行,并保存运行时的修改。当创建容器时,Docker会在镜像的基础上添加一个读写层,用于保存容器运行时的修改。这样,容器就拥有了一个独立的运行环境,同时保持了与镜像的共享。
但是联合文件系统也有以下的不足:
不同的文件系统对于文件属性、大小、名称和字符等都有不同的规则。联合文件系统经常需要在不同的文件系统规则之间进行转换。在情况好的时候,它们能够提供可接受的转换。在不好的时候,某些功能将无法工作。如btrfs和OverlayFS虽然提供了扩展属性,但会导致SELinux无法工作。
联合文件系统使用一种称为写时复制的模式,这使得内存映射文件(mmap()的系统调用)的实现比较苦难。一些Union文件系统提供了在适当的条件下的某种实现方式,但其实应尽量避免镜像中使用内存映射文件。
层数的限制。联合文件系统可能有一个层数的限制。这个层数取决于联合文件系统的实现。同时,随着层数的增加,会带来读写性能的降低。
分层
Docker镜像自身就是由多个文件层组成,每一层有基于内容的唯一的编号(层ID)。可以通过docker history查看一个镜像由哪些层组成。Docker镜像可以通过分层来实现继承。如,用户基于基础镜像(用来生成其他镜像的基础,往往没有父镜像)来制作各种不同的应用镜像。这些应用镜像共享同一个基础镜像层,提高了存储效率。
Docker镜像是一种分层结构,这样每一层构建在其他层之上,从而实现增量增加内容的功能,Docker镜像下载的时候也是分层下载,用户分发镜像的时候,也只需要分发被改动的新层内容(增量部分)。当用户改变了一个Docker镜像(如升级程序到新的版本),则会创建一个新的层(layer)。因此,用户不用替换整个原镜像或者重新建立,只需要添加新层即可。
可以将镜像的分层划分为如下层级:
上图中,bootfs是指操作系统启动时使用的文件系统,**通常包含操作系统内核、启动加载程序和其他引导文件。**操作系统刚启动时会加载bootfs文件系统。这一层包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
rootfs则是指操作系统的根文件系统,是系统的主要文件系统,包含所有的文件和目录,如Linux系统中的/dev,/bin,/etc等标准目录和文件。rootfs是一个虚拟的文件系统,它由内核挂载并提供给用户空间。在Linux系统启动时,内核会将rootfs挂载为基础文件系统,并将其作为所有其他文件系统的基础。可以将rootfs看做不同操作系统的发行版,如Linux、Ubuntu、Centos等。
middle image是指中间镜像。可以有零到多个中间镜像。中间镜像主要包含一些公共能力。如os、vim、gcc、jre等。中间镜像通常用作基础镜像,被其他镜像继承使用。
top image是应用镜像。可以有零到多个应用镜像。应用镜像继承了基础镜像,并在此基础上进行扩展。应用镜像包含了对业务的定制操作,一般不会用作基础镜像。
Docker的联合文件系统通过分层的方式实现了对文件和目录的管理。这种分层结构具有如下优点:
(1)提高了存储效率。如果想安装任意数量的镜像,它们都依赖于公共层,即公共层以及它的所有父层,都只需被下载或安装一次。这意味着,只要安装一个程序,不用保存冗余文件或者下载冗余分层。
(2)更好的实现依赖管理和隔离。分层结构可以帮助更好地实现依赖管理,如通过继承基础镜像来服用基础镜像的能力。分层结构确保各层之间隔离,层与层之间的操作不会相互影响。
(3) 简化专业软件的构建。只要在某个基础镜像上做些细微的变化就可以构建专业的软件。因此提供专门的镜像,可帮助用户通过继承基础镜像,作最小的定制。
读写控制
为了保证镜像的复用,Docker限制镜像是只读的。同时,为了满足用户的读写请求,在容器启动时,Docker会在最顶层添加一个可写层,这个是容器层。所有对容器文件系统的更改都会在这个层上进行。当需要修改某个文件时,Docker采用写时复制的策略。这意味着在文件被修改之前,系统会创建一个该文件的副本,确保在原始文件系统上的文件不会被修改。这有助于保持原始文件系统的完整性。容器运行时,Docker文件系统的层次划分如下:
当容器运行时,所有对文件系统的修改都会发生在可读写的容器层上。这意味着,即使多个容器共享同一个镜像,它们之间的修改也不会相互干扰。同时,由于只读层保持不变,Docker可以通过共享只读层来节省存储空间。当容器被删除时,其容器层会被销毁,而只读层则保持不变。这意味着,其他基于该镜像创建的容器仍然可以继续运行。此外,由于镜像中的只读层不会保存容器的修改,因此可以通过重新创建容器来恢复到初始状态。如果需要保存容器的状态,还需通过将状态持久化到可持久化存储介质,并在容器重新创建时加载状态来实现。
所有对容器的改动,无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的,这样就保证了镜像的不变性,使得镜像可以被多个容器共享。Docker通过写时复制技术来实现文件的修改。当需要修改某个文件时,从上往下查找,找到后Docker会在容器层创建一个该文件的副本,确保在原始文件系统上的文件不会被修改。这有助于保持原始文件系统的完整性;当需要读取某个文件时,从最上层的镜像层开始往下找,找到后读取到内存中,若已经在内存中,可以直接使用。换句话说,运行在同一台机器上的Docker容器共享运行时相同的文件;当需要删除某个文件时,从上往下查找,找到后在容器中记录删除,并不是真正的删除,而是软删除。这导致镜像体积只会增加,不会减少;当需要增加某个文件时,直接在最上层的容器可写层增加,不会影响镜像层。
联合挂载
联合文件系统在主机上使用分层结构,但最终呈现给用户的则是一个普通单层的文件系统,这里把多层以单一层的方式呈现出来的过程叫作联合挂载。具体来说,Docker使用了联合挂载技术将这些只读层和可写层合并在一起,呈现给用户一个统一的文件系统视图。这种技术允许将多个目录(即层)挂载到同一个虚拟目录下,形成一个联合的文件系统。对用户和应用程序而言,联合文件系统提供了一个透明的接口,使得多个文件系统的内容看起来像是一个单一的文件系统。用户无需关心底层的层叠结构,可以直接对文件进行读取和写入操作。用户可以在容器中访问所有的文件和目录,但实际上这些文件和目录可能分布在不同的层上。当访问一个文件时,Docker会按照从顶层到底层的顺序逐层查找,直到找到该文件为止。联合挂载的文件层次结构如下:
选型
Docker通过插件化方式支持多种联合文件系统。在使用时,需要根据自身的业务诉求,选择合适的联合文件系统实现。常见的联合文件系统实现有:AUFS、OverlayFS、Overlay2、ZFS、Btrfs、VFS和Device Mapper等。多种文件系统目前的支持情况总结如下:
(1) AUFS:最早支持的文件系统,对Debian/Ubuntu支持好,虽然没有合并到Linux内核中,但成熟度很高;
(2) OverlayFS:类似于AUFS的层次化文件系统,性能更好,从Linux3.18开始已经合并到内核,但成熟度有待提高;
(3) Overlay2:Docker1.12后推出,原生支持128层,效率比OverlayFS高,要求Linux内核大于4.0;
(4) ZFS:最初设计为Solaris 10上的写时文件系统,拥有不少好的特性,但对Linux支持还不够成熟;
(5) Btrfs:参考ZFS等特性设计的文件系统,由Linux社区开发,试图取代Device Mapper,成熟度有待提高;
(6) VFS:基于普通文件系统(ext、nfs等)的中间层抽象,性能差,比较占用空间,成熟度一般;
(7) Device Mapper: RedHat公司和Docker团队一起开发用于支持RHEL的文件系统,内核支持,性能略慢,成熟度高;
目前,Docker推荐使用的联合文件系统是Overlay2。在可能的情况下,推荐使用Overlay2。对于所有当前支持的Linux发行版,Overlay2存储驱动是首选。CentOS和RHEL的最新版本现在已经支持overlay2存储驱动,并将overlay2作为推荐的存储驱动。对于Docker 18.06或更早的版本,AUFS是首选。
存储
所有的镜像和容器都存储都在Docker指定的存储目录下,以Ubuntu操作系统为例,默认路径是/var/lib/docker。在这个目录下面,存储由Docker镜像和容器运行相关的文件和目录,可能包括builder、containerd、containers、image、network、aufs/overlay2、plugins、runtimes、tmp、trust、volumes等。以联合文件系统是AUFS为例,则最关键的就是aufs目录,保存Docker镜像和容器相关数据和信息。包括layers、 diff和mnt三个子目录。layers子目录包含层属性文件,用来保存各个镜像层的元数据:某镜像的某层下面包括哪些层。diff子目录包含层内容子目录,用来保存所有镜像层的内容数据。mnt子目录下面的子目录是各个容器最终的挂载点,所有相关的AUFS层在这里挂载到一起,形成最终效果。一个运行中容器的根文件系统就挂载在这下面的子目录上。
参考
《Docker技术入门与实战》 杨保华 戴王剑 曹亚仑 著
《Docker实战》 Jeff Nickoloff 著, 胡震,杨润青 黄帅 译
https://cloud.baidu.com/article/3214307 深入解读Docker的Union File System技术
https://blog.csdn.net/qq_39637333/article/details/130411935 Docker存储与DockerFile
https://blog.csdn.net/wpc2018/article/details/121578725 镜像原理之联合文件系统和分层理解
https://docs.docker.com/storage/storagedriver/select-storage-driver/ Docker storage drivers
https://cloud.tencent.com/developer/article/1843958 镜像原理之联合文件系统
https://cloud.tencent.com/developer/article/2367936 Docker的联合文件系统
https://blog.csdn.net/qq_43380180/article/details/125953218 联合文件系统(UnionFS)
https://www.cnblogs.com/FengZeng666/p/14173791.html Docker底层:联合文件系统
https://www.cnblogs.com/v-fan/p/14453223.html docker文件系统分层存储原理
https://blog.csdn.net/baidu_41388533/article/details/108543514 镜像原理之联合文件系统、分层理解、commit 镜像
https://juejin.cn/post/6988029332476133406 docker|容器技术基础之联合文件系统OverlayFS
https://zhuanlan.zhihu.com/p/47683490 Docker 镜像分层机制与 AUFS
https://www.zhihu.com/tardis/zm/art/146703018 Docker文件系统实战
https://www.cnblogs.com/wade-luffy/p/6589254.html Docker核心实现技术(命名空间&控制组&联合文件系统&Linux网络虚拟化支持)
https://blog.csdn.net/qq_51545656/article/details/135574609 Docker 镜像的详解及创建
https://zhuanlan.zhihu.com/p/679328995 探索 UnionFS 与 OverlayFS