用 BuildKit 缓存挂载把 Python 镜像构建从 6 分钟压到 90 秒
每次改一行代码就重新下载几百 MB 依赖,是多阶段镜像构建里最容易被忽视的时间黑洞。本文记录一次把 Python 服务镜像构建时间从 6 分 12 秒压到 1 分 28 秒的实操过程,核心只用到 BuildKit 的一个特性:cache mount。
一、瓶颈在哪里
最初的 Dockerfile 很朴素:COPY requirements.txt 之后直接 RUN pip install。问题在于只要依赖清单的时间戳变化,或者上层 COPY 让缓存层失效,pip 就会把 wheel 重新下载一遍、重新编译一遍。在跨国网络环境下,光下载 torch 和 grpcio 就要四五分钟,构建日志里全是 Downloading 和 Building wheel。
二、用 cache mount 保留下载与编译产物
BuildKit 允许把宿主机的构建缓存目录挂载进 RUN 步骤,而这个目录不会进入最终镜像层:
# syntax=docker/dockerfile:1.6
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=0
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
COPY . .
CMD ["python", "-m", "app"]
关键点有三个:第一,target 必须是 pip 真正使用的缓存目录,以 root 构建时是 /root/.cache/pip,不要写成依赖环境变量展开的路径;第二,PIP_NO_CACHE_DIR 要显式设为 0,否则 pip 主动禁用缓存,挂载等于白做;第三,Dockerfile 首行的 syntax 指令不能省,cache mount 需要较新的 dockerfile 前端支持。
三、构建命令要打开 BuildKit
DOCKER_BUILDKIT=1 docker build -t demo:latest . 如果使用 docker buildx,默认已经启用 BuildKit,可以直接 buildx build --load -t demo:latest . 加上 --progress=plain 能看到缓存命中情况,比默认的进度条更适合排查。
四、实测结果
首次构建 6 分 12 秒,缓存为空;修改一行业务代码后重建 1 分 28 秒;连续三次干净重建稳定在 1 分 30 秒上下。CI 里同一台构建机并行跑三个服务,总耗时从 18 分钟降到 6 分钟以内,构建机的 CPU 峰值也明显下降,因为不再反复编译同一个 wheel。
五、几个容易踩的坑
一是 cache mount 的内容不随镜像发布,换构建机等于冷启动,建议在 CI 里用 cache-to 和 cache-from 把缓存导出到镜像仓库;二是依赖升级后旧 wheel 会留在缓存目录,磁盘可能缓慢膨胀,定期执行 docker builder prune 即可;三是不要把业务代码放进挂了 cache mount 的同一层,否则任何代码改动都会让依赖步骤重跑,缓存优势全丢;四是团队共享构建机时并发写缓存可能相互干扰,BuildKit 会加锁,但多架构构建建议拆成独立流水线。
把依赖安装和代码拷贝分开、让缓存跨构建复用,是成本最低的一次提速:不用改业务代码,也不依赖任何付费服务,只是在 Dockerfile 里多写一行 --mount。
- 点赞
- 收藏
- 关注作者
评论(0)