跳到主要内容

让构建从 8 分钟降到 2 分钟:缓存

构建慢了先别急着加 CPU。绝大多数慢,是每次都在重下依赖。

一、先分清楚两种缓存​

类型作用范围怎么开
节点缓存当前构建节点内代码自动缓存;文件靠 docker.volumes 声明
跨节点缓存所有节点共享把缓存内容做成 Docker 镜像,用 docker:cache

默认情况下,流水线会分配到 3 个左右的构建节点(数量随资源使用动态调整)。节点缓存只在自己那台上有效,所以命中率不是 100%——这是平台在"单点故障风险"和"命中率"之间的折中。

二、节点缓存:一行配置搞定​

.cnb.yml
main:
push:
- docker:
image: node:20
volumes:
- /root/.npm:copy-on-write
stages:
- name: install
script: npm install
- name: test
script: npm test

copy-on-write 是基于 OverlayFS 的即时复制,高并发下也能安全复用同一份缓存。

同类写法:

技术栈缓存目录
npm/root/.npm
yarn 1.x/usr/local/share/.cache/yarn
pnpm/root/.local/share/pnpm/store
pip/root/.cache/pip
Go/root/go/pkg/mod、/root/.cache/go-build
Maven/root/.m2/repository
Gradle/root/.gradle/caches
Cargo/usr/local/cargo/registry

代码本身不需要声明,CNB 会自动缓存并复用。

三、跨节点缓存:要 100% 命中就上 Docker​

节点缓存受限于"分配在哪台节点"。真要做到所有节点都命中,把缓存目录做成镜像:

.cnb.yml
main:
push:
- stages:
- name: restore cache
type: docker:cache
options:
image: ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}/node_modules:cache
path: node_modules
- name: install
script: npm ci

它把 node_modules 打成一个 Docker 层推到制品库,下一次构建直接拉下来。适用于依赖巨大、且很少变动的项目。

代价是每次构建多一次拉/推镜像的时间。依赖中等规模时,节点缓存更划算。

四、提速的优先级顺序​

不要一上来就到处加缓存。按这个顺序做,收益最大:

  1. 看清瓶颈:构建日志里每个 stage 的耗时,先找出最慢的那一个
  2. 砍掉不必要的工作:文档改动没必要跑全量测试(用 ifModify 或 [ci skip])
  3. 并行化:一条事件下写多条 pipeline,它们并发执行;Stage 之间是串行的,别把独立任务串成一串
  4. 加缓存:先节点缓存,命中不够再上跨节点缓存
  5. 换节点规格:最后才考虑加 cpus,而且只对编译类任务有效
  6. 换更大更全的镜像:把依赖预装进镜像,比每次装更快

五、一条容易被忽略的​

docker.build 用 Dockerfile 建镜像时,系统会自动做哈希缓存:镜像内容没变就直接复用远端缓存,不会重复 build。所以自定义镜像并不等于每次都很慢。

但它的前提是 Dockerfile 和构建上下文稳定。把 COPY . . 放在 RUN npm install 之前,会让缓存每次都失效——这是最典型的 Dockerfile 反模式:

# 反例:改一行代码就要重装依赖
COPY . .
RUN npm install

# 正例:先装依赖,再拷代码
COPY package.json yarn.lock ./
RUN yarn install
COPY . .