docker构建镜像与容器的深入理解

本文详细介绍了Dockerfile构建镜像的过程,包括镜像的分层特性、缓存机制以及如何从中间步骤恢复构建。同时,探讨了compose启动服务时的构建过程,证实了compose本质上是调用Docker API,并强调了镜像与容器的关联性。通过实例展示了如何使用Dockerfile和compose进行操作,并分析了不同操作对镜像和容器的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

docker笔记

记录在使用docker过程中产生的一些思考和问题。

其他资料:

关于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)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值