在参加 CNB 官方组织的 玩转云原生开发环境 问答攻守擂台赛 (opens new window)时,我遇到了有两位同学问到如何在 CNB 中利用构建缓存加速的问题,于是深入了解研究了下,今天来把一些心得成果做一下记录。
之前之所以没有折腾这个课题,是因为 CNB 提供的构建环境,已经针对大部分开发语言的仓库做了加速,因此基本不会出现依赖安装很慢或者安装不下去的情况,就算每次新安装也不过一分钟内就完事儿,符合正常的心理预期,便没有过多深入。
官方文档:volumes (opens new window) 这个内容官方文档已经介绍的很详细了,我就不多赘述了。
个人使用的一个案例见:test1.yml (opens new window)
这也是本文要详细介绍的一种缓存机制,比较通用,不受构建机分配的影响。
官方文档:docker-cache (opens new window)
可通过阅读官方文档,了解此功能运行原理和机制,接下来我将直接通过实际应用案例,来介绍一下个人理解的最佳实践。
我这里以前端 node 项目举例子,我们在项目根目录创建构建 docker-cache 缓存的 Dockerfile:
$ cat .cache.dockerfile
FROM node:20.19.5-alpine3.22 as Builder
WORKDIR /space
COPY . .
RUN npm i -g pnpm && pnpm install --registry=http://registry.npmmirror.com
ENV NODE_PATH=/space/node_modules
1
2
3
4
5
6
7
8
9
10
11
然后在 .cnb.yml 中添加如下构建配置:
$:
push:
- runner:
cpus: 16
services:
- docker
- git-clone-yyds
stages:
- name: 构建缓存镜像
type: docker:cache
options:
dockerfile: cache.dockerfile
by:
- package.json
- pnpm-lock.yaml
versionBy:
- pnpm-lock.yaml
exports:
name: DOCKER_CACHE_IMAGE_NAME
- name: 直接使用缓存镜像环境安装依赖并编译
image: $DOCKER_CACHE_IMAGE_NAME
script: |
ln -snf $NODE_PATH node_modules
ls -ahl && du -sh node_modules
time pnpm install
time npm run build
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
这里有几个比较重要的实践心得,统一汇总如下:
tag 中带 alpine 的镜像。这样除了可以保障下边依赖安装和构建的流程走完,还能极大程度上在首次拉取缓存镜像时尽可能消耗更短的时间。ln 命令把依赖软链到工作区,为了确保依赖完全对齐,还可以再执行一次 install,这个时候基本上耗时在一两秒。官方文档介绍的方案是把 node_modules 目录拷贝到工作区,耗时会比这个要高。再补充说明一下构建缓存镜像的机制:
sha1(Dockerfile + versionBy + buildArgs + target + arch),在进行构建时,会先获取到这个值,然后通过 docker image inspect 命令测试构建主机是否存在该镜像,若有,则直接使用,如果没有,则通过 docker pull 命令测试远程制品库是否存在该镜像,若有,则 pull 之后使用,如果没有,则通过 docker build 开始构建缓存镜像。通过下图实际构建日志可见:

Dockerfile 、 buildArgs、target、arch 这几个参数通常是不变的,因此,后续的构建过程中,判断是否重新构建缓存镜像的关键点,就在于 versionBy 指定的文件内容是否变化了。因此这个文件通常建议指定为对应语言的依赖版本管理文件,如 package.json,go.mod 等。如此配置之后,只有第一次构建,以及后续依赖版本有变化的时候才会触发缓存镜像的构建,否则就会直接复用之前构建过程中已经制作好的缓存镜像,从而达到加速构建的目的。
基于如上云原生构建复用依赖缓存的思路,接下来再介绍下云原生开发场景中,如何复用构建缓存。
有不少同学,会在配置云原生开发的配置中,增加依赖安装的命令,以达到打开开发环境,直接可以使用的目的,那么如何在云开发环境中复用构建缓存呢?
可使用如下配置:
$:
vscode:
- docker:
image: docker.cnb.cool/znb/images/debian:new
runner:
cpus: 8
services:
- vscode
- docker
stages:
- name: 构建缓存镜像
type: docker:cache
options:
dockerfile: cache.dockerfile
by:
- package.json
- pnpm-lock.yaml
versionBy:
- pnpm-lock.yaml
exports:
name: DOCKER_CACHE_IMAGE_NAME
- name: 复用构建缓存
image: $DOCKER_CACHE_IMAGE_NAME
script: |
time pnpm install
- name: vscode go
type: vscode:go
- name: test
script: |
ls -al
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
要素说明:
ln 来复用依赖目录了,否则就会导致该步骤完成之后,缓存镜像销毁,软链的源目录自然也就没有了,从而导致云开发环境中的 node_modules 变成一个无效的软链。直接执行 pnpm install 会复用缓存镜像中的 ~/.pnpm 目录下的缓存,从而同样达到加速的目的。vscode:go 的指令,详细说明见官方文档 (opens new window),简单理解该指令在 stages 中就是一个分割线,vscode:go 以上的内容,会在云原生开发环境创建之前执行,vscode:go 以下的内容,会在云原生开发环境创建完成后执行。增加这个指令,是为了保障某些依赖在环境创建之前安装完毕。docker-cache 的缓存利用机制是通用的,这里用 node 项目进行举例,其他语言的项目同理亦可复用。
在参加 CNB 官方组织的 玩转云原生开发环境 问答攻守擂台赛 (opens new window)时,我遇到了有两位同学问到如何在 CNB 中利用构建缓存加速的问题,于是深入了解研究了下,今天来把一些心得成果做一下记录。
之前之所以没有折腾这个课题,是因为 CNB 提供的构建环境,已经针对大部分开发语言的仓库做了加速,因此基本不会出现依赖安装很慢或者安装不下去的情况,就算每次新安装也不过一分钟内就完事儿,符合正常的心理预期,便没有过多深入。
官方文档:volumes (opens new window) 这个内容官方文档已经介绍的很详细了,我就不多赘述了。
个人使用的一个案例见:test1.yml (opens new window)
这也是本文要详细介绍的一种缓存机制,比较通用,不受构建机分配的影响。
官方文档:docker-cache (opens new window)
可通过阅读官方文档,了解此功能运行原理和机制,接下来我将直接通过实际应用案例,来介绍一下个人理解的最佳实践。
我这里以前端 node 项目举例子,我们在项目根目录创建构建 docker-cache 缓存的 Dockerfile:
$ cat .cache.dockerfile
FROM node:20.19.5-alpine3.22 as Builder
WORKDIR /space
COPY . .
RUN npm i -g pnpm && pnpm install --registry=http://registry.npmmirror.com
ENV NODE_PATH=/space/node_modules
1
2
3
4
5
6
7
8
9
10
11
然后在 .cnb.yml 中添加如下构建配置:
$:
push:
- runner:
cpus: 16
services:
- docker
- git-clone-yyds
stages:
- name: 构建缓存镜像
type: docker:cache
options:
dockerfile: cache.dockerfile
by:
- package.json
- pnpm-lock.yaml
versionBy:
- pnpm-lock.yaml
exports:
name: DOCKER_CACHE_IMAGE_NAME
- name: 直接使用缓存镜像环境安装依赖并编译
image: $DOCKER_CACHE_IMAGE_NAME
script: |
ln -snf $NODE_PATH node_modules
ls -ahl && du -sh node_modules
time pnpm install
time npm run build
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
这里有几个比较重要的实践心得,统一汇总如下:
tag 中带 alpine 的镜像。这样除了可以保障下边依赖安装和构建的流程走完,还能极大程度上在首次拉取缓存镜像时尽可能消耗更短的时间。ln 命令把依赖软链到工作区,为了确保依赖完全对齐,还可以再执行一次 install,这个时候基本上耗时在一两秒。官方文档介绍的方案是把 node_modules 目录拷贝到工作区,耗时会比这个要高。再补充说明一下构建缓存镜像的机制:
sha1(Dockerfile + versionBy + buildArgs + target + arch),在进行构建时,会先获取到这个值,然后通过 docker image inspect 命令测试构建主机是否存在该镜像,若有,则直接使用,如果没有,则通过 docker pull 命令测试远程制品库是否存在该镜像,若有,则 pull 之后使用,如果没有,则通过 docker build 开始构建缓存镜像。通过下图实际构建日志可见:

Dockerfile 、 buildArgs、target、arch 这几个参数通常是不变的,因此,后续的构建过程中,判断是否重新构建缓存镜像的关键点,就在于 versionBy 指定的文件内容是否变化了。因此这个文件通常建议指定为对应语言的依赖版本管理文件,如 package.json,go.mod 等。如此配置之后,只有第一次构建,以及后续依赖版本有变化的时候才会触发缓存镜像的构建,否则就会直接复用之前构建过程中已经制作好的缓存镜像,从而达到加速构建的目的。
基于如上云原生构建复用依赖缓存的思路,接下来再介绍下云原生开发场景中,如何复用构建缓存。
有不少同学,会在配置云原生开发的配置中,增加依赖安装的命令,以达到打开开发环境,直接可以使用的目的,那么如何在云开发环境中复用构建缓存呢?
可使用如下配置:
$:
vscode:
- docker:
image: docker.cnb.cool/znb/images/debian:new
runner:
cpus: 8
services:
- vscode
- docker
stages:
- name: 构建缓存镜像
type: docker:cache
options:
dockerfile: cache.dockerfile
by:
- package.json
- pnpm-lock.yaml
versionBy:
- pnpm-lock.yaml
exports:
name: DOCKER_CACHE_IMAGE_NAME
- name: 复用构建缓存
image: $DOCKER_CACHE_IMAGE_NAME
script: |
time pnpm install
- name: vscode go
type: vscode:go
- name: test
script: |
ls -al
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
要素说明:
ln 来复用依赖目录了,否则就会导致该步骤完成之后,缓存镜像销毁,软链的源目录自然也就没有了,从而导致云开发环境中的 node_modules 变成一个无效的软链。直接执行 pnpm install 会复用缓存镜像中的 ~/.pnpm 目录下的缓存,从而同样达到加速的目的。vscode:go 的指令,详细说明见官方文档 (opens new window),简单理解该指令在 stages 中就是一个分割线,vscode:go 以上的内容,会在云原生开发环境创建之前执行,vscode:go 以下的内容,会在云原生开发环境创建完成后执行。增加这个指令,是为了保障某些依赖在环境创建之前安装完毕。docker-cache 的缓存利用机制是通用的,这里用 node 项目进行举例,其他语言的项目同理亦可复用。