当前位置: 首页 > news >正文

Dockerfile 中,把多个 RUN 合并在一起,能减少镜像尺寸吗?

先说结论:
有些时候能,有些时候不能,但你要明白原理 – Docker 使用 UnionFS,镜像尺寸随着层数增多,是单调非减的。

问题

看到一个 Dockerfile:

FROM python:3.17.7-alpine3.20
RUN pip3 install pillow
RUN pip3 install django
RUN pip3 install jieba
RUN pip3 install nltk
RUN pip3 install colormap

有人建议,把这几个 pip3 install 合并成一个 pip3 install -r requirements.txt,可以减小最终打包出来的镜像尺寸,真得是这样吗?

实验一:一次 pip 安装 vs 多次 pip 安装

多次 pip 安装

我们把上面这个最初始的 Dockerfile 打包出来的镜像起名为 temp:multi。
通过 docker image ls temp:multi 看到,这个包的大小为 396MB
然后,通过 docker history temp:multi 看到这个包的层级如下:

IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
a934e19243c7   2 hours ago   RUN /bin/sh -c pip3 install colormap   -i ht…   177MB     buildkit.dockerfile.v0
<missing>      2 hours ago   RUN /bin/sh -c pip3 install nltk       -i ht…   22.8MB    buildkit.dockerfile.v0
<missing>      2 hours ago   RUN /bin/sh -c pip3 install django     -i ht…   39.1MB    buildkit.dockerfile.v0
<missing>      2 hours ago   RUN /bin/sh -c pip3 install jieba      -i ht…   83.5MB    buildkit.dockerfile.v0
<missing>      2 hours ago   RUN /bin/sh -c pip3 install pillow     -i ht…   27.1MB    buildkit.dockerfile.v0
<missing>      2 weeks ago   CMD ["python3"]                                 0B        buildkit.dockerfile.v0
<missing>      2 weeks ago   RUN /bin/sh -c set -eux;  for src in idle3 p…   36B       buildkit.dockerfile.v0
<missing>      2 weeks ago   RUN /bin/sh -c set -eux;   apk add --no-cach…   38.1MB    buildkit.dockerfile.v0
<missing>      2 weeks ago   ENV PYTHON_VERSION=3.12.7                       0B        buildkit.dockerfile.v0
<missing>      2 weeks ago   ENV GPG_KEY=7169605F62C751356D054A26A821E680…   0B        buildkit.dockerfile.v0
<missing>      2 weeks ago   RUN /bin/sh -c set -eux;  apk add --no-cache…   999kB     buildkit.dockerfile.v0
<missing>      2 weeks ago   ENV LANG=C.UTF-8                                0B        buildkit.dockerfile.v0
<missing>      2 weeks ago   ENV PATH=/usr/local/bin:/usr/local/sbin:/usr…   0B        buildkit.dockerfile.v0
<missing>      5 weeks ago   /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      5 weeks ago   /bin/sh -c #(nop) ADD file:5758b97d8301c84a2…   7.8MB

可以看到,pip 安装的这些包,总大小应该在 350MB 左右,加上 python-alpine 原来46MB 的大小,整好是在 396 MB

一次 pip 安装

创建 requirements.txt 文件,用于 pip 集中安装:

-i https://pypi.tuna.tsinghua.edu.cn/simple
pillow
django
jieba
nltk
colormap

修改 Dockerfile

FROM python:3.12.7-alpine3.20
COPY requirements.txt /root/
RUN pip install -r /root/requirements.txt

使用 docker build 打包镜像 temp:one。 可以看到,temp:one 的大注也是 396 MB。
使用 docerk history temp:one 查看:

IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
a802510b2faa   2 hours ago   RUN /bin/sh -c pip3 install -r /home/require…   349MB     buildkit.dockerfile.v0
<missing>      2 hours ago   COPY requirements.txt /home/ # buildkit         79B       buildkit.dockerfile.v0
<missing>      2 weeks ago   CMD ["python3"]                                 0B        buildkit.dockerfile.v0
<missing>      2 weeks ago   RUN /bin/sh -c set -eux;  for src in idle3 p…   36B       buildkit.dockerfile.v0
<missing>      2 weeks ago   RUN /bin/sh -c set -eux;   apk add --no-cach…   38.1MB    buildkit.dockerfile.v0
<missing>      2 weeks ago   ENV PYTHON_VERSION=3.12.7                       0B        buildkit.dockerfile.v0
<missing>      2 weeks ago   ENV GPG_KEY=7169605F62C751356D054A26A821E680…   0B        buildkit.dockerfile.v0
<missing>      2 weeks ago   RUN /bin/sh -c set -eux;  apk add --no-cache…   999kB     buildkit.dockerfile.v0
<missing>      2 weeks ago   ENV LANG=C.UTF-8                                0B        buildkit.dockerfile.v0
<missing>      2 weeks ago   ENV PATH=/usr/local/bin:/usr/local/sbin:/usr…   0B        buildkit.dockerfile.v0
<missing>      5 weeks ago   /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      5 weeks ago   /bin/sh -c #(nop) ADD file:5758b97d8301c84a2…   7.8MB

可以看到,把所有 pip 安装一个 requirements 里安装,实际上并没有减小镜像包的尺寸

使用 pip --no-cache-dir 实际能减少大小

在一次性 pip 安装的基础上,给 Dockerfile 的 RUN pip 命令加上 --no-cache-dir 的参数

FROM python:3.12.7-alpine3.20
COPY requirements.txt /root/
RUN pip install -r /root/requirements.txt

添加这个参数之后,最后生成的镜像大小为 300MB。

实验二:添加一个文件,然后删除

再看这个 Dockerfile

FROM alpine
COPY bigfile /home
RUN rm -f /home/bigfile

这里,我们基于 alpine 镜像,先往里面拷贝了一个 6.4 MB 的大文件,然后又把它给删除了。相当于什么都没做。
最理想的结果,是打包出来的镜像(起名为 temp:add_remove),大小和 alpine 差不多,也应该是 7.8MB 左右的样子。
但实际结果不是这样。
通过 docker image ls temp:add_remove 可以看到,镜像大小为 14.4MB
而用 docker history temp:add_remove 看,结果如下:

IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
81de752d816c   55 minutes ago   RUN /bin/sh -c rm -f /home/bigfile # buildkit   0B        buildkit.dockerfile.v0
<missing>      56 minutes ago   COPY bigfile /home/ # buildkit                  6.62MB    buildkit.dockerfile.v0
<missing>      47 hours ago     RUN /bin/sh -c adduser -D dot # buildkit        3.03kB    buildkit.dockerfile.v0
<missing>      5 weeks ago      /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      5 weeks ago      /bin/sh -c #(nop) ADD file:5758b97d8301c84a2…   7.8MB

可以看到,原来 COPY 的内容实际仍然在打包的镜像里面。为什么删除没有效果呢?

实验三:如果进入一个正在运行的容器,把其中一些大的临时文件删除,再把容器 Commit 成 Image,是否也可以减小尺寸呢?

先编辑一个简单的 Dockerfile

FROM alpine
COPY bigfile /root/

然后 docker build -t temp:bigfile . 构建一个临时镜像。
加载这个镜像运行:docker run -ti temp:bigfile /bin/sh --name bigfile
进入容器后,把 bigfile 删除。#cd /root/; rm bigfile
退出容器,把这个容器 commit 一个新镜像:
docker commit -m "rm bigfile" bigfile temp:bigfileremoved
这时,我们用 docker images 看一下镜像的大小,会发现,temp:bigfileremoved 这个镜像的大小实际并没有减小,还是和 temp:bigfile 一样大。

REPOSITORY                    TAG              IMAGE ID       CREATED          SIZE
temp                          bigfileremoved   0d7e5db66985   32 minutes ago   14.4MB
temp                          bigfile          3faa35337b69   34 minutes ago   14.4MB

这说明:**就算在容器里面删除文件,再把容器 commit 成一个新的镜像,仍然不能减小文件的尺寸“。

原因是 Docker 的 Union FS

Docker 使用 Union FS 来管理文件系统。
它允许将多个目录挂载到同一个挂载点上,这些目录在挂载点处表现为一个连贯的文件系统。
在 Docker 的上下文中,这意味着可以创建含多个只读层的堆叠,并在顶部添加一个可写层。
Union FS支持层叠多个目录,其中每个目录都可以被视为一个独立的层。
这允许 Docker 镜像由多个只读层组成,每个层代表一个 Dockerfile 指令的结果。

Docker 早期使用的是 AUFS,后来改为使用 overlay2。目前的 Linux 内核,决大多数都支持 oerverlay2。

Overlay2 的一个持点就是:写时复制(Copy-on-Write)
当容器尝试修改一个文件时,overlay2 会检查该文件是否存在于下面的只读层中。
如果是,overlay2 会在可写层创建该文件的副本并进行修改,保持原始只读层不变。

所以,每次 RUN 操作,实际上就是对原始记录加了一层。
如果两个动作如果没有重叠,就像用多个 pip install 不同的包,产生的多层和合在一个 pip 安装产生的一层的大小差不多。
但是,对于先添加,又删除,添加的那层文件始终是在的,只是之后又被删除动作在新的一层上标记为删除。

相当于一个本子上先写了一笔,然后又划掉了(而不是用橡皮擦掉),并不能使本子恢复空白。

最根本的原因,是 overlay2,除了最上层的读写层之外,底下的每一层都是只读的。

打个比方 – 就像钢笔写字,不能擦除

每一次 RUN 操作执行完成后,就生成一层。这层生成之后,就是只读的,不可修改的。
对于下一个操作,以前 RUN 的结果生成的各层,就象是一份份由钢笔书写的文件。
就算是我想修改以前的内容,也只能是做一个标记(就像用钢笔把以前某个文件上的某句话划掉),然后在最新的文件上加上要修改的内容。
总而言之,每新做一个操作,会添加一层。总体的镜像尺寸随着层数增多,是单调非减的。


http://www.mrgr.cn/news/55167.html

相关文章:

  • python——类
  • FineReport 分页
  • Vue项目中如何设置角色菜单权限
  • Reverse.Kr—— 前四题
  • vs2017添加自定义快速注释
  • 【PHP】安装swoole时报错:No package ‘libbrotlienc‘ found
  • 特斯拉自动驾驶出租车计划变成泡影?联想与Meta合作,推出面向PC的个人AI智能体AI Now|AI日报
  • 学生学籍管理系统设计与实现(源码+定制+开发)
  • Varchar(255)可以存255个汉字
  • 为什么互动投影墙在儿童展馆中如此受欢迎?创新互动是关键吗?
  • 【AIGC】ChatGPT提示词Prompt高效编写模式:Self-ask Prompt、ReACT与Reflexion
  • 加速应用开发:低代码平台云SaaS和源码交付模式如何选
  • 大路灯有必要买吗?五款口碑最好的护眼大路灯推荐
  • Spring security 自定义 token 身份验证
  • 学习docker第五弹------Docker容器数据卷
  • 【题解】—— LeetCode一周小结42
  • 一般HR面试程序员会问什么问题?
  • 如何在分布式环境中实现高可靠性分布式锁
  • Openlayers高级交互(4/20):手绘多边形,导出KML文件,可以自定义name和style
  • 分布式系统中的Dapper与Twitter Zipkin:链路追踪技术的实现与应用
  • 全面击破工程级复杂缓存难题
  • 温度传感器
  • Java网络编程 - headers
  • YOLOv11模型改进-注意力-引入简单无参数注意力模块SimAM 提升小目标和遮挡检测
  • 华为OD机试 - 贪心歌手 - 动态规划(Python/JS/C/C++ 2024 D卷 200分)
  • 探秘Python读取文件内容的奥秘:从入门到精通