前言

Docker 镜像是用于创建容器的只读模板,并提供了基于多层文件和路径的有序联合文件系统,该文件系统可以与其他镜像和容器共享。共享镜像层是Docker平台的基本组成部分,并且通过实现写​​时复制(COW)机制来实现。在其生命周期内,如果容器需要从提供其文件系统的只读映像中更改文件,它将在进行更改之前将文件复制到其自己的私有读写层。

在Docker镜像构建过程中会创建一个层或"diff",并在容器中运行命令时产生的结果,该命令会生成新的或修改的文件和目录。这些新的或修改的文件和目录被“committed”为新的层。

历史的角度

历史上(Docker v1.10之前的版本),每次由于'commit'操作而创建新层时,Docker还会创建一个对应的镜像,该镜像由随机生成的256位UUID标识,通常称为镜像ID(在UI中以短12位十六进制字符串或长64位十六进制字符串表示)。 Docker将层内容存储在目录中,该目录的名称与镜像ID相同。在内部,镜像由配置对象组成,该对象保留镜像的特征,包括其ID和镜像的父镜像的ID。这样,Docker能够为容器构建文件系统,每个镜像依次引用其父级和相应的层内容,直到到达没有父级的基础镜像为止。每个镜像也可以用有意义的名称标记(例如my_image:1.0),但这通常是为叶子镜像保留的。如下图所示:

使用docker inspect命令:

$ docker inspect my_image:1.0
[
    {
        "Id": "ca1f5f48ef431c0818d5e8797dfe707557bdc728fe7c3027c75de18f934a3b76",
        "Parent": "91bac885982d2d564c0e1869e8b8827c435eead714c06d4c670aaae616c1542c"
        ...
        ...

这种方法可以在持续的一段时间内很好地为Docker服务,但是由于种种原因,随着时间的推移,这种方法被认为不是最佳的。推动变革的主要动力之一是,缺少一种方法来检测图像内容是否在推送或从注册表中拉出时被篡改,例如Docker Hub。这引起了整个社区的强烈批评,并导致了一系列变化,最终形成了内容可寻址的ID。

内容可寻址的IDs

从Docker v1.10开始,镜像和镜像层通常不再是同义词。相反,镜像直接引用最终有助于派生容器的文件系统的一层或多层。

现在,通过摘要标识层,摘要的格式为:algorithm:hex;例如:

sha256:fc92eec5cac70b0c324cec2933cd7db1c0eae7c9e2649e42d02e77eb6da0d15f

十六进制元素是通过将算法(SHA256)应用于镜像层内容来计算的。如果内容更改,则计算的摘要也将更改,这意味着Docker可以使用已发布的摘要检查检索到的层内容,以验证其内容。层没有镜像或属于镜像的概念,它们只是文件和目录的集合。

Docker镜像现在包含一个配置对象,该对象(除其他外)包含一个层摘要的有序列表,这使Docker引擎能够参考层摘要而不是父镜像来组装容器的文件系统。镜像ID也是摘要,并且是镜像配置对象的计算得出的SHA256哈希,其中包含有助于镜像文件系统定义的各层的摘要。下图描述了Docker v1.10之后镜像和层之间的关系:

镜像和层的摘要已缩短,以提高可读性。

现在,用于存储层内容的diff目录以一个随机生成的“cache ID”命名,并且Docker引擎维护该层及其cache ID之间的链接,以便它知道在磁盘上的位置。

因此,当从注册表中提取Docker镜像,并使用docker history命令显示其内容时,输出将提供类似于以下内容:

$ docker history swarm
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
c54bba046158        9 days ago          /bin/sh -c #(nop) CMD ["--help"]                0 B                 
<missing>           9 days ago          /bin/sh -c #(nop) ENTRYPOINT &{["/swarm"]}      0 B                 
<missing>           9 days ago          /bin/sh -c #(nop) VOLUME [/.swarm]              0 B                 
<missing>           9 days ago          /bin/sh -c #(nop) EXPOSE 2375/tcp               0 B                 
<missing>           9 days ago          /bin/sh -c #(nop) ENV SWARM_HOST=:2375          0 B                 
<missing>           9 days ago          /bin/sh -c #(nop) COPY dir:b76b2255a3b423981a   0 B                 
<missing>           9 days ago          /bin/sh -c #(nop) COPY file:5acf949e76228329d   277.2 kB            
<missing>           9 days ago          /bin/sh -c #(nop) COPY file:a2157cec2320f541a   19.06 MB

该命令提供有关镜像及其组成的层的详细信息。 IMAGE字段中除镜像的一层以外的所有<missing>值都具有误导性。它传达了错误的提示,但是没有错误,因为层不再与相应的镜像和ID同义。我认为将字段留空会更合适。同样,镜像ID似乎与最上层相关联,但实际上,镜像ID不“属于”任何层。而是,这些层共同属于镜像,并提供其文件系统定义。

本地构建镜像

尽管此内容可寻址镜像的叙述适用于Docker v1.10之后的所有Docker镜像,但对Docker主机上本地构建的镜像的处理略有不同。本地生成的镜像的通用内容保持不变-它是一个包含配置项的配置对象,包括层摘要的有序列表。

但是,当在本地Docker主机上构建镜像期间提交层时,会同时创建一个“中间”镜像。与其他所有镜像一样,它具有一个配置项,该配置项是要作为镜像一部分合并的层摘要的列表,并且其ID或摘要包含配置对象的哈希。中间镜像没有标记名称,但是它们确实具有“父”键,其中包含父镜像的ID。

中间镜像和对父镜像的引用的目的是促进Docker构建缓存的使用。构建缓存是Docker平台的另一个重要功能,用于帮助Docker引擎利用预先存在的层内容,而不是为相同的构建命令不必要地重新生成内容。它使构建过程更加有效。在本地构建镜像时,docker history命令可能会提供类似于以下内容的输出:

$ docker history jbloggs/my_image:latest 
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
26cca5b0c787        52 seconds ago      /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/bin/b   0 B                 
97e47fb9e0a6        52 seconds ago      /bin/sh -c apt-get update &&     apt-get inst   16.98 MB            
1742affe03b5        13 days ago         /bin/sh -c #(nop) CMD ["/bin/bash"]             0 B                 
<missing>           13 days ago         /bin/sh -c #(nop) ADD file:5d8521419ad6cfb695   125.1 MB

在此示例中,顶层两层是在本地镜像构建期间创建的,而底层则来自构建的基础镜像(例如Dockerfile指令FROM debian)。我们可以使用docker inspect命令来查看与镜像相关的层摘要:

$ docker inspect jboggs/my_image:latest 
[
    {
        ...
        ...
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:4dcab49015d47e8f300ec33400a02cebc7b54cadd09c37e49eccbc655279da90",
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
                "sha256:f22bfbc1df820c465d94215e45bf9b1f0ae0fe3435a90dc5296a4b55712f46e7"
            ]
        }
    }
]

docker history命令将镜像显示为具有四层,但是docker inspect仅三层。这是因为两条CMD指令为镜像生成元数据,不添加任何内容,因此'diff'为空。摘要5f70bf18a08a是一个空层的SHA256哈希,并由两个相关层共享。

当将本地构建的镜像推送到注册表时,只有叶子镜像及其组成层被上载,并且另一位Docker主机随后进行的拉取将不会产生任何中间父镜像。这是因为一旦通过注册表将镜像提供给不同Docker主机上的其他潜在用户使用,该镜像实际上将变为只读状态,并且不再需要支持构建缓存的组件。代替<image>,将<missing>插入到历史记录输出中。

将镜像推送到注册表可能:

$ docker push jbloggs/my_image:latest
The push refers to a repository [docker.io/jbloggs/my_image]
f22bfbc1df82: Pushed 
5f70bf18a086: Layer already exists 
4dcab49015d4: Layer already exists 
latest: digest: sha256:7f63e3661b1377e2658e458ac1ff6d5e0079f0cfd9ff2830786d1b45ae1bb820 size: 3147

总结

  • Docker镜像根据其存储到层差异的引用为派生容器提供文件系统
  • 层差异使用摘要进行引用,该摘要包含差异内容存档的SHA256哈希
  • Docker镜像的ID是摘要,其中包含镜像的JSON配置对象的SHA256哈希
  • Docker在本地镜像构建期间创建中间镜像,目的是维护构建缓存
  • 镜像清单包含镜像层的摘要,其中包含压缩的,已归档差异内容的SHA256哈希

PS: 本文属于翻译,原文


iyacontrol
1.4k 声望2.7k 粉丝

专注kubernetes,devops,aiops,service mesh。