找出缓存失效的位置
本实验在真正的 VM 中运行
这个环境不是 Pod,而是由 KubeVirt 启动的虚拟机。Linux 内核独立运行,systemd 真正管理服务,docker 也不是模拟工具,而是真正的 Docker 引擎。通过 docker run 启动的容器会成为实际进程,docker exec 和 docker logs 也都能照常工作。
过去,这个实验在 Pod 中运行。由于该环境放弃了所有内核权限,启动容器的步骤无法进行,因此只能学习直接解开镜像归档的变通方法。现在不再需要绕路了。
有两点需要了解。
- **首次启动大约需要 1 分钟。**因为 VM 需要启动并安装 Docker,比 Pod 实验(通常 40 秒)更慢。
- **没有浏览器预览。**进入 VM 的连接只开放评分端口。如果启动了 Web 服务器,请在 VM 内使用
curl检查。
目标
通过镜像 ID 证明缓存从哪里失效,并通过两个镜像的体积差异,确认在联合文件系统中删除文件无法缩小镜像。
为什么重要
“构建很慢”绝大多数时候不是缓存开关的问题,而是顺序问题。只要理解缓存键会与父摘要层层关联,就很容易看出修复点永远是“缓存第一次失效的那一行”。它上面的部分无需修改,下面的部分则无从挽救。体积问题也是如此。镜像层无法倒退,因此不应进入最终镜像的内容,从一开始就不该在该层中创建。
步骤
- 创建
/root/build2,统计nginx:1.27-alpine的层数,并在/root/build2/nginx-layers.txt中只写入数字。 - 将
alpine:3.20的逐层历史完整保存到/root/build2/alpine-history.txt,不得截断。docker history所显示的表格正是镜像 config blob 的history数组。在这个环境中,可以使用skopeo inspect --config oci-archive:/opt/images/alpine_3.20.tar | jq -r '.history[]'读取相同的值。 - 在
/root/build2中创建deps.txt和src/main.txt,编写分别 COPY 这两个文件的Dockerfile。构建为labhub/cache:v1后,将镜像 ID 写入/root/build2/id1.txt;在不做任何修改的情况下再次构建,将 ID 写入/root/build2/id2.txt。两个值必须相同。 - 修改
deps.txt的内容,构建为labhub/cache:v2,并将 ID 写入/root/build2/id3.txt。它必须与id1.txt不同。 - 创建
/root/build2/ordered.Dockerfile,使COPY deps.txt位于COPY src/之前,并构建为labhub/cache:v3。 - 创建
/root/build2/big.log,在/root/build2/.dockerignore中加入排除日志文件的规则。使用一个将整个上下文复制到/ctx的 Dockerfile 构建labhub/cache:v4后,镜像中应存在/ctx/deps.txt,但不应存在/ctx/big.log。 - 创建一个 20MB 文件,将在下一个 RUN 中删除它的镜像构建为
labhub/fat:bad,将在同一个 RUN 中删除它的镜像构建为labhub/fat:good。 - 在
/root/build2/cache.md中按实际值写入以下三行。
bad_mb=<labhub/fat:bad 크기(MB)>
good_mb=<labhub/fat:good 크기(MB)>
diff_mb=<두 값의 차>
参考
- 使用
docker image inspect <이미지> | jq -r '.[0].Id'获取镜像 ID,使用jq -r '.[0].Size'获取字节大小。 - 可以使用
dd if=/dev/zero of=/big bs=1M count=20创建 20MB 文件。 - 常见错误 1:如果在第 3 步的两次构建之间改动文件,ID 会不同。
- 常见错误 2:第 8 步的 MB 以 1048576 字节为准。请填写舍去小数部分后的整数。
统计镜像层数
创建 /root/build2,统计 nginx:1.27-alpine 的层数,并在 /root/build2/nginx-layers.txt 中只写入数字。
inspect 结果中的 RootFS.Layers 是一个数组。可以用 jq 求出其长度。
查看每层对应的命令
将 alpine:3.20 的逐层历史完整保存到 /root/build2/alpine-history.txt,不得截断。
docker history 所显示的表格正是镜像 config blob 的 history 数组。在这个环境中,可以使用 skopeo inspect --config oci-archive:/opt/images/alpine_3.20.tar | jq -r '.history[]' 读取相同的值。
有一个子命令可以显示每一层由什么命令创建以及占用多少空间。请完整保存,不要截断。
没有变化就得到相同镜像
在 /root/build2 中创建 deps.txt 和 src/main.txt,编写分别 COPY 这两个文件的 Dockerfile。构建为 labhub/cache:v1 后,将镜像 ID 写入 /root/build2/id1.txt;在不做任何修改的情况下再次构建,将 ID 写入 /root/build2/id2.txt。两个值必须相同。
构建两次,并将每次的镜像 ID 分别保存到文件。如果所有层都命中缓存,最终镜像也会相同。
破坏前面的镜像层
修改 deps.txt 的内容,构建为 labhub/cache:v2,并将 ID 写入 /root/build2/id3.txt。它必须与 id1.txt 不同。
修改最前面通过 COPY 加入的文件后,其下方的所有步骤都会重新执行。请使用新标签构建并比较 ID。
按变更频率排列
创建 /root/build2/ordered.Dockerfile,使 COPY deps.txt 位于 COPY src/ 之前,并构建为 labhub/cache:v3。
将几乎不变的内容(依赖项列表)放在上面,将经常变化的内容(源代码)放在下面。评分会比较两条 COPY 的行号。
从构建上下文中排除
创建 /root/build2/big.log,在 /root/build2/.dockerignore 中加入排除日志文件的规则。使用一个将整个上下文复制到 /ctx 的 Dockerfile 构建 labhub/cache:v4 后,镜像中应存在 /ctx/deps.txt,但不应存在 /ctx/big.log。
排除规则文件应放在构建上下文根目录。若连必要文件也排除,构建会失败。
证明删除也无法缩小体积
创建一个 20MB 文件,将在下一个 RUN 中删除它的镜像构建为 labhub/fat:bad,将在同一个 RUN 中删除它的镜像构建为 labhub/fat:good。
分别构建创建文件后在下一个 RUN 中删除的镜像,以及创建后在同一个 RUN 内删除的镜像,并比较其体积。
整理测量值
在 /root/build2/cache.md 中按实际值写入以下三行。
所有三个值都要根据实际镜像大小计算。请以 1MB = 1048576 字节为准填写整数 MB。