容器

理解容器文件系统

我们在容器里,运行 df 命令,你可以看到在容器中根目录 (/) 的文件系统类型是”overlay”,它不是我们在普通 Linux 节点上看到的 ext4 或者 xfs 之类常见的文件系统。

那么看到这里你肯定想问,Overlay 是一个什么样的文件系统呢,容器为什么要用这种文件系统?

在说容器文件系统前,我们先来想象一下如果没有文件系统管理的话会怎样。假设有这么一个场景,在一个宿主机上需要运行 100 个容器。

每个容器都需要一个镜像,这个镜像就把容器中程序需要运行的二进制文件,库文件,配置文件,其他的依赖文件等全部都打包成一个镜像文件。

如果没有特别的容器文件系统,只是普通的 ext4 或者 xfs 文件系统,那么每次启动一个容器,就需要把一个镜像文件下载并且存储在宿主机上。

我举个例子帮你理解,比如说,假设一个镜像文件的大小是 500MB,那么 100 个容器的话,就需要下载 500MB*100= 50GB 的文件,并且占用 50GB 的磁盘空间。

如果你再分析一下这 50GB 里的内容,你会发现,在绝大部分的操作系统里,库文件都是差不多的。而且,在容器运行的时候,这类文件也不会被改动,基本上都是只读的。特别是这样的情况:假如这 100 个容器镜像都是基于”ubuntu:18.04″的,每个容器镜像只是额外复制了 50MB 左右自己的应用程序到”ubuntu: 18.04″里,那么就是说在总共 50GB 的数据里,有 90% 的数据是冗余的。

讲到这里,你不难推测出理想的情况应该是什么样的?

没错,当然是在一个宿主机上只要下载并且存储存一份”ubuntu:18.04″,所有基于”ubuntu:18.04″镜像的容器都可以共享这一份通用的部分。这样设置的话,不同容器启动的时候,只需要下载自己独特的程序部分就可以。

正是为了有效地减少磁盘上冗余的镜像数据,同时减少冗余的镜像数据在网络上的传输,选择一种针对于容器的文件系统是很有必要的,而这类的文件系统被称为 UnionFS。

UnionFS 这类文件系统实现的主要功能是把多个目录(处于不同的分区)一起挂载(mount)在一个目录下

这种多目录挂载的方式,正好可以解决我们刚才说的容器镜像的问题。比如,我们可以把 ubuntu18.04 这个基础镜像的文件放在一个目录 ubuntu18.04/ 下,容器自己额外的程序文件 app_1_bin 放在 app_1/ 目录下。然后,我们把这两个目录挂载到 container_1/ 这个目录下,作为容器 1 看到的文件系统;对于容器 2,就可以把 ubuntu18.04/ 和 app_2/ 两个目录一起挂载到 container_2 的目录下。这样在节点上我们只要保留一份 ubuntu18.04 的文件就可以了。

UnionFS 类似的有很多种实现,包括在 Docker 里最早使用的 AUFS,还有目前我们使用的 OverlayFS。

前面我们在运行df的时候,看到的文件系统类型”overlay”指的就是 OverlayFS。

在 Linux 内核 3.18 版本中,OverlayFS 代码正式合入 Linux 内核的主分支。在这之后,OverlayFS 也就逐渐成为各个主流 Linux 发行版本里缺省使用的容器文件系统了。

网上 Julia Evans 有个blog,里面有个的 OverlayFS 使用的例子,很简单,我们也拿这个例子来理解一下 OverlayFS 的一些基本概念。

你可以先执行一下这一组命令。

###############################################

#!/bin/bash

umount ./merged

rm upper lower merged work -r

mkdir upper lower merged work

echo "I'm from lower!" > lower/in_lower.txt

echo "I'm from upper!" > upper/in_upper.txt

# `in_both` is in both directories

echo "I'm from lower!" > lower/in_both.txt

echo "I'm from upper!" > upper/in_both.txt

sudo mount -t overlay overlay \

-o lowerdir=./lower,upperdir=./upper,workdir=./work \

./merged

################################################

我们可以看到,OverlayFS 的一个 mount 命令牵涉到四类目录,分别是 lower,upper,merged 和 work,那它们是什么关系呢?

我们看下面这张图,这和前面 UnionFS 的工作示意图很像,也不奇怪,OverlayFS 就是 UnionFS 的一种实现。接下来,我们从下往上依次看看每一层的功能。

首先,最下面的”lower/”,也就是被 mount 两层目录中底下的这层(lowerdir)。

在 OverlayFS 中,最底下这一层里的文件是不会被修改的,你可以认为它是只读的。我还想提醒你一点,在这个例子里我们只有一个 lower/ 目录,不过 OverlayFS 是支持多个 lowerdir 的。然后我们看”uppder/”,它是被 mount 两层目录中上面的这层 (upperdir)。

在 OverlayFS 中,如果有文件的创建,修改,删除操作,那么都会在这一层反映出来,它是可读写的。接着是最上面的”merged” ,它是挂载点(mount point)目录,也是用户看到的目录,用户的实际文件操作在这里进行。

其实还有一个”work/”,这个目录没有在这个图里,它只是一个存放临时文件的目录,OverlayFS 中如果有文件修改,就会在中间过程中临时存放文件到这里。

从这个例子我们可以看到,OverlayFS 会 mount 两层目录,分别是 lower 层和 upper 层,这两层目录中的文件都会映射到挂载点上。

从挂载点的视角看,upper 层的文件会覆盖 lower 层的文件,比如”in_both.txt”这个文件,在 lower 层和 upper 层都有,但是挂载点 merged/ 里看到的只是 upper 层里的 in_both.txt.

如果我们在 merged/ 目录里做文件操作,具体包括这三种:

  • 第一种,新建文件,这个文件会出现在 upper/ 目录中。
  • 第二种是删除文件,如果我们删除”in_upper.txt”,那么这个文件会在 upper/ 目录中消失。

如果删除”in_lower.txt”, 在 lower/ 目录里的”in_lower.txt”文件不会有变化,只是在 upper/ 目录中增加了一个特殊文件来告诉 OverlayFS,”in_lower.txt’这个文件不能出现在 merged/ 里了,这就表示它已经被删除了。

  • 第三种是修改文件,类似如果修改”in_lower.txt”,那么就会在 upper/ 目录中新建一个”in_lower.txt”文件,包含更新的内容,而在 lower/ 中的原来的实际文件”in_lower.txt”不会改变。

通过这个例子,我们知道了 OverlayFS 是怎么工作了。

那么我们可以再想一想,怎么把它运用到容器的镜像文件上?

其实也不难,从系统的 mounts 信息中,我们可以看到 Docker 是怎么用 OverlayFS 来挂载镜像文件的:

容器镜像文件可以分成多个层(layer),每层可以对应 OverlayFS 里 lowerdir 的一个目录,lowerdir 支持多个目录,也就可以支持多层的镜像文件。

在容器启动后,对镜像文件中修改就会被保存在 upperdir 里了。

为什么要有容器自己的文件系统?

很重要的一点是减少相同镜像文件在同一个节点上的数据冗余,可以节省磁盘空间,也可以减少镜像文件下载占用的网络资源。

作为容器文件系统,UnionFS 通过多个目录挂载的方式工作。

OverlayFS 就是 UnionFS 的一种实现,是目前主流 Linux 发行版本中缺省使用的容器文件系统。

OverlayFS 也是把多个目录合并挂载,被挂载的目录分为两大类:lowerdir 和 upperdir。

lowerdir 允许有多个目录,在被挂载后,这些目录里的文件都是不会被修改或者删除的,也就是只读的;upperdir 只有一个,不过这个目录是可读写的,挂载点目录中的所有文件修改都会在 upperdir 中反映出来。

容器的镜像文件中各层正好作为 OverlayFS 的 lowerdir 的目录,然后加上一个空的 upperdir 一起挂载好后,就组成了容器的文件系统。

OverlayFS 在 Linux 内核中还在不断的完善,比如我们在这一讲看到的在 kenel 5.4 中对异步 I/O 操作的缺失,这也是我们在使用容器文件系统的时候需要注意的。

问题:

1. 在上面 OverlayFS 的例子的基础上,建立 2 个 lowerdir 的目录,并且在目录中建立相同文件名的文件,然后一起做一个 overlay mount,看看会发生什么?

经过实验确认,只会在merge即联合挂载点里生成一个文件名,也就是说overlay文件系统为了省存储空间是做了同名文件合并优化。

“merge层”相当于提供给用户进行交互的视图层;

“upper层”相当于存储实际发生变动的地方;

“lower层”是不变的,用户通过merge层视图对lower层文件的所有操作,都被重定向到“upper层”了,特殊的是删除操作,会在upper层生成一个特殊的c类型的文件来代表该文件被删。

用户交互时只能对merge层的视图来操作。

在merge层的视图中,上层覆盖下层同名文件,上下关系不只是upper和lower之间,即便在多个lower层中,也是上层覆盖下层。

2.假如我将一个卷(宿主机上的某个目录)挂在到容器文件系统中的某个目录,我在容器中对这个卷中的数据做读写操作,因为这个挂载路径也是容器文件系统的一部分,性能是不是也是会有影响的

如果以volume的方式挂载到容器中,那么它就不是以overlayfs的文件系统。

性能是否影响要看volume目录的位置在哪个物理磁盘上,和它共享物理磁盘的有哪些读写进程。