镜像分层结构理论详解1

镜像分层结构理论

Docker镜像

据Docker官网的技术文档描述,Image(镜像)是Docker术语的一种,代表一个只读的layer。而layer则具体代表Docker Container文件系统中可叠加的一部分。

笔者如此介绍Docker镜像,相信众多Docker爱好者理解起来依旧是云里雾里。那么理解之前,先让我们来认识一下与Docker镜像相关的4个概念:rootfs、Union mount、image以及layer。

rootfs

Docker容器在运行时,其内部进程可见的文件系统的视角,或者Docker容器的根目录,rootfs 通常包含一个操作系统运行所需的文件系统,例如可能包含典型的类 Unix 操作系统中的目录系统,如 /dev、/proc、/bin、/etc、/lib、/usr、/tmp 及运行 docker 容器所需的配置文件、工具等。

传统来说,Linux系统在启动内核时,内核首先会挂载一个只读的rootfs,当系统检查其是否完整后,再决定是否切换为读写模式。或者在最后rootfs之上,另行挂载一个文件系统,并且忽略rootfs。

docker架构下沿用了linux中的rootfs的思想,当 docker daemon 为 docker 容器挂载 rootfs 时与传统的linux内核极其类似,将其设置为只对模式,在rootfs挂载完成之后,和linux内核不一样的是,docker daemon没有将它切换为读写模块,而是利用mount技术在已有的只读 rootfs 上再挂载一个读写的文件系统,这个文件系统中空无一物

如:

假设用户已经通过Docker Registry拉取了centos的镜像,并通过命令docker run –it centos /bin/bash将其启动运行。则Docker Daemon为其创建的rootfs以及容器可读写的文件系统包括了一下目录:

/bin /dev /home /lib64 /mnt /proc /srv /tmp /var/ boot
/lib /etc /media /opt /root /sbin /sys /usr...等

以上这些就是rootfs

镜像分层结构理论详解1

 

 

 

该容器中的进程对rootfs中的内容只拥有读权限,对于read-write读写文件系统中的内容既拥有读权限也拥有写权限。

容器虽然只有一个文件系统,但该文件系统由“两层”组成,分别为读写文件系统和只读文件系统。这样的理解已然有些层级(layer)的意味。

简单来讲,可以将Docker 容器的文件系统分为两部分,而上面提到是Docker Daemon利用Mount的技术,将两者挂载

这种mount技术就是union mount(联合挂载)

union mount

代表一种文件系统挂载的方式,允许同一时刻多种文件系统挂载在一起,并以一种文件系统的形式,呈现多种文件系统内容合并后的目录。

一般情况下,通过某种文件系统挂载内容至挂载点的话,挂载点目录中原先的内容将会被隐藏。而Union mount则不会将挂载点目录中的内容隐藏,反而是将挂载点目录中的内容和被挂载的内容合并,并为合并后的内容提供一个统一独立的文件系统视角。通常来讲,被合并的文件系统中只有一个会以读写(read-write)模式挂载,而其他的文件系统的挂载模式均为只读(read-only)。实现这种Union mount技术的文件系统一般被称为Union Filesystem,较为常见的有UnionFS、AUFS、OverlayFS等。

Docker实现容器文件系统Union mount时,提供多种具体的文件系统解决方案,如Docker早版本沿用至今的的AUFS,还有在docker 1.4.0版本中开始支持的OverlayFS等。

Docker实现容器文件系统Union mount时,提供多种具体的文件系统解决方案,如Docker早版本沿用至今的的AUFS,还有在docker 1.4.0版本中开始支持的OverlayFS等。

更深入的了解Union mount,可以使用AUFS文件系统来看

可以暂且将该容器整个rootfs当成是一个文件系统。上文也提到,挂载时读写(read-write)文件系统中空无一物。既然如此,从用户视角来看,容器内文件系统和rootfs完全一样,用户完全可以按照往常习惯,无差别的使用自身视角下文件系统中的所有内容;然而,从内核的角度来看,两者在有着非常大的区别。追溯区别存在的根本原因,那就不得不提及AUFS等文件系统的COW(copy-on-write)特性。

COW文件系统和其他文件系统最大的区别就是:从不覆写已有文件系统中已有的内容。由于通过COW文件系统将两个文件系统(rootfs和read-write filesystem)合并,最终用户视角为合并后的含有所有内容的文件系统,然而在Linux内核逻辑上依然可以区别两者,那就是用户对原先rootfs中的内容拥有只读权限,而对read-write filesystem中的内容拥有读写权限。

既然对用户而言,全然不知哪些内容只读,哪些内容可读写,这些信息只有内核在接管,那么假设用户需要更新其视角下的文件/etc/hosts,而该文件又恰巧是rootfs只读文件系统中的内容,内核是否会抛出异常或者驳回用户请求呢?答案是否定的。当此情形发生时,COW文件系统首先不会覆写read-only文件系统中的文件,即不会覆写rootfs中/etc/hosts,其次反而会将该文件拷贝至读写文件系统中,即拷贝至读写文件系统中的/etc/hosts,最后再对后者进行更新操作。如此一来,纵使rootfs与read-write filesystem中均由/etc/ hosts,诸如AUFS类型的COW文件系统也能保证用户视角中只能看到read-write filesystem中的/etc/hosts,即更新后的内容。

当然,这样的特性同样支持rootfs中文件的删除等其他操作。例如:用户通过apt-get软件包管理工具安装Golang,所有与Golang相关的内容都会被安装在读写文件系统中,而不会安装在rootfs。此时用户又希望通过apt-get软件包管理工具删除所有关于MySQL的内容,恰巧这部分内容又都存在于rootfs中时,删除操作执行时同样不会删除rootfs实际存在的MySQL,而是在read-write filesystem中删除该部分内容,导致最终rootfs中的MySQL对容器用户不可见,也不可访。

一个最简单的例子,现有三个文件系统,一个有a文件,一个有b文件,一个有c文件,通过union mount到一个目录后,abc会出现在这个目录中,并不会被覆盖。

image

在linux的grub2文件中有这么两个内容vmlinuzinitramfs,前者在强制更改root密码,重启linux系统时就是在vmlinuz处打断系统启动的,这是linux真正的内核,而initramfs,拆开来看,init用来初始化系统启动,ram是代表内存,fs是文件系统,开机时,initramfs是会被提前加载到计算机中的

linux启动引导过程

首先BIOS进行对硬件的加电自检,没有问题之后,根据BIOS的启动项找到第一个可启动的设备(硬盘),开始读取硬盘中的MBR分区(记录了操作系统在磁盘的哪一个位置)

镜像分层结构理论详解1

 

 

 

知道了位置之后还是找不到系统,因为它读不到系统所在的文件系统vfs(默认可以读到的文件系统在/boot/grub2/i386-pc目录下记录)。这个时候initramfs就有了作用,作为运行在内存上的一个假的操作系统(rootfs的原型),这个操作系统可以读到真正系统所在的文件系统 ,然后就可以找到操作系统,这个时候就会将initramfs卸载,最终就可以在系统之上运行各种程序软件

而在容器中,不会去卸载rootfs,而是采用union mount的方式直接将其中的文件直接挂载到真正操作系统上去用

虽然通过AUFS可以实现rootfs与read-write filesystem的合并,但是考虑到rootfs自身接近200MB的磁盘大小,如果以这个rootfs的粒度来实现容器的创建与迁移等,是否会稍显笨重,同时也会大大降低镜像的灵活性。而且,容器中拥有一个rootfs,那么就要创建一个全新的rootfs,都是物理机的centos的rootfs和镜像的centos的rootfs中有很多一致的内容。是可以共用的

Docker中image的概念,非常巧妙的解决了以上的问题。最为简单的解释image,就是 Docker容器中只读文件系统rootfs的一部分。换言之,实际上Docker容器的rootfs可以由多个image来构成。多个image构成rootfs的方式依然沿用Union mount技术。

多个Image构成rootfs的示意图如图(图中,rootfs中每一层image中的内容划分只为了阐述清楚rootfs由多个image构成,并不代表实际情况中rootfs中的内容划分):

镜像分层结构理论详解1

 

 

从上图可以看出,举例的容器rootfs包含4个image,其中每个image中都有一些用户视角文件系统中的一部分内容。4个image处于层叠的关系,除了最底层的image,每一层的image都叠加在另一个image之上。另外,每一个image均含有一个image ID,用以唯一的标记该image。

layer

Docker术语中,layer是一个与image含义较为相近的词。容器镜像的rootfs是容器只读的文件系统,rootfs又是由多个只读的image构成。于是,rootfs中每个只读的image都可以称为一层layer。

除了只读的image之外,Docker Daemon在创建容器时会在容器的rootfs之上,再mount一层read-write filesystem,而这一层文件系统,也称为容器的一层layer,常被称为top layer。

因此,总结而言,Docker容器中的每一层只读的image,以及最上层可读写的文件系统,均被称为layer。如此一来,layer的范畴比image多了一层,即多包含了最上层的read-write filesystem。

有了layer的概念,大家可以思考这样一个问题:容器文件系统分为只读的rootfs,以及可读写的top layer,那么容器运行时若在top layer中写入了内容,那这些内容是否可以持久化,并且也被其它容器复用?

上文对于image的分析中,提到了image有复用的特性,既然如此,再提一个更为大胆的假设:容器的top layer是否可以转变为image?

答案是肯定的。Docker的设计理念中,top layer转变为image的行为(Docker中称为commit操作),大大释放了容器rootfs的灵活性。Docker的开发者完全可以基于某个镜像创建容器做开发工作,并且无论在开发周期的哪个时间点,都可以对容器进行commit,将所有top layer中的内容打包为一个image,构成一个新的镜像。Commit完毕之后,用户完全可以基于新的镜像,进行开发、分发、测试、部署等。不仅docker commit的原理如此,基于Dockerfile的docker build,其追核心的思想,也是不断将容器的top layer转化为image。

镜像分层结构理论详解1

上一篇:【LeetCode】1.两数之和


下一篇:镜像分层结构理论详解2