Docker:overlay2浅析以及解决overlay2 文件过大的问题

最近在学习docker的实现时看到这么一个概念:Union File System,先让我们来介绍介绍它。

Union File System

定义:联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。
主要有两个细节:

  • 可以将不同目录挂载到同一个虚拟文件系统下:
    这就意味着一个文件系统被挂载时不再只能有一个目录下的内容,而是多个。
  • 支持对文件系统的修改作为一次提交来一层层的叠加:
    这点其实有一点像git的工作方式,每次的commit就相当于一次增量,上层的commit由下层的一层层commit,以增量的方式组织在一起。

那么这么做有什么好处?

我们知道,镜像可以理解为容器的模板,一套镜像可以衍生出多个容器。
那么多个容器势必有相同的镜像层,如果我们每次拷贝一份,那么对存储空间的要求的相当大的,并且也不利于后续的整合和发布,因为这样意味着当别人需要你的容器时,你commit的就是一整份镜像,这好比使用git时,当你需要获取origin的更新资源时,需要pull一整份代码下来。
因此,通过镜像层增量的方式来组织,使不同的docker容器可以共享相同的镜像层,再加上自己的改动进行发布。
而docker就支持多种UnionFS,如aufs、overlay2等等。

overlay2运作方式

overlay2主要由merged、lowerdir、upperdir、workdir组成。
其中,lowerdir对应底层文件系统,也就是那一层层“commit”的内容,它是能被上层文件系统upperdir所共享的只读层。workdir则可以理解为overlay2运作的一个工作目录,用于完成copy-on-write等操作。copy-on-write这点我们会在实操中谈到它。
overlay2运作时,会将lowerdir、upperdir和workdir联合挂载到merged目录,为使用者提供一个“统一视图”。

Docker:overlay2浅析以及解决overlay2 文件过大的问题_第1张图片
这张其实是docker官网中的overlay的原理示意图,不过也能帮助我们理解overlay2的运作方式。这里要注意的是,overlay2中lowerdir可以有很多层,这里只画了一层,我们可以脑补它为很多层。overlay和overlay2的区别可以参考下面。

那么当我们想删除或者修改lowerdir中内容时,overlay2是如何处理的?不是说它是只读层吗,只读意味着无法修改,但我们又确实可能需要修改它,那lowerdir的出现都无法满足我们的使用需求了吗?
下面一起实操来看看overlay2是怎么解决这些问题的吧。

overlay2实操

下面是man mount中对overlay2的描述。

Mount options for overlay
       Since Linux 3.18 the overlay pseudo filesystem implements a union mount for other filesystems.

       An overlay filesystem combines two filesystems - an upper filesystem and a lower filesystem.  When a name exists in both filesystems, the
       object  in  the  upper  filesystem  is  visible while the object in the lower filesystem is either hidden or, in the case of directories,
       merged with the upper object.

       The lower filesystem can be any filesystem supported by Linux and does not need to be writable.  The lower filesystem can even be another
       overlayfs.   The  upper  filesystem will normally be writable and if it is it must support the creation of trusted.* extended attributes,
       and must provide a valid d_type in readdir responses, so NFS is not suitable.

       A read-only overlay of two read-only filesystems may use any filesystem type.  The options lowerdir and  upperdir  are  combined  into  a
       merged directory by using:

              mount -t overlay  overlay  \
                -olowerdir=/lower,upperdir=/upper,workdir=/work  /merged

       lowerdir=directory
              Any filesystem, does not need to be on a writable filesystem.

       upperdir=directory
              The upperdir is normally on a writable filesystem.

       workdir=directory
              The workdir needs to be an empty directory on the same filesystem as upperdir.

我们照葫芦画瓢,创建好对应的目录和文件。

nigo@DESKTOP-95TV8LK ~/overlay2> tree
.
├── lower1
├── lower2
├── merged
├── upper
└── work

5 directories, 0 file
nigo@DESKTOP-95TV8LK ~/overlay2> echo 'I\'m file1, belong to lower1' > lower1/file1.txt
nigo@DESKTOP-95TV8LK ~/overlay2> echo 'I\'m file2, belong to lower2' > lower2/file2.txt
nigo@DESKTOP-95TV8LK ~/overlay2> echo 'I\'m file3, belong to upper' > upper/file3.txt

现在lowerdir和upperdir都有它们各自的文件。

nigo@DESKTOP-95TV8LK ~/overlay2> tree
.
├── lower1
│   └── file1.txt
├── lower2
│   └── file2.txt
├── merged
├── upper
│   └── file3.txt
└── work

5 directories, 3 files

可以看到,我们成功挂载了这些目录。

nigo@DESKTOP-95TV8LK ~/overlay2 [1]> mount | grep overlay

nigo@DESKTOP-95TV8LK ~/overlay2 [0|1]> sudo mount -t overlay overlay -olowerdir=lower1:lower2,upperdir=upper,workdir=work merged/
[sudo] password for nigo:

nigo@DESKTOP-95TV8LK ~/overlay2> mount | grep overlay
overlay on /home/nigo/overlay2/merged type overlay (rw,relatime,lowerdir=lower1:lower2,upperdir=upper,workdir=work)

而merged中也出现了我们期待的内容。

nigo@DESKTOP-95TV8LK ~/overlay2> sudo tree
.
├── lower1
│   └── file1.txt
├── lower2
│   └── file2.txt
├── merged
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
├── upper
│   └── file3.txt
└── work
    └── work

6 directories, 6 files

nigo@DESKTOP-95TV8LK ~/overlay2> cat merged/*
I'm file1, belong to lower1
I'm file2, belong to lower2
I'm file3, belong to upper

改动属于upperdir的内容肯定会映射到upperdir中,毕竟upperdir是属于当前层的可读写层。而上面提到的lowerdir呢?让我们验证一下它吧。

修改lowerdir的文件

修改lowerdir1中的file1。

nigo@DESKTOP-95TV8LK ~/overlay2> echo 'I\'m file1, belong to lower1' > lower1/file1.txt

nigo@DESKTOP-95TV8LK ~/overlay2> echo 'file1 has been changed' > merged/file1.txt

nigo@DESKTOP-95TV8LK ~/overlay2> cat merged/*
file1 has been changed
I'm file2, belong to lower2
I'm file3, belong to upper

nigo@DESKTOP-95TV8LK ~/overlay2> cat lower1/file1.txt
I'm file1, belong to lower1

nigo@DESKTOP-95TV8LK ~/overlay2> sudo tree
.
├── lower1
│   └── file1.txt
├── lower2
│   └── file2.txt
├── merged
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
├── upper
│   ├── file1.txt
│   └── file3.txt
└── work
    └── work

6 directories, 7 files

可以看到,merged中的file1.txt确实被我们修改了,但lowerdir中的内容仍然不变,而是在upperdir中生成了一个file1.txt,这就是copy-on-write。这也验证了lowerdir是只读层这一点。
copy-on-write,即写时复制,概念类似于linux fork后尝试对父子进程共享的页表进行修改时,内核为子进程重新复制一份页表。在这里,overlay2在upperdir中生成了一份file1.txt。

删除lowerdir的文件

试着删除file2.txt。

nigo@DESKTOP-95TV8LK ~/overlay2> rm merged/file2.txt
nigo@DESKTOP-95TV8LK ~/overlay2> sudo tree
.
├── lower1
│   └── file1.txt
├── lower2
│   └── file2.txt
├── merged
│   ├── file1.txt
│   └── file3.txt
├── upper
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
└── work
    └── work

6 directories, 7 files

nigo@DESKTOP-95TV8LK ~/overlay2> cat merged/*
file1 has been changed
I'm file3, belong to upper

nigo@DESKTOP-95TV8LK ~/overlay2> cat lower2/file2.txt
I'm file2, belong to lower2

nigo@DESKTOP-95TV8LK ~/overlay2> ll upper/file2.txt
c--------- 1 root root 0, 0 Apr 22 14:44 upper/file2.txt

从结果来看,file2.txt确实被我们“删掉”了。但在upperdir中,我们看到生成了一个file2.txt的特殊的字符设备文件。

而overlay2在联合挂载时,看到这个特殊的字符设备文件,会选择性的忽略lowerdir中对应的内容。

而在aufs(另一种UnionFS)中表现为whiteout文件(可以查一下这个词,意为临时性失明,挺形象的emm)。感兴趣的同学可以搜一下,目前网上的大多数实验也是关于aufs的。

overlay2与Docker

行实验前,请确保你的docker使用overlay2的驱动方式。
可以使用docker info,或者查看/etc/docker/daemon.json中的内容。
具体请参考:Use the OverlayFS storage driver | Docker Documentation

nigo@DESKTOP-95TV8LK ~> docker info | grep Storage
 Storage Driver: overlay2

我们以ubuntu为例子,pull下来三层镜像层。

nigo@DESKTOP-95TV8LK ~> docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
a70d879fa598: Pull complete
c4394a92d1f8: Pull complete
10e6159c56c0: Pull complete
Digest: sha256:3c9c713e0979e9bd6061ed52ac1e9e1f246c9495aa063619d9d695fb8039aa1f
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

在/var/lib/docker/overlay2中我们可以成功的找到它们,由于一些目录比较深,我们可以通过-L参数来指定tree访问的深度。

root@DESKTOP-95TV8LK:/var/lib/docker/overlay2# tree -L 2
.
├── 809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04
│   ├── committed
│   ├── diff
│   └── link
├── ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
└── l
    ├── 2FMRPFC5X2PFHGEZII4KC47JF4 -> ../ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59/diff
    ├── EVRLLRGLJ5K5374Z6B32BREDLT -> ../809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04/diff
    └── UXEF4DBCSIKECRM2J2IPAD4WY5 -> ../adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/diff

12 directories, 7 files

在/var/lib/docker/overlay2中我们可以成功的找到它们,由于一些目录比较深,我们可以通过-L参数来指定tree访问的深度。

root@DESKTOP-95TV8LK:/var/lib/docker/overlay2# tree -L 2
.
├── 809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04
│   ├── committed
│   ├── diff
│   └── link
├── ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
└── l
    ├── 2FMRPFC5X2PFHGEZII4KC47JF4 -> ../ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59/diff
    ├── EVRLLRGLJ5K5374Z6B32BREDLT -> ../809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04/diff
    └── UXEF4DBCSIKECRM2J2IPAD4WY5 -> ../adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/diff

12 directories, 7 files

但在这里多出了一个l目录,里面存放的指向各层的软链接,而且软链接的名字显然被缩短过。根据docker官网的说明,这些软链接用于避免达到 mount命令对页面参数的页面大小限制。

在各层目录下还存在着link文件,这些目录中放的就是l目录中那些被缩短过的软链接名称。

root@DESKTOP-95TV8LK:/var/lib/docker/overlay2# cat adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/link
UXEF4DBCSIKECRM2J2IPAD4WY5

使用docker inspect,在GraphDriver这里找到关于该ubuntu镜像的信息。

"GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59/diff:/var/lib/docker/overlay2/809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04/diff",
                "MergedDir": "/var/lib/docker/overlay2/adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/merged",
                "UpperDir": "/var/lib/docker/overlay2/adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/diff",
                "WorkDir": "/var/lib/docker/overlay2/adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/work"
            },
            "Name": "overlay2"
        }

从inspect返回的信息中可以看到,各层的diff目录就是该层“different”于下层的内容,对于下层镜像,它是只读层(lowerdir),而对于上层,它是可读写层(upperdir),它们也是和workdir被联合挂载到mergeddir的。

上面没有提到的commit文件则是记录这每个层的相关commit信息。

最后让我们来尝试一下创建容器,以及commit对该目录的影响。

nigo@DESKTOP-95TV8LK ~> docker run -itd --name myubuntu ubuntu
d6adc07566d205f1554b2db9534c76713f830a7705e9f41ab30f9c0f4d118b1d
root@DESKTOP-95TV8LK:/var/lib/docker/overlay2# tree -L 2
.
├── 6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778
│   ├── diff
│   ├── link
│   ├── lower
│   ├── merged
│   └── work
├── 6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778-init
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── 809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04
│   ├── committed
│   ├── diff
│   └── link
├── ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
└── l
    ├── 2FMRPFC5X2PFHGEZII4KC47JF4 -> ../ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59/diff
    ├── EVRLLRGLJ5K5374Z6B32BREDLT -> ../809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04/diff
    ├── M32S6F25IVIE4YRIR2BYQX5S4H -> ../6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778-init/diff
    ├── RPNEL4XVFQMLG5Q4BF7BXUYY5C -> ../6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/diff
    └── UXEF4DBCSIKECRM2J2IPAD4WY5 -> ../adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/diff

运行容器后生成了两个新的层,其中一个为init层,这是用来存储和容器环境相关内容的只读层,由于这些环境在每台机器上都可能不同,docker的策略是放在init层,每个镜像生成容器时去生成环境相关的配置。我们在docker commit时,不提交init层的内容。

写入新的文件,执行docker commit来观察结果。

nigo@DESKTOP-95TV8LK ~> docker start myubuntu
myubuntu

nigo@DESKTOP-95TV8LK ~> docker exec -it myubuntu /bin/bash
root@d6adc07566d2:/# touch hello-overlay2.txt

root@d6adc07566d2:/# exit

root@DESKTOP-95TV8LK:/var/lib/docker/overlay2# tree -L 2
.
├── 0991cf894ea2ed9bb2b8313331ac9eb72c3678c26dc0152241e6228105df25f2
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── 6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── 6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778-init
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── 809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04
│   ├── committed
│   ├── diff
│   └── link
├── ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
└── l
    ├── 2FMRPFC5X2PFHGEZII4KC47JF4 -> ../ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59/diff
    ├── DD5NLJQIUJRFR45OMBJIF26CQ3 -> ../0991cf894ea2ed9bb2b8313331ac9eb72c3678c26dc0152241e6228105df25f2/diff
    ├── EVRLLRGLJ5K5374Z6B32BREDLT -> ../809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04/diff
    ├── M32S6F25IVIE4YRIR2BYQX5S4H -> ../6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778-init/diff
    ├── RPNEL4XVFQMLG5Q4BF7BXUYY5C -> ../6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/diff
    └── UXEF4DBCSIKECRM2J2IPAD4WY5 -> ../adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/diff

24 directories, 15 files

果然,新的镜像层生成了!

nigo@DESKTOP-95TV8LK ~> mount | grep overlay
overlay on /var/lib/docker/overlay2/6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/merged type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/M32S6F25IVIE4YRIR2BYQX5S4H:/var/lib/docker/overlay2/l/UXEF4DBCSIKECRM2J2IPAD4WY5:/var/lib/docker/overlay2/l/2FMRPFC5X2PFHGEZII4KC47JF4:/var/lib/docker/overlay2/l/EVRLLRGLJ5K5374Z6B32BREDLT,upperdir=/var/lib/docker/overlay2/6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/diff,workdir=/var/lib/docker/overlay2/6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/work)

而这些被联合挂载的目录,也是l目录中那些被缩短过的软链接。

overlay和overlay2

由于内核支持的原因,这点我并没有做过具体的实验,下面是道听途说的结论:

不同点:
overlay的lowdir只有一层,每层只读层都通过硬链接共享文件,因此每层只读层都有一套完整的增量。
overlay2的只读层是独立的个体,容器启动时统一挂载到merged。
相同点:
都有work目录用于完成copy-on-write等工作,启动时挂载到merged目录。
都支持页缓存,同一镜像的容器有机会使用同一文件,内存消耗更小。
具体请参考:overlay和overlay2的区别 - ElNinoT - 博客园
实验之前也请同样的修改docker的Storage Driver。

ref:https://blog.csdn.net/qq_45858169/article/details/115918469

overlay2 的文件過多

可通过执行docker system prune 命令可用于清理磁盘,删除关闭的容器、无用的数据卷和网络,以及dangling镜像(即无tag的镜像)

你可能感兴趣的:(linux,docker,容器,运维)