层缓存是什么
构建时每条指令都会计算一个缓存键:由指令文本、相关文件内容与上一层的结果共同决定。键相同就跳过执行,直接复用已有结果。理解这一点就能明白两件事:为什么改一行代码可能触发长时间重装依赖,以及为什么某些写法会让缓存永久失效。
基本信息
| 缓存键组成 | 指令文本、复制文件的内容、上一层的哈希 |
|---|---|
| 命中表现 | 构建日志里标注为已缓存,该步不重新执行 |
| 排序原则 | 变更频率低的放前面,变更频繁的放后面 |
| 相关开关 | 禁用缓存、强制拉取新基础镜像、指定外部缓存来源 |
| 默认引擎 | 新版本引擎在并行与缓存能力上更强 |
典型使用场景
- 加速日常构建:依赖安装层长期复用,只重建源码相关的那几层
- 流水线提速:在构建机之间传递缓存,减少重复下载与编译
- 快速验证改动:大部分层命中缓存时,能快速得到验证结果
- 定位构建变慢原因:通过缓存命中情况判断哪个步骤被反复执行
- 保证发布正确性:依赖更新后做一次不用缓存的构建,排除缓存陈旧的影响
排序正误对照
排序正误对照
# 推荐:依赖清单先复制,源码变化不影响依赖层
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# 反例:任意源码改动都会触发依赖重装
COPY . .
RUN npm ci
# 依赖更新后做一次全量验证构建
docker build --no-cache --pull -t app/web:1.0 .什么会让缓存失效
| 因素 | 影响范围 | 处理方式 |
|---|---|---|
| 改动指令文本 | 该层与之后所有层 | 尽量把频繁改动的指令放后面 |
| 复制的文件内容变化 | 该层与之后所有层 | 缩小复制范围,按文件分组复制 |
| 上一层失效 | 连锁失效直到重建 | 减少层数或重新组织指令顺序 |
| 写入时间或随机值 | 该层永久无法命中 | 去掉这类内容,或用变量在运行时注入 |
| 基础镜像更新 | 按需拉取时可能触发重建 | 锁定版本,需要更新时集中处理 |
实际优化手法
- 把依赖描述文件单独复制并安装依赖,源码复制放在依赖安装之后
- 把系统依赖安装与清理合并成一条指令,既减层也提速
- 把不常改动的配置与脚本放前面,源码与产物放后面
- 外部缓存来源在流水线里配置好,多台机器共享缓存收益最大
- 出现可疑结果时用禁用缓存的方式做一次对照构建
缓存命中不代表正确。缓存键覆盖的是输入内容,如果依赖在远端被覆盖而内容判断没变,可能一直用到旧的结果,定期做一次全量验证构建更稳妥。
构建日志里的命中标注是判断问题的直接依据:看哪一步开始不再命中,就知道改动的实际影响范围。