Docker 中 Storage-driver 启用 Overlay2 并限制单个容器的磁盘空间

1 简介

目前 docker 中常见的 Storage-driver 主要有 AUFS、Devicemapper 以及 Overlay2,这三种文件存储驱动这里简单介绍下。同时着重介绍 Overlay2 的使用事项。

2 AUFS

2.1 如何存储文件?

AUFS 使用多层目录存储,每一次目录在 Docker 中称之为层(layer),最终呈现给用户的则是一个普通的单层文件系统,我们把多层以单一层的方式呈现出来的过程叫做联合挂载。

1
2
3
4
5
6
7
/var/lib/docker/aufs/mnt       联合挂载点
↓ ↓ ↓
/var/lib/docker/aufs/diff 容器层(可读写)
↓ ↓ ↓
/var/lib/docker/aufs/diff
/var/lib/docker/aufs/diff 镜像层(只读)
/var/lib/docker/aufs/diff

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

2.2 读写文件

2.2.1 读文件

当读取的文件在容器层时,直接从容器层读取,当文件不存在容器层时,则从镜像层中读取,当文件既在镜像层又在容器层时,则从容器层读取

2.2.2 修改或删除文件

当第一次修改文件时,AUFS 会触发写时复制,先从镜像层复制文件到容器层,然后在执行修改操作。当修改文件或目录时,AUFS 并不会真正从镜像中删除,而是创建一个特殊的文件或文件夹(whiteout),这种特殊的文件或文件夹会组织容器访问。

2.3 优缺点

在容器密度比较高的场景下,AUFS 是非常好的选择,因为 AUFS 的容器间共享镜像层的特性使其磁盘利用率很高,容器的启动时间很短。

AUFS 的写时复制策略会带来很高的性能开销,因为 AUFS 对文件的第一次更改需要将整个文件复制到读写层,当容器层数很多或文件所在目录很深时尤其明显。其次 AUFS 未能进入 Linux 内核主线。

3 Devicemapper

3.1 如何存储文件?

Devicemapper 使用专门的块设备来实现镜像的存储,并且像 AUFS 一样使用了写时复制的技术来保障最大程度节省存储空间,Devicemapper 的镜像分层使用快照的方式实现。

Devicemapper 创建镜像的过程如下:

  • 创建一个精简配置池,精简配置池由块设备或稀疏文件创建。
  • 接下来创建一个基础设备。
  • 每个镜像和镜像层都是基础设备的快照;在写快照支持写时复制策略,这意味着它们起始都是空的,当有数据写入时才耗费空间。

device mapper

3.2 读写文件

3.2.1 读文件

device mapper read

  • 某个进程发出读取文件的请求;由于容器只是镜像的精简快照 (thin snapshot),它并没有这个文件。但它有指向这个文件在下面层中存储位置的指针。
  • Devicemapper 由指针找到在镜像层号为 a005e 中的块号为 0xf33 的数据;
  • Devicemapper 将这个位置的文件复制到容器的存储区内;
  • Devicemapper 将数据返回给应用进程;

3.2.2 写文件

在 Devicemapper 中,对容器的写操作由“需要时分配”策略完成。更新已有数据由“写时复制”策略完成,这些操作都在块的层次上完成。当我们需要写数据时,则向瘦供给池(thinpool)动态申请存储空间生成读写层,然后把数据复制到读写层进行修改。Devicemapper 默认每次申请的大小是 64KB 或者 64KB 的倍数,因此每次新生成的读写层的大小都是 64KB 或者 64KB 的倍数。

3.3 优缺点

Devicemapper 的写时复制策略以 64KB 作为粒度,意味着无论是对 32KB 的文件还是对 1GB 大小的文件的修改都仅复制 64KB 大小的文件。这相对于在文件层面进行的读操作具有很明显的性能优势。

但是,如果容器频繁对小于 64KB 的文件进行改写,Devicemapper 的性能是低于 AUFS 的。同时 Devicemapper 不是最有效使用存储空间的 storage driver,启动 n 个相同的容器就复制了 n 份文件在内存中,这对内存的影响很大。所以 Devicemapper 并不适合容器密度高的场景。

4 Overlay2

4.1 如何存储文件?

Overlay2 将一个 Linux 主机中的两个目录组合起来,一个在上,一个在下,对外提供统一的视图。这两个目录就是层 layer,将两个层组合在一起的技术被称为联合挂载(union mount)。在 Overlay2 中,上层的目录被称作 upperdir,下层的,目录被称作 lowerdir,对外提供的统一视图被称作 merged
overlay2

注意到,镜像层和容器层可以有相同的文件,这种情况下,upperdir 中的文件覆盖 lowerdir 中的文件。

Overlay2 文件系统最多支持 128 个层数叠加,也就是说你的 Dockerfile 最多只能写 128 行。

4.2 读写文件

4.2.1 读文件

要读的文件不在 container layer 中,那就从 lowerdir 中读,会耗费一点性能;要读的文件存在于 container layer 中,直接从 upperdir 中读;要读的文件在container layer 和 image layer 中都存在, 从 upperdir 中读文件

4.2.2 写文件

在第一次修改时,文件不在 container layer(upperdir) 中,overlay driver 调用写时复制将文件从 lowerdir 读到 upperdir 中,然后对文件的副本做出修改。文件被删除时,和 AUFS 一样,相应的 whiteout 文件被创建在 upperdir。并不删除容器层(lowerdir) 中的文件,whiteout 文件屏蔽了它的存在。

4.3 优缺点

Overlay2 的拷贝操作工作在文件层面上,也就是对文件的第一次修改需要复制整个文件,会带来一些性能开销,在修改大文件时尤其明显。但 Overlay2 的拷贝操作比 AUFS 还是快一点,因为 AUFS 有很多层,而 Overlay2 只有两层,所以 Overlay2 在文件的搜索方面相对于 AUFS 具有优势。Overlay2 支持页缓存的共享,这意味着多个使用同一文件的容器可以共享同一页缓存,这使得 Overlay2 具有很高的内存使用效率。

5 如何使用 Overlay2

5.1 先决条件

  1. Docker 版本必须高于 17.06.02
  2. 如果操作系统是 RHEL 或 CentOS,内核版本必须高于 3.10.0-514,其他 Linux 内核版本必须高于 4.0
  3. Overlay2 最好搭配 xfs 文件系统使用,并且使用 xfs 作为底层文件系统时,d_type 必须开启

    验证 d_type 是否开启:

    1
    xfs_info /var/lib/docker | grep ftype

    当输出结果中有 ftype=1 时,表示 d_type 已经开启。如果为 0 时,则需要重新格式化磁盘。命令如下:

    1
    mkfs.xfs -f -n ftype=1 /path/to/disk

5.2 限制单个容器可占用磁盘空间

5.2.1 开启 xfs 的 quota 特性

/etc/fstab 中设置

1
UUID=4cbf4a19-1fba-4027-bf92-9aa969683fa9     /var/lib/docker   xfs    defaults,pquota  0   0

/var/lib/docker 卸载后重新挂载即可。

5.2.2 配置 docker daemon

/etc/docker/daemon.json 配置文件如下,这里将每个容器可以使用的磁盘空间设置为1G:

1
2
3
4
5
6
7
{
"storage-driver": "overlay2",
"storage-opts": [
"overlay2.override_kernel_check=true",
"overlay2.size=1G"
]
}

5.2.3 写入文件测试

重启docker后,启动一个容器,在容器中创建文件。

先创建一个1000M的文件:

1
2
3
/ # dd if=/dev/zero of=/a bs=100M count=10
10+0 records in
10+0 records out

然后创建第二个1000M的文件:

1
2
3
4
/ # dd if=/dev/zero of=/b bs=100M count=10
dd: writing '/b': No space left on device
2+0 records in
0+1 records out

可以看到第二个 1000M 文件因为空间不足创建失败。

微信订阅号

-------------本文结束感谢您的阅读-------------