Skip to content
60.Docker»10.文件系统»LV005-AUFS文件系统.md

LV005-AUFS文件系统

AUFS 是 Docker 最早使用的文件系统驱动,多用于 Ubuntu 和 Debian 系统中。在 Docker 早期,OverlayFS 和 Devicemapper 相对不够成熟,AUFS 是最早也是最稳定的文件系统驱动。 接下来,我们就看看如何配置 Docker 的 AUFS 模式。

一、主机是否支持 AUFS 模式

AUFS 目前并未被合并到 Linux 内核主线,因此只有 Ubuntu 和 Debian 等少数操作系统支持 AUFS。你可以使用以下命令查看我们的系统是否支持 AUFS:

shell
grep aufs /proc/filesystems

【例】

shell
sumu@sumu-vm:~/test$ grep aufs /proc/filesystems
nodev	aufs

执行以上命令后,如果输出结果包含 aufs,则代表当前操作系统支持 AUFS。AUFS 推荐在 Ubuntu 或 Debian 操作系统下使用,如果想要在 CentOS 等操作系统下使用 AUFS,需要单独安装 AUFS 模块(生产环境不推荐在 CentOS 下使用 AUFS,如果想在 CentOS 下安装 AUFS 用于研究和测试,可以参考这个 bnied/kernel-ml-aufs: Mainline kernel packages, with AUFS support. ),安装完成后使用上述命令输出结果中有aufs即可。

二、配置 Docker 的 AUFS 模式

当确认完操作系统支持 AUFS 后,就可以配置 Docker 的启动参数了。先在 /etc/docker 下新建 daemon.json 文件,并写入以下内容:

json
{
  "storage-driver": "aufs"
}

然后使用以下命令重启 Docker:

shell
sudo systemctl restart docker

Docker 重启以后使用docker info命令即可查看配置是否生效:

shell
$ sudo docker info
Client:
 Debug Mode: false
Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 1
 Server Version: 19.03.12
 Storage Driver: aufs
  Root Dir: /var/lib/docker/aufs
  Backing Filesystem: extfs
  Dirs: 1
  Dirperm1 Supported: true

可以看到 Storage Driver 已经变为 aufs,证明配置已经生效,配置生效后就可以使用 AUFS 为 Docker 提供联合文件系统了。

三、AUFS 工作原理

1. AUFS 是如何存储文件的?

AUFS 是联合文件系统,意味着它在主机上使用多层目录存储,每一个目录在 AUFS 中都叫作分支,而在 Docker 中则称之为层(layer),但最终呈现给用户的则是一个普通单层的文件系统,我们把多层以单一层的方式呈现出来的过程叫作联合挂载。

img

如图 1 所示,每一个镜像层和容器层都是 /var/lib/docker 下的一个子目录,镜像层和容器层都在 aufs/diff 目录下,每一层的目录名称是镜像或容器的 ID 值,联合挂载点在 aufs/mnt 目录下,mnt 目录是真正的容器工作目录。

下面我们针对 aufs 文件夹下的各目录结构,在创建容器前后的变化做详细讲述。

当一个镜像未生成容器时,AUFS 的存储结构如下。

  • diff 文件夹:存储镜像内容,每一层都存储在以镜像层 ID 命名的子文件夹中。
  • layers 文件夹:存储镜像层关系的元数据,在 diif 文件夹下的每个镜像层在这里都会有一个文件,文件的内容为该层镜像的父级镜像的 ID。
  • mnt 文件夹:联合挂载点目录,未生成容器时,该目录为空。

当一个镜像已经生成容器时,AUFS 存储结构会发生如下变化。

  • diff 文件夹:当容器运行时,会在 diff 目录下生成容器层。
  • layers 文件夹:增加容器层相关的元数据。
  • mnt 文件夹:容器的联合挂载点,这和容器中看到的文件内容一致。

以上便是 AUFS 的工作原理,那你知道容器的在工作过程中是如何使用 AUFS 的吗?

2. AUFS 是如何工作的?

AUFS 的工作过程中对文件的操作分为读取文件和修改文件。下面我们分别来看下 AUFS 对于不同的文件操作是如何工作的。

2.1 读取文件

当我们在容器中读取文件时,可能会有以下场景。

  • 文件在容器层中存在时:当文件存在于容器层时,直接从容器层读取。
  • 当文件在容器层中不存在时:当容器运行时需要读取某个文件,如果容器层中不存在时,则从镜像层查找该文件,然后读取文件内容。
  • 文件既存在于镜像层,又存在于容器层:当我们读取的文件既存在于镜像层,又存在于容器层时,将会从容器层读取该文件。(由于写时复制,所以此时肯定是修改过的文件才会复制到容器层,所以应该读取容器层的文件)

2.2 修改文件或目录

AUFS 对文件的修改采用的是写时复制的工作机制,这种工作机制可以最大程度节省存储空间。具体的文件操作机制如下。

  • 第一次修改文件:当我们第一次在容器中修改某个文件时,AUFS 会触发写时复制操作,AUFS 首先从镜像层复制文件到容器层,然后再执行对应的修改操作。

AUFS 写时复制的操作将会复制整个文件,如果文件过大,将会大大降低文件系统的性能,因此当我们有大量文件需要被修改时,AUFS 可能会出现明显的延迟。好在,写时复制操作只在第一次修改文件时触发,对日常使用没有太大影响。

  • 删除文件或目录:当文件或目录被删除时,AUFS 并不会真正从镜像中删除它,因为镜像层是只读的,AUFS 会创建一个特殊的文件或文件夹,这种特殊的文件或文件夹会阻止容器的访问。

四、AUFS 示例

说明:这里没有用 docker,因为我是在 CNB 中无论是直接运行还是创建 ubuntu 容器运行,后面挂载的时候都是提示权限不足,我就直接在自己的虚拟机中尝试了。这里我使用的是 ubuntu22.04.6

1. 创建测试文件

我们在 ubuntu 中执行下面的命令创建测试文件:

shell
# 创建一个容器层目录和两个镜像层目录
mkdir -pv test/container1 test/image1 test/image2

# 进入测试目录
cd test

# 在每个目录中创建文件
echo "Hello, Container layer!" > container1/container1.txt
echo "Hello, Image layer1!" > image1/image1.txt
echo "Hello, Image layer2!" > image2/image2.txt

最终目录结构如下:

shell
sumu@sumu-vm:~/test$ tree
.
├── container1
│   └── container1.txt
├── image1
│   └── image1.txt
└── image2
    └── image2.txt

3 directories, 3 files

2. 创建 AUFS 联合文件系统

然后我们将通过 mount 命令把 container1、 image1 和 image2 目录「联合」起来,建立一个 AUFS 的文件系统,并挂载到当前目录下的 mnt 目录下:

shell
mkdir -pv mnt

# 这里 mount: 只有 root 用户能使用“--options”选项
sudo mount -t aufs -o dirs=./container1:./image1:./image2 none ./mnt

mount 命令创建 AUFS 类型文件系统时,这里要注意,冒号是分隔符,dirs 参数第一个目录默认为读写权限,后面的目录均为只读权限,与 Docker 容器使用 AUFS 的模式一致。

mnt 变成了 AUFS 的联合挂载目录,我们可以使用 mount 命令查看一下已经创建的 AUFS 文件系统:

shell
sumu@sumu-vm:~/test$ mount -t aufs
none on /home/sumu/test/mnt type aufs (rw,relatime,si=d108209ec1b694d0)

我们每创建一个 AUFS 文件系统,AUFS 都会为我们生成一个 ID,这个 ID 在 /sys/fs/aufs/ 会创建对应的目录,在这个 ID 的目录下可以查看文件挂载的权限。

shell
sumu@sumu-vm:~/test$ cat /sys/fs/aufs/si_d108209ec1b694d0/*
/home/sumu/test/container1=rw
/home/sumu/test/image1=ro
/home/sumu/test/image2=ro
64
65
66
/home/sumu/test/container1/.aufs.xino

可以看到 container1 目录的权限为 rw(代表可读写),image1 和 image2 权限为 ro(代表只读)。

然后我们看一下 mnt 目录内容:

shell
sumu@sumu-vm:~/test$ tree mnt/
mnt/
├── container1.txt
├── image1.txt
└── image2.txt

0 directories, 3 files

通过 ./mnt 目录结构的输出结果,可以看到原来容器层和镜像层的目录下的内容都被合并到了一个 mnt 的目录下。

3. 验证 AUFS 的写时复制

默认情况下,如果我们不对「联合」的目录指定权限,内核将根据从左至右的顺序将第一个目录指定为可读可写的,其余的都为只读。那么,当我们向只读的目录做一些写入操作的话,会发生什么呢?

我们先看一下整体目录文件情况

shell
sumu@sumu-vm:~/test$ tree
.
├── container1
│   └── container1.txt
├── image1
│   └── image1.txt
├── image2
│   └── image2.txt
└── mnt
    ├── container1.txt
    ├── image1.txt
    └── image2.txt

4 directories, 6 files

然后我们修改 mnt/image1.txt这个文件(这个文件来自于 image1/image1.txt ):

shell
echo "Hello, Image layer1 changed!" > mnt/image1.txt

此时我们再来看一下test目录下的文件结构:

shell
sumu@sumu-vm:~/test$ tree
.
├── container1
│   ├── container1.txt
│   └── image1.txt
├── image1
│   └── image1.txt
├── image2
│   └── image2.txt
└── mnt
    ├── container1.txt
    ├── image1.txt
    └── image2.txt

4 directories, 7 files

会发现,container1 目录下多了一个 image1.txt 文件。我们看一下上面三个地方的 image1.txt 文件内容:

image-20251101125840557

通过对上面代码段的观察,我们可以看出,当写入操作发生在 image1/image1.txt 文件时, 对应的修改并没有反映到原始的目录 image1 中。而是在 container1 目录下又创建了一个名为 image1.txt 的文件,并将字符串写入了进去。

看起来很奇怪的现象,其实这正是 Union File System 的厉害之处:Union File System 联合了多个不同的目录,并且把他们挂载到一个统一的目录上。在这些「联合」的子目录中, 有一部分是可读可写的,但是有一部分只是可读的。当我们 对可读的目录内容做出修改的时候,其结果只会保存到可写的目录下,不会影响只读的目录

比如,我们可以把我们的服务的源代码目录和一个存放代码修改记录的目录「联合」起来构成一个 AUFS。前者设置只读权限,后者设置读写权限。那么,一切对源代码目录下文件的修改都只会影响那个存放修改的目录,不会污染原始的代码。

在 AUFS 中还有一个特殊的概念需要提及一下:

  • branch – 就是各个要被 union 起来的目录。

  • Stack 结构 - AUFS 它会根据 branch 被 Union 的顺序形成一个 Stack 的结构,从下至上,最上面的目录是可读写的,其余都是可读的。如果按照我们刚刚执行 aufs 挂载的命令来说,最左侧的目录就对应 Stack 最顶层的 branch。

所以:下面的命令中,最为左侧的为 container1,而不是 image1或者 image2

shell
sudo mount -t aufs -o dirs=./container1:./image1:./image2 none ./mnt

参考资料:

Docker 底层:AUFS 文件系统原理 - 拾月凄辰 - 博客园

莫道桑榆晚 为霞尚满天.