通过先前的几篇博客, 我应该将Docker的启动关闭以及运行调试的各种操作介绍完了, 从本节开始的两节, 会着重介绍Dockerfile的编写. 此小节会介绍一些基础内容.

基础镜像的选择问题

有关基础镜像的一些说明, 请查看我先前的博客Docker基础镜像tag含义及选择.

个人看法:

  1. 如果一个项目已经使用了Docker, 并且依赖于基础镜像, 那么还是保持现状比较好

  2. 新的项目, 我觉得考虑一下alpine也是可以的, 你可以按需安装软件, 可以用最少的 依赖让项目跑起来, 而且如果你去Docker hub上翻看基础镜像, 几乎每个基础镜像 下面的tag都会有几个是用alpine构建的.

  3. 我在业余的时候也会写写golang, 其实我觉得这个语言本身就可以跑在不同的发行版上 使用Docker的时候完全是为了隔离宿主机环境, 使用alpine也是合适的.

  4. 暂时没有考虑使用slim.

官方文档 - Dockerfile编写最佳实践

建议大家至少也去看看官方文档中的Best practices for writing Dockerfiles.

Docker的build操作, 是以层为概念的, Dockerfile中的每个语句会成为镜像中一个只读的层. 一个好消息就是, 合理的组织层结构可以有效复用先前的层, 以下面的语句为例.

演示构建时的缓存

1
2
3
4
5
6
FROM python:3.7-alpine

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
COPY main.py main.py
ENTRYPOINT ["python", "main.py"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")

def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])

if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
1
2
# requirements.txt
tornado==6.0.3

第一次构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Sending build context to Docker daemon  4.096kB
Step 1/5 : FROM python:3.7-alpine
---> b11d2a09763f
Step 2/5 : COPY requirements.txt requirements.txt
---> 2936fbbc42a0
Step 3/5 : RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
---> Running in ac062d0f6a21
Looking in indexes: https://mirrors.aliyun.com/pypi/simple/
Collecting tornado==6.0.3
Downloading https://mirrors.aliyun.com/pypi/packages/30/78/2d2823598496127b21423baffaa186b668f73cd91887fcef78b6eade136b/tornado-6.0.3.tar.gz (482kB)
Building wheels for collected packages: tornado
Building wheel for tornado (setup.py): started
Building wheel for tornado (setup.py): finished with status 'done'
Created wheel for tornado: filename=tornado-6.0.3-cp37-cp37m-linux_x86_64.whl size=410711 sha256=80949481cc31a3ea333993961965fb109ad338102d14e8dd7715c55e31714cbc
Stored in directory: /root/.cache/pip/wheels/df/a7/02/7a4c8f28d662723d5f5cc490138d18000b059e197ada8851b3
Successfully built tornado
Installing collected packages: tornado
Successfully installed tornado-6.0.3
Removing intermediate container ac062d0f6a21
---> 21428e5cf880
Step 4/5 : COPY main.py main.py
---> 400b8bb0856f
Step 5/5 : ENTRYPOINT ["python", "main.py"]
---> Running in f7bb65ee7c0d
Removing intermediate container f7bb65ee7c0d
---> 04441e7b5051
Successfully built 04441e7b5051
Successfully tagged test:latest

假如我们修改了main.py

再次构建时, 可以发现Step 3/5以及之前的语句显示的是Using cache, 说明缓存被复用了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Sending build context to Docker daemon  4.096kB
Step 1/5 : FROM python:3.7-alpine
---> b11d2a09763f
Step 2/5 : COPY requirements.txt requirements.txt
---> Using cache
---> 2936fbbc42a0
Step 3/5 : RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
---> Using cache
---> 21428e5cf880
Step 4/5 : COPY main.py main.py
---> 2e9bc4b4f879
Step 5/5 : ENTRYPOINT ["python", "main.py"]
---> Running in 40991f958270
Removing intermediate container 40991f958270
---> a38a8b6cb396
Successfully built a38a8b6cb396
Successfully tagged test:latest

FAQ

如果大家对Dockerfile有任何问题, 可以在评论中写, 我会继续更新这部分的内容.

apt语句为什么要合并

官方一直推荐的apt-get书写形式是类似下面这样的, 为什么apt-get update要和后面的语句写到一起.

1
2
3
4
5
6
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion

我简要说明一下:

apt-get update的主要作用是去源上爬取数据库, 这份数据库存储了apt-get可以安装的软件, 并且包含了下载地址. 那么, 这份数据库其实是会随时间变化的, 那么也就是说, 如果apt-get updateinstall语句分开了, 类似下面这样:

1
2
3
4
5
6
7
RUN apt-get update
RUN apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion

本身这样写是没有关系的, 你仍然可以正常构建. 但是, 当你想要安装新的软件时, 例如增加了vim:

1
2
3
4
5
6
7
8
RUN apt-get update
RUN apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion \
vim

那么apt-get update会因为这一层的语句没有变化, 而不会执行. 因此install语句就有可能使用了旧的软件数据, 甚至下载地址都已经失效了, 这就会造成构建时的失败. 请一定要将installupdate语句合并在一起写.