最近云原生构建团队针对一个占用磁盘 124.44GB,超过 1400 仓库的项目进行开发。这样的代码规模背后面临拉取耗时长、存储空间占用大、效率低、并发构建受限等问题。面对这些持续存在的挑战,团队意识到需要从根本上解决代码 clone 的速度问题,这不仅关系到开发效率,还直接影响 CI/CD 流程和资源利用。
于是,我们关注到了这款最新发布的 CI 神器。本篇文章是编译部分的技术详解。
关注腾讯云开发者,一手技术干货提前解锁👇
在上一篇文章《125G 代码,10秒内准备完成:这款 CI 神器有点强!》中,我们分享了如何通过 CNB 的 git-clone-yyds 插件,把一个 125 GB 代码库的克隆时间从 20 分钟降至 10 秒内。显著缩减了我们团队的流水线运行时间,大大提高了构建效率。
然而,在实际针对 AOSP 进行定制开发时,我们遇到了一个更加棘手问题:
即便成功加速了代码库的克隆过程,全量编译如此庞大的代码库依然是个非常耗时的过程,需时约 46 分钟。
常规的解决策略,就是对编译产物的缓存,避免后续构建的全量重新编译,加快编译过程。这也就是为什么前文团队会专门采购一台编译构建机用来做编译的原因。
虽然这个方案可行,但是显然也是一个比较挫的方法:
既然 CNB 已经帮我们解决了 clone 代码的速度问题,是否 CNB 也提供了编译缓存的方案呢?
有的,而且还非常先进!
基于 Docker 生态构建的 CNB,在处理缓存的方式也是非常的云原生、异常先进。
底层原理是通过在母机上存放需要被缓存的产物,然后在编译环境的 Docker 容器起来时,通过 volume 参数挂载上去,在编译环境容器中即可使用缓存。
Docker 玩家对 volumes 都很熟悉,常见的有 rw/ro 两种模式(读写和只读模式)。
CNB 在此之上,还提供了 Copy-on-Write 模式,挂载到容器上的缓存目录,是基于 Copy-on-Write的机制实现的。Copy-on-Write (CoW 写时复制)允许系统在需要修改数据之前共享相同的数据副本,从而实现高效的缓存复制。在并发环境中,这种方法避免了缓存的读写冲突,因为只有在实际需要修改数据时,才会创建数据的私有副本。
这种 volumes + Copy-on-Write的机制显著提高了并发性能。
按照官方文档的配置,修改一下流水线的配置,团队在编译 AOSP 的时候,通过 Copy-on-Write 的方式缓存 ./out 目录,配置的方法很简单,修改 docker.volumes 字段即可:
build_config: &aosp_build_config
runner:
cpus: 64
docker:
build: .ide/Dockerfile
# 在这里挂载 volume 缓存,声明为 copy-on-write
volumes:
- out:copy-on-write
stages:
- name: build
env:
BUILD_HOSTNAME: cnb-build
shell: |
source build/envsetup.sh
lunch aosp_arm-eng
make -j64
script: bash -e -c "${shell}"
# 测试并发 6 流水线测试
master:
push:
- *aosp_build_config
- *aosp_build_config
- *aosp_build_config
- *aosp_build_config
- *aosp_build_config
- *aosp_build_config
按照官方 quick start 的测试用例,修改了 SurfaceFlinger.cpp 的系统默认颜色配置,并且触发 6 条流水线同时编译出多个包。
从上面截图可以看到,第一次编译后,通过 CNB 配置 volume 挂载缓存的方式,6 条并行的流水线已经可以利用上缓存。
因为我们声明了是 Copy-on-Write 的方式来使用缓存,在 6 条流水线中编译都实现了 100% 的增量编译,
编译用时是从 46 分 40 秒降低到 1 分 30 秒。
Docker Volume 是一种持久化和共享数据的机制,允许将数据存储在容器外部(如母机上),确保数据不随容器销毁而丢失,并支持不同容器之间的数据共享。
CNB 使用 Docker Volume 来实现缓存,支持多种缓存的策略,如 :
这里我们以 Copy-on-Write 缓存方式,介绍一下 CNB 是如何结合 CoW 和 Docker Volume 实现高效的缓存策略的。
如前文提到 Copy-on-Write 是在数据需要被修改时,才会创建数据的私有副本,而读操作是可以安全并发运行的。
我们从文件系统挂载的角度看,当流水线起来时,CNB 检测到配置文件中有配置 volumes 字段,从而会在 /data/cache 下面创建一个缓存的文件夹,用户缓存编译产物。
当我们声明了 volume 缓存使用 Copy-on-Write 的数据卷类型来做缓存的时候,CNB 还会在母机的 /data/copy-on-write 下建立 3 个文件夹,upper dir、 work dir、 merged dir。
最终通过 mount -t overlay 挂载成 CoW,其中 lower dir ,就是 volume 缓存所在的文件夹, 这个缓存最终会在编译容器启动的时候(docker run)通过 -v (volumes) 的方式挂载到容器的对应目录下,从而让在流水线运行时,可以直接在容器中使用编译缓存。
下图可以看到,在编译容器中,/workspace/out 目录下,已经挂载了缓存目录。
当有多条流水线并发使用缓存的时候,情况也是类似,我们并发多条流水线,CNB 会通过建立多套 CoW 文件夹(lower dir 都是 /data/cache/ 下的目录),多个构建容器并发使用缓存,互不冲突,换言之需要同时运行多个编译任务的时候,CNB 可以并发命中并使用编译缓存,从而加速编译的过程。
基于 Docker 生态构建的 CNB,除了 git-clone-yyds 加速 clone 代码的速度、volume 缓存加速编译速度之外,还有更炸裂的功能——远程开发。CNB 的远程开发结合了上面的优点:
后面我们将探讨远程开发背后的更多细节,尤其是 CNB 是如何解决并发性问题,从而提供一个流畅、高效的远程开发体验。
-End-
原创作者|黎志航