用多阶段把体积削下来
本实验在真正的 VM 中运行
这个环境不是 Pod,而是由 KubeVirt 启动的虚拟机。Linux 内核独立运行,systemd 真正管理服务,docker 也不是模拟工具,而是真正的 Docker 引擎。通过 docker run 启动的容器会成为实际进程,docker exec 和 docker logs 也都能照常工作。
过去,这个实验在 Pod 中运行。由于该环境放弃了所有内核权限,启动容器的步骤无法进行,因此只能学习直接解开镜像归档的变通方法。现在不再需要绕路了。
有两点需要了解。
- **首次启动大约需要 1 分钟。**因为 VM 需要启动并安装 Docker,比 Pod 实验(通常 40 秒)更慢。
- **没有浏览器预览。**进入 VM 的连接只开放评分端口。如果启动了 Web 服务器,请在 VM 内使用
curl检查。
目标
只把构建器阶段生成的产物移入最终镜像,并通过体积数值确认如何用小得多的镜像交付相同产物。
为什么重要
镜像体积不是美观问题,而是部署速度和回滚速度的问题。每当节点扩容或进行回滚时,都要重新拉取相应大小的镜像。而缩小体积最有力的手段并非压缩或清理,而是“不应进入最终镜像的内容,从一开始就不要在那个阶段创建”。镜像层无法倒退,一旦违反这一原则,之后无论怎样删除都无法恢复原来的体积。
步骤
- 创建
/root/build3,并在/root/build3/Dockerfile中创建两个阶段。第一阶段使用python:3.12-alpine,命名为builder,并向/out/app.txt写入built-in-builder。第二阶段使用alpine:3.20。构建为labhub/ms:v1。 - 修改最终阶段,使其只将构建器中的
/out/app.txt复制到/app/app.txt,并构建为labhub/ms:v2。运行并读取该文件时,必须得到built-in-builder。 - 在
/root/build3/fat.Dockerfile中编写一个以单阶段生成相同产物的版本(基础镜像为python:3.12-alpine),并构建为labhub/ms:fat。 - 只构建第一阶段,生成
labhub/ms:builder镜像。镜像中必须存在/out/app.txt。 - 在
Dockerfile的最终阶段添加一行,直接从busybox:1.36镜像将/bin/busybox复制到/app/busybox,并构建为labhub/ms:v3。 - 运行
labhub/ms:v3,确认其中没有python3。镜像体积必须小于 60MB。 - 创建
/root/build3/scratch.Dockerfile,在FROM scratch之上仅放入busybox:1.36的/bin/busybox,并以数组格式指定ENTRYPOINT,使其原样输出作为参数传入的字符串。构建为labhub/ms:scratch;传入scratch-works时,必须输出该字符串。 - 在
/root/build3/size.md中按实际值写入以下四行。
fat_mb=<labhub/ms:fat 크기(MB)>
slim_mb=<labhub/ms:v3 크기(MB)>
scratch_mb=<labhub/ms:scratch 크기(MB)>
reduction_pct=<(fat_mb - scratch_mb) / fat_mb * 100 의 정수부>
参考
- 使用
docker build --target builder -t labhub/ms:builder /root/build3只构建中间阶段。 - 像
COPY --from=busybox:1.36 /bin/busybox /app/busybox这样,也可以引用并非阶段的镜像。 - busybox 采用静态链接,因此在 scratch 上也能运行。它以
busybox echo <문자열>的形式执行。 - 常见错误 1:如果在第 2 步复制整个阶段目录,体积几乎不会减小。
- 常见错误 2:如果在第 7 步以 Shell 格式编写 ENTRYPOINT,由于 scratch 中没有 Shell,会立即失败。
创建两个阶段
创建 /root/build3,并在 /root/build3/Dockerfile 中创建两个阶段。第一阶段使用 python:3.12-alpine,命名为 builder,并向 /out/app.txt 写入 built-in-builder。第二阶段使用 alpine:3.20。构建为 labhub/ms:v1。
写两次 FROM,并为前一个阶段命名。之后会在 COPY 中引用该名称。
只取出构建产物
修改最终阶段,使其只将构建器中的 /out/app.txt 复制到 /app/app.txt,并构建为 labhub/ms:v2。运行并读取该文件时,必须得到 built-in-builder。
只选择并复制构建器阶段生成的文件。如果复制整个阶段,就失去了使用多阶段构建的意义。
与单阶段镜像比较体积
在 /root/build3/fat.Dockerfile 中编写一个以单阶段生成相同产物的版本(基础镜像为 python:3.12-alpine),并构建为 labhub/ms:fat。
请与将相同产物直接留在较重基础镜像上的版本比较。体积应减少 30% 以上。
只构建中间阶段
只构建第一阶段,生成 labhub/ms:builder 镜像。镜像中必须存在 /out/app.txt。
调试时,可以单独将构建器阶段生成为镜像。有一个选项可以在到达最终阶段前停止。
直接从其他镜像复制
在 Dockerfile 的最终阶段添加一行,直接从 busybox:1.36 镜像将 /bin/busybox 复制到 /app/busybox,并构建为 labhub/ms:v3。
--from 不仅可以接收阶段名称,也可以直接指定镜像引用。
不保留构建工具
运行 labhub/ms:v3,确认其中没有 python3。镜像体积必须小于 60MB。
请实际运行,确认仅用于构建的运行时已从最终阶段消失。
放到空白基础镜像上
创建 /root/build3/scratch.Dockerfile,在 FROM scratch 之上仅放入 busybox:1.36 的 /bin/busybox,并以数组格式指定 ENTRYPOINT,使其原样输出作为参数传入的字符串。构建为 labhub/ms:scratch;传入 scratch-works 时,必须输出该字符串。
scratch 中没有 Shell,因此 ENTRYPOINT 必须使用数组格式。还必须选择静态链接的二进制文件。
体积报告
在 /root/build3/size.md 中按实际值写入以下四行。
测量并填写三个镜像的实际体积,然后计算缩减率。1MB = 1048576 字节。