为什么改了一行代码,依赖又重新装了一遍?构建缓存到底看什么?这篇用 sleep 做可控的耗时,逐项量出:改 mtime、改内容、调换顺序、加注释、改命令各自让缓存怎样。
第 1 次构建(冷):用时 5.8 秒;RUN echo salt-1791641893.564141 → 执行 | COPY app.txt /app.txt → 执行 | RUN sleep 2 && echo A > /a → 执行 | COPY dep.txt /dep.txt → 执行 | RUN sleep 2 && echo B > /b → 执行 第 2 次:什么都没改:用时 0.2 秒;RUN sleep 2 && echo A > /a → CACHED | COPY dep.txt /dep.txt → CACHED | COPY app.txt /app.txt → CACHED | RUN echo salt-1791641893.564141 → CACHED | RUN sleep 2 && echo B > /b → CACHED 第 3 次:只改 app.txt 的修改时间(touch):用时 0.2 秒;RUN sleep 2 && echo A > /a → CACHED | COPY dep.txt /dep.txt → CACHED | COPY app.txt /app.txt → CACHED | RUN echo salt-1791641893.564141 → CACHED | RUN sleep 2 && echo B > /b → CACHED 第 4 次:改 app.txt 的内容:用时 4.7 秒;RUN echo salt-1791641893.564141 → CACHED | COPY app.txt /app.txt → 执行 | RUN sleep 2 && echo A > /a → 执行 | COPY dep.txt /dep.txt → 执行 | RUN sleep 2 && echo B > /b → 执行 第 5 次:只改 dep.txt 的内容:用时 2.5 秒;RUN echo salt-1791641893.564141 → CACHED | COPY app.txt /app.txt → CACHED | RUN sleep 2 && echo A > /a → CACHED | COPY dep.txt /dep.txt → 执行 | RUN sleep 2 && echo B > /b → 执行 第 6 次:app.txt 写入同样的内容(v2):用时 0.4 秒;COPY dep.txt /dep.txt → CACHED | RUN sleep 2 && echo A > /a → CACHED | RUN echo salt-1791641893.564141 → CACHED | COPY app.txt /app.txt → CACHED | RUN sleep 2 && echo B > /b → CACHED
COPY 指令的缓存键是所拷文件的内容(不是 mtime),RUN 指令的缓存键是命令字符串加上前面所有步骤的结果。一步失效,后面的步骤全部失效。内容改了再改回去,又会命中之前的缓存(第 6 次构建)。
先 COPY . 再装依赖(RUN):改了源码 main.py 后:用时 3.4 秒;RUN echo salt2-1791641893.564141 → CACHED | COPY . /src → 执行 | RUN sleep 3 && echo deps > /deps → 执行 先 COPY 依赖清单、装依赖,再 COPY 源码:改了源码 main.py 后:用时 0.3 秒;COPY deps.txt /src/deps.txt → CACHED | RUN echo salt3-1791641893.564141 → CACHED | RUN sleep 3 && echo deps > /deps → CACHED | COPY . /src → 执行
把变化少的放前面:先只拷依赖清单、安装依赖,再拷源码。这样改源码只会让最后的 COPY 重新执行。
加了一行注释:用时 0.3 秒;RUN echo salt4-1791641893.564141 → CACHED | RUN sleep 2 && echo X > /x → CACHED RUN 命令末尾多一个空格:用时 0.2 秒;RUN echo salt4-1791641893.564141 → CACHED | RUN sleep 2 && echo X > /x → CACHED RUN 命令里改了一个字符:用时 2.4 秒;RUN echo salt4-1791641893.564141 → CACHED | RUN sleep 2 && echo Y > /x → 执行 在前面加一个 ENV:用时 2.4 秒;RUN echo salt4-1791641893.564141 → CACHED | RUN sleep 2 && echo X > /x → 执行
注释和 RUN 命令末尾的空格没有让缓存失效;改命令里的字符、在前面加 ENV 则会。这里只试了这四种改法,其它改法(如换行、续行符)我没有测。
不加参数(全部命中缓存):用时 0.2 秒;RUN echo salt5-1791641893.564141 & → CACHED | RUN echo salt6-1791641893.564141 & → CACHED | COPY --from=one /1 /1 → CACHED | COPY --from=two /2 /2 → CACHED --no-cache-filter one(只让 one 阶段重新执行):用时 2.4 秒;RUN echo salt5-1791641893.564141 & → 执行 | COPY --from=one /1 /1 → CACHED | RUN echo salt6-1791641893.564141 & → CACHED | COPY --from=two /2 /2 → CACHED --no-cache(全部重新执行):用时 2.6 秒;RUN echo salt5-1791641893.564141 & → 执行 | RUN echo salt6-1791641893.564141 & → 执行 | COPY --from=one /1 /1 → 执行 | COPY --from=two /2 /2 → 执行
--no-cache 全部重跑;--no-cache-filter 阶段名 只重跑一个阶段,多阶段构建里很有用。