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` 存储驱动程序。

  1. 停止 Docker。

    $ sudo systemctl stop docker
    
  2. 将 `/var/lib/docker` 的内容复制到一个临时位置。

    $ cp -au /var/lib/docker /var/lib/docker.bk
    
  3. 如果您想使用与 `/var/lib/` 不同的后备文件系统,请格式化该文件系统并将其挂载到 `/var/lib/docker`。请务必将此挂载添加到 `/etc/fstab` 以使其永久生效。

  4. 编辑 `/etc/docker/daemon.json`。如果它还不存在,请创建它。假设该文件是空的,添加以下内容。

    {
      "storage-driver": "overlay2"
    }

    如果 `daemon.json` 文件包含无效的 JSON,Docker 将不会启动。

  5. 启动 Docker。

    $ sudo systemctl start docker
    
  6. 验证守护进程是否正在使用 `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` 的目录公开,该目录实际上是容器的挂载点。

How Docker constructs map to OverlayFS constructs

当镜像层和容器层包含相同的文件时,容器层 (`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)` 系统调用。您的应用程序需要能够检测到它的失败,并回退到“复制和取消链接”的策略。
© . This site is unofficial and not affiliated with Kubernetes or Docker Inc.