Docker镜像篇(9) - 镜像原理

1)镜像构造

Docker镜像由bootfs和rootfs构造;

Docker镜像篇(9) - 镜像原理_第1张图片

bootfs包含了bootloader(引导加载程序)和 kernel(Linux内核)。用户是不能对这层作任何修改的。在容器启动之后,bootfs会被卸载以节约内存资源。

rootfs位于bootfs之上,代表一个Docker容器在启动时(而非运行后)其内部进程可见的文件系统视角, 或者 Docker 容器的根目录。当然,该目录下含有 Docker 容器所需要的系统文件、工具、容器文件等。

Docker镜像篇(9) - 镜像原理_第2张图片

rootfs 包含4 个 image,其中每个 image 中都有一些用户视角文件系统中的一部分内容。 4个image处于层叠的关系,除了最底层的 image,每一层的 image 都叠加在另一个 image 之上。另外,每一个 image 均含有一个image ID,用于唯一地标记该image。
基于以上概念, Docker Image中又抽象出两种概念:父镜像以及基础镜像。除了容器 rootfs 最底层的镜像,其余镜像都依赖于其底下的一个或多个镜像。这种情况下,Docker 将 下一层的镜像称为上一层镜像的父镜像。以上图图为例, imageID_O 是 imageID_l 的父镜像,imageID_2 是 imageID_3 的父镜像,而 imageID_O 没有父镜像。对于最下层的镜像,即没有父镜像的镜像,在Docker中我们习惯称之为基础镜像。
通过 image 的形式,原先较为雕肿的 rootfs 被逐渐打散成轻便的多层。除了轻便的特性之外, image 同时还有只读特性,如此一来,在不同的容器、不同的 rootfs 中 image 完全可以用来复用。 多 image组织关系与复用关系如下图图所示(图中镜像名称的举例只为将 image 之间的 关系阐述清楚,并不代表实际情况下相应名称 image之间的关系):

Docker镜像篇(9) - 镜像原理_第3张图片

我们也可以使用命令来查看指定镜像的分层信息(layer),
[root@docker ~]# docker image inspect tomcat:8.5.6
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:f96222d75c5563900bc4dd852179b720a0885de8f7a0619ba0ac76e92542bbc8",
                "sha256:149636c850120e59e6bb79f2fc23ed423030afc73841c221906a147d61da11a9",
                "sha256:0de9d866d419b5c7a38e6d60b8eca9eedfb5fee763834c9ec3cbfaae6a056a80",
                "sha256:1be3b08730252094dce6b5fe05729e2fcf2e8cb985b00753c04789f19704de04",
                "sha256:133d54bcd235951815740af274367d28c352bd0770b680072d1d046e16df76ca",
                "sha256:e4b690d9ba335e212b0ed37e32b15be0b1f3afdeeb4388d5b00cbd83f6785b9c",
                "sha256:c045569e5db3ff9650889438f2f2e8ba0eeac8ab113bc147ab35fee3a8d32b61",
                "sha256:2814c1f146d2777f74ffac4d9889f4ec55d32be384cf25f224da477ba0271835",
                "sha256:66f5f2748d68c3c33240fe6194d560689b48ace3c330acba80b6b861ae703bcc",
                "sha256:246e44796d35a4ba7537430500e4f2295e9810855ad505409131f65900a5261a",
                "sha256:8a5517a61a9eda3cae59a7e9bbfbe9a97a22fb85b73a6ef9d35d132de7104e21",
                "sha256:a7330f0c1dc4d2f7a5c84addfc30fcffddeb0eb0d9f0b06cd810a107bb545565",
                "sha256:0ea36b9ed4026a534f3b497ae585a537a7f538e53dcbc2aca48867edfabdd4b6"
            ]
        },

2)联合文件系统

那么如何让这些分层的镜像文件堆叠在一起,提供统一的视图呢?这个时候就要提及联合文件系统( Union Filesystem)。
联合文件系统,允许同一时刻多种文件系统叠加挂载在一起, 并以一种文件系统的形式,呈现多种文件系统内容合并后的目录。一般情况下,若通过某种文件系统挂载内容至挂载点,挂载点目录中原先的内容将会被 隐藏。而 Union Mount则不会将挂载点目录中的内容隐藏反而是将挂载点目录中的内容和被挂载的内容合并,并为合并后的内容提供一个统一独立的文件系统视角。通常来讲,被合并的文件系统中只有一个会以读写( read-write) 模式挂载,其他文件系统的挂载模式均 为只读( read-only) 。
较为常见的联合文件系统有 UnionFS、 AUFS、 OverlayFS 等。

Docker镜像篇(9) - 镜像原理_第4张图片

在Docker早期使用的是AUFS(分层的文件系统),后来因为效率不高,所以就采用了Overylay2这样的文件系统来取代。Overylay2是目前Docker最新默认使用的存储引擎。
Overlay2只是docker的存储驱动名称,但底层使用的技术其实是OverlayFS。
overlayFS 是从 aufs 之上改进和简化而来的,它将文件简化为上、下两层,上面的称为 upperdir,可读写,下面的称为 lowerdir,只读,统一后暴露的视图称为 merged。
我们运行的docker容器,我们看到的是merged层,当我们修改容器内文件时,也就是改变了upperdir层,而我们使用的镜像层是永远不变的。

3)写时复制特性

Linux 操作系统内核启动时,内核首先会挂载一个只读( read-only) 的 rootfs, 当系统检测其完整性之后,决定是否将其切换为读写模式,或者最后在 rootfs 之上另行挂载一种文件系统并忽略 rootfs。
Docker沿用 Linux 中 rootfs 的思想。 当 Docker Daemon 为 Docker 容器挂载 rootfs 的时候,与传统 Linux 内核类似,将其设定为只 读模式。在 rootfs 挂载完毕之后,和 Linux 内核不一样的是, Docker Daemon 没有将 Docker 容器的文件系统设为读写模式,而是利用 Union Mount 的技术,在这个只读的 rootfs 之上再 挂载一个读写的文件系统,挂载时该读写文件系统内空无一物。也就是说镜像本身是只读。
从用户视角来看,容器内文件系统和 rootfs 完全一样,用户完全可以按照往常习惯,无差别地使用自身视角下文件系统 中的所有内容;然而,从内核的角度来看,两者有非常大的区别。追溯区别存在的根本原因,那就不得不提及 aufs 等文件系统的 COW (copy-on-write) 特性。

Docker镜像篇(9) - 镜像原理_第5张图片

COW 文件系统和其他文件系统最大的区别就是:前者从不覆写已有文件系统中已有的 内容。通过 COW 文件系统将两个文件系统( rootfs 和读写文件系统)合井,最终用户视角为 合并后含有所有内容的文件系统,然而在 Linux 内核逻辑上依然可以区别两者,那就是用户 对原先 rootfs 中的内容拥有只读权限,而对读写文件系统中的内容拥有读写权限.
既然对用户而言,全然不知哪些内容只读,哪些内容可读写,这些信息只有内核在接管,那么假设用户需要更新其视角下的文件/etc/bash.bashrc ,而该文件又恰巧是 rootfs 只读 文件系统中的内容,内核是否会抛出异常或者驳回用户请求呢?答案是否定的。当此情形 发生时, COW 文件系统首先不会覆写只读文件系统中的文件,即不会覆写 rootfs 中的 /etc/ bash.bashrc ,其次反而会将该文件复制至读写文件系统中,即将/etc/bash.bashrc 复制至读写文件系统中的 /etc/bash.bashrc,此时,rootfs文件系统和读写文件系统中各含有一份 /etc/bash.bashrc),最后再对后者进行更新操作。如此一来,纵使 rootfs 与读写文件系统中均有 /etc/ bash.bashrc,诸如 aufs 类型的 COW 文件系统也能保证用户视角中只能看到读写文件系统中 的 /etc/bash.bashrc,即更新后的内容。
当然,这样的特性同样支持 rootfs 中文件的删除等其他操作。例如用户通过 apt-get 软 件包管理工具安装 Golang,所有与 Golang 相关的内容都会安装在读写文件系统中,而不会 安装在 rootfs 中。此时用户又希望通过 apt-get 软件包管理工具删除所有关于 MySQL 的内 容,恰巧这部分内容又都存在于 rootfs 中,删除操作执行时间样不会删除 rootfs 实际存在的 MySQL,而是在读写文件系统中删除该部分内容,导致最终 rootfs 中的 MySQL 对容器用户 不可见,也不可访。由于读写文件系统中根本不存在 MySQL 的相关内容,因此似乎在读写 文件系统中找不到需要删除的对象。此时, aufs 保障在读写文件系统中对这些文件内容做相 关的标记( whiteout),确保用户在查看文件系统内容时,读写文件系统中的 whiteout 将遮盖住 rootfs 中的相应内容,导致这些内容不可见,以达到与删除这部分内容相类似的效果。

4)运行的Docker容器分层

Docker镜像篇(9) - 镜像原理_第6张图片

init层在只读层和读写层之间。init层是Docker项目单独生成的一个内部层,专门用来存放/etc/hosts、/etc/resolv.conf等信息。
有这一层的原因是这些文件属于Ubuntu镜像的一部分,但是用户往往需要在启动容器时写入一些指定的值比如hostname,所以就需要在可读写层对它们进行修改。可是,这些修改往往只对当前的容器有效,我们并不希望执行docker commit时,把这些信息联通可读写层一起提交掉。所以在修改这些文件后,以一个单独的层挂载了出来。用户执行docker commit只会提交可读写层,所以是不包含这些内容的。

Docker镜像篇(8) - 镜像仓库 - Harbor
Docker镜像篇(10)- 综合练习 - springboot项目制作镜像

你可能感兴趣的:(Docker,docker)