Docker多阶段构建:让你的镜像尽可能小
我正在参加「掘金·启航计划」
前言
说到Docker,熟悉的人都知道用Docker容器来快速构建应用非常方便。但docker的使用有很多窍门,初学者肯定遇到过测试一个小项目而镜像文件非常大的问题,你估计纳闷了,我代码就两三行,为啥镜像这么大。今天就以一个简单的Python项目来说说Docker的多阶段构建知识。
首先,我们对镜像不做处理,来看看构建过程。
构建过程都放入一个Dockerfile
将所有构建过程都放在一个Dockerfile中,包含项目及其依赖库的编译、测试、打包等过程。这就会带来一个问题:
- 镜像层次多,镜像体积大,部署时间长;
- 源代码存在泄露的风险。
以一个Python flask项目为例,主要代码文件manage.py如下:
from flask import Flask
# import redis
app = Flask(__name__)
@app.route('/')
def index_func():
return f"hello world"
if __name__ == '__main__':
app.run()
代码的运行我们通过gunicorn来实现,gunicorn的配置代码如下:
worker = 4
worker_class = "gevent"
bind = "0.0.0.0:5000"
编写依赖文件requirements.txt:
Flask==2.0.3
gunicorn==20.1.0
gevent==21.12.0
最后编写Dockerfile:
FROM python:3.7
WORKDIR /myapp/
COPY requirements.txt ./
RUN python -m pip install --upgrade pip \
&& pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
COPY . .
CMD [ "gunicorn", "manage:app", "-c", "./gunicorn_conf.py" ]
构建镜像:
docker build -t myapp .
构建后我们来看看镜像的信息,镜像的体积达到了900MB。也就是说你自己的代码只有不到1MB,但镜像达到了900MB。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp latest cd03a8d7cc8e 13 minutes ago 900MB
那有没有办法把镜像缩小呢?答案肯定是有的,那就是多阶段构建。我们先来看看Docker镜像分层原理。
Docker镜像分层原理
docker镜像其实是一层层的文件系统组成,文件系统基于UnionFS,每一层都在上一层的基础上构建。
使用docker image inspect <镜像名>
可看出镜像的分层结构。
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:dd5b48ca5196c48f1a866eec959e88eb41d9aaf47b708e343e1b12802a421264",
"sha256:fe09b9981fd21fd1aaa81e504536f550ce9d9f5c415f97cba5434de181481505",
"sha256:cba7a92f211b56f4f2f1e7d2549bf3d913abfaecfae5b3b0cf96af17abcb5238",
"sha256:ffd50287b468e69a2042645381db5c5650912ca89edb1ed89ee6ac7c4f2636ea",
"sha256:fef6f293382ecc6a9ab8545dfc728d6edc56463921d7fda32a091aa6a891f65e",
"sha256:dbf3773c055c4ec795c8c9b34bc352ae3f3d5c0cf3349913d212b92d0382bff3",
"sha256:2fbc97bf885e6e66ecc91e63e648ffb81065b8056961749d21f134bd1c9517fd",
"sha256:7bbc35c590816dc5d49b50abfa422a474510a931454ccff0303abdf75b2b8138",
"sha256:5e35b3a4c86f78964922969cecad65dc548d1103b02d9136fbf703e307e52240",
"sha256:5daa7c7d95ca7696ce462ffbd1821db2df119a1aede6ba244941569b0be6ef90",
"sha256:04c818a2b1ea8c5c5305cbe513af28fae24f972549af6bbe25f1b5ecc589d0b2",
"sha256:a52fd73441a31f615d60e9ba176df76b8680e6124e9bd76f459b86c36482aa53",
"sha256:c99fdb7acd9fef32e25df9faf24b0a5557fb2ce625d6f752421c5ad30b49882c"
]
},
回到Dockerfile上,比如我们的Python代码镜像。首先第一层是操作系统,第二层是Python环境和依赖,第三层为具体的Python代码。将这几个层打包起来就是一个镜像。
Dockerfile多阶段构建
在Docker 17.05版本前,如果你不想将所有构建过程都放在一个Dockerfile中,就得将镜像拆分,分散到多个Dockerfile中,最后在一个shell中构建多个Dockerfile。这种方式部署起来较复杂,这里不多介绍。主要来讲多阶段构建。
Docker 17.05版本之后支持多阶段构建,也就是可以在一个Dockerfile中将多个镜像的构建都放在一个Dockerfile中。
举个例子,在一个Dockerfile中构建:
- 第一个镜像:安装编译器,并安装所需的虚拟环境。
- 第二个镜像:复制第一个镜像已构建的环境,运行Python代码。
通过多阶段构建,可以得到更小的镜像文件。
先说说简单项目的多阶段构建方法,以Python项目为例,镜像的构建无非就是编译环境镜像+运行代码镜像。 其中运行代码的镜像大同小异,主要在于编译环境镜像的构建上。
Python项目的多阶段构建
方法一:用pip install --user构建编译环境
当使用pip install --user
来安装依赖包时,安装的文件都会放在当前用户主目录的.local
下,因此使用多阶段构建时,第一个镜像将环境构建在.local
下,第二个镜像直接将.local
复制过来用就行了。
FROM python:3.7-slim AS venv_image
COPY requirements.txt .
RUN pip install --user -r requirements.txt
FROM python:3.7-slim AS code_image
COPY --from=venv_image /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
WORKDIR /myapp
COPY . /myapp
CMD [ "gunicorn", "manage:app", "-c", "./gunicorn_conf.py" ]
备注:
- python:3.7-slim:使用-slim镜像,因Python3.7镜像包含了完整的构建工具,这里用不到,可用slim镜像。
- --FROM:可从上一阶段的镜像复制文件,这里将.local复制过来了.
构建镜像:
docker build -t myapp_0 .
方法二:用virtualenv构建编译环境
注意看FROM AS
和COPY --from
在Dockerfile中的使用,直接看例子:
FROM python:3.7-slim AS venv_image
WORKDIR /py_venv
COPY requirements.txt .
ENV PATH="/py_venv/bin:$PATH"
RUN python -m venv --copies /py_venv \
&& pip install -r requirements.txt
FROM python:3.7-slim as code_image
ENV PATH=/py_venv/bin:$PATH
WORKDIR /myapp
COPY --from=venv_image /py_venv /py_venv
COPY . /myapp
CMD [ "gunicorn", "manage:app", "-c", "./gunicorn_conf.py" ]
备注:
- --copies:加上这个参数, venv会将py_venv文件复制到虚拟环境.
- --FROM:可从上一阶段的镜像复制文件,这里将虚拟环境文件/py_venv复制过来了.
这个Dockerfile很简单,分为2个镜像。第一个镜像是构建Python编译环境以及安装依赖包。第二个镜像是将第一个镜像的虚拟环境复制过来,然后运行代码。
docker build -t myapp_1 .
两种构建方式的对比
构建镜像后,我们来看看多阶段构建Dockerfile和普通Dockerfile的大小区别:
# ethans @ ethan-mac in ~ [15:27:24]
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp latest cd03a8d7cc8e About an hour ago 900MB
myapp_0 latest 91fdb92da04e 26 seconds ago 138MB
myapp_1 latest 337010eb02e6 About an hour ago 149MB
可以看出,通过多阶段构建的操作后,镜像大小从900MB降到了149MB和138MB,差距非常大。
考量到用pip install --user的方式会和系统安装的Python包共存,环境包不是一个隔离目录,推荐使用virtualenv方式。
小结
本文讲述了普通构建和多阶段构建的区别,在我们用Docker部署项目时,功能需求完成后,可用多阶段构建的方法,把我们的镜像尽可能缩小,节省空间资源。
转载自:https://juejin.cn/post/7202066794298769466