Clarify image build semantics

This commit is contained in:
yeasy
2026-04-24 10:51:49 -07:00
parent 4b44d64cd8
commit 94f74fc86e
3 changed files with 39 additions and 46 deletions
+15 -13
View File
@@ -1,21 +1,21 @@
## 4.4 利用 commit 理解镜像构成
> 注意如果是初学者可以暂时跳过后面的内容直接学习[容器](../05_container/)一节
> 注意如果是初学者可以暂时跳过后面的内容直接学习[容器](../05_container/)一节
注意`docker commit` 命令除了学习之外还有一些特殊的应用场合比如被入侵后保存现场等但是不要使用 `docker commit` 定制镜像定制镜像应该使用 `Dockerfile` 来完成如果你想要定制镜像请查看下一小节
`docker commit` 除了帮助理解镜像分层之外在少数场景下也可用于留存现场例如事后分析被入侵容器的状态但是日常定制镜像不应依赖 `docker commit`而应使用下一节介绍的 `Dockerfile`
镜像是容器的基础每次执行 `docker run` 的时候都会指定哪个镜像作为容器运行的基础在之前的例子中我们所使用的都是来自于 Docker Hub 的镜像直接使用这些镜像是可以满足一定的需求而当这些镜像无法直接满足需求时我们就需要定制这些镜像接下来的几节就将讲解如何定制镜像
回顾一下之前我们学到的知识镜像是多层存储每一层是在前一层的基础上进行的修改而容器同样也是多层存储是在以镜像基础在其基础上加一层作为容器运行时的存储
回顾一下之前我们学到的知识镜像是多层存储每一层是在前一层的基础上进行的修改而容器同样也是多层存储以镜像层为只读基础并在最上方增加一层供运行时写入的容器
现在让我们以定制一个 Web 服务器为例子来讲解镜像是如何构建的
```bash
$ docker run --name webserver -d -p 80:80 nginx
$ docker run --name webserver -d -p 8080:80 nginx
```
这条命令会用 `nginx` 镜像启动一个容器命名为 `webserver`并且映射了 80 端口这样我们可以用浏览器去访问这个 `nginx` 服务器
这条命令会用 `nginx` 镜像启动一个容器命名为 `webserver`其中`-p 8080:80` 表示把宿主机的 `8080` 端口映射到容器内的 `80` 端口这样我们可以用浏览器去访问这个 `nginx` 服务器
如果是在本机运行的 Docker那么可以直接访问`http://localhost`如果是在虚拟机云服务器上安装的 Docker则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址
如果是在本机运行的 Docker那么可以直接访问`http://localhost:8080`如果是在虚拟机云服务器上安装的 Docker则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址并保留 `8080` 端口
直接用浏览器访问的话我们会看到默认的 Nginx 欢迎页面
@@ -58,6 +58,8 @@ A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp
```
其中`A` 表示新增Added`C` 表示变更Changed`D` 表示删除Deleted
现在我们定制好了变化我们希望能将其保存下来形成镜像
要知道当我们运行一个容器的时候 (如果不使用卷的话)我们做的任何文件修改都会被记录于容器存储层里 Docker 提供了一个 `docker commit` 命令可以将容器的存储层保存下来成为镜像换句话说就是在原有镜像的基础上再叠加上容器的存储层并构成新的镜像以后我们运行这个新镜像的时候就会拥有原有容器最后的文件变化
@@ -67,7 +69,7 @@ A /var/cache/nginx/uwsgi_temp
```bash
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
```
我们可以用下面的命令将容器保存为镜像
我们可以用下面的命令将容器保存为镜像默认情况下`docker commit` 会在提交时暂停容器进程以降低数据损坏的风险如果确实不希望暂停可以显式指定 `--no-pause`
```bash
$ docker commit \
@@ -79,7 +81,7 @@ sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214
```
其中 `--author` 是指定修改的作者 `--message` 则是记录本次修改的内容这点和 `git` 版本控制相似不过这里这些信息可以省略留空
我们可以在 `docker image ls` 中看到这个新定制的镜像
我们可以在 `docker image ls` 中看到这个新定制的镜像下面的输出仅为示例标签创建时间和大小会随着镜像版本和本地环境不同而变化
```bash
$ docker image ls nginx
@@ -88,7 +90,7 @@ nginx v2 07e334659748 9 seconds ago
nginx 1.27 05a60462f8ba 12 days ago 181.5 MB
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB
```
我们还可以用 `docker history` 具体查看镜像内的历史记录如果比较 `nginx:latest` 的历史记录我们会发现新增了我们刚刚提交的这一
我们还可以用 `docker history` 具体查看镜像内的历史记录例如先执行 `docker history nginx:v2`再对比 `docker history nginx:latest`就能看到我们刚刚提交出来的新
```bash
$ docker history nginx:v2
@@ -106,18 +108,18 @@ e43d811ce2f4 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "da
新的镜像定制好后我们可以来运行这个镜像
```bash
docker run --name web2 -d -p 81:80 nginx:v2
$ docker run --name web2 -d -p 81:80 nginx:v2
```
这里我们命名为新的服务 `web2`且映射到 `81` 端口访问 `http://localhost:81` 看到结果内容应该和之前修改后的 `webserver` 一样
这里我们将新容器命名为 `web2`把宿主机的 `81` 端口映射到容器的 `80` 端口访问 `http://localhost:81` 看到的内容应该和之前修改后的 `webserver` 一样
至此我们第一次完成了定制镜像使用的是 `docker commit` 命令手动操作给旧的镜像添加了新的一层形成新的镜像对镜像多层存储应该有了更直观的感觉
### 4.4.1 慎用 `docker commit`
使用 `docker commit` 命令虽然可以比较直观帮助理解镜像分层存储的概念是实际环境中并不会这样使用
使用 `docker commit` 命令虽然可以比较直观帮助理解镜像分层存储的概念它不应作为常规定制镜像的方式
首先如果仔细观察之前的 `docker diff webserver` 的结果你会发现除了真正想要修改的 `/usr/share/nginx/html/index.html` 文件外由于命令的执行还有很多文件被改动或添加了这还仅仅是最简单的操作如果是安装软件包编译构建那会有大量的无关内容被添加进来将会导致镜像极为臃肿
此外使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作生成的镜像也被称为 **黑箱镜像**换句话说就是除了制作镜像的人知道执行过什么命令怎么生成的镜像别人根本无从得知而且即使是这个制作镜像的人过一段时间后也无法记清具体的操作这种黑箱镜像的维护工作是非常痛苦的
而且回顾之前提及的镜像所使用的分层存储的概念除当前层外之前的每一层都不会发生改变换句话说任何修改的结果仅仅是在当前层进行标记添加修改而不会改动上一层如果使用 `docker commit` 制作镜像以及后期修改的话每一次修改都会让镜像更加臃肿一次所删除的上一层的东西并不会丢失会一直如影随形的跟着这个镜像即使根本无法访问到这会让镜像更加臃肿
而且回顾之前提及的镜像所使用的分层存储的概念除当前层外之前的每一层都不会发生改变换句话说任何修改的结果仅仅是在当前层进行标记添加修改而不会改动上一层如果使用 `docker commit` 制作镜像以及后期继续修改那么每一次修改都会让镜像再膨胀一层即使某些文件在更高层里被删除了它们仍然存在于更低层中只是在最终视图里被隐藏而已因此日常定制镜像应使用下一节介绍的 `Dockerfile`把构建过程写成可重复执行便于审查的文本