由浅入深 docker 系列: (6) 镜像分层

这篇文章我们简单聊聊 docker 的镜像分层。

思考题

在第四篇文章容器与虚拟机中,我们说到 docker 其实就是把可执行程序及其所有的依赖打包成镜像文件,然后调用宿主机内核接口运行容器。可以想象,像 ubuntu 等基础镜像,体积必然不小。那么,思考以下几个问题:

  • 我们基于同一个镜像(ubuntu 18.4)启动了两个容器,会占用两倍磁盘空间吗?
  • 我们在容器内修改或者新建了某个文件,要修改原镜像吗?
  • 我们基于某镜像(ubuntu 18.04)新建一个镜像(myubuntu),需要将原镜像文件全部拷贝到新镜像中吗?

首先,让我们尝试思考下,如果我们去做,该如何高效的解决这些问题?

  • 问题 1,只要将同一个镜像文件加载到内存不同位置就行了,没必要在磁盘上存储多份,可以节省大量存储空间。
  • 问题 2,我们可以参考 Linux 内核管理内存的 *Copy-On-Write 策略,也即读时大家共用一份文件,如果需要修改再复制一份进行修改,而大部分文件是其实不会修改的,这样可以最大限度节省空间,提升性能。
  • 问题 3,我们可以将镜像文件分为多个独立的层,然后新镜像文件只要引用基础镜像文件就可以了,这样可以节省大量空间。至于修改基础镜像文件的情况,参考问题 2 。

如果你能想到以上思路,那么恭喜你,因为 Docker 就是这么做的,你已经具备为写 docker 写一套文件系统的实力了(哈哈哈哈,不要飘,还有大量技术细节需要思考)。

Docker 的镜像分层

接下来,我们来看看 Docker 的镜像分层机制。

Docker 镜像是分层构建的,Dockerfile 中每条指令都会新建一层。例如以下 Dockerfile:

以上四条指令会创建四层,分别对应基础镜像、复制文件、编译文件以及入口文件,每层只记录本层所做的更改,而这些层都是只读层。当你启动一个容器,Docker 会在最顶部添加读写层,你在容器内做的所有更改,如写日志、修改、删除文件等,都保存到了读写层内,一般称该层为容器层,如下图所示:

事实上,容器(container)和镜像(image)的最主要区别就是容器加上了顶层的读写层。所有对容器的修改都发生在此层,镜像并不会被修改,也即前面说的 COW (copy-on-write) 技术。容器需要读取某个文件时,直接从底部只读层去读即可,而如果需要修改某文件,则将该文件拷贝到顶部读写层进行修改,只读层保持不变。

每个容器都有自己的读写层,因此多个容器可以使用同一个镜像,另外容器被删除时,其对应的读写层也会被删除(如果你希望多个容器共享或者持久化数据,可以使用 Docker volume)。

最后,执行命令 docker ps -s,可以看到最后有两列 size 和 virtual size。其中 size 就是容器读写层占用的磁盘空间,而 virtual size 就是读写层加上对应只读层所占用的磁盘空间。如果两个容器是从同一个镜像创建,那么只读层就是 100% 共享,即使不是从同一镜像创建,其镜像仍然可能共享部分只读层(如一个镜像是基于另一个创建)。因此,docker 实际占用的磁盘空间远远小于 virtual size 的总和。

以上就是 Docker 镜像分层的主要内容,至于这些层的交互、管理就需要存储驱动程序,也即联合文件系统(UnionFS)。Docker 可使用多种驱动,如目前已经合并入 Linux 内核、官方推荐的 overlay, 曾在 Ubuntu、Debian 等发行版中得到广泛使用的 AUFS,以及 devicemapper、zfs 等等,需要根据 Docker 以及宿主机系统的版本,进行合适的选择。接下来以 AUFS 为例,简单介绍 UnionFS 的使用。


AUFS

AUFS 是一种 UnionFS,所谓 UnionFS 就是把不同物理位置的目录合并到同一个目录中。例如把 CD 和硬盘 mount 到一起,就可以对 CD 上的文件进行修改,再比如上面所讲 docker 的使用场景。

AUFS 是 Another UnionFS 的首字母缩略字,2006 年由冈岛顺治郎开发,是之前的 UnionFS 的完全重写,其稳定性和性能上确实好很多,但从第 2 版开始它代表 advanced multi-layered unification filesystem。

这里插播一个悲情的小故事,AUFS 被 Linus 拒绝合并到主线 Linux,其代码被批评为 “稠密,不可读,无注释”。然后作者不断改进代码,不断提交,不断被 Linus 拒掉,最终放弃。而 2014 年,OverlayFS 被合并到 Linux 内核 3.18 版本,冈岛顺治郎再无希望。

言归正传,下面我们简单试验下 AUFS 的使用。(Ubuntu 18.04 环境)

首先,创建两个文件夹代表只读层以及读写层。

然后,将两个文件夹进行联合挂载。命令中默认 dirs 后第一个文件夹为读写权限,之后的文件夹为只读权限。

接下来,我们可以查看下 unionFs 文件夹下的内容,发现已经有了 readFile、writeFile 两个文件,说明挂载成功。

然后,我们再来测试下对文件的修改操作,我们分别修改读写层以及只读层的文件。

根据我们之前的分析,针对读写层 writeLayer 的 writeFile 的修改,应该是直接在源文件上生效;而针对只读层 readLayer 的 readFile 的修改,应该是源文件保持不变,将文件拷贝到 writeLayer,然后再修改,我们来检查下。

首先,查看下只读层 readLayer,文件内容并没有改变。

然后查看下读写层,可以看到新增的文件 readFile,且文件内容为修改后内容。

可以看到,我们成功模拟了只读层、读写层的联合使用,Docker 的镜像分层也是此原理,只是实现更加复杂。

参考资料

以上即是 docker 镜像分层技术的简单介绍,通过这项技术,节省了大量的存储空间,也提升了容器的下载、构建、启动速度,推动了 Docker 技术的广泛使用。

如果你想更进一步了解相关细节,可以参考以下文章:

DOCKER 基础技术:AUFS

About storage drivers

 

« »

发表评论

电子邮件地址不会被公开。 必填项已用*标注

昵称 *