理解镜像层

解释

正如您在 什么是镜像? 中了解到的,容器镜像由层组成。每个层一旦创建就是不可变的。但这到底意味着什么?这些层是如何用于创建容器可以使用的文件系统的?

镜像层

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

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

此示例可能如下所示

screenshot of the flowchart showing the concept of the image layers

这很有益,因为它允许在镜像之间重用层。例如,假设您想创建另一个 Python 应用程序。由于分层,您可以利用相同的 Python 基础。这将使构建速度更快,并减少分发镜像所需的存储空间和带宽。镜像分层可能类似于以下内容

screenshot of the flowchart showing the benefits of the image layering

层允许您通过重用他人的基础层来扩展镜像,只添加您的应用程序需要的那些数据。

堆叠层

分层是通过内容寻址存储和联合文件系统实现的。虽然这会变得很技术性,但以下是其工作原理

  1. 每个层下载后,都会解压缩到主机文件系统上的其自身目录中。
  2. 当您从镜像运行容器时,会创建一个联合文件系统,其中层彼此叠加,从而创建一个新的统一视图。
  3. 当容器启动时,其根目录被设置为该统一目录的位置,使用 chroot

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

试一试

在本实践指南中,您将使用 docker container commit 命令手动创建新的镜像层。请注意,您很少会以这种方式创建镜像,因为您通常会 使用 Dockerfile。但是,它使您更容易理解其工作原理。

创建一个基础镜像

在第一步中,您将创建您自己的基础镜像,然后在后续步骤中使用它。

  1. 下载并安装 Docker Desktop。

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

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

    镜像下载完毕且容器启动后,您应该会看到一个新的 shell 提示符。这是在容器内部运行的。它看起来类似于以下内容(容器 ID 会有所不同)

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

    $ apt update && apt install -y nodejs
    

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

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

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

    然后您应该在控制台中看到“Hello world!”。

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

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

    $ docker image history node-base
    

    您将看到类似于以下内容的输出

    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)  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,您可以使用此新镜像启动一个新的容器

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

    这样,您应该在终端中获得“Hello again”输出,表明 Node 已安装并正常工作。

  8. 现在您已经创建了基础镜像,可以删除该容器

    $ docker rm -f base-container
    

基础镜像定义

基础镜像是构建其他镜像的基础。可以使用任何镜像作为基础镜像。但是,某些镜像是专门创建为构建块,为应用程序提供基础或起点。

在此示例中,您可能不会部署此 node-base 镜像,因为它目前还没有实际执行任何操作。但它是您可以用于其他构建的基础。

构建一个应用程序镜像

现在您有了基础镜像,可以扩展该镜像来构建其他镜像。

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

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

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

    要运行此 Node 程序,您可以使用以下命令,并查看屏幕上打印的消息

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

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

    此命令不仅创建了一个名为 sample-app 的新镜像,而且还向镜像添加了其他配置,以便在启动容器时设置默认命令。在这种情况下,您将其设置为自动运行 node app.js

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

    $ docker image history sample-app
    

    然后您将看到类似于以下内容的输出。请注意顶层注释为“Add app”,下一层为“Add node”

    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. 最后,使用全新的镜像启动一个新的容器。由于您指定了默认命令,因此您可以使用以下命令

    $ docker run sample-app
    

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

  6. 现在您已完成容器,可以使用以下命令删除它们

    $ docker rm -f app-container
    

其他资源

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

下一步

正如前面所暗示的,大多数镜像构建不使用 docker container commit。相反,您将使用 Dockerfile,它会自动执行这些步骤。