Docker镜像分层的实现示例
作者:哈密猿
docker镜像分层
Docker 镜像由一些松耦合(关系不怎么紧密)的只读镜像层组成,Docker Daemon 负责堆叠这些镜像层,并将它们关联为一个统一的整体,即对外表现出的是一个独立的对象。
介绍
Docker 镜像采用分层结构来构建和管理,这是其轻量、高效和可复用性的关键。镜像的分层结构使得 Docker 镜像在构建、部署和更新过程中非常灵活,同时节省存储空间和下载时间。下面是 Docker 镜像分层的详细介绍:
只读分层:
Docker 镜像由多个只读的文件系统层组成,每个分层包含了一个或多个文件或目录的更改。这些分层按照从底部到顶部的顺序叠加在一起,形成了一个完整的镜像。底部的分层通常是一个基础操作系统的根文件系统,而顶部的分层包含了应用程序代码和配置等信息。由于每个分层是只读的,所以镜像在创建后不会被更改。
联合文件系统:
Docker 使用联合文件系统(UnionFS)技术将多个只读分层组合成一个单一的虚拟文件系统。联合文件系统使得各个分层看起来像是一个整体,使得镜像中的每个分层的内容在文件系统层次结构中可见,但实际上并不复制这些内容。这样的设计节省了存储空间,并且可以在不同的镜像之间共享公共层,从而加快镜像的构建和下载速度。
分层继承:
Docker 镜像支持分层继承,这意味着可以基于现有的镜像构建新的镜像。当新的镜像构建时,它只需在现有镜像的基础上添加新的分层,而不需要重新复制现有的分层。这种分层继承的特性使得镜像构建变得高效和快速,并允许镜像的复用。
可读写容器层:
当基于镜像创建一个容器时,Docker 会在镜像的顶部添加一个可读写的容器层。这个容器层允许容器在运行时对文件系统进行写操作,例如应用程序的日志输出、数据库文件等。容器层是临时的,只在容器运行时存在,当容器停止时,对容器层的修改也会被丢弃,保持镜像的不可变性。
镜像的复用和共享:
Docker 镜像分层的结构使得镜像可以复用和共享。多个镜像可以共享相同的基础层,从而节省存储空间,并减少镜像拉取和构建的时间。这对于持续集成、持续部署和分布式系统的部署非常有益。
总结来说,Docker 镜像的分层结构是一种高效、灵活和可复用的设计,它使得 Docker 容器化应用程序在不同的环境中可以轻松部署和运行,同时节省了存储空间和提高了构建和下载速度。这种设计也促进了 Docker 生态系统的发展和镜像的共享,使得 Docker 成为一种广泛使用的容器化技术。
例子
通过 docker pull 命令拉取指定的镜像时,每个 Pull complete 结尾的行就代表下载完毕了一个镜像层。
[root@docker ~]# docker pull nginx Using default tag: latest latest: Pulling from library/nginx a2abf6c4d29d: Already exists a9edb18cadd1: Pull complete 589b7251471a: Pull complete 186b1aaa4aa6: Pull complete b4df32aa5a72: Pull complete a0bcbecc962e: Pull complete Digest: sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31 Status: Downloaded newer image for nginx:latest docker.io/library/nginx:latest
有些版本不一样,但是他们的镜像分层有些是一样的。即不同镜像对相同下层
镜像的复用
[root@docker ~]# docker pull zookeeper:3.7.1-temurin 3.7.1-temurin: Pulling from library/zookeeper 9d19ee268e0d: Already exists 32db0ad82863: Already exists e01bb55fbcae: Already exists 297c6e3f57ee: Already exists 49c4f235b5f6: Already exists 82b2637e3748: Already exists
镜像层构成
Docker 镜像是由一系列只读的文件系统层构成的,这些层会按特定顺序叠加在一起,构成一个完整的镜像。镜像层的设计使得 Docker 具有轻量、高效和可复用的特性。下面是 Docker 镜像层构成的详细介绍:
基础镜像层:
Docker 镜像的第一层是基础镜像层,它通常包含一个最小化的操作系统,如Alpine Linux、Ubuntu、CentOS等。这个基础镜像提供了运行应用程序所需的最基本的文件和工具。基础镜像通常是公共或私有的,供其他镜像构建和扩展使用。
应用程序依赖层:
在基础镜像之上,Docker 可以添加应用程序的依赖项和运行时环境,这些依赖项可能包括软件包、库文件等。这些层用于支持应用程序的执行和运行所需的软件和工具。
应用程序代码层:
在依赖层之上,Docker 可以添加应用程序的实际代码和资源文件。这些层包含了应用程序的源代码、配置文件、静态资源等。这使得 Docker 镜像能够完整地包含应用程序的所有代码。
只读层:
镜像的每个层都是只读的,这意味着在构建后,镜像层的内容不会再改变。这种设计有助于镜像的高效性和可复用性。如果需要修改镜像,Docker 将在现有层之上创建新的镜像层,保持原有层的完整性。
共享相同层:
当多个镜像共享相同的基础层时,它们可以节省存储空间和下载时间。因为这些镜像只需在自己的特定层上添加差异层,而不是复制整个基础镜像。这使得镜像的存储和传输变得更加高效。
镜像的唯一标识:
每个镜像都有一个唯一的标识符,称为镜像 ID,它是根据镜像内容生成的哈希值。镜像 ID 是根据所有镜像层的内容计算得出的,即使一个镜像只有一个小的改动,它的镜像 ID 也会发生变化。这种特性有助于确保镜像的唯一性和数据的完整性。
每个镜像层由两部分构成:镜像文件系统与镜像 json 文件。这两部分具有相同的 ImageID。镜像文件系统就是对镜像占有的磁盘空间进行管理的文件系统,拥有该镜像所有镜像层的数据内容。而镜像 json 文件则是用于描述镜像的相关属性的集合,通过 docker inspect [镜像]就可以直观看到。
镜像FS 构成
Docker 镜像的 FS(文件系统)构成是指镜像是如何由多个只读文件系统层叠加在一起的。镜像的 FS 构成具有联合文件系统(UnionFS)的特点,这使得 Docker 镜像可以共享和重用底层层,并且能够快速高效地创建新的镜像。
下面是 Docker 镜像 FS 构成的详细介绍:
分层结构:
Docker 镜像由多个文件系统层组成,每个层包含一个或多个文件或目录的更改。每一层都是只读的,并且在创建后不会被更改。这种分层结构是 Docker 镜像的核心特点之一,它使得镜像非常轻量且可共享。
镜像层:
每个 Docker 镜像都由一个或多个镜像层组成,这些层会按照特定的顺序堆叠在一起。每个镜像层都包含在上一个层基础上的变更。这种分层结构使得容器镜像的构建和分发变得高效,因为每个层都可以独立地下载和使用。
Copy-on-Write:
Docker 使用了联合文件系统的技术来实现镜像的分层和构成。这包括 Copy-on-Write(写时复制)的特性。当容器需要修改一个层中的文件时,Docker 不会直接修改原始层,而是创建一个新的层,并在新层中存储修改后的文件。这样,只有发生更改的文件会在新层中存在,而其他文件仍然链接到原始层,节省了存储空间。
镜像的继承:
Docker 镜像的分层结构使得镜像可以基于现有镜像创建。例如,如果你有一个基础镜像,并在其上添加了一些修改,Docker 只需创建一个新的层,其中包含这些修改,而不需要复制整个基础镜像。这使得容器镜像的构建非常高效,并允许镜像的复用。
存储驱动:
Docker 支持多种存储驱动来管理镜像的 FS 构成。常见的存储驱动有 OverlayFS、AUFS、Btrfs、DeviceMapper 等。不同的存储驱动可能在性能和特性上有所不同,你可以根据需求选择适合你的存储驱动。
一个 docker 镜像的文件系统 FS 由多层只读的镜像层组成,每层都完成了特定的功能。而这些只读镜像层根据其位置与功能的不同可分为两类:基础镜像层与扩展镜像层。
基础镜像层
Docker 的基础镜像层是构成 Docker 镜像的核心组成部分之一。Docker 镜像是由多个只读的文件系统层叠加在一起构成的,基础镜像层是这些层中的第一层。下面是基础镜像层的详细介绍:
只读的文件系统层:
基础镜像层是一个只读的文件系统层,意味着一旦创建,就不能对其进行更改。所有对基础镜像的更改都将作为上面的一系列可读写的镜像层添加。这种分层的结构是 Docker 镜像轻量且高效的关键所在。
Dockerfile 中的 FROM 指令:
基础镜像是通过 Dockerfile 文件中的 FROM 指令来指定的。FROM 指令定义了基于哪个现有镜像构建新镜像。Docker 在构建镜像时,会在指定的基础镜像之上创建新的只读层,然后在其上添加其他层。
操作系统和软件环境:
基础镜像通常包含操作系统(如 Ubuntu、Alpine、CentOS 等)以及一些最基本的软件工具和运行时库。这些软件环境为后续的镜像层提供了一个稳定的运行基础。
特定用途的基础镜像:
Docker 官方镜像仓库和其他可信的镜像仓库通常提供了许多特定用途的基础镜像,如数据库镜像、Web 服务器镜像、编程语言运行时环境镜像等。这些基础镜像已经配置好了特定的软件和环境,使得用户可以基于它们构建和部署特定类型的应用程序。
镜像缓存:
Docker 镜像构建过程中,如果使用的基础镜像已经存在于本地主机上,则 Docker 将从本地缓存中直接使用该镜像,而不是重新下载或构建。这样可以节省时间和网络带宽。
基础镜像的选择对于 Docker 镜像的大小、性能、安全性和可维护性都有重要影响。因此,在选择基础镜像时,需要考虑以下几点:
- 选择官方或经过验证的发布者提供的镜像,以确保镜像的质量和安全性。
- 尽量选择小巧、轻量级的基础镜像,以减小镜像的大小。
- 确保基础镜像提供了适合你应用程序的运行环境和所需的依赖项。
所有镜像的最下层都具有一个可以看得到的基础镜像层 Base Image,基础镜像层的文件系统称为根文件系统 rootfs。而 rootfs 则是建立在 Linux 系统中**“看不到的”引导文件系统bootfs 之上。
扩展镜像层
在 Docker 中,“扩展镜像层” 是指在一个现有的基础镜像上构建新的镜像层,以添加或修改容器所需的内容和配置。Docker 使用分层文件系统的方式来管理镜像,每个镜像层都包含一个或多个文件或目录的更改。当你在现有的镜像上进行更改时,Docker 会在原有的镜像层之上创建一个新的镜像层,这个过程就是所谓的 “扩展镜像层”。
下面是扩展镜像层的详细介绍:
基础镜像:
扩展镜像层的起点是一个现有的基础镜像。基础镜像通常是一个已经构建好的、可用于部署特定应用程序的镜像。这个基础镜像可以是 Docker 官方镜像、第三方镜像或者你自己构建的镜像。
创建 Dockerfile:
扩展镜像层的过程通常是通过创建一个 Dockerfile 文件来完成的。Dockerfile 是一个文本文件,其中包含一系列的指令,用于定义新镜像的构建过程。Dockerfile 可以基于已有的基础镜像,然后在其上添加更改,以满足特定的需求。
添加新的镜像层:
Docker 会根据 Dockerfile 中的指令逐步构建新的镜像。当 Docker 构建过程遇到一个指令时,它会在当前镜像层的基础上创建一个新的镜像层,并应用该指令所描述的更改。这样,新的镜像层就被添加到了构建过程中。
缓存机制:
Docker 在构建过程中使用了缓存机制,这意味着当一个指令没有发生变化时,Docker 将重用之前构建的层,而不会重新创建。这样可以提高构建的速度和效率。
多层继承:
在 Dockerfile 中,你可以利用多层继承的特性,基于多个基础镜像构建一个新的镜像。这使得扩展镜像层的过程更加灵活,可以根据实际需求选择不同的基础镜像,并在其上添加所需的更改。
不可变性:
镜像层是不可变的,一旦创建后就不会更改。这意味着每次在扩展镜像层上做出的更改都会创建一个新的镜像层,而原有的镜像层保持不变。这有助于确保镜像的稳定性和可重现性。
在基础镜像层之上的镜像层称为扩展镜像层。顾名思义,其是对基础镜像层功能的扩展。在 Dockerfile 中,每条指令都是用于完成某项特定功能的,而每条指令都会生成一个扩展镜像层。
容器层
Docker 的容器层是 Docker 容器的一个核心概念,它是 Docker 镜像的运行实例。当你从 Docker 镜像创建一个容器时,Docker 在镜像的基础上添加了一个可写的容器层,用于存储容器运行时的变更和数据。容器层与镜像层相互结合,使得容器能够在镜像的基础上添加或修改文件、运行进程,并保留这些更改,而不影响基础镜像的原始内容。
下面是容器层的详细介绍:
可写性:
容器层是一个可读写的文件系统层,它允许在容器运行时修改和保存数据。容器中的进程可以向容器层添加、修改或删除文件,这些更改将仅影响容器本身,而不会影响基础镜像或其他容器。
联合文件系统:
容器层使用联合文件系统(UnionFS)技术,它允许将多个文件系统合并到一个单一的文件系统中。Docker 使用联合文件系统来将容器层和基础镜像层组合在一起,以形成容器的完整文件系统视图。
轻量和高效:
容器层是基于镜像层的增量修改,这使得容器非常轻量且高效。因为容器只保存与基础镜像的差异,所以它们通常只需要很少的磁盘空间,并且在创建和启动时非常快速。
容器生命周期:
当容器运行时,容器层处于活动状态。容器内的进程可以读取和写入容器层的文件系统,并在运行时进行修改。当容器停止后,容器层仍然存在,但它将保持在停止的状态,并且可以随时重新启动和使用。
临时性:
容器层是临时的,任何对容器的更改都只存在于容器的生命周期中。如果容器被删除,容器层中的所有数据和修改也会被丢弃。这使得容器可以非常容易地重新创建和重置。
持久化数据:
尽管容器层通常是临时的,但通过挂载主机目录或使用数据卷,可以实现容器数据的持久化。持久化数据不存储在容器层中,而是存储在主机的文件系统中或者专门管理的 Docker 数据卷中,这样即使容器被删除,数据仍然保留。
一旦镜像运行了起来就形成了容器,而容器就是一个运行中的 Linux 系统,其也是具有文件系统的。容器的这个文件系统是在 docker 镜像最外层之上增加了一个可读写的容器层,对文件的任何更改都只存在于容器层。因此任何对容器的操作都不会影响到镜像本身。
容器层如果需要修改某个文件,系统会从容器层开始向下一层层的查找该文件,直到找到为止。任何对于文件的操作都会记录在容器层。例如,要修改某文件,容器层会首先把在镜像层找到的文件 copy 到容器层,然后再进行修改。删除文件也只会将存在于容器层中的文件副本删除。
可以看出,Docker 容器就是一个叠加后的文件系统,而这个容器层称为 Union File System,联合文件系统。
镜像摘要
每个镜像都有一个长度为 64 位的 16 进制字符串作为其摘要 digest。
介绍
Docker 镜像摘要是指镜像的摘要信息,通常被称为镜像的 Digest。它是镜像内容的唯一标识符,类似于 Git 中的 SHA 值,用于确保镜像的完整性和唯一性。摘要是一个由算法计算得出的长字符串,它代表了镜像内容的具体信息,包括镜像层和配置信息。
以下是 Docker 镜像摘要的详细介绍:
唯一性:
Docker 镜像摘要是由 SHA256 算法计算得出的,因此它是唯一的。不同的镜像内容必定会产生不同的摘要值。这保证了镜像的唯一性,可以避免两个镜像在不同的仓库或环境中具有相同的名称和标签,但内容实际上是不同的。
完整性验证:
摘要值可以用来验证镜像的完整性。当你从 Docker 镜像仓库拉取镜像时,Docker 会根据摘要值来检查镜像内容是否完整。如果镜像内容在传输过程中发生了任何变化,摘要值将会不匹配,Docker 将会拒绝使用这个镜像。
不可篡改性:
镜像摘要是根据镜像内容计算得出的,任何对镜像内容的修改都会导致摘要值的改变。因此,镜像摘要具有不可篡改性,可以保护镜像的完整性和安全性。
使用摘要拉取镜像:
你可以使用镜像的摘要值来拉取镜像,而不仅仅使用标签。这在确保你拉取的是特定版本的镜像时非常有用。使用摘要值拉取镜像的格式为:**docker pull <image-name>@<digest>**
镜像更新:
镜像的摘要值是基于镜像内容计算的,因此,当镜像内容发生更改时,摘要值也会随之改变。这意味着每次更新镜像时,都会产生一个新的摘要值。
摘要,即 digest,是镜像内容的一个 Hash 值,即所谓的 Content Hash(内容散列)。只要镜像内容发生了变更,其内容散列值就一定会发生改变。也就是说,一个镜像一旦创建完毕,其 digest 就不会发生改变了,因为镜像是只读的。
Docker 默认采用的 Hash 算法是 SHA256,即 Hah 值是一个长度为 256 位的二进制值。Docker 使用 16 进制表示,即变为了长度为 64 位的字符串。
通过 docker inspect 命令可以查看指定镜像的详细信息。其中就包含该镜像的摘要信息。
[root@docker ~]# docker inspect nginx
通过 docker images --digests 命令也可以查看到镜像的摘要信息。
[root@docker ~]# docker images nginx --digests REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE nginx latest sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31 605c77e624dd 18 months ago 141MB
分发散列值
在 push 或 pull 镜像时,都会对镜像进行压缩以减少网络带宽和传输时长。但压缩会改变镜像内容,会导致经过网络传输后,镜像内容与其 digest 不相符。出现问题。(也就是在自己机器上进行压缩的hash值和传输到docker hub后hsah值不相同了,因为传输的时候还会压缩)
为了避免该问题,Docker 又为镜像配置了 Distribution Hash(分发散列值)。在镜像被压缩后立即计算分发散列值,然后使该值随压缩过的镜像一同进行发送。在接收方接收后,立即计算压缩镜像的分发散列值,再与携带的分发散列值对比。如果相同,则说明传输没有问题
镜像的hsah值和压缩后的hash值会一起传输到docker hub
多架构镜像
Docker 多架构镜像(Multi-architecture Images)是指一种能够在不同硬件架构上运行的 Docker 镜像。传统上,Docker 镜像是为特定的硬件架构(如x86、ARM等)构建的,而多架构镜像则允许在不同的硬件架构上共享和使用相同的镜像。
下面是 Docker 多架构镜像的详细介绍:
跨平台支持:
Docker 多架构镜像允许在不同的硬件架构上运行相同的镜像。这意味着你可以使用同一个镜像构建和部署应用程序,而无需为不同的硬件架构编写和维护多个镜像。
架构选择:
Docker 多架构镜像支持在构建镜像时选择目标架构。这意味着你可以根据目标平台的硬件架构,选择适当的基础镜像和依赖项来构建镜像。例如,你可以使用不同的 Dockerfile 和构建过程来创建适用于 x86、ARM、ARM64 等不同架构的镜像。
Manifest List:
Docker 通过使用 Manifest List 来支持多架构镜像。Manifest List 是一个包含多个平台的描述文件,它允许 Docker 客户端根据当前环境自动选择最合适的镜像。Docker 客户端会检查当前主机的架构,并根据 Manifest List 中的信息拉取和使用对应的镜像。
跨架构构建:
Docker 多架构镜像的构建过程通常涉及交叉编译或使用具有原生支持多架构构建的工具链。这样可以确保在一个架构上构建的镜像可以在其他支持的架构上运行。
镜像推送与拉取:
Docker 多架构镜像可以通过 Docker Hub 或私有镜像仓库进行推送和拉取。你可以将多架构镜像推送到 Docker Hub 的同一个仓库中,并通过标签来区分不同的架构。Docker 客户端会根据当前架构自动选择并拉取适合的镜像。
Multi-architecture Image,即多架构镜像,是某中的某镜像针对不同操作系统/系统架构的不同镜像实现。即多架构镜像中包含的镜像的:都是相同的,但它们针对的操作系统/系统架构是不同的
工作原理
Docker 的多架构镜像(Multi-architecture Images)是指在同一个镜像中包含了多个不同硬件架构的二进制文件,使得同一个镜像可以在多个不同架构的计算机上运行。这种机制使得在不同硬件平台上部署和运行容器变得更加灵活和便捷。下面是 Docker 多架构镜像的工作原理的详细介绍:
多平台构建:
Docker 多架构镜像的工作原理涉及到多平台构建。在构建过程中,通过 Docker CLI 或构建工具(如 Buildx)指定目标平台架构,然后 Docker 引擎会自动根据目标架构进行构建。这意味着同一个 Dockerfile 可以用于构建多个不同架构的镜像。
平台特定文件:
在多架构镜像中,镜像的文件系统中可能包含了针对特定架构的二进制文件。这些文件通常存储在不同的文件夹或路径中,并使用与架构相关的标识。例如,Linux x86 架构的文件可能存储在 /x86 文件夹下,而 ARM 架构的文件可能存储在 /arm 文件夹下。
平台标签和映射:
Docker 多架构镜像通过使用平台标签和映射来管理不同架构的镜像文件。每个镜像都可以包含一个或多个平台标签,用于指示镜像适用的架构。标签的格式通常是 架构-操作系统。例如,linux/amd64 表示适用于 x86 架构的 Linux 操作系统。
平台选择策略:
在运行容器时,Docker 引擎会根据主机的架构选择最合适的镜像进行部署。它会根据主机的架构和可用镜像的架构进行匹配,并选择匹配的镜像来运行容器。如果找不到完全匹配的镜像,它会尝试选择最接近的架构进行运行。
Manifest 文件:
多架构镜像还使用了 Docker Manifest 文件来存储有关不同架构的镜像的元数据信息。Manifest 文件包含了不同平台的映射关系、镜像 ID 和 Digest 等信息。通过 Manifest 文件,Docker 引擎能够在运行容器时自动选择合适的镜像。
在 Docker Hub 中,镜像的多架构信息保存在 Manifest 文件中。在拉取镜像时,Docker会随着 pull 命令将当前 Docker 系统的 OS 与架构信息一并提交给 Docker Hub。Docker Hub 首先会根据镜像的:查找是否存在 Manifest。如果不存在,则直接查找并返回:镜像即可;如果存在,则会在 Manifest 中查找是否存在指定系统/架构的镜像。如果存在该系统/架构,则根据 Manifest 中记录的地址找到该镜像的位置。
到此这篇关于Docker镜像分层的实现示例的文章就介绍到这了,更多相关docker镜像分层内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!