Docker图像“层”是什么?

我对Docker是全新的,我试图确切地理解Docker 图像是什么。Docker映像的每一个定义都使用术语“层”,但似乎没有定义的意思。

来自官方码头工人文档:

我们已经看到Docker镜像是启动Docker容器的只读模板。每张图像都由一系列图层组成。Docker利用联合文件系统将这些层组合成单个映像。联合文件系统允许透明地覆盖独立文件系统(称为分支)的文件和目录,从而形成一个统一的文件系统。

所以我问,什么是层;有人能举几个具体的例子吗?这些图层是如何“合在一起”形成图像的呢?

104708 次浏览

使用dockerfile创建docker容器映像。dockerfile中的每一行都将创建一个层。考虑下面的假例子:

FROM ubuntu             #This has its own number of layers say "X"
MAINTAINER FOO          #This is one layer
RUN mkdir /tmp/foo      #This is one layer
RUN apt-get install vim #This is one layer

这将创建一个总层数为X + 3的最终图像

我可能会迟到,但以下是我的看法(补充ashishjain的回答):

基本上,一个层,或图像层是一个图像的变化,或中间的图片。你在Dockerfile中指定的每个命令(FROMRUNCOPY等)都会导致之前的图像发生变化,从而创建一个新层。在使用git时,您可以将其视为阶段性更改:添加一个文件的更改,然后是另一个文件,然后是另一个文件……

考虑下面的Dockerfile:

FROM rails:onbuild
ENV RAILS_ENV production
ENTRYPOINT ["bundle", "exec", "puma"]
首先,我们选择一个起始图像:rails:onbuild,它又有许多. c。 我们在开始的图像上添加另一个层,使用ENV命令设置环境变量RAILS_ENV。然后,我们告诉docker运行bundle exec puma(引导rails服务器)。这是另一层

层的概念在构建图像时派上了用场。因为层是中间图像,如果你对Dockerfile进行了更改,docker将重建只有被更改的层和之后的层。这被称为层缓存。

你可以阅读更多关于它在这里

谢谢@David Castillo提供的有用的信息。 我认为这个层是一个图像的二进制改变或指令,可以很容易地完成或撤消。 它们是一步一步完成的,就像一层加一层一样,所以我们称之为“层”

要了解更多信息,你可以像这样查看“docker历史”:

docker images --tree
Warning: '--tree' is deprecated, it will be removed soon. See usage.
└─511136ea3c5a Virtual Size: 0 B Tags: scratch:latest
└─59e359cb35ef Virtual Size: 85.18 MB
└─e8d37d9e3476 Virtual Size: 85.18 MB Tags: debian:wheezy
└─c58b36b8f285 Virtual Size: 85.18 MB
└─90ea6e05b074 Virtual Size: 118.6 MB
└─5dc74cffc471 Virtual Size: 118.6 MB Tags: vim:latest


我认为官方文件给出了非常详细的解释: https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/ . < / p > < p > < img src = " https://i.stack.imgur.com/J2cge.jpg " alt = " / >
(来源:docker.com) < /订阅>

图像由许多层组成,这些层通常是由Dockerfile生成的,Dockerfile中的每一行都将创建一个新层,结果是一张图像,用repo:tag的形式表示,如ubuntu:15.04

欲了解更多信息,请考虑阅读上面的官方文档。

根据Docker的形象规范通过白鲸计划:

图像是由图层组成的。每一层都是一组文件系统 的变化。层没有配置元数据,比如环境 变量或默认参数-这些是图像的属性

因此,从本质上讲,一层只是对文件系统所做的一组更改。

自Docker v1.10以来,随着内容可寻址存储的引入,“层”的概念变得非常不同。图层没有图像或属于图像的概念,它们只是可以在图像之间共享的文件和目录的集合。图层和图像被分离。

例如,在一个从基本映像本地构建的映像上,比方说ubuntu:14.04docker history命令会生成映像链,但是一些映像id将显示为'missing',因为构建历史不再加载。组成这些图像的图层可以通过

docker inspect <image_id> | jq -r '.[].RootFS'

如果存储驱动程序选择为aufs,则层内容存储在/var/lib/docker/aufs/diff。但是这些层是用一个随机生成的缓存ID命名的,似乎只有出于安全考虑,Docker引擎才知道层和它的缓存ID之间的链接。我还在想办法弄清楚

  1. 图像与其构成层之间的对应关系
  2. 磁盘上层的实际位置和大小

这个博客提供了很多见解。

我个人的理解是我们可以将docker层与github提交进行比较。对于你的base image(你的fresh master repo),你做了几次提交,每一次提交都在改变你的master状态,这在docker中也是一样的,每一层都在做一些基于之前中间层的操作。然后,这一层成为下一层的新中间层。

举个例子,它们对我来说最有意义……

使用docker diff检查自己构建的层

让我们看一个人为的例子Dockerfile:

FROM busybox


RUN mkdir /data
# imagine this is downloading source code
RUN dd if=/dev/zero bs=1024 count=1024 of=/data/one
RUN chmod -R 0777 /data
# imagine this is compiling the app
RUN dd if=/dev/zero bs=1024 count=1024 of=/data/two
RUN chmod -R 0777 /data
# and now this cleans up that downloaded source code
RUN rm /data/one


CMD ls -alh /data

这些dd命令中的每一个都将一个1M文件输出到磁盘。让我们用一个额外的标志来创建映像来保存临时容器:

docker image build --rm=false .

在输出中,你会看到每个正在运行的命令都发生在一个临时容器中,我们现在保留它,而不是自动删除:

...
Step 2/7 : RUN mkdir /data
---> Running in 04c5fa1360b0
---> 9b4368667b8c
Step 3/7 : RUN dd if=/dev/zero bs=1024 count=1024 of=/data/one
---> Running in f1b72db3bfaa
1024+0 records in
1024+0 records out
1048576 bytes (1.0MB) copied, 0.006002 seconds, 166.6MB/s
---> ea2506fc6e11

如果你在每个容器id上运行docker diff,你会看到在这些容器中创建了什么文件:

$ docker diff 04c5fa1360b0  # mkdir /data
A /data
$ docker diff f1b72db3bfaa  # dd if=/dev/zero bs=1024 count=1024 of=/data/one
C /data
A /data/one
$ docker diff 81c607555a7d  # chmod -R 0777 /data
C /data
C /data/one
$ docker diff 1bd249e1a47b  # dd if=/dev/zero bs=1024 count=1024 of=/data/two
C /data
A /data/two
$ docker diff 038bd2bc5aea  # chmod -R 0777 /data
C /data/one
C /data/two
$ docker diff 504c6e9b6637  # rm /data/one
C /data
D /data/one

A为前缀的每一行都是在添加文件,C表示对现有文件的更改,而D表示删除。

这里是TL和DR部分

上面这些容器文件系统的不同之处都属于一个“层”。当您将映像作为容器运行时,它将被组装起来。当有添加或更改时,整个文件都在每一层中,因此每一个chmod命令,尽管只是更改了权限位,但都会导致整个文件被复制到下一层。删除的/data/one文件仍然在前一层,实际上是3次,当你拉出图像时,它将通过网络复制并存储在磁盘上。

检查现有图像

你可以看到使用docker history命令创建现有图像图层的命令。你也可以在图像上运行docker image inspect,并在RootFS部分下查看层列表。

以下是上图的历史:

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
a81cfb93008c        4 seconds ago       /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "ls -…   0B
f36265598aef        5 seconds ago       /bin/sh -c rm /data/one                         0B
c79aff033b1c        7 seconds ago       /bin/sh -c chmod -R 0777 /data                  2.1MB
b821dfe9ea38        10 seconds ago      /bin/sh -c dd if=/dev/zero bs=1024 count=102…   1.05MB
a5602b8e8c69        13 seconds ago      /bin/sh -c chmod -R 0777 /data                  1.05MB
08ec3c707b11        15 seconds ago      /bin/sh -c dd if=/dev/zero bs=1024 count=102…   1.05MB
ed27832cb6c7        18 seconds ago      /bin/sh -c mkdir /data                          0B
22c2dd5ee85d        2 weeks ago         /bin/sh -c #(nop)  CMD ["sh"]                   0B
<missing>           2 weeks ago         /bin/sh -c #(nop) ADD file:2a4c44bdcb743a52f…   1.16MB

最新的层被列在最上面。值得注意的是,底部有两层相当古老。它们来自于忙碌的形象本身。当你构建一个图像时,你继承了你在FROM行中指定的图像的所有层。还添加了用于更改图像元数据的层,如CMD行。它们几乎不占用任何空间,更多的是用于记录应用于您正在运行的映像的设置。

为什么层?

这些层有几个优点。首先,它们是不可变的。一旦创建,由sha256哈希标识的层将永远不会改变。这种不可变性允许映像安全地相互构建和分离。如果两个dockerfile具有相同的初始行集,并且构建在同一台服务器上,它们将共享相同的初始层集,从而节省磁盘空间。这也意味着,如果你重建一个图像,只有Dockerfile的最后几行发生变化,只需要重建那些层,其余的可以从层缓存中重用。这可以使docker映像的重建非常快。

在容器中,可以看到映像文件系统,但是没有复制该文件系统。在这些映像层之上,容器装载它自己的读写文件系统层。文件的每次读取都沿着层向下,直到它到达一个标记了该文件要删除的层,在该层中有该文件的副本,或者读取耗尽了可以搜索的层。每次写入都在容器特定的读写层中进行修改。

减少层膨胀

分层的一个缺点是构建的图像会复制文件或传送在后面的层中删除的文件。解决方案通常是将多个命令合并为一个RUN命令。特别是在修改现有文件或删除文件时,您希望这些步骤在第一次创建它们时的相同命令中运行。上面Dockerfile的重写如下所示:

FROM busybox


RUN mkdir /data \
&& dd if=/dev/zero bs=1024 count=1024 of=/data/one \
&& chmod -R 0777 /data \
&& dd if=/dev/zero bs=1024 count=1024 of=/data/two \
&& chmod -R 0777 /data \
&& rm /data/one


CMD ls -alh /data

如果你比较得到的图像:

  • busybox: ~ 1 mb
  • 第一张图片:约6MB
  • 第二张图片:约2MB

通过合并这个虚构示例中的一些行,我们在图像中得到了相同的结果内容,并将图像从5MB缩小到最终图像中所看到的1MB文件。

我过去认为它们就像以前层上的差别。在阅读了这里的一些答案后,我不太确定;它们被描述为对文件系统的更改集。我写了一些dockerfile来说明它们更像差异文件,也就是说,它们确实依赖于前面的层。

给定这两个dockerfile

FROM bash
RUN mkdir /data
RUN dd if=/dev/zero bs=1024 count=1024 of=/data/one
RUN dd if=/dev/zero bs=1024 count=1024 of=/data/two
RUN dd if=/dev/zero bs=1024 count=1024 of=/data/three

而且

FROM bash
RUN mkdir /data
RUN dd if=/dev/zero bs=1024 count=1024 of=/data/three
RUN dd if=/dev/zero bs=1024 count=1024 of=/data/two
RUN dd if=/dev/zero bs=1024 count=1024 of=/data/one

如果它们只是关于文件系统的更改,人们会期望相同的层集,但事实并非如此:

$ docker history img_1
IMAGE               CREATED             CREATED BY                                      SIZE
30daa166a9c5        6 minutes ago       /bin/sh -c dd if=/dev/zero bs=1024 count=102…   1.05MB
4467d16e79f5        6 minutes ago       /bin/sh -c dd if=/dev/zero bs=1024 count=102…   1.05MB
c299561fd031        6 minutes ago       /bin/sh -c dd if=/dev/zero bs=1024 count=102…   1.05MB
646feb178431        6 minutes ago       /bin/sh -c mkdir /data                          0B
78664daf24f4        2 weeks ago         /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>           2 weeks ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B
<more missing...>

而且

$ docker history img_2
IMAGE               CREATED             CREATED BY                                      SIZE
f55c91305f8c        6 minutes ago       /bin/sh -c dd if=/dev/zero bs=1024 count=102…   1.05MB
29b3b627c76f        6 minutes ago       /bin/sh -c dd if=/dev/zero bs=1024 count=102…   1.05MB
18360be603aa        6 minutes ago       /bin/sh -c dd if=/dev/zero bs=1024 count=102…   1.05MB
646feb178431        6 minutes ago       /bin/sh -c mkdir /data                          0B
78664daf24f4        2 weeks ago         /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>           2 weeks ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B
<more missing...>

你可以看到,即使在这两种情况下文件系统的更改是相同的,顺序也很重要。

我可以添加到这个问题线程的事情是,你可以使用Git是一个比喻来理解层、图像和容器的概念。

事情是这样的:

  1. 一个git回购交叉到映像。
  2. 提交一个git回购交叉提示来添加一个层。
      git repo从git init开始,另一方面,镜像构建从git init开始 FROM。李虽然你可以在多级构建中使用多个FROM,但概念保持不变,因为只使用了最后一个FROM。< / >
    1. git repo更新或基于一系列提交图像是由一系列命令/层构建的(除了CMD),其中每个命令都会创建映像的新
  3. 克隆并运行一个git repo对应于启动容器(CMD)。因此,当克隆一个git repo时,你通常会克隆最新的提交,而docker在启动时使用最新创建的镜像层(docker run ...)

最后要注意的是,要理解层,很难将其视为一个孤立的概念,而应该将其视为使容器/图像工作的组件或构建块之一。类似地,commit是git版本控制系统的构建块之一,它可以被称为任何东西,而选择commit肯定是有意义的:)。

图层是包含文件和文件夹的文件夹,这些文件和文件夹是创建图像的结果。

例如:

FROM alpine:3.14            # Layer 1
RUN apk add --no-cache tree # Layer 2
COPY test.txt /tmp/         # Layer 3
ENTRYPOINT ["tree"]

这个Dockerfile将创建三个文件夹,然后将它们复制到主机系统并“合并”。一起创建所谓的联合文件系统。这些文件夹实际上并没有在物理上合并,但是使用了Union Mount来创建它们合并的假象。

在上面的例子中,会有:

# Layer 1
/var/lib/docker/overlay2/1d06...35310/diff
bin    dev    etc    home   lib    media  mnt    opt    proc   root   run    sbin   srv    sys    tmp    usr    var


# Layer 2
/var/lib/docker/overlay2/23wgom2anm2uysvg988r3tw9c/diff
etc  lib  usr  var
bin
tree


# Layer 3
/var/lib/docker/overlay2/41htpkg76b3zwg29kqsb103of/diff
tmp
test.txt

所有这些文件夹都将被“合并”。使用前面提到的mount命令创建最终的Linux文件系统,然后将其设置为正在运行的进程(也就是容器)的根目录("tree"在本例中)使用chroot命令或类似命令。

更多信息可以在这里找到:https://martinheinz.dev/blog/44