OverlayFS存储驱动
OverlayFS是一个现代的Union Filesystem,类似于AUFS,但速度更快,实现更简单。Docker为OverlayFS提供了两个存储驱动程序:overlay,以及更新和更稳定的overlay2。(本次主题在Linux内核中对应的驱动是OverlayFS,在Docker中对应的存储驱动是 overlay 或 overlay2)
注意:如果你的Linux使用的是OverlayFS,请使用overlay2作为驱动而不是overlay,因为overlay2在inode利用率上更高效。使用overlay2要求Linux内核版本在4.0或者更高。
先决条件
OverlayFS是Docker推荐的存储驱动,但在使用前,需要满足如下先决条件:
● Linux内核版本需要为4.0或者更高。RHEL和CentOS的版本需要为3.10.0-514或者更高。如果我们使用比较老的内核,我们需要使用不推荐的 overlay 驱动。
● 当 d_type=true 时,overlay 和 overlay2 才被 xfs 备份文件系统支持。
使用 xfs_info 来验证 ftype 选择是否设置为1。为了正确格式化 xfs 文件系统,需要使用标志 -n ftype=1。
d_type 是 Linux 内核的一个术语,表示 “目录条目类型”,而目录条目,其实是文件系统上目录信息的一个数据结构。d_type,就是这个数据结构的一个字段,这个字段用来表示文件的类型,是文件,还是管道,还是目录还是套接字等。d_type 从 Linux 2.6 内核开始就已经支持了,只不过虽然 Linux 内核虽然支持,但有些文件系统实现了 d_type,而有些,没有实现,有些是选择性的实现,也就是需要用户自己用额外的参数来决定是否开启d_type的支持。
注意其中的 ftype,1表示支持 d_type,0表示不支持。
警告:在没有 d_type 支持的XFS上运行Docker,会导致Docker跳过尝试使用overlay或overlay2驱动程序的阶段。现有的版本将继续运行,但会产生错误。这个操作会允许用户迁移他们的数据。在将来的版本中,这将是一个致命的错误,它将阻止Docker启动。
● 更改存储驱动程序会使本地系统上现有的容器和镜像无法访问。在更改存储驱动程序之前,使用 docker save 保存已构建的任何镜像或将其推送到docker Hub或私有仓库中,以后便不需要重新创建它们。
配置 overlay 或者 overlay2 存储驱动
如果能使用 overlay2 存储驱动,请尽量使用 overlay2 存储驱动,而不是 overlay 存储驱动。Docker EE不在支持 overlay 存储驱动。使用 overlay 存储驱动要求Linux内核版本为3.18或者更新。使用 overlay2 存储驱动要求Linux内核版本为4.0或者更新。
在配置 overlay 或者 overlay2 存储驱动时,请先满足上一节的先决条件。
下面的步骤将讲述如何配置 overlay2 存储驱动。如果您需要使用旧版的 overlay 驱动程序,请指定它。
- 暂停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/sysconfig/docker-storage 文件。
修改后红线标注的存储驱动名称。目前,我只尝试过将其从overlay2修改为overlay。
- 启动Docker
$ sudo systemctl start docker
- 验证daemon是否在使用overlay。
$ docker info
这是修改之前的信息:
Docker现在正使用 overlay2存储驱动程序,并自动创建了包含所需 lowerdir、upperdir、merged 和 workdir 结构。
这是修改后的信息:
Overlay2存储驱动是如何工作的
OverlayFS在一个Linux主机上分层为两个目录,并将它们显示为一个目录。这些目录称为 layers
,统一为一个目录的过程称为union mount
。OverlayFS
将下层目录称为lowerdir
,将上层目录称为upperdir
。统一视图将它们统一为一个目录,这个过程称为merged
。overlay2
驱动程序原生支持多达128个较低的OverlayFS层。此功能为与层相关的Docker命令(如docker build
和docker commit
)提供了更好的性能,并且减少了在后台文件系统上inodes的消耗。
Overlay2如何在磁盘上存储镜像层和容器层
使用命令docker pull ubuntu:18.04
拉取具有三层的ubuntu 18.04镜像。
注意:不要直接操作/var/lib/docker/中的任何文件或目录。这些文件和目录由Docker管理。
在/var/lib/docker/overlay2
文件夹中,我们可以看到四个文件夹。
新的l
(lowercase L
)目录包含作为符号链接的缩短层标识符。这些标识符用于避免达到mount
命令参数的页面大小限制。
最底层镜像5a54cd...729包含一个link
和一个diff
文件夹。link
存储的是镜像层的id。文件夹diff
包含了镜像层的内容。
查看link
文件内容:
查看diff
文件夹内容:
倒数第二层镜像和其往上的每一层镜像都包含:link
文件:镜像层的id。lower
文件:表示其依赖的上层镜像。以及一个名为diff
的文件夹,包含其镜像层的内容。还包含一个merged
文件夹,其中包含上层镜像和自身统一的内容,以及一个work
文件夹,供OverlayFS在内部使用。
查看倒数第二层镜像509376...65c的link
文件和lower
文件
link
文件:镜像层的ID
lower
文件:表示其依赖的上层镜像
2J60...CONP是最底层镜像5a54cd...729的ID,所以表明倒数第二层镜像509376...65c是依赖于最底层镜像5a54cd...729创立的。
倒数第三层镜像d6f68c...49b也是Ubuntu 18.04镜像的最顶层镜像
查看倒数第三层镜像d6f68c...49b的link
文件和lower
文件
link
文件:镜像层的ID
lower
文件:表示其依赖的上层镜像
V27C...NNCB是倒数第二层镜像509376...65c的ID,2J60...CONP是最底层镜像5a54cd...729的ID。表明倒数第三层镜像d6f68c...49b是依赖于倒数第二层镜像509376...65c和最底层镜像5a54cd...729建立的。镜像id出现越早表示镜像在越高层,镜像id出现越晚表示镜像在越底层。
Overlay存储驱动是怎么工作的?
此内容仅适用于overlay存储驱动程序。Docker建议使用overlay2驱动程序,它的工作方式不一样。
OverlayFS在一个Linux主机上分层两个目录,并将它们显示为一个目录。这些目录称为layers,统一过程称为union mount。OverlayFS底层的目录叫做lowerdir
,上层目录叫做upperdir
。两个目录的统一视图称为merged
。
下图展示了Docker镜像和容器是如何分层的。镜像层就是lowerdir
,容器层就是upperdir
。统一视图通过名为merged
的目录公开,该目录实际上是容器装载点。该图显示了Docker存储架构如何映射到OverlayFS存储架构。
当图像层和容器层包含相同的文件时,容器层会掩盖图像层中存在相同的文件,优先显示容器层的文件。Overlay
存储驱动只适用于两层。这意味着多层镜像不能实现为多个OverlayFS层。相反,每个镜像层在/var/lib/docker/overlay
下实现为自己的目录。然后,硬链接被用作为一种节省空间的方式与底层镜像共享的数据。使用硬链接会导致过度使用inode,这是overlay
存储驱动程序的已知限制,并且可能需要额外配置备份文件系统来解决。
创建容器时,overlay
驱动会将镜像顶层目录与容器的新目录进行结合。在overlay
中,镜像顶层就是lowerdir
,它是只读的。容器的新目录是指upperdir
,它是可读写的。
Overlay如何在磁盘上存储镜像层和容器层
使用命令docker pull ubuntu:18.04
拉取具有三层的ubuntu 18.04镜像。镜像层的ID与目录的ID不对应。
镜像层
每个镜像层在/var/lib/docker/overlay/中都有自己的目录,如下所示。
overlay的每一个镜像层都有一个完整的目录结构,镜像与镜像之间通过硬链接共享文件。镜像层目录包含该层特有的文件以及与较低层共享的数据的硬链接。这样可以有效地利用磁盘空间。
查看1768...5cb镜像层和4273..c5c6镜像层通过硬链接共享的文件:
可以看到1768...5cb镜像层和4273..c5c6镜像层的ls命令文件都有相同的inode号,表明它们是通过硬链接共享的。
(一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。)
容器层
容器层也存在于磁盘上Docker主机的文件系统的/var/lib/Docker/overlay下。
运行一个ubuntu18.04镜像。
新增加的两个文件夹是容器层,5c7a...b0a-init是容器初始层。
查看5c7a...b0a层的内容:
5c7a...b0a容器层包含三个文件夹和一个文件。lower-id
文件包含容器基于镜像的顶层id,即OverlayFS的lowerdir
。
查看5c7a...b0a容器层的lower-id
:
5c7a...b0a容器层的lower-id
正好是镜像层lowerdir
的ID。
查看5c7a...b0a容器层的ls命令文件:
跟前面的1768...5cb镜像层和4273..c5c6镜像层的ls命令文件都有相同的inode号,这表明每次新创建容器时都会创建大量的硬链接,消耗大量的inode。在Linux系统中,inode的数量即硬链接的数量是有限的,达到一定数量后将无法再创建新的容器。这是使用overlay存储驱动的一个缺点。
upper
目录包含容器读写层的内容,它对应于OverlayFS的upperdir
。
merged
目录是lowerdir
和upperdir
的联合挂载,它是运行容器中的文件系统视图。
work
目录是OverlayFS的内部目录。
容器是如何使用overlay或者overlay2进行读写的?
读取文件
考虑三种情况,容器使用overlay驱动打开一个文件进行读取访问。
● 文件不存在于容器层:如果容器打开或者读取一个文件,这个文件不存在于容器层(upper
目录),容器将从镜像层(lowerdir
目录)读取该文件。这会产生很小的性能开销。
● 文件只存在于容器层:如果容器打开或者读取一个文件,这个文件存只在于容器层(upper
目录),不存在于镜像层(lowerdir
目录),容器会直接从容器层中读取该文件。
● 文件在容器层和镜像层中都存在:如果容器打开或者读取一个文件,这个文件在容器层和镜像层中都存在,容器将会读取容器层中的文件。容器层(upper
目录)中的文件会模糊镜像层(lowerdir
目录)中的同名文件。
修改文件或文件夹
考虑如下场景,当容器中的文件被修改时。
● 第一次写文件:容器第一次写入存在于镜像层(lowerdir
目录),不存在于容器层(upper
目录)的文件。overlay
或者overlay2
驱动会执行copy_up操作,将文件从镜像层(lowerdir
目录)拷贝到容器层(upper
目录)。容器会将更改写入容器层中文件的新副本。但是,OverlayFS是在文件级别,而不是块级别上工作的。这意味着所有OverlayFS的copy_up操作都将复制整个文件,即使该文件非常大但只有一小部分被修改。这会对容器写入性能产生显著影响。然而,有两点值得注意:
○ copy_up操作只在第一次写入给定文件时发生。对同一文件的后续写入操作是针对已复制到容器的文件副本。
○ OverlayFS仅适用于两层。这意味着性能应该比AUFS好,AUFS在搜索具有多个层的镜像中的文件时,AUFS可能会有明显的延迟。这项优势应用于 overlay
和overlay2
驱动。overlayfs2
在初始读取时,性能会略低于overlayfs
。因为overlayfs2
必须遍历更多层,但它会缓存结果,因此这只是一个很小的损失。
● 删除文件和文件夹:
○ 当在容器中删除文件时,容器层(upperdir目录)中会创建一个 whiteout文件(空白文件)。镜像层(lowerdir
目录)中的文件不会被删除,因为镜像层(lowerdir
目录)是只读的。但是,whiteout文件(空白文件)将阻止容器使用它。
○ 当在容器中删除文件夹时,容器层(upperdir目录)中会创建一个opaque文件夹(不透明文件夹)。这与whiteout文件的工作方式相同,可以有效地防止目录被访问,即使它仍然存在于镜像层(lowerdir
目录)。
● 修改目录名称:只有当源路径和目标路径都在顶层时,才允许对目录调用rename(2)
。否则,会返回EXDEV
错误(“不允许跨设备链接”)。我们的程序需要设计处理EXDEV
错误的功能,并回退到”copy and unlikn“策略。
OverlayFS和Docker的性能
overlay2
和overlay
的性能都要比aufs
和devicemapper
好。在某些特定的场景中,overlay2
的性能要比btrfs
好。然后,我们需要明白如下细节:
● 页缓存。OverlayFS支持页缓存共享。访问同一文件的多个容器共享该文件的单个页缓存项。这使得overlay2
和overlay
驱动具有高效的内存使用效率,并且是PaaS等高密度用例的好选择。
● copy_up。与AUFS一样,OverlayFS在容器第一次写入文件时执行copy_up操作。这会增加写入操作的延迟,特别是对于大文件。但是,一旦文件被复制,对该文件的所有后续写入都发生在容器层(upper
目录),而不需要进一步的复制操作。
OverlayFS的copy_up操作比AUFS的copy_up操作更快。因为AUFS支持的层比OverlayFS多,在众多的AUFS层中搜索文件可能会造成比较大的延迟。overlay2
同样支持多层,但是overlay2
通过缓存技术减轻了多层搜索对性能的影响。
● Inode限制。使用overlay
驱动可能会消耗大量的innode。当主机上存在大量镜像和容器时,这种情况会更加严重。增加文件系统可用inode数量的唯一方法是重新格式化它。为了避免出现这个问题,强烈推荐使用overlay2
驱动。
性能的最佳实践
以下通用性能最佳实践也适用于OverlayFS。
● 使用更快的存储介质:固态硬盘(SSDs)比传统的磁盘具有更快的读写速度。
● 对于写操作繁重的工作负载,使用volumes:Volumes为写操作繁重的工作负载提供了最佳的性能。这是因为volumes绕过了存储驱动程序,并且不会产生thin provisioning和copy-on-write带来的任何潜在开销。volumes还有其他好处,比如允许我们在容器之间共享数据,并会持久化保存数据即使没有容器使用这些数据。
OverlayFS兼容性的限制
下面概述OverlayFS与其他文件系统不兼容的方面:
● open(2):OverlayFS只是实现了POSIX标准的一个子集。这可能导致某些OverlayFS操作违反POSIX标准。其中一种操作是copy_up操作。我们的程序在运行操作fd1=open("foo", O_RDONLY)
和fd2=open("foo", O_RDWR)
。在这种情况下,我们程序期望的是fd1
和fd2
指向的是同一个文件。但是,由于在第二次调用open(2)
之后发生copy_up操作,文件描述符引用了不同的文件。fd1
继续引用镜像层(lowerdir
目录)中的文件,而fd2
则引用容器层(upperdir目录)中的文件。一种解决方法是touch导致copy_up操作发生的文件。所有后续的open(2)
操作,无论是只读还是读写访问模式,都会引用容器层(upperdir目录)中的文件。
据悉,yum
会受到影响,除非安装了yum-plugin-ov1
软件包。如果yum-plugin-ov1
软件包我们的系统中(比如RHEL/CentOS 6.8之前的版本,或者7.2)不支持,我们可能需要在运行yum install
之前运行touch /var/lib/rpm/*
。yum-plugin-ov1
软件包实现了上面针对yum
的touch
操作。
● rename(2):OverlayFS并没有完成支持rename(2)
系统调用。我们的程序需要检测这个失败,并且回退到“copy and unlink”策略。
参考文章
[1] Use the OverlayFS storage driver,https://docs.docker.com/storage/storagedriver/overlayfs-driver/
[2] overlay和overlay2的区别,https://www.cnblogs.com/elnino/p/11015076.html
转自:知乎用户吴小正的文章