Docker 镜像打的包有1.3个G 多阶段构建缩小镜像体积(不算成功)

实际的场景:harbor 主备双战,每次拉取的时候拉到备战导致镜像拉取失败,最终排查是因为镜像包太大了,主站在往备战同步的时候失败或者太慢,导致K8s 拉不到镜像。 运维建议缩减镜像的大小。

镜像内容:python 以及 项目依赖的.venv 虚拟环境

原Dockerfile

bash 复制代码
FROM harbor.xxx.com/ai-engineering/python:3.12.10-slim

# 时区配置
ENV TZ=Asia/Shanghai

# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PATH="/app/backend/.venv/bin:$PATH" \
    UV_INDEX_URL="https://assets.hwwt2.com/repository/pypi-public/simple" \
    UV_EXTRA_INDEX_URL="https://mirrors.aliyun.com/pypi/simple/"

# 安装基本系统依赖
# curl: 用于网络请求测试等
# git: 如果依赖项中有 git 仓库引用则需要
RUN if command -v apt-get >/dev/null 2>&1; then \
        if [ -f /etc/apt/sources.list.d/debian.sources ]; then \
            sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources; \
            sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources; \
        elif [ -f /etc/apt/sources.list ]; then \
            sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list; \
            sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list; \
        fi; \
        apt-get update && apt-get install -y --no-install-recommends curl git && rm -rf /var/lib/apt/lists/*; \
    elif command -v microdnf >/dev/null 2>&1; then \
        microdnf install -y curl git ca-certificates gcc gcc-c++ && microdnf clean all; \
    elif command -v yum >/dev/null 2>&1; then \
        yum install -y curl git ca-certificates gcc gcc-c++ && yum clean all; \
    elif command -v apk >/dev/null 2>&1; then \
        apk add --no-cache curl git ca-certificates gcc g++; \
    else \
        echo "Error: No supported package manager found (apt-get, microdnf, yum, apk)" && exit 1; \
    fi

# 安装 uv
RUN python3 -m pip install --upgrade pip && python3 -m pip install uv

# 设置工作目录
WORKDIR /app

# 复制项目依赖配置文件
# 我们先只复制依赖文件以利用 Docker 缓存层
COPY backend/pyproject.toml backend/uv.lock* ./backend/

# 安装依赖
WORKDIR /app/backend
# 打印版本信息和网络连通性检查
RUN uv --version && python3 --version && \
    echo "Checking network connectivity..." && \
    curl -I -m 5 https://assets.hwwt2.com/repository/pypi-public/simple || echo "WARNING: Failed to connect to primary PyPI source"

# 调试:检查文件是否存在
RUN ls -la

# 创建虚拟环境
RUN python3 -m venv .venv

# Step 1: Install dependencies using uv export to respect uv.lock
# 使用 uv export 生成 requirements.txt 并安装,以确保使用锁定版本的依赖
RUN uv export --frozen --no-emit-project --format requirements-txt > requirements.txt && \
    uv pip install --no-cache -p .venv/bin/python -r requirements.txt && \
    rm requirements.txt

# 复制项目其余文件
WORKDIR /app
COPY . .

# 安装项目本身
WORKDIR /app/backend
RUN uv pip install --no-cache -p .venv/bin/python .

# 创建必要的临时目录并建立软链接
# 这是一个特定于业务逻辑的设置
RUN mkdir -p /app/backend/tmp/workshop/output /tmp/workshop && \
    ln -sf /app/backend/tmp/workshop/output /tmp/workshop/output

# 暴露应用端口
EXPOSE 8080

# 切换到 backend 目录并启动应用
WORKDIR /app/backend
CMD ["/bin/bash", "-c", "python3 -m src.app"]

优化的Dockerfile

bash 复制代码
# Stage 1: Builder
# 用于构建依赖和编译环境
FROM harbor.xxx.com/ai-engineering/python:3.12.10-slim AS builder

# 时区配置和构建相关的环境变量
ENV TZ=Asia/Shanghai \
    UV_INDEX_URL="https://assets.hwwt2.com/repository/pypi-public/simple" \
    UV_EXTRA_INDEX_URL="https://mirrors.aliyun.com/pypi/simple/"

# 安装构建依赖 (git, gcc, g++ 等)
# 相比原版,这里显式安装 apt 依赖,因为基础镜像是 debian based slim
# 同时替换为阿里云源以加速下载
RUN if [ -f /etc/apt/sources.list.d/debian.sources ]; then \
        sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
        sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources; \
    else \
        sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
        sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list; \
    fi && \
    apt-get update && apt-get install -y --no-install-recommends \
    curl \
    git \
    gcc \
    g++ \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# 安装 uv
RUN python3 -m pip install --upgrade pip && python3 -m pip install uv

# 设置工作目录
WORKDIR /app

# 复制项目依赖配置文件
COPY backend/pyproject.toml backend/uv.lock* ./backend/

# 创建虚拟环境并安装依赖
WORKDIR /app/backend
RUN python3 -m venv .venv

# 使用 uv export 生成 requirements.txt 并安装
# 这里在 builder 阶段安装所有依赖(包括编译型依赖)
RUN uv export --frozen --no-emit-project --format requirements-txt > requirements.txt && \
    uv pip install --no-cache -p .venv/bin/python -r requirements.txt

# 复制项目其余文件 (用于安装项目本身)
WORKDIR /app
COPY . .

# 安装项目本身到虚拟环境
WORKDIR /app/backend
RUN uv pip install --no-cache -p .venv/bin/python --no-deps .

# Stage 2: Runtime
# 用于最终运行的精简镜像
FROM harbor.xxxx.com/ai-engineering/python:3.12.10-slim AS runtime

# 运行时环境变量
ENV TZ=Asia/Shanghai \
    PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PATH="/app/backend/.venv/bin:$PATH"

# 安装运行时系统依赖 (仅保留 curl 用于健康检查等,去除 gcc/git)
RUN if [ -f /etc/apt/sources.list.d/debian.sources ]; then \
        sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
        sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources; \
    else \
        sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
        sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list; \
    fi && \
    apt-get update && apt-get install -y --no-install-recommends \
    curl \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 复制项目代码
# 注意:先复制所有代码,再覆盖虚拟环境
COPY . .

# 从 builder 阶段复制构建好的虚拟环境
COPY --from=builder /app/backend/.venv /app/backend/.venv

# 创建必要的临时目录并建立软链接
WORKDIR /app/backend
RUN mkdir -p /app/backend/tmp/workshop/output /tmp/workshop && \
    ln -sf /app/backend/tmp/workshop/output /tmp/workshop/output

# 暴露应用端口
EXPOSE 8080

# 启动应用
CMD ["/bin/bash", "-c", "python3 -m src.app"]

多阶段构建如何解决?
阶段 1:Builder 阶段(负责"重活")

基于 slim 或 full 镜像;

安装 gcc、python3-dev、git 等构建依赖;

下载并编译所有 Python 包(包括 C/C++ 扩展);

把编译好的 .so 文件、Python 模块等安装到一个干净目录(如 /install)。
阶段 2:Runtime 阶段(只放"必需品")

重新 FROM python:3.12.10-slim(干净起点);

只安装运行时最小依赖(如 curl, ca-certificates);

从 builder 阶段 COPY 已编译好的依赖(/install → site-packages);

COPY 应用代码;

不包含任何编译工具、缓存、虚拟环境结构。

通俗易懂的原理

❌ 传统方式(单阶段构建):

你请了一个厨师(Docker 镜像),他带着:

所有厨具(锅、刀、炉子 → 相当于 gcc, g++)

所有食材(米、菜、肉 → 相当于 torch, transformers)

还有一大堆包装盒、塑料袋、说明书(.venv 结构、缓存、临时文件)

做完饭后,你把整个厨房(包括不用的工具和垃圾)一起打包带走。

→ 虽然你只需要"那碗饭",但背了个 1.3GB 的大背包!

✅ 多阶段构建(聪明的做法):

你分两步:

第一步:在厨房里做饭(Builder 阶段)

厨师用全套工具把饭做好,只把做好的饭装进一个干净饭盒。

第二步:只带走饭盒(Runtime 阶段)

你扔掉所有锅碗瓢盆、包装垃圾,只拿走那个饭盒去吃。

→ 现在你背的只有 500MB 的饭盒,轻便又干净!

💡 关键区别:

饭 = 你的程序 + 必要依赖(如 torch) → 这部分没法省。

锅、垃圾、包装盒 = 编译工具、虚拟环境、缓存 → 这些运行时根本用不到,却占了大半体积!
多阶段构建就是:做完饭,只留饭,扔掉厨房。


那怎么做到两个FROM 切换的

Docker 构建时,会按顺序执行每个 FROM 开始的"独立小世界",最后只保留最后一个 FROM 的结果作为最终镜像。

但你可以从前面的小世界里"偷东西"过来(用 COPY --from=xxx)。

举个生活例子:做蛋糕

你想做一个干净漂亮的蛋糕(最终镜像),但做蛋糕需要厨房(Builder)。

步骤 1:在厨房里做蛋糕(第一个 FROM ... AS builder)

你在一个大厨房里(Linux 环境 #1);

用面粉、鸡蛋、烤箱(gcc、pip、依赖)做出一个蛋糕;

蛋糕做好了,放在盘子里。

步骤 2:换到客厅摆盘(第二个 FROM ... AS runtime)

你现在进入一个全新的、干净的客厅(Linux 环境 #2);

客厅里什么都没有(只有基础 Python);

但你可以从厨房里把蛋糕端过来

bash 复制代码
COPY --from=builder /cake ./cake   # 把厨房的蛋糕拿过来

最后,你只把客厅的样子打包送人 ------ 厨房和厨具全部不要了!

✅ 所以:两个 FROM = 两个完全独立的房间(容器构建环境)

Docker 先建好"厨房",再建"客厅",最后只交付"客厅"。

🔧 技术上 Docker 是怎么做到的?

Docker 按顺序处理每个 FROM 块,把它当作一个临时镜像来构建;

每个块有自己的文件系统、环境变量、已安装的包;

当遇到 COPY --from=builder ... 时,Docker 会:

暂停当前阶段(runtime);

回头去已经构建好的 builder 镜像里;

把指定的文件复制出来,放进当前阶段;

所有阶段构建完后,只有最后一个 FROM 的内容成为最终镜像,前面的阶段只留文件,不留环境。

⚠️ 注意:两个 FROM 必须用相同或兼容的基础镜像!


实际效果并没有达到理想

基础镜像其实并不大

看了venv Lib 依赖,包特别大,说明项目依赖的包很大
: .venv 中的 Lib (准确说是 site-packages )是混合体,但核心涉及系统兼容性的部分是"动态链接库"。


运维提供的解决方案:主备同推,然后等待运维解决大同步问题 😬

后期优化的方向

相关推荐
明洞日记2 小时前
【软考每日一练008】Web 服务器性能测试指标
运维·服务器·操作系统·软考
Kendra9193 小时前
K8s集群组件启动不成功排查
云原生·容器·kubernetes
努力搬砖的咸鱼3 小时前
用 Minikube 或 Kind 在本地跑起 Kubernetes
微服务·云原生·容器·架构·kubernetes·kind
噎住佩奇3 小时前
单节点K8s集群中安装StorageClass(SC)
云原生·容器·kubernetes
23124_803 小时前
Cookie伪造
运维·服务器
陈陈CHENCHEN3 小时前
【Kubernetes】镜像拉取密钥 - Docker Registry
docker·kubernetes
Bits to Atoms3 小时前
宇树机器人二次开发环境配置 -- docker创建
运维·docker·容器
RisunJan3 小时前
Linux命令-killall(根据进程名称来终止一个或多个进程)
linux·运维·服务器
信码由缰3 小时前
塑造2026年的六大软件开发与DevOps趋势
运维·devops