关于dockerfile构建镜像的过程

原理 里已经提到过镜像是一层层只读层构成的,上层由下层构建,并指向下层。这点在使用Dockerfile的build过程中也可以看出,每一层都有其ID。也可以透过 history 命令看出。其中最上层就作为库有着自己的名字和tag,当然镜像ID也是有的,这可以通过 docker images 查看。

由于Docker的镜像是分层(Diff层)构建的,因此每一层都有缓存下来,以ID作为该层的标识符。这些分层的镜像是能够在其上构建出其他镜像的,或者说是可被视作cache来利用的,下面可以简单的验证一下,写一个Dockerfile,然后构建:

FROM python:latest
ENV PYTHONUNBUFFERED 0

构建过程:

kimono@kimono:~/dockerTest$ docker build -t casual .
Sending build context to Docker daemon  3.072kB
Step 1/2 : FROM python:latest
 ---> 2c31ca135cf9
Step 2/2 : ENV PYTHONUNBUFFERED 0
 ---> Running in 266e3b4554f3
Removing intermediate container 266e3b4554f3
 ---> 20fdcb77c85c
Successfully built 20fdcb77c85c
Successfully tagged casual:latest

当然之前就已经pull了python:latest,因此这里的第一步是直接引用相应镜像的,第二步会根据这个镜像创建一个容器执行相应的操作,然后再将这个中间容器提交成为更上一层的镜像,并将中间容器移除,这与「原理」一致,即docker build本质上是docker rundocker commit的交叉动作。

接下来将Dockerfile更进一步试试:

FROM python:latest
ENV PYTHONUNBUFFERED 0
RUN mkdir -p /home/coredumped

构建过程:

kimono@kimono:~/dockerTest$ docker build -t casual1 .
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM python:latest
 ---> 2c31ca135cf9
Step 2/3 : ENV PYTHONUNBUFFERED 0
 ---> Using cache
 ---> 20fdcb77c85c
Step 3/3 : RUN mkdir -p /home/coredumped
 ---> Running in 0c3c5a0905cb
Removing intermediate container 0c3c5a0905cb
 ---> 80420152d333
Successfully built 80420152d333
Successfully tagged casual1:latest

可以看出第二步的构建是引用的上层镜像,相当于前两步都“跳过”了。继续构建一个casual2:

kimono@kimono:~/dockerTest$ docker build -t casual2 .
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM python:latest
 ---> 2c31ca135cf9
Step 2/3 : ENV PYTHONUNBUFFERED 0
 ---> Using cache
 ---> 20fdcb77c85c
Step 3/3 : RUN mkdir -p /home/coredumped
 ---> Using cache
 ---> 80420152d333
Successfully built 80420152d333
Successfully tagged casual2:latest

这一次相当于同一个镜像有两个名字了。

由此不难推断,只要仍存在某一只读层,那么任何镜像都可以在此之上继续构建,而不需要再有在该层之前的pull或者构建等动作,这无疑加快了构建镜像的速度。已存在的这些中间只读层也可以看做cache层,这在构建的过程中也有诸如以下说明:

 ---> Using cache
 ---> 20fdcb77c85c

这还有另外一个好处:当Dockerfile较大,或者有较多非常耗时的RUN命令时,有可能会出现某一步超时或者出问题构建失败(这在国内可是相当常见的,例如git clone或者apt-get install用的默认国外源,甚至pip numpy)。而在Dockerfile中每一个命令就是一层只读层镜像,这意味着我们可以从中间开始继续构建过程,因为前面的每一步都有作为cache存下来,只需要比较过程是否相同就可以知道这一步是否可以「Using cache」来跳过。当然这一切的前提是那些构建到一半失败之前的镜像还在,能够调用,如何肯定这些镜像确实还在呢?构建失败后通过docker images可以看到有个没有名字和tag的镜像,因为并没有走到最后一步,docker build -t [name:tag] .中的name和tag自然就没有依附的对象,只有一个目前成功构建的最顶层镜像的ID,这时候可以用docker history [image-ID]查看这个镜像的层间关系,表明之前成功构建的部分都有镜像留存,随时可以从中间的任何一步继续下去。

既然提到了镜像的存在,还可以延伸到镜像的删除。如果某一层只读层镜像还有上层镜像在用着,或者该镜像对应一个库,那么这一层就不会被删除,反之则会被一起删除,有点像inode与硬链接的关系。下面来验证一下这个想法。

先依次删除casual1和casual2:

kimono@kimono:~/dockerTest$ docker rmi casual1
Untagged: casual1:latest
kimono@kimono:~/dockerTest$ docker rmi casual2
Untagged: casual2:latest
Deleted: sha256:80420152d333a914576610f8f3011536070e685df674b93780a23d20a8c8eea1
Deleted: sha256:f76e7e3a22c27d5e06bf9dd90ea0d898cd4d5f1e017839569f8e7f38897a5a79

第一个删除只是untagged操作,因为顶层镜像还有casual2在用着。第二个删除就会将顶层的ID为8042的镜像删除。

这时候我们再构建回casual1:

kimono@kimono:~/dockerTest$ docker build -t casual1 .
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM python:latest
 ---> 2c31ca135cf9
Step 2/3 : ENV PYTHONUNBUFFERED 0
 ---> Using cache
 ---> 20fdcb77c85c
Step 3/3 : RUN mkdir -p /home/coredumped
 ---> Running in 67c1a176728c
Removing intermediate container 67c1a176728c
 ---> 0798a22d9e2e
Successfully built 0798a22d9e2e
Successfully tagged casual1:latest

这里有两个信息值得注意:1)虽然前面删除过casual1和casual2,但由于casual还存在,因此第二层还是直接用的已有镜像,那层镜像并没有被删除。2)虽然指令一致,但新建的顶层镜像已经是完全不同的另一个ID了,这说明ID可能随build时间的不同而改变,来决定是否使用cache的比较对象并不是ID,而是动作,这些应该会被记录在每层的元数据(metadata)中。

接下来把所有的casual删除:

kimono@kimono:~/dockerTest$ docker images | grep casual | cut -d ' ' -f1 | xargs docker rmi
Untagged: casual1:latest
Deleted: sha256:0798a22d9e2ef572f7388126259262c2dd429faf243150090e6b9019cee118ad
Deleted: sha256:8836c2c33b568f8fdee8ab41ac3c768b864419eeafb0ee4c2ad2f4d8b6e08ced
Untagged: casual:latest
Deleted: sha256:20fdcb77c85cca8d3d75e4fa5ff76814f62592a123f422a7a1e6c7be19168f8a

可以发现原来的第二层镜像(ID前缀为20fd)也被删除了,因为此时没有任何库再用到这一层。不过最底层的python:latest镜像(ID: 2c31ca135cf9)并没有被删除,这都验证了上述的想法。当然,其中的任何一步都可以穿插docker history来从另一方面验证。

关于compose启动服务的构建过程

与Dockerfile的构建过程类似,对compose启动服务过程中的构建也做类似的测试,对其构建过程也能有个比较清晰的了解,新建一个docker-compose.yml

version: "3"
services:
    casual:
        build: .
        ports:
          - "50808:50808"

在构建前我们试试先用Dockerfile构建:

kimono@kimono:~/dockerTest$ docker build -t casual .
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM python:latest
 ---> 2c31ca135cf9
Step 2/3 : ENV PYTHONUNBUFFERED 0
 ---> Running in 94583acf1f9c
Removing intermediate container 94583acf1f9c
 ---> 76bb8b5bf3de
Step 3/3 : RUN mkdir -p /home/coredumped
 ---> Running in b899fa987840
Removing intermediate container b899fa987840
 ---> 96df3fda6c07
Successfully built 96df3fda6c07
Successfully tagged casual:latest

然后在用compose启动服务:

kimono@kimono:~/dockerTest$ docker-compose up -d
Building casual
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM python:latest
 ---> 2c31ca135cf9
Step 2/3 : ENV PYTHONUNBUFFERED 0
 ---> Using cache
 ---> 76bb8b5bf3de
Step 3/3 : RUN mkdir -p /home/coredumped
 ---> Using cache
 ---> 96df3fda6c07
Successfully built 96df3fda6c07
Successfully tagged dockertest_casual:latest
WARNING: Image for service casual was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating dockertest_casual_1 ... done

可以发现compose确实就是一个集成功能的工具,调用的还是原有docker的api,这里与原有的build过程没有任何区别。

看一下容器和镜像:

kimono@kimono:~/dockerTest$ docker ps -a
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS                      PORTS     NAMES
e3284ccc5bf5   dockertest_casual      "python3"                2 minutes ago   Exited (0) 2 minutes ago              dockertest_casual_1
kimono@kimono:~/dockerTest$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED         SIZE
casual              latest    96df3fda6c07   3 minutes ago   885MB
dockertest_casual   latest    96df3fda6c07   3 minutes ago   885MB

虽然两个库用的都是相同的镜像(ID: 96df3),但compose启动服务还是会新建一个库,然后新建的容器也是用的该库。compose创建的库名会带上文件夹名作为前缀,容器则会用数字作后缀。

如果我们再用compose启动一次服务,会直接启动已有的容器。

kimono@kimono:~/dockerTest$ docker-compose up -d
Starting dockertest_casual_1 ... done

即使删除容器也不过是重新根据 dockertest_casual 库再创建一次容器:

kimono@kimono:~/dockerTest$ docker rm dockertest_casual_1 
dockertest_casual_1
kimono@kimono:~/dockerTest$ docker-compose up -d
Creating dockertest_casual_1 ... done

可以判断这个compose中的casual服务是与 dockertest_casual 库名绑定的。这一点也可以由前面的一句 WARNING: Image for service casual was built because it did not already exist. 佐证。

尝试一下之前提到的--build选项,该选项是重新构建的镜像的意思。

kimono@kimono:~/dockerTest$ docker-compose up -d --build
Building casual
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM python:latest
 ---> 2c31ca135cf9
Step 2/3 : ENV PYTHONUNBUFFERED 0
 ---> Using cache
 ---> 76bb8b5bf3de
Step 3/3 : RUN mkdir -p /home/coredumped
 ---> Using cache
 ---> 96df3fda6c07
Successfully built 96df3fda6c07
Successfully tagged dockertest_casual:latest
Starting dockertest_casual_1 ... done
kimono@kimono:~/dockerTest$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
casual              latest    96df3fda6c07   12 minutes ago   885MB
dockertest_casual   latest    96df3fda6c07   12 minutes ago   885MB

继续验证一下之前关于库名绑定的想法:

kimono@kimono:~/dockerTest$ docker rm dockertest_casual_1 
dockertest_casual_1
kimono@kimono:~/dockerTest$ docker rmi dockertest_casual:latest 
Untagged: dockertest_casual:latest
kimono@kimono:~/dockerTest$ docker tag casual:latest dockertest_casual:latest
kimono@kimono:~/dockerTest$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
casual              latest    96df3fda6c07   19 minutes ago   885MB
dockertest_casual   latest    96df3fda6c07   19 minutes ago   885MB
kimono@kimono:~/dockerTest$ docker rmi casual:latest 
Untagged: casual:latest
kimono@kimono:~/dockerTest$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
dockertest_casual   latest    96df3fda6c07   20 minutes ago   885MB

启动casual服务:

kimono@kimono:~/dockerTest$ docker-compose up -d
Creating dockertest_casual_1 ... done

呵,果然。不过还可以再极端一点,删除原有的库,重新构建一个 dockertest_casual 库:

kimono@kimono:~/dockerTest$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED              SIZE
dockertest_casual   latest    c7c812ca7836   About a minute ago   885MB

构建完毕后修改Dockerfile的内容:

FROM python:latest
ENV PYTHONUNBUFFERED 3
RUN mkdir -p /home/core

不加入--build选项启动服务:

kimono@kimono:~/dockerTest$ docker-compose up -d
Creating dockertest_casual_1 ... done

看来绑定库名是毫无疑问的事情了,现在加入--build选项:

kimono@kimono:~/dockerTest$ docker-compose up -d --build
Building casual
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM python:latest
 ---> 2c31ca135cf9
Step 2/3 : ENV PYTHONUNBUFFERED 3
 ---> Running in 17f3e839f53a
Removing intermediate container 17f3e839f53a
 ---> ae1917d51d08
Step 3/3 : RUN mkdir -p /home/core
 ---> Running in 15bf5f88fbce
Removing intermediate container 15bf5f88fbce
 ---> 4c59d7c9e9a8
Successfully built 4c59d7c9e9a8
Successfully tagged dockertest_casual:latest
Recreating dockertest_casual_1 ... done

由于重新构建库时每一层镜像都会重新构建,因此会出现跟原来的库很大的不同(毕竟Dockerfile不同了),虽然库名没变,但里面的每一层镜像都发生了改变,甚至是新建的镜像,并将原来的 dockertest_casual_1 容器重新构建。那么原来的那个镜像去哪了?

kimono@kimono:~/dockerTest$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
dockertest_casual   latest    4c59d7c9e9a8   34 seconds ago   885MB
<none>              <none>    c7c812ca7836   5 minutes ago    885MB

显然,原有的镜像还保留着id,但库名和tag名都被取代了(none)。
t_casual_1 容器重新构建。那么原来的那个镜像去哪了?

kimono@kimono:~/dockerTest$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
dockertest_casual   latest    4c59d7c9e9a8   34 seconds ago   885MB
<none>              <none>    c7c812ca7836   5 minutes ago    885MB

显然,原有的镜像还保留着id,但库名和tag名都被取代了(none)。

docker笔记记录在使用docker过程中产生的一些思考和问题。其他资料:基础原理指令doc实例解析关于dockerfile构建镜像的过程在原理里已经提到过镜像是一层层只读层构成的,上层由下层构建,并指向下层。这点在使用Dockerfile的build过程中也可以看出,每一层都有其ID。也可以透过history命令看出。其中最上层就作为库有着自己的名字和tag,当然镜像ID也是有的,这可以通过docker images查看。由于Docker的镜像是分层(Diff层)构建的,因此每一层 最近在做一个比赛,要求使用docker提交,就入门了一下。在入门的过程中,网上关于docker的内容太理论和繁琐,这里给出一个快速上手并使用docker的方法。 1 Dcoker基础 关于docker入门的第一步请参考,下面的两个网址,我就不重复造轮子了,看的很快: Docker 教程 https://yeasy.gitbooks.io/docker_practice/content/i... Untagged: ubuntu:14.04 Deleted: sha256:a8e78858b03ba02c3df71d555f90057f890495aabc86e7a39396c68c87ed5ff2 当我们在docker中执行docker images命令查看到一些不想使用的镜像容器时,会执行docker rmi -f xxx来对相应的目标进行删除操作,但相应的命令执行完成后会出现上面的两种情况,一种是删除成功,另一种则只是将镜像对应的标签解除了
故障描述[root@entel1 ~]# docker rmi entel_zmc_images:zmc_base Untagged: entel_zmc_images:zmc_base操作步骤先移除掉exited状态的容器 ,然后删除dangling 状态的镜像docker rm $(docker ps -q -f status=exited) docker rmi $(docker image
Cappuccinooos-MacBook-Pro:.docker Cappuccinooo$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE runoob/centos 6.7 4994f...
背景测试环境和生产环境 部署了不同的registry服务,通过cli 操作过生产环境或测试环境push/pull image功能。本地虚拟机,docker image 残留了很多image ,现在想删除掉,发现有一个imageid(e9d3f7300f03)无法删除。 原因:docker rmi 只能删除tag ,多处引用将无法删除 测试环境export QCOSINDEXHOST=https
### 回答1: 我可以回答这个问题。Docker 是一种容器化技术,可以将应用程序及其依赖项打包成一个可移植的容器,方便在不同的环境中部署和运行。构建 Docker 镜像可以使用 Dockerfile 文件来定义镜像构建过程,推送镜像可以使用 Docker Hub 或其他镜像仓库来存储和分享镜像。 ### 回答2: Docker是一种开源的容器化平台,能够帮助开发人员和运维人员更高效地构建、打包、分发和运行应用程序。Docker的核心概念是镜像(Image)和容器(Container)。 首先,构建镜像是指将一个应用程序的代码、依赖项以及配置文件等打包成一个可执行的镜像文件。在构建镜像之前,我们需要编写一个Dockerfile,其中包含了构建镜像所需的步骤和指令。Dockerfile可以指定基础镜像、安装软件、配置环境变量等内容。通过执行docker build命令,Docker会根据Dockerfile的指令来逐步构建镜像,并生成一个唯一的镜像标识符。 接下来,推送镜像是将构建好的镜像上传到Docker镜像仓库中,以便其他人或其他机器可以下载和使用这个镜像Docker镜像仓库是一个集中存储和管理镜像的地方,可以方便地共享和分发镜像。推送镜像之前,我们需要先登录到镜像仓库,然后使用docker push命令将本地镜像推送到指定的仓库地址和版本号。 通过Docker构建镜像并推送到镜像仓库,可以带来一些好处。首先,镜像可以快速部署和启动,减少了应用程序的依赖和配置问题。其次,镜像具有良好的可重复性,可以在不同的环境中使用相同的镜像来保持一致性。此外,镜像可以方便地进行版本控制和更新,而且可以在不同的主机上快速部署相同的应用程序。 总体而言,Docker构建镜像和推送镜像Docker平台的核心功能之一,可以帮助开发人员和运维人员更加高效地管理和分发应用程序。通过合理地使用Docker构建和推送镜像,可以提高开发效率、降低部署成本,并且可以方便地进行版本控制和更新。