社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  docker

【提效】docker镜像构建优化-提速10倍

阿里云开发者 • 6 天前 • 45 次点击  

阿里妹导读


本文主要记录了自己通过查阅相关资料,一步步排查问题,最后通过优化Docerfile文件将docker镜像构建从十几分钟降低到1分钟左右,效率提高了10倍左右。

一、概述

最近在开发python应用程序,在部署应用的时候发现构建镜像过程十分缓慢,极大影响开发效率。既然遇到了问题就不要逃避,而应该尝试解决一下。本文主要记录了自己通过查阅相关资料,一步步排查问题,最后通过优化Docerfile文件将docker镜像构建从十几分钟降低到1分钟左右,效率提高了10倍左右。

本文通过如下几个部分进行介绍:

  • 现状:简单介绍一下未优化前的情况;
  • 优化效果:简单介绍优化后的情况;
  • 分析过程:介绍如何分析镜像构建存在的问题;
  • 优化过程:介绍如何通过优化Dockerfile提高镜像构建效率;
  • 优化总结:最后总结镜像构建的几个优化方法;

通过本文的学习,你将有如下收获:

1.了解镜像构建优化的过程。

2.了解一些常用的镜像构建优化的技巧。

二、优化前效果



未优化前可以看到镜像构建耗时16分钟,构建完成后镜像大小约8G,使用的Dockerfile文件如下:

FROM reg.docker.alibaba-inc.com/aci-images/python-service:3.8.0-63928922
# init folderRUN mkdir -p /home/admin/logs && mkdir -p /home/admin/bin && mkdir -p /home/admin/conf && mkdir -p /home/admin/nginx && mkdir -p /home/admin/.maxhub/env_helper_util/zeta-local-env
# install zetaRUN pushd /home/admin/.maxhub/env_helper_util/zeta-local-env && \ wget https://artifacts.antgroup-inc.cn/artifact/repositories/softwares-common/antcode/zeta/0.7.9/zeta-linux-amd64-0.7.9.sh -O zeta-release.sh && \ chmod +x zeta-release.sh && \ ./zeta-release.sh --prefix=/usr/local && \ popd
# init env and install software COPY conf/docker/build.yaml /root/RUN python3.10 -m pip install -U antimgbuilder -i https://pypi.antfin-inc.com/simple && \ python3.10 -m antimgbuilder --config-file /root/build.yaml
# copy source file# COPY --chown=admin:admin mydemo /home/admin/release/mydemoCOPY --chown=admin:admin aml_core /home/admin/release/aml_coreCOPY --chown=admin:admin backend /home/admin/release/backend
# install requirements.txtCOPY --chown=admin:admin requirements.txt /home/admin/release/RUN python3.10 -m venv /home/admin/run && \ . /home/admin/run/bin/activate && \ python3.10 -m pip install -i https://pypi.antfin-inc.com/simple-remote --upgrade pip &&\ python3.10 -m pip install -i https://pypi.antfin-inc.com/simple -r /home/admin/release/requirements.txt
# copy scriptsCOPY --chown=admin:admin conf/docker/scripts/admin /home/adminCOPY --chown=admin:admin conf/nginx /home/admin/nginx
# 最后确保admin目录下文件权限RUN chown admin:admin -R /home/admin
RUN chmod a+xw /home/admin/bin/fetch_ollama.sh /tmp


三、优化后效果



优化后可以看到镜像构建时间为1分钟左右,镜像大小约5G,使用的Dockerfile如下:

# 第一阶段:下载依赖FROM reg.docker.alibaba-inc.com/antfin-sqa/amlregservermodel-dev:20241016125401_b0296dab as builder
# install requirements.txtCOPY --chown=admin:admin requirements.txt /home/admin/release/RUN python3.10 -m venv /home/admin/run && \ . /home/admin/run/bin/activate && \ python3.10 -m pip install -i https://pypi.antfin-inc.com/simple-remote --upgrade pip &&\ python3.10 -m pip install -i https://pypi.antfin-inc.com/simple -r /home/admin/release/requirements.txt --no-cache-dir

# 第二阶段:构建应用程序镜像FROM reg.docker.alibaba-inc.com/aci-images/python-service:3.8.0-63928922
# init folderRUN mkdir -p /home/admin/logs && mkdir -p /home/admin/bin && mkdir -p /home/admin/conf && mkdir -p /home/admin/nginx && mkdir -p /home/admin/.maxhub/env_helper_util/zeta-local-env
# install zetaRUN pushd /home/admin/.maxhub/env_helper_util/zeta-local-env && \ wget https://artifacts.antgroup-inc.cn/artifact/repositories/softwares-common/antcode/zeta/0.7.9/zeta-linux-amd64-0.7.9.sh -O zeta-release.sh && \ chmod +x zeta-release.sh && \ ./zeta-release.sh --prefix=/usr/local && \ rm -f zeta-release.sh && \ popd
# install virtualenv and uvicornRUN python3.10 -m venv /home/admin/run && \ . /home/admin/run/bin/activate && \ python3.10 -m pip install -i https://pypi.antfin-inc.com/simple-remote --upgrade pip &&\ python3.10 -m pip install -i https://pypi.antfin-inc.com/simple uvicorn --no-cache-dir
# init env and install softwareCOPY conf/docker/build.yaml /root/RUN python3.10 -m pip install -U antimgbuilder -i https://pypi.antfin-inc.com/simple && \ python3.10 -m antimgbuilder --config-file /root/build.yaml

# copy scriptsCOPY --chown=admin:admin conf/docker/scripts/admin /home/adminCOPY --chown=admin:admin conf/nginx /home/admin/nginxCOPY --chown=admin:admin aml_core /home/admin/release/aml_core
RUN chmod a+xw /home/admin/bin/fetch_ollama.sh /tmp
# 从第一阶段复制下载的依赖到第二阶段COPY --from=builder /home/admin/run/lib/python3.10/site-packages/ /home/admin/run/lib/python3.10/site-packages/
# copy source fileCOPY --chown=admin:admin backend /home/admin/release/backend


四、分析过程


4.1. 镜像构建耗时分析

分析优化前的镜像构建构成,找到最耗时的阶段,进入镜像构建任务详情页:



点击 image-build-3 找到耗时最长的指令:



可以看到在指令:
COPY --chown=admin:admin conf/docker/scripts/admin /home/admin

的前一步耗时达到了 10 分钟左右,对照着 Dockerfile 文件可以看到,是下面下载依赖比较耗时。



由于构建出来的镜像比较大,导致推送镜像耗时约:4分钟



镜像构建耗时分析总结:

1.从构建的日志中可以看到是下载依赖比较耗时约:10 分钟。
2. 并且前面的指令缓存失效, 则随后指令构建的镜像都不再使用缓存导致耗时增加。

3.构建出来的镜像比较大,导致推送镜像耗时约:4分钟。


4.2. 镜像构建体积较大分析

从前面的Dockfile文件中可以看到,使用的基础镜像是:

reg.docker.alibaba-inc.com/aci-images/python-service:3.8.0-63928922,拉取该镜像,查看基础镜像的体积:



可以看到该镜像的大小是:2.33G

我们进入Docker容器,查看下载依赖的大小以及缓存的大小,下载依赖的缓存目录一般是 /root/.cache/pip:



镜像构建体积较大分析总结:

1.基础镜像体积较大:2.33G。

2.安装的依赖较大,并且下载依赖时默认开启了缓存,导致占用更多的内存空间约:3.1G(包括下载的依赖和缓存占用:2.6G + 729M )。

为什么使用 pip install 安装依赖时没有添加 --no-cache-dir 参数会导致占用的内存更多?

如果在使用 pip install 安装依赖时没有添加 --no-cache-dir 参数,会导致缓存目录中的文件不断增加,占用更多的内存空间。每次使用 pip install 安装依赖时,pip 会默认将下载的依赖包保存在缓存目录 /root/.cache/pip 中,如果没有添加 --no-cache-dir 参数,pip 会在安装依赖时从缓存目录中检查已有的依赖包,如果有相同的包就会直接使用缓存中的包,而不是重新下载。因此,随着时间的推移,缓存目录中会存放越来越多的依赖包,占用更多的内存空间。

为了避免占用更多的内存空间,可以在使用 pip install 安装依赖时添加 --no-cache-dir 参数,这样将禁用缓存,使得每次安装依赖都会重新下载依赖包,从而避免占用更多的内存空间。


4.3. 使用 docker history 分析

接下来我们使用 docker history 进行分析。

docker history :用于查看 Docker 镜像的构建历史,显示每一层的提交信息,包括镜像 ID、创建人、创建时间和指令。这个命令可以帮助用户理解镜像是如何构建的,了解每个操作对镜像大小的影响,以及对镜像进行优化和精简。通过查看镜像的构建历史,用户可以更好地理解和管理镜像,提高镜像的性能和安全性。

下载镜像到本地或者在本地构建未优化的Dockerfile镜像,使用下面的命令构建镜像:

docker build -f conf/docker/Dockerfile  -t amlservermodel:latest .
使用下面的命令分析镜像,可以看到各个操作对镜像大小的影响如下:
docker history amlservermodel:latest


使用 docker history 分析镜像总结,占用镜像体积较大的两个层是:

1.下载依赖占用约:3.18G(包括下载的依赖和缓存)。

2.给目录设置权限:

在构建docker镜像时,Dockerfile文件中使用指令:RUN chown admin:admin -R /home/admin,为什么会导致镜像体积变大?

这条指令会导致镜像体积变大的原因是,每一条指令在Dockerfile中都会创建一个新的镜像层。当在Dockerfile中使用RUN chown命令时,会创建一个新的镜像层,其中包含了文件权限的更改。这意味着原本的文件和目录仍然存在于之前的镜像层中,而新的镜像层只是在其基础上进行了更改。因此,即使在新的镜像层中删除了一些文件或更改了文件权限,但之前的镜像层仍然包含了这些文件,导致镜像体积变大。

为了避免镜像体积变大,可以在Dockerfile中尽量减少使用RUN指令,或者在同一条RUN指令中一次性执行多个操作,以减少创建的镜像层数。也可以在构建镜像的过程中清理不必要的文件和缓存,以减小镜像的体积。

五、优化过程


5.1. 优化方案

在进行优化之前,我们需要了解一些docker镜像的构建原则:

5.1.1. 动静分离原则

我们应该把变化最少的部分放在 Dockerfile 的前面,这样可以充分利用镜像缓存。

1.每条指令只要前面的指令缓存失效, 则随后指令构建的镜像都不再使用缓存。

2.对应COPY和ADD文件会检验文件的校验和, 如果发现改变则缓存失效。

5.1.2. 多阶段构建

Docker多阶段构建镜像的原理是利用多个Docker容器来处理不同的构建阶段,并将最终构建产物传递给下一个容器。每个阶段可以定义自己的基础镜像、依赖和构建执行环境,使得镜像的构建过程更加灵活和高效。

多阶段构建镜像可以降低最终镜像的体积的原因包括以下几点:

1.优化构建产物:多阶段构建可以在不同的阶段处理不同的构建任务,比如编译、打包、测试等,从而避免将构建产物暴露给最终镜像,减小了最终镜像的体积。

2.移除构建环境:多阶段构建可以将构建时用到的工具、依赖等移除,只将必要的产物传递到最终镜像中,避免了构建环境对最终镜像的影响,减小了最终镜像的体积。

3.优化基础镜像:多阶段构建可以根据需要选择不同的基础镜像,每个阶段可以选择适合自己需求的基础镜像,从而避免了不必要的依赖和工具被打包到最终镜像中,减小了最终镜像的体积。

综上所述,多阶段构建镜像可以将构建过程分解成多个阶段,根据需要进行优化,避免了不必要的依赖和工具被打包到最终镜像中,从而降低了最终镜像的体积。


5.2. 优化分析

通过前面的分析,我们做出如下优化:

5.2.1. 构建耗时优化

通过多阶段构建的方式,可以并行的处理不同阶段的构建,只将必要的产物传递到最终镜像,为了提高下载依赖的效率,我们还可以将项目中使用的依赖提前下载好,构建在第一阶段或者基础镜像中,避免每次重新下载全部依赖。我的优化方案如下:

使用多阶段构建,第一阶段下载依赖,第二阶段构建应用程序镜像。

对于第一阶段下载依赖,我将应用程序需要的依赖构建在基础镜像中,避免重新下载全部依赖,如果依赖文件 requirements.txt有变化,则会重新下载依赖,并且和第二阶段的构建是并行进行,任然是可以提高构建效率的,我的修改如下:



最后将下载的依赖从第一阶段复制到第二阶段,因为应用程序会频繁修改,所以将应用程序的代码放在了Dockerfile文件的最后,将不经常变化的内容放在Dockerfile文件前面,可以充分利用镜像的缓存提高效率,修改后的Dockerfile文件如下:



我们查看优化后的构建过程如下:



5.2.2. 镜像体积优化

针对前面的分析,当前案例中镜像体积较大的原因有如下几点:

1.基础镜像较大;
2.安装的依赖较大,并且开启了缓存;
3.使用RUN chown 指令导致镜像较大;

4.由于镜像构建中发现有很多指令,构建了很多层,导致镜像体积变大;

针对的优化方案

1.基础镜像较大我们可以选择较小的基础镜像,可以在 蚂蚁的基础镜像中查找对应的基础镜像;
2.安装依赖时使用 pip install --no-cache-dir 关闭缓存;
3.移除 RUN chown指令,因为在这里可以针对特定的文件或者文件夹指定就行,不需要对所有的目录修改权限;

4.合并多个RUN指令,减少镜像的层数,进而减少镜像的体积;

最后通过针对性的优化,镜像体积减小到原来的一半,本来想找一个体积更小的基础镜像,但是在基础镜像库中没有找到合适的版本,并且通过前面的一系列优化,镜像的构建时间以及可以达到秒级了,所以后续有需要再自定义一个合适的基础镜像。

六、构建缓存失效

构建镜像时,Docker 会逐步执行 Dockerfile 中的指令,并按指定的顺序执行每条指令。对于每条指令, 构建器都会检查是否可以重用构建缓存中的指令。


6.1. 一般规则

构建缓存失效的基本规则如下:

  • 构建器首先检查基础镜像是否已缓存。随后的每个指令都会与缓存的层进行比较,如果没有缓存的层与指令完全匹配,则缓存将失效。
  • 在大多数情况下,将 Dockerfile 指令与相应的缓存层进行比较就足够了,但是有些指令需要额外的检查和解释。
  • 对于ADDCOPY指令以及RUN带有绑定挂载的指令(RUN --mount=type=bind),构建器会根据文件元数据计算缓存校验和,以确定缓存是否有效。在缓存查找期间,如果涉及的任何文件的文件元数据发生更改,则缓存将失效。计算缓存校验和时不考虑文件的修改时间(mtime),如果只有复制的文件的 mtime发生了更改,则缓存不会失效。
  • 除了ADDCOPY命令之外,缓存检查不会查看容器中的文件来确定缓存匹配。例如,在处理命令时,RUN apt-get -y update不会检查容器中更新的文件来确定是否存在缓存命中。在这种情况下,只使用命令字符串本身来查找匹配项。

一旦缓存失效,所有后续的 Dockerfile 命令都会生成新的图像,并且不会使用缓存。

如果构建的镜像包含多个层,并且想要确保构建缓存可重复使用,请尽可能按从更改频率较低的顺序排列指令。


6.2. RUN 指令

指令缓存RUN不会在构建之间自动失效。假设您的 Dockerfile 中有一步要安装curl:

FROM alpine:3.20 AS installRUN apk add curl
这并不意味着curl在镜像中的版本始终是最新的,一周后重建镜像仍将获得与之前相同的软件包,如果要强制重新执行该RUN指令,可以:
  • 确保之前的一个层已经改变;
  • 使用以下方法在构建之前清除构建缓存 docker builder prune
  • 使用--no-cache--no-cache-filter选项;

--no-cache-filter选项允许您指定特定的构建阶段以使缓存无效:

$ docker build --no-cache-filter install .
如果要使RUN指令的缓存失效,可以传递一个构建参数,该参数带有变化的值,构建参数确实会导致缓存失效,因为RUN指令是使用命令字符串本身来查找匹配缓存的。

七、优化总结

要对镜像进行优化和精简,你可以采取以下步骤:

1.使用多阶段构建:使用多阶段构建可以减少镜像的大小,因为你可以在不同的镜像中执行不同的构建步骤,并在最终镜像中只保留必要的文件和依赖。
2.清理不需要的文件和依赖:在Dockerfile中,你可以使用一系列命令来清理不需要的文件和依赖,例如使用rm命令删除不需要的文件,使用--no-cache选项来清理缓存等。
3.使用轻量的基础镜像:选择一个轻量的基础镜像作为你的镜像的基础,这样可以减少镜像的大小。
4.合并镜像层:在Dockerfile中,你可以使用多个命令来合并多个操作,这样可以减少镜像的层数和大小。

5.我们应该把变化最少的部分放在 Dockerfile 的前面,将经常变化的内容放在最后面,这样可以充分利用镜像缓存。

通过以上步骤,你可以对镜像进行优化和精简,减少其大小并提高性能。

Dockerfile 编码规约:

规约项

Level

说明

Dockerfile指令不应超过20条

WARN

层数过多

不应该超过3条连续RUN命令

WARN

层数过多

CMD/ENTRYPOINT/EXPOSE/LABEL指令位置应在COPY/RUN之前

INFO

动静分离原则

RUN 指令应在COPY主包指令之前

ERROR

动静分离原则

RUN yum指令后应以yum clean all收尾

WARN

最小原则

RUN pip install应该加--no-cache-dir参数

ERROR

最小原则

RUN npm install指令应加--no-cache参数

ERROR

最小原则

单层镜像最大的编译时间不应超过80秒

WARN

构建效率过低

单层镜像体积不应超过500M

WARN

最小原则

构建时发生变化的层不应该超过3层

INFO

动静分离

base镜像体积不应超过2G

WARN

最小原则

最后在网上找到一些其他的优化手段,在这里汇总一下:
  • 编写.dockerignore 文件

  • 容器只运行单个应用

  • 将多个 RUN 指令合并为一个

  • 基础镜像的标签不要用 latest

  • 每个 RUN 指令后删除多余文件

  • 选择合适的基础镜像(alpine 版本最好)

  • 设置 WORKDIR 和 CMD

  • 使用 ENTRYPOINT (可选)

  • 在 entrypoint 脚本中使用 exec

  • COPY 与 ADD 优先使用前者

  • 合理调整 COPY 与 RUN 的顺序

  • 设置默认的环境变量,映射端口和数据卷

  • 使用 LABEL 设置镜像元数据

  • 添加 HEALTHCHECK

参考文档

Building best practices:https://docs.docker.com/build/building/best-practices/


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/175900
 
45 次点击