OverlayFS -- A Container File System

基本概念和特征

Docker容器最常用的文件系统是AUFS, 但随着Linux Kernel 3.18把Overlay纳入其中后,Overlay文件系统的地位就变得尤为重要。目前,Docker默认的存储文件系统是Overlay,相比于AUFS,其速度更快,实现也更为简单。

Overlay文件系统将不同的目录(底层目录和高层目录)划分为不同的层次,并通过联合挂载过程将数个高、底层目录合并成一个统一的目录,即统一合并视图层。如下图所示,在Docker与Overlay文件系统的分层图中,只读镜像层是底层目录,读写容器层是高层目录,而容器运行实际的应用层是统一合并视图层。

overlayarch

在Overlay文件系统中,用户只能看到存在于统一合并视图层中的目录和文件,但这些目录和文件既可能来自于高层目录,也可能来自于底层目录。因此,当用户查看统一合并视图层的目录或文件时,用户会感觉到上下层同名目录合并和上下层同名文件覆盖:

  1. 上下层同名目录合并:如果某个目录并不是仅来自单独的一层(仅高层目录或底层目录),它会逐级遍历(从高层目录到底层目录,再到更低的底层目录)扫描所有层中的同名目录,然后将同名目录中的内容合并后,返回给用户唯一一个包含全部内容的同名目录。

  2. 上下层同名文件覆盖:如果在遍历扫描过程中发现同名文件,Overlay文件系统会优先向用户呈现较高层中的文件,即忽略来自底层目录的文件而只显示来自高层目录的文件,或者忽略来自较低底层目录的文件而显示来自更高底层目录的文件。

值得注意的是,高层目录和底层目录中文件的存储和组织方式是基于宿主机文件系统,即Ext4文件系统,而不是基于Overlay文件系统。Overlay文件系统提供的功能仅是将数个目录层联合挂载到统一合并视图层,并提供相应的文件访问接口。

基本文件操作

在Overlay文件系统中,用户视角只能看到统一合并视图层的内容。因此,用户相应的Overlay基本文件操作(如文件创建、删除、读取、写入等)也都起始于统一合并视图层。然而,统一合并视图层仅提供实际文件的视图,并不存储和组织实际的文件(存储在属于Ext4文件系统的高层目录和底层目录中)。因此,当统一合并视图层的基本文件操作发生时,Overlay文件系统通过自身文件系统定义的函数接口找到操作对象在高层目录和底层目录对应的实际文件,然后将控制权转移给Ext4文件系统,最后通过VFS进一步调用操作系统定义的文件操作。而这个跨层文件的转换过程被称为文件重定向(file redirection)。

Overlay文件系统的基本文件操作包括文件读取、文件写入、文件创建文件删除。但是根据不同的操作类型和实际文件所在的层次,Overlay文件系统会分别采取不同的处理策略:

1) 文件读取操作

场景1. 文件和目录只存在于底层目录,却不存在于高层目录:Overlay文件系统通过视图层的文件和目录的目录项,使用文件重定向找到其在底层目录中对应的实际目录项与索引节点,接着将操作的控制权转交给VFS,并由VFS进一步执行操作系统已定义好的读取操作;

场景2. 文件和目录只存在于高层目录,却不存在于底层目录:与场景1的过程基本相同,唯有通过文件重定向找到在高层目录中对应的实际目录项(real_dentry)与索引节点,再将控制权转交给VFS;

场景3. 文件和目录同时存在于高层目录和底层目录:由于Overlay文件系统的视图层会用高层目录中的文件覆盖底层目录中的文件,所以读取过程和场景2相同。

2) 文件写入操作

场景1. 文件和目录是首次写入,仅存在于底层目录,且不存在于高层目录中:Overlay文件系统通过文件重定向找到底层目录中的实际文件,再执行写时复制(Copy-on-Write, CoW),将对象从底层目录拷贝到高层目录;然后,将复制后的目录和文件的目录项和控制权转交给VFS;接着,VFS进一步执行宿主机文件系统已定义的文件写入操作,读取拷贝的文件和目录并修改,再将修改后的结果保存在高层目录。值得注意的是,Overlay属于文件级别的文件系统:即使文件只有很小一部分的修改,也会触发写时复制;

场景2. 文件和目录是首次写入,但存在于高层目录:与场景1处理基本相同,但不会发生写时复制(Copy-on-Write)。Overlay文件系统仅通过文件重定向将视图层对象的目录项转换为高层目录中对应的目录项,并将其和它的控制权转交给VFS;

场景3. 文件和目录是非首次写入,则一定存在于高层目录:解决方案与场景2完全相同。

3) 文件创建操作

场景1. 创建在底层目录和高层目录中都不存在的文件或目录:直接在高层目录中创建新的文件或目录;

场景2. 创建在底层目录已经存在,且在高层目录有whiteout文件的同名文件:Overlay文件系统会删除高层目录中的whiteout文件,并用新创建的文件替换它。通过这种方式,用户就能在统一合并视图层中看到高层目录中创建的新文件;

场景3. 创建在底层目录已经存在,且在高层目录有whiteout文件的同名目录:由于同名目录上下层合并,所以若这时仅仅在高层目录中新建一个目录,则底层目录中对应目录中的内容也会暴露。因此,Overlay文件系统引入opaque属性,在读取上下层同名目录的目录项时,对高层目录中具有opaque属性的目录,Overlay文件系统会忽略在底层目录中同名目录下的所有目录项,以保证在用户视图中该新建目录仅是一个空目录。

4) 文件删除操作

场景1. 删除的文件或目录来自高层目录,且在底层目录中没有同名对象:由于高层目录中的对象是可读可写的,因此所有的文件操作都可以直接作用在高层目录。因此,Overlay文件系统会直接删除高层目录中对应的文件或目录;

创建2. 删除的文件或目录来自底层目录,且在高层目录不存在覆盖文件:由于底层对象是只读的,因此Overlay文件系统不能直接删除底层目录中的文件或目录。Overlay文件系统必须既让用户认为目标已删除,却又不执行真正的删除操作;
为了达到这种效果,Overlay文件系统会在高层目录中删除文件的同时,创建同名的whiteout文件,用于屏蔽底层目录中的文件或目录。因此,当用户在视图层查看文件或目录时,由于whiteout文件的存在,底层目录中对应的文件或目录对用户不可见,就像被删除一样。但实际对象依旧存在于底层目录中;

场景3. 删除的文件是高层目录的文件覆盖底层目录的文件,或删除的目录是上下层合并的目录:该场景的属于场景1和场景2的合并,Overlay文件系统既要删除高层目录中的文件和目录,也会在高层目录中创建同名的whiteout文件,从而保证高层目录的文件或目录被删除后而不至于底层对象被暴露出来。

原子性保证

文件系统的原子性指所有的文件操作要么全部完成,要么全部不完成,不可能停滞在中间某个环节,否则有可能会破坏文件数据或元数据的原子性。而Overlay文件系统必须要保证文件创建、文件删除和写时复制等过程的原子性:

场景1. 删除高层目录中的文件并创建whiteout文件:Overlay文件系统要保证高层目录中的删除目标文件和创建whiteout文件两个操作同时发生的原子性,不存在删除文件后whiteout文件还未创建的状态;

场景2. 删除上下层合并目录:Overlay文件系统必须要保证被删除的目录为空,即目录和文件同时被删除的原子性,不存在部分数据删除,部分数据保留的状态;

场景3. 新创建与whiteout同名的文件和目录:Overlay文件系统要保证删除whiteout文件和创建新文件两个操作同时发生的原子性,不存在删除whiteout文件后新文件还未创建的状态;

场景4. 底层文件的写时复制:Overlay文件系统要保证文件拷贝的原子性,不存在只拷贝部分数据的状态。

overlayatom

为了保证自身文件操作的原子性,Overlay文件系统使用工作目录(workdir)和重命名操作(rename)。如上图所示,以删除同时存在于高 / 底层目录的文件为例,其原子性保证的工作原理主要分为三个步骤:

①创建:在工作目录中创建用于同名whiteout文件(若是写时复制过程,则将目标文件从底层目录复制到工作目录);

②交换:使用重命名操作将被删除的目标文件与工作目录中的whiteout文件进行交换 (若是写诗赋值过程,则通过重命名操作移动拷贝文件)来实现文件的原子性替换,而其原子性由宿主机文件系统已定义的重命名操作来保证;

③删除:将工作目录中的目标文件删除(若是写时复制,则没有这个步骤)。

在上述的每个步骤中,即使系统出现异常或崩溃,磁盘也只会在工作目录中多出未被及时删除的目标文件、whiteout文件或拷贝不完全的文件;当操作系统恢复正常且再次挂载Overlay文件系统时,工作目录中的文件会被继续清除。通过这种方式,Overlay文件系统能够保证文件系统中所有基本文件操作的原子性。