容器镜像就像是一个将操作系统、依赖库、应用程序、配置参数等,所有容器运行时所需组合成的一个集合。它包含了我们的应用程序所需的所有依赖。

另外容器镜像是可以接收增量变更的,也就是说我们可以对镜像进行一些自定义的调整与修改,而这些变更是可以在原有镜像基础之上做增量,而不是每次改动就产生一个新副本。

容器镜像为了实现这种增量保存的机制,设计出了一个“分层”概念。也就是将镜像分为多个层(layer)进行组织,每当发生变更时,就创建一个新层来保存这些变更的部分。多个层叠加在一起就是一个完整的镜像,而这种层与层组织叠加是有顺序的,最先创建出的层(基础镜像)总是在底层,而最新变更则通常处于最上层。

 如上图所示,容器镜像就如同上图一样,从最底层的rootfs层层叠加,将每一次变更的增量使用一个layer来保存然后叠加上去,最终形成了含有多个layer的镜像包。

这里的“层”,是一个抽象的表示,其实当我们在操作系统中拉取一个镜像后,这个过程将包含两个步骤,那就是下载与解压。当拉取镜像并在操作系统解压后,是以文件形式存在的。而镜像的多个层则对应多个文件夹,这些文件通常被存储在Docker默认的数据目录中(/var/lib/docker)。

Docker在处理镜像时会调用存储引擎的支持,而这在不同的操作系统中则有所不同,在RHEL系,例如CentOS中可能使用overlay存储引擎,除此之外例如还有AUFS存储引擎等。当你使用docker pull 拉取一个镜像后,在会在docker的数据目录(/var/lib/docker/overlay2)中出现对应的文件夹,这些文件夹是以sha256形式来命名的。

首先我们查看/var/lib/docker/overlay2,当前文件中没有镜像内容。

安装nginx:

如上所示,我们可以看到在overlay2中出现了5个sha256命名的文件夹,那么由此可以推断该nginx镜像应该有5个layer构成。而在overlay2下的l文件夹下则是5个链接文件,可以看到这些链接文件的指向则是以sha256命名的layer文件夹下diff目录。那么在这个diff文件夹中其实是存储着该层的增量。

我们可以继续使用tree命令并且增加查看的目录层级,就可以看到在有的layer文件夹中的diff目录中包含了完整的操作系统目录,而有的则包含了含有nginx文件的目录,那么这表示着这个nginx镜像是基于一个操作系统镜像来构建的。如下图所示

由上我们得知,一个镜像会被分为多个层,而在操作系统中则以多个文件夹形式存在,每个层对应的文件中的diff目录保存着属于该层的增量变更。那么要想得到一个完整的镜像就必须将多个增量,也就是多个层组合起来,这在我们使用镜像去创建容器时,Docker帮我们完成了这个操作。组合多个层,也就意味着需要将我们前面提到的操作系统中多个文件夹所包含的内容放在一起,即要保证这些原有层不被改变又要将新的变更单独存储,那么Docker又是怎样实现的呢?

这就不得不提UnionFS了,也就是联合文件系统。那么这类文件系统有一个特点就是允许将多个目录以挂载的方式,挂载至同一个挂载点上,并且以增加叠加的方式进行组织。

在我们前面内容中提到Docker会在不同的操作系统上使用不同的存储引擎支持,那么在CentOS上默认使用的overlay2存储引擎的支持。那么这个overlay2就是属于这类联合文件系统,它具备这种特性。

我们下面通过一个示例来解释这种特性

创建一个demo文件夹,然后在文件夹分别创建lower、upper、worker、merged目录

命令:

mkdir demo
cd demo/
mkdir lower upper worker merged 
touch lower/lo.txt && mkdir lower/lo
touch upper/up.txt && mkdir upper/up

其中lower与upper代表镜像的两个layer增量,而merged则代表一个联合挂载点。那么接下来执行以下命令来完成这个联合挂载操作。

我们进入demo目录,执行:

[root@localhost demo]# mount -t overlay overlay -o upperdir=upper,lowerdir=lower,workdir=worker merged/
[root@localhost demo]# mount -l |grep overlay
overlay on /root/demo/merged type overlay (rw,relatime,lowerdir=lower,upperdir=upper,workdir=worker)

 此时再看目录结构中的变化,可以看到在merged目录中则包含了lower与upper两者的增量。

而当我们使用一个镜像运行容器时,docker所完成的操作也是此类似的操作。容器引擎会将该镜像的多层layer使用联合挂载(UnionFS)方式挂载到一个路径下,并创建一个新的layer叠加在其最上方,用于保存当前容器的更改。最上层为可读可写层,因此可以在当前容器做出变更,而当使用docker commit 提交这个容器ID时,这就等同于将最上层layer进行一个固话操作,它将变成一个只读层。而在下一次基于该镜像创建容器时,将再次创建一个新layer作为可读可写层。

其实可以将容器镜像的layer分为三类:只读层、初始化层、可读写层

只读层:当您拉取一个镜像在操作系统中时,这时该镜像所包含的layer全部为只读层,只读层是不可改变的;

初始化层:当您使用一个镜像创建容器时,这时会创建一个可读写层与初始化层,初始化层是用于存储一些初始配置,例如hosts文件resolv.conf文件等。

可读写层:当基于一个镜像创建一个容器里,将为该镜像创建一个新layer,该layer则用于保存当前容器的更改。

我们可以使用nginx镜像来运行一个容器,来更好的观察这种实际上变化。

docker run -d --name=nginx nginx:1.19.6

那么此时我们使用tree命令可查看/var/lib/docker/overlay2下的变化

tree -L 2 /var/lib/docker/overlay2/

如下所示,此时多出来一个文件夹和以init结尾的文件夹,那么这就是为当前容器变更创建的一个可读写层,以及一个初始化层。

那么我们可以使用mount -l来检查onverlay挂载情况

mount -l | grep overlay

可以看到这里显示的与我们之前那个overlay的示例是相似的,只不过这里使用upper与lower字段都指定了多个目录,这是因为该镜像有多个layer。那么可以看到这里挂载时lower所指定的目录则是/var/lib/docker/overlay2/l文件夹中的链接文件,这实际上和指定layer文件夹实际路径是一样的,这里使用链接文件缩短了挂载时所设置的字符长度。

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐