Skip to content

理解镜像层

原文:https://docs.docker.com/guides/docker-concepts/building-images/understanding-image-layers/

解释

正如你在什么是镜像?中所学到的,容器镜像是由多个层组成的。而且,一旦这些层被创建,它们就是不可变的。但是,这实际上意味着什么?这些层是如何用来创建容器可以使用的文件系统的呢?

镜像层

镜像中的每一层包含一组文件系统的变更——添加、删除或修改。让我们看一个理论上的镜像:

  1. 第一层添加了基本命令和一个包管理器,如apt。
  2. 第二层安装了Python运行环境和pip用于依赖管理。
  3. 第三层复制了一个应用的特定requirements.txt文件。
  4. 第四层安装了该应用的特定依赖。
  5. 第五层复制了应用的实际源代码。

这个例子可能看起来像这样:

流程图截图显示镜像层的概念

这样做的好处是它允许在镜像之间重用层。例如,想象一下你想要创建另一个Python应用。由于分层,你可以利用相同的Python基础。这将使构建更快,并减少分发镜像所需的存储和带宽。镜像层的布局可能看起来类似于下图:

流程图截图显示镜像层重用的好处

层允许你通过重用其他人的基础层来扩展镜像,只添加你的应用所需的数据。

层的叠加

层叠是通过可寻址存储和联合文件系统实现的。虽然这会变得技术性,但其工作方式如下:

  1. 下载每层后,它被提取到宿主文件系统上的一个单独的目录中。
  2. 当你从一个镜像运行容器时,会创建一个联合文件系统,层叠在彼此之上,创建一个新的统一视图。
  3. 容器启动时,其根目录设置为这个统一目录的位置,使用chroot

创建联合文件系统时,除了镜像层之外,还会为正在运行的容器专门创建一个目录。这允许容器进行文件系统更改,同时保持原始镜像层不受影响。这使你能够从同一底层镜像运行多个容器。

试一试

在这个动手指南中,你将使用docker container commit命令手动创建新的镜像层。注意,你很少以这种方式创建镜像,因为你通常会使用Dockerfile。但这有助于理解它是如何工作的。

创建基础镜像

在这第一步中,你将创建你自己的基础镜像,然后用于以下步骤。

  1. 下载并安装 Docker桌面版。

  2. 在终端中运行以下命令以启动一个新容器:

    console
    docker run --name=base-container -ti ubuntu

    一旦镜像被下载并容器启动,你应该看到一个新的shell提示符。这在你的容器内部运行。它会看起来类似于以下内容(容器ID会有所不同):

    console
    root@d8c5ca119fcd:/#
  3. 在容器内部,运行以下命令来安装Node.js:

    console
    apt update && apt install -y nodejs

    当这个命令运行时,它在容器内部下载并安装了Node。在联合文件系统的上下文中,这些文件系统的变更发生在这个容器特有的目录内。

  4. 验证Node是否已安装,运行以下命令:

    console
    node -e 'console.log("Hello world!")'

    然后你应该在控制台看到“Hello world!”出现。

  5. 现在你已经安装了Node,你准备好将你所做的更改保存为一个新的镜像层,从这个镜像层你可以启动新容器或构建新镜像。为此,你将使用docker container commit命令。在新终端中运行以下命令:

    console
    docker container commit -m "Add node" base-container node-base
  6. 查看你的镜像层使用docker image history命令:

    console
    docker image history node-base

    你将看到类似以下的输出:

    console
    IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
    d5c1fca2cdc4   10 seconds ago   /bin/bash                                       126MB     Add node
    2b7cc08dcdbb   5 weeks ago      /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
    <missing>      5 weeks ago      /bin/sh -c #(nop) ADD file:07cdbabf782942af0…   69.2MB
    <missing>      5 weeks ago      /bin/sh -c #(nop)  LABEL org.opencontainers…   0B
    <missing>      5 weeks ago      /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B
    <missing>      5 weeks ago      /bin/sh -c #(nop)  ARG RELEASE                  0B

    注意最上面一行的“Add node”评论。这一层包含了你刚刚安装的Node.js。

  7. 若要证明你的镜像已安装Node,你可以使用这个新镜像启动一个新容器:

    console
    docker run node-base node -e "console.log('Hello again')"

    随后,你应该在终端得到一个“Hello again”的输出,显示Node已安装并运行。

  8. 现在你已经完成了你的基础镜像创建,你可以移除那个容器:

    console
    docker rm -f base-container

基础镜像定义

基础镜像是构建其他镜像的基础。任何镜像都可以用作基础镜像。然而,有些镜像是故意创建为构建块,提供应用程序的基础或起点。

在这个例子中,你可能不会部署这个node-base镜像,因为它实际上还没有做任何事情。但这是你可以用于其他构建的基础。

构建应用镜像

现在你有了一个基础镜像,你可以扩展这个镜像来构建其他镜像。

  1. 使用新创建的node-base镜像启动一个新容器:

    console
    docker run --name=app-container -ti node-base
  2. 在这个容器内部,运行以下命令来创建一个Node程序:

    console
    echo 'console.log("Hello from an app")' > app.js

    若要运行这个Node程序,你可以使用以下命令并在屏幕上看到消息打印出来:

    console
    node app.js
  3. 在另一个终端中,运行以下命令将这个容器的更改保存为一个新镜像:

    console
    docker container commit -c "CMD node app.js" -m "Add app" app-container sample-app

    这个命令不仅创建了一个名为sample-app的新镜像,还为镜像添加了额外的配置来设置启动容器时的默认命令。在这种情况下,你设置它为自动运行node app.js

  4. 在容器外部的终端中,运行以下命令查看更新的层:

    console
    docker image history sample-app

    然后你将看到如下输出。注意顶层评论有“Add app”,下一层有“Add node”:

    console
    IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
    c1502e2ec875   About a minute ago   /bin/bash                                       33B       Add app
    5310da79c50a   4 minutes ago        /bin/bash                                       126MB     Add node
    2b7cc08dcdbb   5 weeks ago          /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
    <missing>      5 weeks ago          /bin/sh -c #(nop) ADD file:07cdbabf782942af0…   69.2MB
    <missing>      5 weeks ago          /bin/sh -c #(nop)  LABEL org.opencontainers…   0B
    <missing>      5 weeks ago          /bin/sh -c #(nop)  LABEL org.opencontainers…   0B
    <missing>      5 weeks ago          /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B
    <missing>      5 weeks ago          /bin/sh -c #(nop)  ARG RELEASE                  0B
  5. 最后,使用全新的镜像启动一个新容器。因为你指定了默认命令,你可以使用以下命令:

    console
    docker run sample-app

    你应该看到你的问候语出现在终端,来自你的Node程序。

  6. 现在你已经完成了你的容器,你可以使用以下命令移除它们:

    console
    docker rm -f app-container

额外资源

如果你想更深入了解你所学到的内容,请查看以下资源: