Docker的 Overlay/Overlay2 文件系统

    Overlay 文件系统(OverlayFS)是一个很接近  AUFS(一个ubantu自带的文件系统,有兴趣可以百度)的文件系统,但设计更轻量,速度也更快。Docker提供了两种 OverlayFS,一个是原本的 overlay,另一个是更新、更稳定的 overlay2。在日常使用中,应该更倾向于使用更好更稳定的 overlay2而不是 overlay。

配置、启用overlay2

    较高版本的docker已经默认启用了 overlay2,如果处于之前的版本,那么可以手动开启overlay2来取代overlay:

  1. 先停掉docker.

    $ sudo systemctl stop docker
    
  2.  将/var/lib/docker 下的内容拷贝出去一个临时的地方备份好,Just in case。

    $ cp -au /var/lib/docker /var/lib/docker.bk
    
  3. 如果你考虑在 /var/lib/docker 里用上另外一个backing filesystem(文件系统默认是宿主机的,但我们可以选择另一个设备挂载上去),那么要注意把这个文件系统格式化好后,挂载到 /etc/fstab 下,这是因为 /etc/fstab 下的设备会被操作系统自动加载,而不需要手动挂载,这样docker才不会第一时间找不到路径而导致其他问题。

  4. 编辑 /etc/docker/daemon.json文件,如果还没有就创建。假设现在是全新创建,把下面的内容加进去。

    {
      "storage-driver": "overlay2"
    }
    

    如果 daemon.json 文件不符合JSON格式,docker服务会启动失败。

  5. 启动Docker.

    $ sudo systemctl start docker
    
  6. 验证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 等结构。

overlay2 驱动是如何工作的

OverlayFS  在一台Linux主机上将两个目录分层,并合并为一个目录呈现。这些分层的文件夹被称作layers(层)然后这个联合结合的过程叫做union mount(联合挂载)。OverlayFS将低层的文件夹引用为lowerdir,高层的文件夹引用为 upperdir。高低两层合并显示出来的结果在一个叫merged的文件夹呈现出来(看上去就是一个普通的文件结构了)。 

overlay2 驱动原生支持最高128层的OverlayFS 层。这个能力对跟层有关的操作相当友好,比如docker builddocker commit,它将消耗主机上更少的inodes资源。 

磁盘上的镜像和容器层

这里举拉取乌邦图镜像的例子来说明。

在使用指令docker pull ubuntu下载五层镜像的ubantu后,你可以在/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 文件夹,这个文件夹就是之前提到的底层跟自己层联合显示的内容了。还有一个 work 文件夹给 OverlayFS使用,我们并不关心。

$ 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

可以使用mount指令来查看overlay存储驱动的挂载。下面的输出截取了一部分方便阅读。

$ 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 挂载是可以读写的。

overlay 驱动是如何工作的

*现时只建议使用 overlay2.有关overlay2请参阅上面。

OverlayFS  在一台Linux主机上将两个目录分层,并合并为一个目录呈现。这些分层的文件夹被称作layers(层)然后这个联合结合的过程叫做union mount(联合挂载)。OverlayFS将低层的文件夹引用为lowerdir,高层的文件夹引用为 upperdir。高低两层合并显示出来的结果在一个叫merged的文件夹呈现出来(看上去就是一个普通的文件结构了)。 

下图显示了docker镜像跟容器是怎么层叠起来的。 镜像层是 lowerdir ,容器层是 upperdir. 联合视图通过文件夹merged展现出来,并有效地挂载到容器里。 该图展示了Docker结构与OverlayFS结构的映射关系。

Docker的 Overlay/Overlay2 文件系统_第1张图片

当镜像层跟容器层有着同一个文件时,容器层的文件会“胜出”,把镜像层的文件屏蔽后将自己呈现给联合视图。(这些同样可以用于理解 overlay2

The overlay 驱动仅对两层负责,也就是说,并不支持多层镜像结构,不像overlay2。与之相对,OverlayFS的层们都会在/var/lib/docker/overlay拥有自己的层次文件夹。硬链接(Hard links)做为一个比较节省磁盘空间的用法,会被用在两层间进行文件共享(在overlay2中,则是一个lower的id进行引用)。硬链接的使用会带来另一个问题:那就是目录文件对inodes的过度使用,这是一个已知的经典款overlay存储驱动的限制之处。这个限制很可能对主机的存储系统提出了一些额外的配置要求。关于这个参考下面的性能、限制说明 。

创建一个容器时,overlay驱动会合并镜像的最顶层和一个全新的属于容器的文件夹。镜像的最顶层做为容器文件夹的lowerdir,并且是只读的。容器的新文件夹层则是upperdir,而且可被读写。 

磁盘上的镜像和容器层

下面的 docker pull 命令展示了如何下载一个有五层结构压缩的镜像。

$ 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(从 docker 1.10 后),这两者是不同的。

警告: 不要直接操作/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

这些镜像层文件夹下,有此层独有的文件,也有对下层引用的硬链接文件。这样可让硬盘空间的利用率有效上升。下图展示了两个层对同一个inode文件的硬链接引用。

$ ls -i /var/lib/docker/overlay/38f3ed2eac129654acef11c32670b534670c3a06e483fce313d72e3e0a15baa8/root/bin/ls

19793696 /var/lib/docker/overlay/38f3ed2eac129654acef11c32670b534670c3a06e483fce313d72e3e0a15baa8/root/bin/ls

$ ls -i /var/lib/docker/overlay/55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358/root/bin/ls

19793696 /var/lib/docker/overlay/55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358/root/bin/ls

容器层

容器层同样以硬盘文件的形式存在于/var/lib/docker/overlay/。如果你用命令来现实容器在overlay下的子文件夹,会得到下面的结果,三个文件夹和一个文件:

$ ls -l /var/lib/docker/overlay/

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/overlay/ec444863a55a9f1ca2df72223d459c5d940a721b2288ff86a3f27be28b53be6c/lower-id

55f1e14c361b90570df46371b20ce6d480c434981cbda5fd68c6ff61aa0a5358

upper 文件夹就是本层的可读写层(在 overlay2中的变成了diff,更强调差异语义)。它也就是 OverlayFS概念中的 upperdir

merged 文件夹是和文件夹的联合挂载,在运行中的容器看过去的文件系统就是它(有点拗口)。

work 文件夹则是OverlayFS内部文件,我们可不关心。

使用overlay存储驱动来结合docker后,用 mount 指令可查看挂载情况. 下面的输出删减了一些以方便阅读。

$ mount | grep overlay

overlay on /var/lib/docker/overlay/ec444863a55a.../merged
type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay/55f1e14c361b.../root,
upperdir=/var/lib/docker/overlay/ec444863a55a.../upper,
workdir=/var/lib/docker/overlay/ec444863a55a.../work)

上面第二行的 rw 表示此 overlay 挂载是可以读写的。

overlay或者overlay2的基础上,容器如何读写文件 

读取文件

考虑容器在overlay下打开或读取文件的三种场景。

  • 文件不在容器层上: 如果容器要访问一个不在容器层 (upperdir) 上的文件,那他会从镜像层 (lowerdir)上读取。 这产生的性能开销很小。

  • 文件只存在于容器层上:如果容器要访问一个只在容器层 (upperdir) 上而不在镜像层 (lowerdir)上的文件时,他会直接从容器里读取。

  • 文件同时存在于容器层和镜像层: 如果容器要访问一个同时存在于容器层 (upperdir) 和镜像层 (lowerdir)上的文件时,会去读取容器层的版本。镜像层的同名文件会被容器层的文件屏蔽。

修改文件或文件夹

考虑几种在容器中修改文件的可能场景。

  • 第一次写入某个文件: 第一次写入一个已经存在的文件时,这个文件并不在容器层(upperdir).overlay/overlay2 驱动会执行一个写时拷贝(copy_up) 操作,把文件从镜像层中 (lowerdir) 拷贝到镜像层 (upperdir). 容器写入的内容会被写入到容器层的这个拷贝中。

    然而, OverlayFS 只针对文件的层次结构生效,而不是文件的块。这样会导致,就算是一个很大的文件,只要修改了一点点,就会导致整个文件在不同层次间的拷贝。这会对容器的写性能带来显著的负面影响。同样的然而, 两件事需要注意:

    • 向上写时拷贝(copy_up)只会发生在第一次写文件时。后续的写操作,就只会对已经拷贝上来的文件进行了。

    • OverlayFS 只在两层中进行。这意味着它的性能要好于AUFS,因为AUFS需要扫描多层,它在找文件的时候会有非常大的延迟。这个优势同时体现在overlayoverlay2驱动上。overlayfs2 在初始化时读取文件的性能要比 overlayfs 稍差,因为它需要寻找多层,不过它会缓存好结果以提升下次的访问速度,所以这个惩罚也就不算严重了。

  • 删除文件或者文件夹:

    • 当在容器中删除一个文件时, 容器层 (upperdir)中会创建一个空白(whiteout )文件。此文件在镜像层 (lowerdir) 中的版本将不会被删除 (因为lowerdir 是只读的)。同时,在容器层中的空白文件,会屏蔽掉下层镜像层的文件的可见性,以达到“删除”的效果。

    • 当在容器中删除一个文件夹时,一个隐藏文件夹(opaque directory)也会在容器层(upperdir)中被创建出来。这有点像创建空白文件的方式,来屏蔽掉镜像层(lowerdir)的内容,即使此时镜像层的文件夹还是存在的。

  • 重命名文件夹: 只有源文件夹跟目的文件夹都在最上层的时候,才允许调用 rename(2) 操作。否则,它会返回 EXDEV 错误 (“跨设备链接是被禁止的(cross-device link not permitted)”)。你的应用程序必须处理 EXDEV错误,并退回到复制、断开链接(copy and unlink)的策略。也就是先复制一份过去,再删除旧的。

OverlayFS 和 Docker 的性能表现

 overlay2 和overlay 驱动的性能,比 aufs 和 devicemapper都要好。 在一些特定的情况下, overlay2 的性能甚至比 btrfs 也还要更优秀。同时,有一些问题是需要注意的。

  • 页面缓存(Page Caching). OverlayFS 支持页面缓存共享。多个容器访问同一个文件时,会共用此文件的页面缓存。这让overlayoverlay2驱动的内存效率很高,非常适合高频使用场景,比如PaaS。 

  • 向上写时拷贝(copy_up). 跟AUFS系统一样,OverlayFS在容器第一次写入文件时,会执行一次文件的向上拷贝操作。这会使得写操作的延迟增加,特别是写入一些大文件的时候。当然这只会发生在第一次写入时,后续的所有写入都会基于第一次拷贝上来的文件来进行。

    OverlayFS 向上写时拷贝(copy_up )操作会比在AUFS执行同样的操作要快,这还是因为AUFS支持更多的层次,在AUFS中进行文件的查找会导致更加严重的延迟。overlay2 也支持多层次,不过它通过缓存机制减少了这种影响。

  • Inode 的限制. 经典的 overlay 存储驱动会导致 Inode 的过量使用。特别是在宿主机上使用大量的镜像和容器的情况下,此问题会更加突出。而唯一的提高inode可用数量的方法,是重新格式化文件系统。这个代价如此之高,所以强烈建议使用overlay2而非overlay。可能有些小朋友会问了:overlay用的是hardlink,怎么会导致inode的耗尽呢?这是因为目录本身也是个文件,且不可进行hardlink,于是上层对下层的引用必须新建一个目录,也占用一个inode。目录里只有目录项,本身体积很小,属于典型的小文件。如果下层的文件结构一复杂,就要创建出很多同款的目录来,对inode资源来说是个巨大的消耗。为了优化这一点,overlay2使用了id(层文件夹下的lower文件)映射下层的方式去查找下层文件。查找的过程中,缓存其结果到内存里减少下次查找的时间,减轻了inode创建的大量消耗。

性能优化方法

下面的通用性能优化方法同样适用于 OverlayFS.

  • 使用更快的存储介质: 固态盘(solid)的性能会比传统机械寻址盘(spining)更好。

  • 在高写负载时,使用卷(volumes): 卷在高写负载的场景下提供了相当好以及可控的性能。这是因为使用卷可以绕开上面提到的层文件的种种逻辑, 也就避开了写时拷贝、轻量化配置(thin provisioning)的潜在开销。此外,卷也有其他的好处,比如让你在不同容器间共享数据,以及脱离容器的生命周期来持久化数据。

OverlayFS 的兼容性限制

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

  • open(2): OverlayFS只实现了POSIX标准的一个子集。这可能会导致某些OverlayFS操作违反POSIX标准。其中一个操作就是写时向上拷贝(copy-up)。假设你的应用程序调用了 fd1=open("foo", O_RDONLY) ,跟着调用 fd2=open("foo", O_RDWR)。在这种情况下,fd1fd2应该指向同一个文件。但由于写时向上拷贝(copy-up)的存在,导致fd1fd2引向了不同的文件。fd1引用的是镜像层(lowerdir)文件,fd2 引用的则是容器层(upperdir)文件。一个变通方法是 touch 这些文件,让这些空白文件先触发向上写时拷贝,之后的所有  open(2) 操作,不管是只读还是读写模式,都会正确指向容器层 (upperdir) 里的文件了。

    yum 就是一个已知的上述问题受害者。除非安装了yum-plugin-ovl 包,否则比如在6.8或者7.2前的RHEL/CentOS里,你必须先执行touch /var/lib/rpm/* 才能执行yum install 。yum-plugin-ovl 包其实就是帮yum实现了touch 变通。

  • rename(2): OverlayFS 没有完全支持 rename(2) 系统调用. 你的应用需要检测它的报错,然后采取 “复制、断链(copy and unlink)” 策略。

你可能感兴趣的:(杂七杂八笔记,后端,centos,linux,docker)