哥啊,为啥你手册上不写呢!
Docker 是新世界的痛
在我们那时候,自动化构建都是通过 Jenkins 完成的。你只需要在 Jenkins 上配置好构建方式,在节点上配好工具链,然后它就能用了。现在做什么都得要容器要镜像,以前写一个 Shell 脚本或者麻烦点 Jenkinsfile 就能完成的工作,现在非得再配上一堆 Dockerfile 和 docker-compose.yml. 我不知道这到底是为了谁好。
Docker 是新世界的痛。随着硬盘容量越来越不要钱,以前需要花费精力去维护一致性的内容被一个个容器所取代——毕竟,有什么比复制一份更省力、更能保证一致的方法呢?
但是这种做法也带来了一个问题:每次构建的时候,CI 工具都必须再次下载这个项目的依赖。现在一个个工程的依赖都和海一样,Node 这种一拖就是几十上百个包的更是重量级。而且最重要的一点是——
网络还是要钱的
如果是 Node, 那么配置一下国内镜像或许能解决这个问题(虽然每次下载几百个包也是着实蛋疼),但是 Vcpkg 这种压根没法镜像的就必须得挂着梯子才能正常拉取依赖。如果你的工程又不幸地需要用 boost
等重量级库,那么光拉取库的时间就够喝一壶了。更不要提梯子是按流量计费的!
或许你觉得可以配置一下 fastgithub
之类的神奇玩意到镜像里——不,继续往镜像里塞各种怪玩意不是好文明,而且你还是得等包下载。我们需要配置缓存机制把网络 IO 尽可能降到最小。毕竟,就算公司网络不要钱,摸鱼的带宽还是要给自己留的。
配置缓存机制
我们使用 drillster/drone-volume-cache
镜像来实现缓存机制。这个插件的实现方式是将工程中要被缓存的目录增量复制到 /cache
下,或者将已经缓存的目录从 /cache
中复制到工程中。注意到这个插件的主要限制:它只能操作工作空间内的文件!如果你的插件会在工作目录之外(比如用户家目录)写入缓存的话,这玩意就不能用了。
Vcpkg 会将所有的东西扔到 ${VCPKG_ROOT}
里。如果你将 Vcpkg 作为 Git 子模块加入到工程中,那么它的下载和构建都会在工程内进行。这样我们就可以使用缓存镜像来执行对应的缓存操作了!
设置该工程使用容器
首先,我们先声明该构建会使用一个容器,且这个容器是持久化到主机上的容器:
volumes:
- name: vcpkg-cache # 这里的名字后面要用到
host:
path: /mnt/ephemeral/cache/vcpkg # 这里换成你的主机上的路径
这样声明了之后,必须在 Drone CI 后台里将该项目设置为「受信任的 (Trusted)」,否则构建会直接失败。什么?你说你进到项目设置里面找不到这个开关?那是因为你的账号不是管理员账号。你需要编辑 Drone CI 的 docker-compose.yml
给主机加一个环境:
services:
server:
image: drone/drone:2
...
environment:
- DRONE_USER_CREATE=username:<这里换成你登录 Drone 的用户名,去掉两边尖括号>,admin:true
然后重启一下 Drone CI.
设置恢复缓存步骤
在开始构建的步骤之前新增一个步骤来恢复缓存。这个步骤即使之前没有缓存也不会出错,所以不需要任何构建条件。
steps:
- name: restore-cache
image: drillster/drone-volume-cache
pull: if-not-exists
volumes: # 这里列出要用到的容器列表,但是这个插件只用一个
- name: vcpkg-cache # 这里填之前创建的容器的名字
path: /cache # 这个值是固定的,不能改
settings:
restore: true # 这里是 true, 表示将缓存内容复制到工程内
mount: # 这里是相对于工程内的路径列表,填你要恢复的文件夹,以工程根目录为当前目录
- ./vcpkg/buildtrees # Vcpkg 会产生的三个需要缓存的目录如下
- ./vcpkg/downloads
- ./vcpkg/packages
- ./vcpkg/vcpkg # 也可以缓存编译好的 vcpkg 运行文件
...
设置增加缓存步骤
在完成构建的步骤之后新增一个步骤来保存缓存。大部分的配置和之前一样,只是把 rebuild
换成了 true
.
注意 mount
部分需要和你之前定义的顺序保持一致。如果前后不一样的话,可能会导致一些难以预料的结果。最好的情况是一些文件没有被缓存,最差的情况是会损坏缓存。
steps:
...
- name: rebuild-cache
image: drillster/drone-volume-cache
pull: if-not-exists
volumes:
- name: vcpkg-cache
path: /cache
settings:
rebuild: true # 这里
mount:
- ./vcpkg/buildtrees
- ./vcpkg/downloads
- ./vcpkg/packages
- ./vcpkg/vcpkg
假如要清除或跳过缓存
遇到需要清除缓存重新编译时,在提交信息里添加 [CLEAR CACHE]
即可清除缓存。如果本次构建需要跳过缓存,则在提交信息里添加 [NO CACHE]
.
更高级的配置
这个镜像还支持缓存过期机制、更改缓存结构等等操作。但是在我的用例里并没有这些需求,所以还是需要查阅原文档进行配置。
完整的 .drone.yml
下述是完整的 .drone.yml
文件。注意到其中的 dousha99/vcpkg-image:5-alpine
可能并不包含你的项目所需要的全部依赖,所以你可能会需要制作自己的构建镜像,或者使用其他开发镜像。具体的构建命令也需要跟随你的项目而修改。
kind: 'pipeline'
type: docker
name: default
clone:
depth: 3
steps:
- name: submodule
image: alpine/git
pull: if-not-exists
commands:
- git submodule update --init --recursive
- name: restore-cache
image: drillster/drone-volume-cache
pull: if-not-exists
volumes:
- name: vcpkg-cache
path: /cache
settings:
restore: true
mount:
- ./vcpkg/buildtrees
- ./vcpkg/downloads
- ./vcpkg/packages
- ./vcpkg/vcpkg
- name: bootstrap
image: dousha99/vcpkg-image:5-alpine
pull: if-not-exists
commands:
- if [ -f vcpkg/vcpkg ]; then echo "vcpkg exists"; else sh vcpkg/bootstrap-vcpkg.sh; fi
- name: configure
image: dousha99/vcpkg-image:5-alpine
pull: if-not-exists
commands:
- cmake --preset ci
- name: build
image: dousha99/vcpkg-image:5-alpine
pull: if-not-exists
commands:
- cmake --build --preset ci-debug-all
- name: test
image: dousha99/vcpkg-image:5-alpine
pull: if-not-exists
commands:
- cd cmake-build-ci/test
- ctest
- name: rebuild-cache
image: drillster/drone-volume-cache
pull: if-not-exists
volumes:
- name: vcpkg-cache
path: /cache
settings:
rebuild: true
mount:
- ./vcpkg/buildtrees
- ./vcpkg/downloads
- ./vcpkg/packages
- ./vcpkg/vcpkg
volumes:
- name: vcpkg-cache
host:
path: /mnt/ephemeral/cache/vcpkg
[…] 但是自从上一次和 Drone CI 的缓存机制搏斗之后,我就觉得这个东西多少有点问题了。当然这可以认为是因为我对于容器管理理解薄弱导致的。但是现在我又遇到了一个绝世难题: […]