作者: 张首富
blog:zhangshoufu.com
相信无需再强调 Docker 镜像,大家都已经清楚 Docker 除了传统的 Linux 容器技术之外,还有另辟蹊径的镜像技术。镜像技术的采用,使得 Docker 自底向上打包一个完整的应用,将更多的精力专注于应用本身;而容器技术的延用,则更是在应用的基础上,囊括了应用对资源的需求,通过容器技术完成资源的隔离与管理。
Docker 镜像与 Docker 容器相辅相成,共同作为技术基础支撑着 Docker, 为 Docker 的生态带来巨大的凝聚力。然而,这两项技术并非相互孤立,两者之间的互相转换使得 Docker 的使用变得尤为方便。说到,Docker 镜像与 Docker 容器之间的转化,自然需要从两个角度来看待:从 Docker 镜像转化为 Docker 容器一般是通过 docker run 命令,而从 Docker 容器转化为 Docker 镜像,则完全依靠 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 操作。简单的示意图如下:
深入学习docker commit 的原理前,我不妨先来看看一下 docker help 中关于 commit 命令的阐述:
commit Create a new image from a container's changes 结合上图与命令docker commit 的描述,我们可以发现有三个关键字Image、Container 与Changes 。如何理解这三个关键字,我们可以从以下三个步骤入手:
观察上图,我们可以发现:对于 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 可以实现将一个运行中容器的可读写层,打包为一个镜像。对于 Dockerfile 中的 RUN 命令,Docker的机制同样如此,主要的步骤如下: