一个周二的早上,GitHub 拒绝为我们的组织启动任何一个 Actions 任务。提示说我们的付款失败了,或者需要提高支出上限。两者都不是事实:没有任何欠款,绑定的信用卡刚刚通过了预授权,也没有什么支出上限可以提高。支持团队那天没有回复。
我们决定往好处想。GitHub 是在委婉地告诉我们:你们已经用不下它的运行器了,该去找自己的机器了。于是我们当晚就去找了。
冻结只影响 GitHub 自己的机器
计费冻结拒绝的是 GitHub 托管运行器上的任务,碰不到你自己带来的运行器。我们是这样发现的:在一台实验室 Mac 上注册了一个运行器,看着一个探测任务跑完,而组织里所有托管任务都还在被拒绝。所以最快的出路不是去和计费系统争辩,而是不再向 GitHub 要机器。
"自带运行器"曾经意味着一整个周末的基础设施工作。现在不再是了。
现成的方案已经存在
如今有一整类服务商能以自托管运行器的身份接入 Actions:安装一个 GitHub App,在工作流里改一个标签,每个任务就会得到一台任务结束即销毁的临时虚拟机。Linux 这边我们选了 Blacksmith:在我们比较过的几家里单价最低,每个任务一台微型虚拟机,而且用的是和 GitHub 运行器相同的基础镜像,我们的任务依赖的工具都已经在里面了。Apple 这边我们本来就有一台实验室 Mac,所以 iOS 和 macOS 套件跑在上面不花一分钱的分钟费。
是开关,不是迁移
我们最自豪的一点是这个改动有多小。每一个进入必需门禁的 Linux 和 Apple 任务都从一个仓库变量里读取自己的运行器,旧的 GitHub 标签作为兜底:
runs-on: ${{ vars.LINUX_CI_RUNS_ON || 'ubuntu-latest' }}
设置变量,任务就迁过去。删掉变量,任务就迁回来,不需要任何拉取请求。一个小小的守卫测试会在任何 Linux 门禁任务脱离这个变量时让构建失败。
我们没有把所有东西都迁走。产品部署、发布晋级,以及任何持有静态云密钥的任务都留在我们原本就信任的机器上,因为对这些任务来说,运行器本身就是信任边界的一部分。这部分加固按我们自己的节奏来做,而不是在故障当晚。
服务商这边:大约十分钟的工程师时间和一个拉取请求,它自己就验证了自己,因为拉取请求会运行它自己的工作流。Mac 这边:一个晚上。
数字
主分支最近二十次托管运行,对比新集群上的最初几次运行:
| 任务 | 托管 | 新集群 |
|---|---|---|
| 前端测试与 lint | 13.5 分钟 | 6.1 分钟 |
| 后端测试,8 核 | 11.6 分钟 | 5.0 分钟 |
| 视觉回归 | 2.1 分钟 | 0.9 分钟 |
| iOS 单元测试 | 14 分钟 | 2 到 4 分钟 |
| 整个门禁 | 约 14 分钟 | 约 7 分钟 |
同一组任务的 Linux 计费分钟数从每次运行 46.7 分钟降到 21.8 分钟。再加上更低的单价,据此推算我们的 Linux CI 账单会从每月大约 730 美元降到 350 到 400 美元;我们会用第一张完整账单来核对。macOS 此前是最大的一项,按总用量计每月约 1600 美元,而且因为 macOS 分钟按十比一计入包含额度,它还吃掉了我们大部分的包含分钟数。现在它跑在一台我们本来就有的 Mac 上。
一点说明:这种提速对于缓存已预热、CPU 密集的套件是真实的;我们那些不到一分钟的守卫任务并没有变快。请衡量计费分钟数,而不是营销数字。
一句话总结:门禁用时缩短到大约一半(实测从 13.8 分钟降到 7 分钟);此前在托管运行器上按总用量计每月约 2600 美元的 Linux 和 macOS 测试套件,在新集群上预计只需 350 到 400 美元,减少约 85%。Windows 通道和少数部署、晋级任务目前仍在托管运行器上,不在这个数字之内。
如果你也是一个小团队
- 在需要之前就把开关装好。一个带托管兜底的、由变量驱动的标签不花任何成本,却能把下一次故障变成十分钟的一次切换。
- 点击之前,先看清服务商的迁移向导要做什么。我们遇到的那个会把服务商标签硬编码进每一个工作流,包括部署,而且不留任何兜底。
- 把携带凭证的任务留在你已经信任的机器上,先加固它们获取密钥的方式,再考虑迁移。
当我们合入最后一部分时,GitHub 的冻结仍然没有解除。我们始终没有收到回复。只是它已经无关紧要了。