docker镜像原理

什么是 docker 镜像

docker 镜像是一个只读的 docker 容器模板,含有启动 docker 容器所需的文件系统结构及其内容,因此是启动一个 docker 容器的基础。
docker 镜像的文件内容以及一些运行 docker 容器的配置文件组成了 docker 容器的静态文件系统运行环境:rootfs。可以这么理解,docker 镜像是 docker 容器的静态视角,docker 容器是 docker 镜像的运行状态。我们可以通过下图来理解 docker daemon、docker镜像以及 docker 容器三者的关系:

docker镜像原理

从上图中我们可以看到,当由 ubuntu:14.04 镜像启动容器时,ubuntu:14.04 镜像的镜像层内容将作为容器的 rootfs;而 ubuntu:14.04 镜像的 json 文件,会由 docker daemon 解析,并提取出其中的容器执行入口 CMD 信息,以及容器进程的环境变量 ENV 信息,最终初始化容器进程。当然,容器进程的执行入口来源于镜像提供的 rootfs

Docker镜像的核心原理一:镜像的存储和管理方式

镜像的设计

 一个镜像都由多个镜像层组成。

为了区别镜像层,Docker为每个镜像层都计算了UUID,根据镜像层中的数据使用加密哈希算法生成UUID。所有镜像层和容器层都保存在宿主机的文件系统/var/lib/docker/中,由存储驱动进行管理。在下载镜像时,Docker Daemon会检查镜像中的镜像层与宿主机文件系统中的镜像层进行对比,如果存在则不下载,只下载不存在的镜像层。

docker镜像原理

栈层式管理镜像层

docker 中存储驱动用于管理镜像层和容器层。不同的存储驱动使用不同的算法和管理方式。在容器和镜像管理中,使用的两大技术是栈式层管理和写时复制。Dockerfile中的每一条指令都会对应于Docker镜像中的一层,因此在docker build完毕之后,镜像的总大小将等于每一层镜像的大小总和。每个镜像都由多个镜像层组成,从下往上以栈的方式组合在一起形成容器的根文件系统,Docker的存储驱动用于管理这些镜像层,对外提供单一的文件系统。Docker 的镜像实际上由一层一层的文件系统组成,这种层级的文件系统叫 UnionFS(联合文件系统)。

由于联合文件系统的存在,容器文件系统内容的大小不等于Docker镜像大小。

docker镜像原理

由于联合文件系统的存在,如果镜像层是相同的,则不同的镜像会共享该层。
如上图中,镜像A与镜像B就共享第二层镜像,使得A+B镜像文件大小并不等于A+B镜像占用宿主机存储空间容量的大小。docker images命令列出的镜像体积总和并不能代表实际使用的磁盘空间,需要使用docker system df命令来代替。

UnionFS (联合文件系统)

联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。

分层的原因:

  1. 分层最大的一个好处就是共享资源
  2. 有多个镜像都从相同的base镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像;
  3. 同时内存中也只需加载一份base镜像,就可以为所有容器服务了,而且镜像的每一层都可以被共享。

联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

通俗地讲,联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载内容进行整合,使得最终可见的文件系统将会包含整合之后的各层的文件和目录。实现这种联合挂载技术的文件系统通常被称为联合文件系统(union filesystem)。以下图所示的运行 Ubuntu:14.04 镜像后的容器中的 aufs 文件系统为例:

docker镜像原理

Docker镜像的核心原理二:镜像结构

要了解docker的镜像结构,需要先对linux的文件系统结构有所了解。

linux文件系统结构

Linux 文件系统由 bootfs和 rootfs 两部分组成。

bootfs(boot file system) 主要包含 bootloader 和 kernel,bootloader 主要是引导加载 kernel(内核),当 kernel 被加载到内存中后 bootfs 就被 umount (卸载)了。

rootfs (root file system) 包含的就是典型 Linux 系统中的 /dev,/proc,/bin,/etc 等标准目录和文件。rootfs就是各种Linux发行版。比如redcat、centOS。

docker镜像原理

docker镜像结构

docker镜像原理

docker的分层镜像结构如图所示,镜像的最底层必须是一个启动文件系统(bootfs)的镜像层。bootfs的上层镜像称为根镜像(rootfs)或者基础镜像(Base Image),它一般是操作系统,比如centos、debian或者Ubuntu。在传统的 Linux 操作系统内核启动时,首先挂载一个只读的 rootfs,当系统检测其完整性之后,再将其切换为读写模式。而在 docker 架构中,当 docker daemon 为 docker 容器挂载 rootfs 时,沿用了 Linux 内核启动时的做法,即将 rootfs 设为只读模式。在挂载完毕之后,利用联合挂载(union mount)技术在已有的只读 rootfs 上再挂载一个读写层。这样,可读写的层处于 docker 容器文件系统的最顶层,其下可能联合挂载了多个只读的层,只有在 docker 容器运行过程中文件系统发生变化时,才会把变化的文件内容写到可读写层,并隐藏只读层中的旧版本文件。

用户的镜像必须构建在基础镜像之上。如图所示, emacs镜像层就是在基础镜像上安装emacs创建出来的镜像,在此基础上安装apache又创建了新的镜像层。利用这个新的镜像层启动的容器里运行的是一个已经安装好emacs和apache的Debian系统。

docker镜像分层的理解

docker 镜像是采用分层的方式构建的,每个镜像都由一系列的 “镜像层” 组成。分层结构是 docker 镜像如此轻量的重要原因。当需要修改容器镜像内的某个文件时,只对处于最上方的读写层进行变动,不覆写下层已有文件系统的内容,已有文件在只读层中的原始版本仍然存在,但会被读写层中的新版本所隐藏。当使用 docker commit 提交这个修改过的容器文件系统为一个新的镜像时,保存的内容仅为最上层读写文件系统中被更新过的文件。分层达到了在不的容器同镜像之间共享镜像层的效果。

查看镜像分层方式可以通过docker image inspect [IMAGE]命令。其中RootFS部分则是表示了分层信息。

docker镜像原理

所有的Docker镜像都起始于一个基础镜像层,当镜像修改或者新增新的内容时,就会在当前镜像层之上,创建新的镜像层。即在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合。docker通过存储引擎(新版本采用快照机制)的方式实现镜像层堆栈,并保证多个镜像层对外展示为统一的文件系统。示例:

docker镜像原理

这个镜像中包含了三个镜像层,第一层有三个文件,第二层也有三个文件,第三层镜像中仅有一个文件,且这个文件是对第二层镜像中的文件5的一个更新版本。在这种情况下,上层镜像层中的文件会覆盖底层镜像层的文件,这样就使得文件的更新版本作为一个新的镜像层添加到镜像当中。

 最后docker通过存储引擎将所有镜像层堆叠并合并,对外提供统一的视图。

docker镜像原理

  Dockerfile中的操作对于镜像分层的影响:在镜像构建过程中需要向镜像写入数据的时候会产生分层,一个写操作指令产生一个分层

综合考虑镜像的层级结构,以及 volumeinit-layer、可读写层这些概念,一个完整的、在运行的容器的所有文件系统结构可以用下图来描述:

docker镜像原理

从图中我们不难看到,除了 echo hello 进程所在的 cgroups 和 namespace 环境之外,容器文件系统其实是一个相对独立的组织。可读写部分(read-write layer 以及 volumes)、init-layer、只读层(read-only layer) 这 3 部分结构共同组成了一个容器所需的下层文件系统,它们通过联合挂载的方式巧妙地表现为一层,使得容器进程对这些层的存在一无所知。

Docker镜像的核心原理三:写时复制策略(Copy On Write)

当某个容器修改了基础镜像的内容,比如 /bin文件夹下的文件,这时其他容器的/bin文件夹是否会发生变化呢?答案是不会的。根据容器镜像的写时复制(Copy-on-Write)技术,某个容器对基础镜像的修改会被限制在单个容器内。

写时复制策略采用了共享和复制技术,针对相同的数据系统只保留一份,所有操作都访问这一份数据。当有操作需要修改或添加数据时,操作系统会把这部分数据复制到新的地方再进行修改或添加,而其他操作仍然访问原数据区数据,这项技术节约了镜像的存储空间,加快了系统启动时间。

docker镜像原理

如图所示,当需要对镜像中的文件进行修改时,会将文件复制到容器层进行修改,上层文件会覆盖原始镜像文件。注意:该文件存在于容器层,容器重启之后容器层重新建立,上一次容器运行时对于文件的修改全部丢失!

只有当需要修改时才复制一份数据,这种特性被称作Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。

Docker镜像的核心原理四:内容寻址原理

在docker中,内容寻址就是根据文件内容来索引对应的镜像和镜像层,实际上就是对于镜像层的内容计算和校验后生成一个内容哈希值,并作为这个镜像层的唯一ID。在构造镜像时,根据这个ID来索引镜像层。

在 docker 1.10 版本后,docker 镜像改动较大,其中最重要的特性便是引入了内容寻址存储(content-addressable storage) 的机制,根据文件的内容来索引镜像和镜像层。与之前版本对每个镜像层随机生成一个 UUID 不同,新模型对镜像层的内容计算校验和,生成一个内容哈希值,并以此哈希值代替之前的 UUID 作为镜像层的唯一标识。该机制主要提高了镜像的安全性,并在 pull、push、load 和 save 操作后检测数据的完整性。另外,基于内容哈希来索引镜像层,在一定程度上减少了 ID 的冲突并且增强了镜像层的共享。对于来自不同构建的镜像层,主要拥有相同的内容哈希,也能被不同的镜像共享。

发布者:LJH,转发请注明出处:https://www.ljh.cool/39443.html

(0)
上一篇 2024年1月5日 下午5:24
下一篇 2024年1月7日 下午9:13

相关推荐

发表回复

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