Files
docker_practice/04_image/4.4_commit.md
T

131 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 4.4 利用 commit 理解镜像构成
> 注意如果你是初学者可以暂时跳过后面的内容直接学习[容器](../05_container/)一节
`docker commit` 除了帮助理解镜像分层之外在少数场景下也可用于留存现场例如事后分析被入侵容器的状态但是日常定制镜像不应依赖 `docker commit`而应使用下一节介绍的 `Dockerfile`
镜像是容器的基础每次执行 `docker run` 的时候都会指定哪个镜像作为容器运行的基础在之前的例子中我们所使用的都是来自于 Docker Hub 的镜像直接使用这些镜像是可以满足一定的需求而当这些镜像无法直接满足需求时我们就需要定制这些镜像接下来的几节就将讲解如何定制镜像
回顾一下之前我们学到的知识镜像是多层存储每一层是在前一层的基础上进行的修改而容器同样也是多层存储它以镜像层为只读基础并在最上方增加一层供运行时写入的容器层
现在让我们以定制一个 Web 服务器为例子来讲解镜像是如何构建的
> **版本提示**以下示例中 `nginx` 镜像使用默认 `latest` 标签生产环境建议指定具体版本号 `nginx:1.30`),以避免镜像更新带来的不兼容性
```bash
$ docker run --name webserver -d -p 8080:80 nginx
```
这条命令会用 `nginx` 镜像启动一个容器命名为 `webserver`其中`-p 8080:80` 表示把宿主机的 `8080` 端口映射到容器内的 `80` 端口这样我们就可以用浏览器去访问这个 `nginx` 服务器
如果是在本机运行的 Docker那么可以直接访问`http://localhost:8080`如果是在虚拟机云服务器上安装的 Docker则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址并保留 `8080` 端口
直接用浏览器访问的话我们会看到默认的 Nginx 欢迎页面
![](../_images/images-mac-example-nginx.png)
现在假设我们非常不喜欢这个欢迎页面我们希望改成欢迎 Docker 的文字我们可以使用 `docker exec` 命令进入容器修改其内容
```bash
$ docker exec -it webserver bash
root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
exit
```
我们以交互式终端方式进入 `webserver` 容器并执行了 `bash` 命令也就是获得一个可操作的 Shell
然后我们用 `<h1>Hello, Docker!</h1>` 覆盖了 `/usr/share/nginx/html/index.html` 的内容
现在我们再刷新浏览器的话会发现内容被改变了
![](../_images/images-create-nginx-docker.png)
我们修改了容器的文件也就是改动了容器的存储层我们可以通过 `docker diff` 命令看到具体的改动
```bash
$ docker diff webserver
C /root
A /root/.bash_history
C /run
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
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` 命令可以将容器的存储层保存下来成为镜像换句话说就是在原有镜像的基础上再叠加上容器的存储层并构成新的镜像以后我们运行这个新镜像的时候就会拥有原有容器最后的文件变化
`docker commit` 的语法格式为
```bash
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
```
我们可以用下面的命令将容器保存为镜像默认情况下`docker commit` 会在提交时暂停容器进程以降低数据损坏的风险如果确实不希望暂停可以显式指定 `--no-pause`
```bash
$ docker commit \
--author "Tao Wang <twang2218@gmail.com>" \
--message "修改了默认网页" \
webserver \
nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214
```
其中 `--author` 是指定修改的作者 `--message` 则是记录本次修改的内容这点和 `git` 版本控制相似不过这里这些信息可以省略留空
我们可以在 `docker image ls` 中看到这个新定制的镜像下面的输出仅为示例标签创建时间和大小会随着镜像版本和本地环境不同而变化
```bash
$ docker image ls nginx
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v2 07e334659748 9 seconds ago 181.5 MB
nginx 1.30 05a60462f8ba 12 days ago 181.5 MB
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB
```
> **版本说明**上面示例中 `nginx:1.30` 代表 1.30 系列的最新 patch 版本际应用中应根据需求选择确切的版本号而不是盲目使用 `latest`
我们还可以用 `docker history` 具体查看镜像内的历史记录例如先执行 `docker history nginx:v2`再对比 `docker history nginx:latest`就能看到我们刚刚提交出来的新层
```bash
$ docker history nginx:v2
IMAGE CREATED CREATED BY SIZE COMMENT
07e334659748 54 seconds ago nginx -g daemon off; 95 B 修改了默认网页
e43d811ce2f4 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) EXPOSE 443/tcp 80/tcp 0 B
<missing> 4 weeks ago /bin/sh -c ln -sf /dev/stdout /var/log/nginx/ 22 B
<missing> 4 weeks ago /bin/sh -c apt-key adv --keyserver hkp://pgp. 58.46 MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.27.0-1 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) MAINTAINER NGINX Docker Ma 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:23aa4f893e3288698c 123 MB
```
新的镜像定制好后我们可以来运行这个镜像
```bash
$ docker run --name web2 -d -p 81:80 nginx:v2
```
这里我们将新容器命名为 `web2`并把宿主机的 `81` 端口映射到容器的 `80` 端口访问 `http://localhost:81` 看到的内容应该和之前修改后的 `webserver` 一样
至此我们第一次完成了定制镜像使用的是 `docker commit` 命令手动操作给旧的镜像添加了新的一层形成新的镜像对镜像多层存储应该有了更直观的感觉
### 4.4.1 慎用 `docker commit`
使用 `docker commit` 命令虽然可以比较直观地帮助理解镜像分层存储的概念但它不应作为常规定制镜像的方式
首先如果仔细观察之前的 `docker diff webserver` 的结果你会发现除了真正想要修改的 `/usr/share/nginx/html/index.html` 文件外由于命令的执行还有很多文件被改动或添加了这还仅仅是最简单的操作如果是安装软件包编译构建那会有大量的无关内容被添加进来将会导致镜像极为臃肿
此外使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作生成的镜像也被称为 **黑箱镜像**换句话说就是除了制作镜像的人知道执行过什么命令怎么生成的镜像别人根本无从得知而且即使是这个制作镜像的人过一段时间后也无法记清具体的操作这种黑箱镜像的维护工作是非常痛苦的
而且回顾之前提及的镜像所使用的分层存储的概念除当前层外之前的每一层都不会发生改变换句话说任何修改的结果仅仅是在当前层进行标记添加修改而不会改动上一层如果使用 `docker commit` 制作镜像以及后期继续修改那么每一次修改都会让镜像再膨胀一层即使某些文件在更高层里被删除了它们仍然存在于更低层中只是在最终视图里被隐藏而已因此日常定制镜像应使用下一节介绍的 `Dockerfile`把构建过程写成可重复执行便于审查的文本