作者: 张首富
blog:zhangshoufu.com

说明

相信无需再强调 Docker 镜像,大家都已经清楚 Docker 除了传统的 Linux 容器技术之外,还有另辟蹊径的镜像技术。镜像技术的采用,使得 Docker 自底向上打包一个完整的应用,将更多的精力专注于应用本身;而容器技术的延用,则更是在应用的基础上,囊括了应用对资源的需求,通过容器技术完成资源的隔离与管理。

Docker 镜像与 Docker 容器相辅相成,共同作为技术基础支撑着 Docker, 为 Docker 的生态带来巨大的凝聚力。然而,这两项技术并非相互孤立,两者之间的互相转换使得 Docker 的使用变得尤为方便。说到,Docker 镜像与 Docker 容器之间的转化,自然需要从两个角度来看待:从 Docker 镜像转化为 Docker 容器一般是通过 docker run 命令,而从 Docker 容器转化为 Docker 镜像,则完全依靠 docker commit 的实现。

docker commit 的作用

是否还记得 docker build 的实现?Dockerfile 唯一的定义了一个镜像,docker build 对于 Dockerfile 中的每一条命令都会创建一个单独的镜像(包含镜像层内容和镜像 json 文件),最终产生一个含有标签(tag)的镜像,而之前与 Dockerfile 命令对应的镜像均是这个含有 tag 的镜像的祖先镜像。如以下 Dockerfile:

FROM ubuntu:14.04
RUN apt-get update ADD run.sh /
VOLUME /data
CMD ["./run.sh"]

docker build 实现 Dockerfile 到 Docker 镜像的构建,而对于单条 Dockerfile 中的命令(如命令RUN apt-get update ),则是通过针对 Docker 容器的 commit 操作,实现将其构建为单层镜像,也就是大家熟悉的 docker commit 操作。简单的示意图如下:

https://zhangshoufu-images.oss-accelerate.aliyuncs.com/image-20200517112254946.png

docker commit 的原理

深入学习docker commit 的原理前,我不妨先来看看一下 docker help 中关于 commit 命令的阐述:

commit Create a new image from a container's changes 结合上图与命令docker commit 的描述,我们可以发现有三个关键字Image、Container 与Changes 。如何理解这三个关键字,我们可以从以下三个步骤入手:

  1. Docker Daemon 会通过一个 Docker 镜像运行一个 Docker 容器,Docker 通过层级文件系统为 Docker 容器提供文件系统视角,最上层的是可读可写层(Read-Write Layer)。
  2. Docker 容器初始的可读可写层内容均为空,Docker 容器对文件系统的内容更新将全部更新于可读可写层(Read-Write Layer)。
  3. 实现 docker commit 操作时,Docker 仅仅是将可读可写层(Read-Write Layer)中的更新内容,打包为一个全新的镜像。 具体的 docker commit 示意图如下:

观察上图,我们可以发现:对于 commit 命令,几乎所有的操作都围绕着可读可写层(Read-Write Layer),一次 commit 将可读可写层打包为一个全新的镜像,同时也保证镜像之间的独立性。当然,由于一个镜像同时包含镜像层文件系统内容和镜像 json 文件,因此对于一个 commit 操作,Docker Daemon 还会为镜像产生一个全新的 json 文件。

![<https://zhangshoufu-images.oss-accelerate.aliyuncs.com/image-20200517112305079.png>](<https://zhangshoufu-images.oss-accelerate.aliyuncs.com/image-20200517112305079.png>)

结合上图,我们也看到 docker commit 命令也会有一些注意事项,最为重要的是:docker commit 命令仅仅打包可读写写层内容 。对于 Docker 容器而言,文件系统视角包含的内容有 Docker 镜像构成的内容(一个可读可写层加上多个只读层)、数据卷 VOLUME 挂载的目录内容,还有类似于 hosts、hostsname 和resolv.conf 等挂载文件,当然还有一些如 /proc 和 /sys 等虚拟文件系统的内容。commit 操作只专注于可读可写层(Read-Write Layer),因此其他的内容都将不会出现在打包后的镜像中。举例说明,类似于 MySQL 的数据容器,由于其自身的数据一般都持久化到数据卷 VOLUME 中,因此 MySQL在运行过程中产生的数据将不会在 commit 操作后被打包进入镜像。

从 docker commit 看 build 注意事项

命令docker commit 可以实现将一个运行中容器的可读写层,打包为一个镜像。对于 Dockerfile 中的 RUN 命令,Docker的机制同样如此,主要的步骤如下: