如何减小 Python 的 Docker 镜像的大小
2024 5 16 12:17 AM 444次查看
这些巨量的字节会在构建时在中美之间来回传递,运气好可能 5 分钟能构建完毕,运气不好就可能要等 1 小时了。
那么怎样才能减小这些镜像的大小呢?
首先自然是选一个合适的基础镜像,选项大致有这几种:
python
:优点是用它一般不会出现兼容性和工具链缺失的问题,缺点是比较大,超过 1 GB。bitnami/python:latest
:优缺点同上,尺寸稍微小点,但也有 700 MB。此外,没有最新的测试版,只有稳定版。python:slim
:优点是尺寸较小,约 150 MB,缺点是可能缺少一些东西,在apt-get install
时可能会花更多的时间。python:alpine
:优点是尺寸小,缺点是需要用apk
取代apt-get
,而且因为使用 musl,与 glibc 存在兼容性问题,导致pip install
时很多库需要从源码编译,没有构建好的二进制版本,大大增加了构建时间,而且最终的镜像大小可能差不多。gcr.io/distroless/python3
:优点是尺寸小,缺点是没有 shell 和 pip,且最新版本只支持到 Python 3.11。debian
或ubuntu
:用apt-get install python3
来安装,优点是传说性能较好,尺寸也比python:slim
大不了多少,缺点是要花更多的构建时间,且版本更新不及时,无法使用最新的 Python 版本。debian
或ubuntu
:自行编译 Python 源码,这个其实和前三种干的事差不多,构建时间非常长,但是看不到明显的收益。
python:slim
是最佳的选择。然后就是分层构建了,基本上遵循这样的一个流程:
FROM python:slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ...
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app /app
越不常改动的部分放在越前面执行,这样可以最大限度地利用构建缓存来缩短构建时间。可是在
apt-get install
时,基本上都需要安装 build-essential
和 python3-dev
才能正常编译许多 Python 库,而装上它们就会使镜像大小增大到 500 MB。如果要减少空间占用的话,就需要改成这样:
FROM python:slim
COPY requirements.txt .
RUN apt-get update \
&& apt-get install -y --no-install-recommends ...
&& pip install --no-cache-dir -r requirements.txt
&& apt-get uninstall ...
&& rm -rf /var/lib/apt/lists/*
COPY app /app
然而这样会导致一旦需要修改 requirements.txt
,就得重新构建整个镜像,花费的时间非常长。那么有没有解决办法呢?答案是 multi-stage 构建:
FROM python:slim AS base
RUN apt-get update \
&& apt-get install -y --no-install-recommends ...
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:slim
COPY --from=base /usr/local/lib/python3.12/site-packages/ /usr/local/lib/python3.12/site-packages/
COPY app /app
如果还依赖一些 C 库的话也需要 COPY 到最终镜像里,路径主要在 /usr/lib/x86_64-linux-gnu/
或 /usr/lib/aarch64-linux-gnu/
(如果是 ARM 平台)。登到镜像里执行一下,看看缺少什么文件就复制过去吧。如果 so 文件存在仍然报 not found 的错误,一般是这个 so 依赖的其他 so 不存在,可以用 ldd
检查一下。这样优化后,体积应该能控制在 400 MB 以内了。缺点是如果改了依赖的 C 库,可能要重新检查一下缺少的文件,但这个过程也可以用另一个脚本来检测,然后自动补全。
再进一步,
python:slim
中其实也有很多用不到的东西,我们可以直接将 debian:bookworm-slim
作为基础镜像,再把 Python 和它的依赖库 COPY 过去:FROM python:slim AS base
RUN apt-get update \
&& apt-get install -y --no-install-recommends ...
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM debian:bookworm-slim
COPY --from=base /usr/local/bin/python /usr/local/bin/
COPY --from=base /usr/local/lib/libpython3.12.so.1.0 /usr/local/lib/
COPY --from=base /usr/local/lib/python3.12/ /usr/local/lib/python3.12/
COPY app /app
还是老惯例,登到镜像里执行下试试,缺少啥 C 库就 COPY 进去。这样一来,体积就不到 300 MB 了。
另一个办法是用
gcr.io/distroless/python3
作为基础镜像:FROM python:3.11-slim AS base
RUN apt-get update \
&& apt-get install -y --no-install-recommends ...
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM gcr.io/distroless/python3
COPY --from=base /usr/local/lib/python3.11/site-packages/ /usr/lib/python3.11/dist-packages/
COPY app /app
最终大小不到 140 MB。那么还能不能更进一步呢?毕竟 Go 是可以编译成静态链接,直接用 scratch 镜像来运行的。
要说解决方案也不是没有,Python 可以用 pyinstaller 打包成一个可执行文件,然后再用 staticx 转成静态链接。这样打包的 web 应用大概只要 20 多 MB,和 Go 差不多了。这里有个示例,有兴趣可以试试。
但是这样打包可能存在一些兼容性问题,需要花更多的精力去修改代码和测试,而且需要花费更多的时间去构建,更新时也无法利用历史镜像已缓存的层,所以不是很推荐。
实际上经过 gzip 压缩后(
docker pull
和 docker push
本来就会自动处理压缩),基于 python:slim
的镜像就不到 80 MB 了,继续节省的空间已经不值得为此浪费的更多时间了。
向下滚动可载入更多评论,或者点这里禁止自动加载。