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 DriverBacking filesystem

    $ docker info
    
    Containers: 0
    Images: 0
    Storage Driver: overlay2
     Backing Filesystem: xfs
     Supports d_type: true
     Native Overlay Diff: true
    <...>
    

Docker 现在正在使用 overlay2 存储驱动程序,并已自动创建带有所需 lowerdirupperdirmergedworkdir 构造的 overlay 挂载。

继续阅读,了解 OverlayFS 在 Docker 容器中的工作原理、性能建议以及其与不同后备文件系统兼容性的限制。

overlay2 驱动程序的工作原理

OverlayFS 在单个 Linux 主机上分层两个目录,并将它们呈现为一个单一目录。这些目录称为层,统一过程称为联合挂载(union mount)。OverlayFS 将较低的目录称为 lowerdir,将较高的目录称为 upperdir。统一视图通过一个称为 merged 的自有目录暴露。

overlay2 驱动程序原生支持多达 128 个较低的 OverlayFS 层。此功能为与层相关的 Docker 命令(如 docker builddocker 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 目录是 lowerdirupperdirs 的联合挂载,构成了从正在运行的容器内部看的文件系统视图。

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) 中创建一个 whiteout 文件。镜像层 (lowerdir) 中的文件版本不会被删除(因为 lowerdir 是只读的)。但是,whiteout 文件会阻止该文件对容器可用。

  • 在容器内删除目录时,会在容器内创建一个不透明目录 (upperdir)。这与白out文件的工作方式相同,并有效阻止对该目录的访问,即使它仍然存在于镜像中 (lowerdir)。

重命名目录

对目录调用 rename(2) 仅当源路径和目标路径都在顶层时才允许。否则,它会返回 EXDEV 错误("不允许跨设备链接")。您的应用程序需要设计来处理 EXDEV 并回退到"复制然后删除"的策略。

OverlayFS 和 Docker 性能

overlay2 的性能可能优于 btrfs。然而,请注意以下详细信息

页缓存

OverlayFS 支持页面缓存共享。多个容器访问同一文件时,会共享该文件的一个页面缓存条目。这使得 overlay2 驱动程序内存效率高,并且是高密度使用场景(例如 PaaS)的良好选择。

Copyup

与其他写时复制文件系统一样,OverlayFS 会在容器首次写入文件时执行 copy-up 操作。这会增加写入操作的延迟,特别是对于大文件。然而,一旦文件被 copy-up 后,对该文件的所有后续写入都发生在顶层,无需进一步的 copy-up 操作。

性能最佳实践

以下通用性能最佳实践适用于 OverlayFS。

使用快速存储

固态硬盘 (SSD) 比传统机械硬盘提供更快的读写速度。

使用卷用于写密集型工作负载

卷为写密集型工作负载提供最佳且最可预测的性能。这是因为它们绕过了存储驱动程序,并且不会产生由精简配置和写时复制引入的任何潜在开销。卷还有其他好处,例如允许您在容器之间共享数据,并且即使没有正在运行的容器使用它们,也能持久化您的数据。

OverlayFS 兼容性的限制

总结一下 OverlayFS 与其他文件系统不兼容的方面

open(2)
OverlayFS 仅实现 POSIX 标准的一个子集。这可能导致某些 OverlayFS 操作违反 POSIX 标准。其中一个操作就是 copy-up 操作。假设您的应用程序调用 fd1=open("foo", O_RDONLY),然后调用 fd2=open("foo", O_RDWR)。在这种情况下,您的应用程序期望 fd1fd2 引用同一个文件。然而,由于在第二次调用 open(2) 后发生的 copy-up 操作,这些描述符引用不同的文件。fd1 继续引用镜像中的文件 (lowerdir),而 fd2 引用容器中的文件 (upperdir)。解决此问题的一种方法是 touch 这些文件,这会触发 copy-up 操作。所有后续的 open(2) 操作,无论读写模式如何,都引用容器中的文件 (upperdir)。

已知 yum 会受到影响,除非安装了 yum-plugin-ovl 包。如果在您的发行版中(例如 RHEL/CentOS 6.8 或 7.2 之前)此包不可用,您可能需要在运行 yum install 之前运行 touch /var/lib/rpm/*。此包为 yum 实现了上面提到的 touch 解决方法。

rename(2)
OverlayFS 不完全支持 rename(2) 系统调用。您的应用程序需要检测其失败并回退到"复制然后删除"的策略。
Page options