OverlayFS 存储驱动程序
OverlayFS 是一个联合文件系统。
本页将 Linux 内核驱动程序称为 `OverlayFS`,将 Docker 存储驱动程序称为 `overlay2`。
注意对于 `fuse-overlayfs` 驱动程序,请查阅无根模式文档。
先决条件
OverlayFS 是推荐的存储驱动程序,如果您满足以下先决条件,则受支持
Linux 内核版本 4.0 或更高,或者使用内核版本 3.10.0-514 或更高的 RHEL 或 CentOS。
`overlay2` 驱动程序支持 `xfs` 后备文件系统,但仅在启用了 `d_type=true` 的情况下。
使用 `xfs_info` 验证 `ftype` 选项是否设置为 `1`。要正确格式化 `xfs` 文件系统,请使用标志 `-n ftype=1`。
更改存储驱动程序会使本地系统上现有的容器和镜像无法访问。在更改存储驱动程序之前,使用 `docker save` 保存您已构建的任何镜像,或将它们推送到 Docker Hub 或私有注册表,这样您以后就不需要重新创建它们了。
使用 `overlay2` 存储驱动程序配置 Docker
在执行此过程之前,您必须首先满足所有先决条件。
以下步骤概述了如何配置 `overlay2` 存储驱动程序。
停止 Docker。
$ sudo systemctl stop docker将 `/var/lib/docker` 的内容复制到一个临时位置。
$ cp -au /var/lib/docker /var/lib/docker.bk如果您想使用与 `/var/lib/` 不同的后备文件系统,请格式化该文件系统并将其挂载到 `/var/lib/docker`。请务必将此挂载添加到 `/etc/fstab` 以使其永久生效。
编辑 `/etc/docker/daemon.json`。如果它还不存在,请创建它。假设该文件是空的,添加以下内容。
{ "storage-driver": "overlay2" }如果 `daemon.json` 文件包含无效的 JSON,Docker 将不会启动。
启动 Docker。
$ sudo systemctl start docker验证守护进程是否正在使用 `overlay2` 存储驱动程序。使用 `docker info` 命令并查找 `Storage Driver` 和 `Backing filesystem`。
$ docker info Containers: 0 Images: 0 Storage Driver: overlay2 Backing Filesystem: xfs Supports d_type: true Native Overlay Diff: true <...>
Docker 现在正在使用 `overlay2` 存储驱动程序,并已自动创建了具有所需 `lowerdir`、`upperdir`、`merged` 和 `workdir` 构造的 overlay 挂载。
继续阅读以了解 OverlayFS 如何在 Docker 容器内工作,以及有关性能建议和与不同后备文件系统兼容性限制的信息。
`overlay2` 驱动程序的工作原理
OverlayFS 在单个 Linux 主机上将两个目录分层,并将它们呈现为单个目录。这些目录称为层,统一过程称为联合挂载。OverlayFS 将较低的目录称为 `lowerdir`,将较高的目录称为 `upperdir`。统一视图通过其自己的目录 `merged` 公开。
`overlay2` 驱动程序原生支持多达 128 个较低的 OverlayFS 层。此功能为与层相关的 Docker 命令(如 `docker build` 和 `docker commit`)提供了更好的性能,并消耗了后备文件系统上更少的 inode。
磁盘上的镜像和容器层
使用 `docker pull ubuntu` 下载一个五层镜像后,您可以在 `/var/lib/docker/overlay2` 下看到六个目录。
警告不要直接操作 `/var/lib/docker/` 内的任何文件或目录。这些文件和目录由 Docker 管理。
$ ls -l /var/lib/docker/overlay2
total 24
drwx------ 5 root root 4096 Jun 20 07:36 223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7
drwx------ 3 root root 4096 Jun 20 07:36 3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b
drwx------ 5 root root 4096 Jun 20 07:36 4e9fa83caff3e8f4cc83693fa407a4a9fac9573deaf481506c102d484dd1e6a1
drwx------ 5 root root 4096 Jun 20 07:36 e8876a226237217ec61c4baf238a32992291d059fdac95ed6303bdff3f59cff5
drwx------ 5 root root 4096 Jun 20 07:36 eca1e4e1694283e001f200a667bb3cb40853cf2d1b12c29feda7422fed78afed
drwx------ 2 root root 4096 Jun 20 07:36 l
新的 `l` (小写 `L`) 目录包含作为符号链接的缩短层标识符。这些标识符用于避免触及 `mount` 命令参数的页面大小限制。
$ ls -l /var/lib/docker/overlay2/l
total 20
lrwxrwxrwx 1 root root 72 Jun 20 07:36 6Y5IM2XC7TSNIJZZFLJCS6I4I4 -> ../3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b/diff
lrwxrwxrwx 1 root root 72 Jun 20 07:36 B3WWEFKBG3PLLV737KZFIASSW7 -> ../4e9fa83caff3e8f4cc83693fa407a4a9fac9573deaf481506c102d484dd1e6a1/diff
lrwxrwxrwx 1 root root 72 Jun 20 07:36 JEYMODZYFCZFYSDABYXD5MF6YO -> ../eca1e4e1694283e001f200a667bb3cb40853cf2d1b12c29feda7422fed78afed/diff
lrwxrwxrwx 1 root root 72 Jun 20 07:36 NFYKDW6APBCCUCTOUSYDH4DXAT -> ../223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7/diff
lrwxrwxrwx 1 root root 72 Jun 20 07:36 UL2MW33MSE3Q5VYIKBRN4ZAGQP -> ../e8876a226237217ec61c4baf238a32992291d059fdac95ed6303bdff3f59cff5/diff
最底层包含一个名为 `link` 的文件,其中包含缩短标识符的名称,以及一个名为 `diff` 的目录,其中包含该层的内容。
$ ls /var/lib/docker/overlay2/3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b/
diff link
$ cat /var/lib/docker/overlay2/3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b/link
6Y5IM2XC7TSNIJZZFLJCS6I4I4
$ ls /var/lib/docker/overlay2/3a36935c9df35472229c57f4a27105a136f5e4dbef0f87905b2e506e494e348b/diff
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
第二底层以及每个更高层,都包含一个名为 `lower` 的文件,它表示其父层,以及一个名为 `diff` 的目录,其中包含其内容。它还包含一个 `merged` 目录,其中包含其父层和自身的统一内容,以及一个由 OverlayFS 内部使用的 `work` 目录。
$ ls /var/lib/docker/overlay2/223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7
diff link lower merged work
$ cat /var/lib/docker/overlay2/223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7/lower
l/6Y5IM2XC7TSNIJZZFLJCS6I4I4
$ ls /var/lib/docker/overlay2/223c2864175491657d238e2664251df13b63adb8d050924fd1bfcdb278b866f7/diff/
etc sbin usr var
要查看在使用 `overlay` 存储驱动程序与 Docker 时存在的挂载,请使用 `mount` 命令。为了便于阅读,下面的输出已被截断。
$ mount | grep overlay
overlay on /var/lib/docker/overlay2/9186877cdf386d0a3b016149cf30c208f326dca307529e646afce5b3f83f5304/merged
type overlay (rw,relatime,
lowerdir=l/DJA75GUWHWG7EWICFYX54FIOVT:l/B3WWEFKBG3PLLV737KZFIASSW7:l/JEYMODZYFCZFYSDABYXD5MF6YO:l/UL2MW33MSE3Q5VYIKBRN4ZAGQP:l/NFYKDW6APBCCUCTOUSYDH4DXAT:l/6Y5IM2XC7TSNIJZZFLJCS6I4I4,
upperdir=9186877cdf386d0a3b016149cf30c208f326dca307529e646afce5b3f83f5304/diff,
workdir=9186877cdf386d0a3b016149cf30c208f326dca307529e646afce5b3f83f5304/work)
第二行上的 `rw` 表示 `overlay` 挂载是读写的。
下图显示了 Docker 镜像和 Docker 容器如何分层。镜像层是 `lowerdir`,容器层是 `upperdir`。如果镜像有多个层,则使用多个 `lowerdir` 目录。统一视图通过一个名为 `merged` 的目录公开,该目录实际上是容器的挂载点。

当镜像层和容器层包含相同的文件时,容器层 (`upperdir`) 优先,并隐藏了镜像层中相同文件的存在。
为了创建一个容器,`overlay2` 驱动程序将代表镜像顶层的目录与一个新的容器目录组合起来。镜像的层是 overlay 中的 `lowerdirs`,并且是只读的。新的容器目录是 `upperdir`,并且是可写的。
磁盘上的镜像和容器层
以下 `docker pull` 命令显示了一个 Docker 主机下载一个包含五层的 Docker 镜像。
$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
5ba4f30e5bea: Pull complete
9d7d19c9dc56: Pull complete
ac6ad7efd0f9: Pull complete
e7491a747824: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:46fb5d001b88ad904c5c732b086b596b92cfb4a4840a3abd0e35dbb6870585e4
Status: Downloaded newer image for ubuntu:latest
镜像层
每个镜像层在 `/var/lib/docker/overlay/` 中都有自己的目录,其中包含其内容,如下例所示。镜像层 ID 与目录 ID 不对应。
警告不要直接操作 `/var/lib/docker/` 内的任何文件或目录。这些文件和目录由 Docker 管理。
$ ls -l /var/lib/docker/overlay/
total 20
drwx------ 3 root root 4096 Jun 20 16:11 38f3ed2eac129654acef11c32670b534670c3a06e483fce313d72e3e0a15baa8
drwx------ 3 root root 4096 Jun 20 16:11 55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358
drwx------ 3 root root 4096 Jun 20 16:11 824c8a961a4f5e8fe4f4243dab57c5be798e7fd195f6d88ab06aea92ba931654
drwx------ 3 root root 4096 Jun 20 16:11 ad0fe55125ebf599da124da175174a4b8c1878afe6907bf7c78570341f308461
drwx------ 3 root root 4096 Jun 20 16:11 edab9b5e5bf73f2997524eebeac1de4cf9c8b904fa8ad3ec43b3504196aa3801
镜像层目录包含该层独有的文件以及与下层共享数据的硬链接。这可以有效地利用磁盘空间。
$ ls -i /var/lib/docker/overlay2/38f3ed2eac129654acef11c32670b534670c3a06e483fce313d72e3e0a15baa8/root/bin/ls
19793696 /var/lib/docker/overlay2/38f3ed2eac129654acef11c32670b534670c3a06e483fce313d72e3e0a15baa8/root/bin/ls
$ ls -i /var/lib/docker/overlay2/55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358/root/bin/ls
19793696 /var/lib/docker/overlay2/55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358/root/bin/ls
容器层
容器也以磁盘形式存在于 Docker 主机的 `/var/lib/docker/overlay/` 文件系统中。如果您使用 `ls -l` 命令列出正在运行的容器的子目录,会存在三个目录和一个文件
$ ls -l /var/lib/docker/overlay2/<directory-of-running-container>
total 16
-rw-r--r-- 1 root root 64 Jun 20 16:39 lower-id
drwxr-xr-x 1 root root 4096 Jun 20 16:39 merged
drwxr-xr-x 4 root root 4096 Jun 20 16:39 upper
drwx------ 3 root root 4096 Jun 20 16:39 work
`lower-id` 文件包含容器所基于的镜像顶层的 ID,即 OverlayFS 的 `lowerdir`。
$ cat /var/lib/docker/overlay2/ec444863a55a9f1ca2df72223d459c5d940a721b2288ff86a3f27be28b53be6c/lower-id
55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358
`upper` 目录包含容器读写层的内容,它对应于 OverlayFS `upperdir`。
`merged` 目录是 `lowerdir` 和 `upperdirs` 的联合挂载,它构成了从正在运行的容器内部看到的文件系统视图。
`work` 目录是 OverlayFS 内部使用的。
要查看使用 `overlay2` 存储驱动程序与 Docker 时存在的挂载,请使用 `mount` 命令。为了便于阅读,以下输出已被截断。
$ mount | grep overlay
overlay on /var/lib/docker/overlay2/l/ec444863a55a.../merged
type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/55f1e14c361b.../root,
upperdir=/var/lib/docker/overlay2/l/ec444863a55a.../upper,
workdir=/var/lib/docker/overlay2/l/ec444863a55a.../work)
第二行上的 `rw` 表示 `overlay` 挂载是读写的。
容器读写如何与 `overlay2` 协同工作
读取文件
考虑三种情况,其中容器使用 overlay 打开文件进行读取访问。
文件在容器层中不存在
如果容器打开一个文件进行读取访问,并且该文件尚未存在于容器(`upperdir`)中,它将从镜像(`lowerdir`)中读取。这几乎不会产生性能开销。
文件只存在于容器层
如果容器打开一个文件进行读取访问,并且该文件存在于容器(`upperdir`)中而不在镜像(`lowerdir`)中,它将直接从容器中读取。
文件同时存在于容器层和镜像层
如果容器打开一个文件进行读取访问,并且该文件在镜像层和容器层中都存在,则读取容器层中的文件版本。容器层 (`upperdir`) 中的文件会遮蔽镜像层 (`lowerdir`) 中同名的文件。
修改文件或目录
考虑一些容器中文件被修改的场景。
首次写入文件
当容器首次写入一个现有文件时,该文件在容器(`upperdir`)中不存在。`overlay2` 驱动程序会执行一个 `copy_up` 操作,将文件从镜像(`lowerdir`)复制到容器(`upperdir`)。然后容器将更改写入到容器层中文件的新副本。
然而,OverlayFS 在文件级别而不是块级别工作。这意味着所有 OverlayFS `copy_up` 操作都会复制整个文件,即使文件很大且只有一小部分被修改。这可能对容器写入性能产生显著影响。但有两点值得注意
`copy_up` 操作仅在第一次写入给定文件时发生。后续对同一文件的写入操作将针对已经复制到容器的文件副本进行。
OverlayFS 支持多层结构。这意味着当在具有多层的镜像中搜索文件时,性能可能会受到影响。
删除文件和目录
当在容器内删除一个*文件*时,会在容器(`upperdir`)中创建一个*白化*文件。镜像层(`lowerdir`)中的文件版本不会被删除(因为`lowerdir`是只读的)。然而,这个白化文件会阻止它对容器可用。
当在容器内删除一个*目录*时,会在容器(`upperdir`)中创建一个*不透明目录*。这与白化文件的工作方式相同,有效地阻止了对该目录的访问,即使它仍然存在于镜像(`lowerdir`)中。
重命名目录
只有当源路径和目标路径都在顶层时,才允许对目录调用 `rename(2)`。否则,它会返回 `EXDEV` 错误(“不允许跨设备链接”)。您的应用程序需要被设计为能够处理 `EXDEV` 并回退到“复制和取消链接”策略。
OverlayFS 和 Docker 性能
`overlay2` 的性能可能优于 `btrfs`。但是,请注意以下细节
页面缓存
OverlayFS 支持页面缓存共享。多个访问同一文件的容器会共享该文件的单个页面缓存条目。这使得 `overlay2` 驱动程序在内存使用上非常高效,是高密度用例(如 PaaS)的良好选择。
写时复制
与其他写时复制文件系统一样,每当容器首次写入文件时,OverlayFS 都会执行复制操作。这会给写操作增加延迟,特别是对于大文件。然而,一旦文件被复制上去,所有后续对该文件的写入都发生在顶层,无需进一步的复制操作。
性能最佳实践
以下通用性能最佳实践适用于 OverlayFS。
使用快速存储
固态硬盘(SSD)比旋转磁盘提供更快的读写速度。
为写入密集型工作负载使用卷
卷为写入密集型工作负载提供了最佳且最可预测的性能。这是因为它们绕过了存储驱动程序,并且不会产生由瘦配置和写时复制引入的任何潜在开销。卷还有其他好处,例如允许您在容器之间共享数据,并在没有运行中的容器使用它们时也能持久化您的数据。
OverlayFS 兼容性的限制
总结一下 OverlayFS 与其他文件系统不兼容的方面:
open(2)- OverlayFS 只实现了 POSIX 标准的一个子集。这可能导致某些 OverlayFS 操作违反 POSIX 标准。其中一个操作就是写时复制(copy-up)操作。假设您的应用程序调用 `fd1=open("foo", O_RDONLY)`,然后调用 `fd2=open("foo", O_RDWR)`。在这种情况下,您的应用程序期望 `fd1` 和 `fd2` 指向同一个文件。然而,由于在第二次调用 `open(2)` 后发生了写时复制操作,这两个描述符指向了不同的文件。`fd1` 继续引用镜像中的文件(`lowerdir`),而 `fd2` 引用容器中的文件(`upperdir`)。一个解决方法是 `touch` 这些文件,这会触发写时复制操作。所有后续的 `open(2)` 操作,无论是只读还是读写访问模式,都将引用容器中的文件(`upperdir`)。
已知 `yum` 会受到影响,除非安装了 `yum-plugin-ovl` 包。如果 `yum-plugin-ovl` 包在您的发行版中不可用,例如 RHEL/CentOS 6.8 或 7.2 之前的版本,您可能需要在运行 `yum install` 之前运行 `touch /var/lib/rpm/*`。该包为 `yum` 实现了上述的 `touch` 解决方法。
rename(2)- OverlayFS 不完全支持 `rename(2)` 系统调用。您的应用程序需要能够检测到它的失败,并回退到“复制和取消链接”的策略。