KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

01 · 层 — keel 龙骨

类型:衍生概念(服务于「镜像」) · 应用课对应:02 章 · 层、缓存与指令顺序

类型:衍生概念(服务于「镜像」) · 应用课对应:02 章 · 层、缓存与指令顺序

这一页只回答四件事:它为什么会出现、它是什么、它长什么样、它怎么工作。

一、它为什么会出现

两个现象,都指向同一件事:

① 改了一行业务代码,构建却重装了一遍依赖 —— 60 秒变成 5 分钟
② 两张来自不同仓库的镜像,磁盘上只占了一份空间

它面对的需求是:镜像要在网络上来回搬,一台机器上还要同时存很多张,而这些镜像长得很像(大多数机器上都有一张 Node 或 Python 基础镜像)。

问题在于:如果每张镜像都存一整份文件系统,那么传输量和磁盘占用都会随镜像数量线性上涨。

答案不是"压缩得更好",而是只存变化的部分。

二、它是什么

一句话:层是镜像里"相对上一层改了哪些文件"的一次记录——新增了什么、改了什么、删了什么。它的名字,就是这段内容算出来的摘要。

由此有两个直接后果,都是日常要用的判断:

它不是这两样东西——混淆会带来误判:

三、它长什么样

一张镜像的层清单,就是一组带摘要与体积的条目(以下取自公开镜像的元数据,本机用不依赖 daemon 的方式读到):

L1  sha256:e2de96513ba9eb53b4…   3849738 B    ← 基础系统
L2  sha256:f7f2d304681aaa935c…  55585204 B    ← 装出来的运行时
L3  sha256:e554276b05e6306c5a…   1262001 B
L4  sha256:d39db1cf9caa4f49c5…       447 B    ← 最后一层只是几行启动脚本

看它有两个命令,各自的用处不同:

docker history <镜像>     # 逐条历史 + 每条的 SIZE:SIZE 不为 0 的那几行才落在层上
docker system df -v      # 每个镜像的 SHARED SIZE / UNIQUE SIZE:共享了多少、独占多少

本机 Docker daemon 没有运行,上面两条命令的执行结果没有实测。这里给的是"该看哪一列、判据是什么",不是"我跑出来是这样"。

四、它怎么工作

一次改动变成一层,是四步:

① 执行   一条会改文件系统的指令跑完
② 差分   把执行前后的文件系统差异打成一份 tar(新增 / 修改 / 删除都在里面)
③ 命名   对这份 tar 算摘要——摘要就是这一层的名字
④ 存放   按摘要存进本地库;下次要的层已经在库里,就直接复用

这四步带来三处可复用的地方。它们不是三个功能,而是同一个机制在三个场景下的表现:

场景 复用的判据
磁盘 这个摘要已经存在 → 不存第二份(跨镜像共享)
构建 这一层的输入没变 → 不重做(缓存命中)
传输 本地已有这个摘要 → 不重传(pull 只取缺的那几层)

五、它会带出哪些概念

六、回到应用课

应用课里的那句话 现在你知道它为什么成立
"每条会改动文件系统的指令都产出一层" 判据其实是"有没有产生非空的文件系统差异";只改 config 的指令(ENV、CMD)不产层
"缓存键 = 父层的缓存键 + 本指令的指纹" 因为一层只记"相对上一层改了什么",所以"上一层是谁"必须参与这一层的身份计算
"清理必须与产生写在同一层,否则体积不降" 删除在层里不是"减掉字节",而是新增一次遮蔽记录;下面那一层的字节还在原地

这一页改变了你的哪个判断:看到"两张镜像共享了 N MB"这类报告时,你知道要去比对层摘要——摘要相同才叫共享,名字像不算(都叫 alpine 并不保证共用同一层)。

自测题

  1. docker history 打印出 9 行,但镜像只有 4 层。这 5 行的差别在哪?
  2. 为什么把 rm -rf 拆到单独一行 RUN 里,镜像不会变小?

进入 keel 阅读