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 run
和docker 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)。