LabHub
学习 学习路径 课程

构建镜像

找出缓存失效的位置

在 LabHub 中继续学习

本实验在真正的 VM 中运行

这个环境不是 Pod,而是由 KubeVirt 启动的虚拟机。Linux 内核独立运行,systemd 真正管理服务,docker 也不是模拟工具,而是真正的 Docker 引擎。通过 docker run 启动的容器会成为实际进程,docker execdocker logs 也都能照常工作。

过去,这个实验在 Pod 中运行。由于该环境放弃了所有内核权限,启动容器的步骤无法进行,因此只能学习直接解开镜像归档的变通方法。现在不再需要绕路了。

有两点需要了解。

目标

通过镜像 ID 证明缓存从哪里失效,并通过两个镜像的体积差异,确认在联合文件系统中删除文件无法缩小镜像。

为什么重要

“构建很慢”绝大多数时候不是缓存开关的问题,而是顺序问题。只要理解缓存键会与父摘要层层关联,就很容易看出修复点永远是“缓存第一次失效的那一行”。它上面的部分无需修改,下面的部分则无从挽救。体积问题也是如此。镜像层无法倒退,因此不应进入最终镜像的内容,从一开始就不该在该层中创建

步骤

  1. 创建 /root/build2,统计 nginx:1.27-alpine 的层数,并在 /root/build2/nginx-layers.txt 中只写入数字。
  2. 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[]' 读取相同的值。
  3. /root/build2 中创建 deps.txtsrc/main.txt,编写分别 COPY 这两个文件的 Dockerfile。构建为 labhub/cache:v1 后,将镜像 ID 写入 /root/build2/id1.txt;在不做任何修改的情况下再次构建,将 ID 写入 /root/build2/id2.txt。两个值必须相同。
  4. 修改 deps.txt 的内容,构建为 labhub/cache:v2,并将 ID 写入 /root/build2/id3.txt。它必须与 id1.txt 不同。
  5. 创建 /root/build2/ordered.Dockerfile,使 COPY deps.txt 位于 COPY src/ 之前,并构建为 labhub/cache:v3
  6. 创建 /root/build2/big.log,在 /root/build2/.dockerignore 中加入排除日志文件的规则。使用一个将整个上下文复制到 /ctx 的 Dockerfile 构建 labhub/cache:v4 后,镜像中应存在 /ctx/deps.txt,但不应存在 /ctx/big.log
  7. 创建一个 20MB 文件,将在下一个 RUN 中删除它的镜像构建为 labhub/fat:bad,将在同一个 RUN 中删除它的镜像构建为 labhub/fat:good
  8. /root/build2/cache.md 中按实际值写入以下三行。
bad_mb=<labhub/fat:bad 크기(MB)>
good_mb=<labhub/fat:good 크기(MB)>
diff_mb=<두 값의 차>

参考

统计镜像层数

创建 /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.txtsrc/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。