理解镜像层
原文:https://docs.docker.com/guides/docker-concepts/building-images/understanding-image-layers/
解释
正如你在什么是镜像?中所学到的,容器镜像是由多个层组成的。而且,一旦这些层被创建,它们就是不可变的。但是,这实际上意味着什么?这些层是如何用来创建容器可以使用的文件系统的呢?
镜像层
镜像中的每一层包含一组文件系统的变更——添加、删除或修改。让我们看一个理论上的镜像:
- 第一层添加了基本命令和一个包管理器,如apt。
- 第二层安装了Python运行环境和pip用于依赖管理。
- 第三层复制了一个应用的特定requirements.txt文件。
- 第四层安装了该应用的特定依赖。
- 第五层复制了应用的实际源代码。
这个例子可能看起来像这样:
这样做的好处是它允许在镜像之间重用层。例如,想象一下你想要创建另一个Python应用。由于分层,你可以利用相同的Python基础。这将使构建更快,并减少分发镜像所需的存储和带宽。镜像层的布局可能看起来类似于下图:
层允许你通过重用其他人的基础层来扩展镜像,只添加你的应用所需的数据。
层的叠加
层叠是通过可寻址存储和联合文件系统实现的。虽然这会变得技术性,但其工作方式如下:
- 下载每层后,它被提取到宿主文件系统上的一个单独的目录中。
- 当你从一个镜像运行容器时,会创建一个联合文件系统,层叠在彼此之上,创建一个新的统一视图。
- 容器启动时,其根目录设置为这个统一目录的位置,使用
chroot
。
创建联合文件系统时,除了镜像层之外,还会为正在运行的容器专门创建一个目录。这允许容器进行文件系统更改,同时保持原始镜像层不受影响。这使你能够从同一底层镜像运行多个容器。
试一试
在这个动手指南中,你将使用docker container commit
命令手动创建新的镜像层。注意,你很少以这种方式创建镜像,因为你通常会使用Dockerfile。但这有助于理解它是如何工作的。
创建基础镜像
在这第一步中,你将创建你自己的基础镜像,然后用于以下步骤。
下载并安装 Docker桌面版。
在终端中运行以下命令以启动一个新容器:
consoledocker run --name=base-container -ti ubuntu
一旦镜像被下载并容器启动,你应该看到一个新的shell提示符。这在你的容器内部运行。它会看起来类似于以下内容(容器ID会有所不同):
consoleroot@d8c5ca119fcd:/#
在容器内部,运行以下命令来安装Node.js:
consoleapt update && apt install -y nodejs
当这个命令运行时,它在容器内部下载并安装了Node。在联合文件系统的上下文中,这些文件系统的变更发生在这个容器特有的目录内。
验证Node是否已安装,运行以下命令:
consolenode -e 'console.log("Hello world!")'
然后你应该在控制台看到“Hello world!”出现。
现在你已经安装了Node,你准备好将你所做的更改保存为一个新的镜像层,从这个镜像层你可以启动新容器或构建新镜像。为此,你将使用
docker container commit
命令。在新终端中运行以下命令:consoledocker container commit -m "Add node" base-container node-base
查看你的镜像层使用
docker image history
命令:consoledocker image history node-base
你将看到类似以下的输出:
consoleIMAGE 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。
若要证明你的镜像已安装Node,你可以使用这个新镜像启动一个新容器:
consoledocker run node-base node -e "console.log('Hello again')"
随后,你应该在终端得到一个“Hello again”的输出,显示Node已安装并运行。
现在你已经完成了你的基础镜像创建,你可以移除那个容器:
consoledocker rm -f base-container
基础镜像定义
基础镜像是构建其他镜像的基础。任何镜像都可以用作基础镜像。然而,有些镜像是故意创建为构建块,提供应用程序的基础或起点。
在这个例子中,你可能不会部署这个
node-base
镜像,因为它实际上还没有做任何事情。但这是你可以用于其他构建的基础。
构建应用镜像
现在你有了一个基础镜像,你可以扩展这个镜像来构建其他镜像。
使用新创建的node-base镜像启动一个新容器:
consoledocker run --name=app-container -ti node-base
在这个容器内部,运行以下命令来创建一个Node程序:
consoleecho 'console.log("Hello from an app")' > app.js
若要运行这个Node程序,你可以使用以下命令并在屏幕上看到消息打印出来:
consolenode app.js
在另一个终端中,运行以下命令将这个容器的更改保存为一个新镜像:
consoledocker container commit -c "CMD node app.js" -m "Add app" app-container sample-app
这个命令不仅创建了一个名为
sample-app
的新镜像,还为镜像添加了额外的配置来设置启动容器时的默认命令。在这种情况下,你设置它为自动运行node app.js
。在容器外部的终端中,运行以下命令查看更新的层:
consoledocker image history sample-app
然后你将看到如下输出。注意顶层评论有“Add app”,下一层有“Add node”:
consoleIMAGE 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
最后,使用全新的镜像启动一个新容器。因为你指定了默认命令,你可以使用以下命令:
consoledocker run sample-app
你应该看到你的问候语出现在终端,来自你的Node程序。
现在你已经完成了你的容器,你可以使用以下命令移除它们:
consoledocker rm -f app-container
额外资源
如果你想更深入了解你所学到的内容,请查看以下资源: