VPSSpark 博客
← 返回开发日记

我用 3 台 Cloud Mac,撑住了每天 500 次 iOS CI 构建

机房手记 · 2026.06.02 · 约 14 分钟阅读

Cloud Mac 上 GitHub Actions Self-hosted Runner 与 iOS CI 队列

先给结论:每天约 500 次 iOS CI 构建,多数团队不需要 8 台 Mac mini,3 台 Apple Silicon Cloud Mac 就够——前提是你们把 GitHub Actions 里的 Job 分层,并用 Self-hosted Mac Runner 做快队列与 Release 队列隔离,而不是让每台机器同时跑三个完整 Archive。

三个月前,我们接手一个 iOS 团队的 Xcode CI 环境。当时他们的配置是:

  • 18 名开发者
  • 2 个 App(共用 Monorepo)
  • GitHub Actions 作为唯一 CI
  • 每天约 500 次 macOS Job(含 PR、单测、夜间发版)

采购方案写的是:买 8 台 Mac mini 做机房自建 Runner。我们先把两周的 workflow 日志按 Job 类型拆开统计,发现真实构成与直觉差很多——大约 70% 是 PR 校验与集成编译,20% 是 Simulator 单测,只有约 10% 是 Archive + 上传(若把 TestFlight 上传单独记账,则 Archive 与 Upload 可再拆,见下图)。

最终落地:2 台 Fast Mac Runner(跑 PR / 单测)+ 1 台 Release Mac Runner(跑 Archive、公证、TestFlight)——三台 Cloud Mac 稳定扛住全部 iOS CI/CD。下文是完整容量算法,以及我们为什么没选 Xcode CloudBitrise 全托管,而用 Self-hosted Runner 接专属节点。

图 1 · 每天约 500 次 iOS CI 构建组成(该团队 GitHub Actions 两周实测)

PR Build
65%
Unit Test
20%
Archive
10%
Upload
5%
3
台 Cloud Mac(实测够用)
8→3
采购方案缩减
63
机时/天(开缓存后)

容量怎么算:从 91 机时压到 63 机时

500 次/天 spread 在 24 小时,均值约 21 次/小时——但真正要命的是尖峰:欧美上午合并、发版日前 PR 风暴,峰值常常是均值的 3~5 倍。容量必须按峰值排队算,不能按午夜均值。

第二个变量是单次 Job 机时:Simulator 单测约 4~8 分钟;带依赖解析的 PR 构建 12~20 分钟;Release Archive + 公证 + 上传常达 25~45 分钟。用该团队两周 P50 粗算:轻 Job 6 分钟、重 Job 35 分钟;若错误地假设 30% 都是 Archive,会得出「必须 8 台 Mac」——而真实分项后,500 次里重 Job 远少于轻 Job。

无缓存时,按 325 轻 + 175 重估算,每日约需 91 机时,超过 3 台 Mac 24 小时物理上限(72 机时)。开启 DerivedData / SPM 缓存、L2 串行与队列标签后,轻 Job 均时约 4 分钟、重 Job 热路径约 22 分钟,总需求降到约 63 机时,在峰值系数 1.3 左右可接受——这就是「3 台够」的数学依据。

图 3 · 每日 macOS 机时需求(同一 500 次/天模型)

无缓存
91 机时
开启缓存
63 机时

Xcode CI 分层:别把 Archive 和 PR 塞进同一 Mac Runner 队列

能撑住 500 次/天的 GitHub Actions 流水线,几乎都会做三档 Xcode CI 分层:

  • L0:SwiftLint、单模块编译——走 macos-fast 标签。
  • L1:PR 集成构建(不 Archive)——同样走 Fast Mac Runner,每台可并行 2 个轻 Job。
  • L2:Archive、公证、TestFlight——走 macos-archive每台同时最多 1 个

GitLab CI、BuildkiteJenkins 的 macOS Agent 同理:用标签路由,而不是一个大杂烩队列。Buildkite 突发弹性见 Buildkite Mac Agent 对接云 Mac;Runner 池买租见 企业 Mac Runner 资源池决策

三台 Cloud Mac 怎么分工(含拓扑图)

我们落地的静态三节点如下(名称可改,职责建议不变):

节点 Runner 标签 职责 并发
Mac-A macos-fast PR Build、Unit Test 2 个 L0/L1
Mac-B macos-fast 与 Mac-A 对称,承接快队列 2 个 L0/L1
Mac-C macos-archive Archive、公证、Upload 1 个 L2

图 2 · 三节点与队列拓扑(GitHub Actions Self-hosted Runner)

Mac-AFast Mac Runner
Mac-BFast Mac Runner
Fast QueuePR Build · Unit Test · L0/L1
Mac-CRelease Mac Runner
Archive QueueArchive · 公证 · TestFlight Upload
我们实际压测与上线使用的是 VPSSpark 云 Mac mini M4M4 Pro 节点(两台 fast + 一台 release)。下文容量数据、排队 P95 与缓存命中率,均基于 Apple Silicon Cloud Mac 上的 Self-hosted Runner 环境,而非 Hackintosh 或灰色虚拟机。

两台 fast 扛吞吐,一台 release 扛确定性交付。发版日若 L2 排队超标,可临时给 Mac-A 加 macos-archive 标签,但须关掉其 L0 并行,否则 Keychain 与 DerivedData 锁竞争会导致签名偶发失败。三台 Xcode 小版本须与 Xcode 发布说明 锁定一致。

16GB M4 上 GitHub Actions Mac Runner 并发建议
轻量 Xcode CI:每台 2 个 Job;完整 Archive:每台 1 个 Job。两个 Archive 并行在同一台 16GB 机器上,是 500 次/天里「假饱和」的头号原因。

GitHub Actions Mac Runner 为什么容易排队

很多团队先买 GitHub 托管 macOS Runner 分钟包,发现 PR 一多就排队。原因通常不是「GitHub 慢」,而是:(1) 组织级 macOS 并发上限;(2) 每个 workflow 默认跑全量 Archive;(3) 没有按 Job 类型拆 Self-hosted Mac Runner,快 Job 被慢 Job 堵住。该团队在迁到 3 台专属 Cloud Mac 前,托管 Runner 的 queue_wait P95 曾到 40+ 分钟;自建 fast/archive 双队列后,L1 的 P95 降到 8 分钟以内。

常见做法是:3 台 Cloud Mac 做基线 Self-hosted 池,仅在开源贡献周或极端尖峰用托管 Runner 顶 L0——成本可控,且密钥不必每周迁移。

Self-hosted Runner 与 Xcode Cloud 对比

Xcode Cloud 适合 Apple 生态深度绑定、希望零运维的团队;Self-hosted Mac Runner 适合要控队列、控缓存键、要把 Archive 与内部 Jenkins/Buildkite 混用的组织。对比要点:

维度 Xcode Cloud Cloud Mac + Self-hosted Runner
计费 分钟包 + 并发上限 订阅/按日 Cloud Mac,机时可控
队列 平台统一排队 自管 fast/archive 标签
密钥 ASC 集成省心 Match / Keychain 需自建 runbook
适合 低频发版、少自定义 每天 500+ 次 iOS CI/CD

分钟包打满后的切换信号,见 Xcode Cloud 分钟包与按天云 Mac 承接 Archive FAQ

为什么这个团队没有选 Xcode Cloud

他们评估过 Xcode Cloud,最终仍选 GitHub Actions + Self-hosted Runner,核心理由是:(1) 后端与 Android 已在 GitHub,不想拆双 CI;(2) 需要自定义缓存键与 Monorepo 矩阵;(3) 发版周 Job 数超过分钟包舒适区,排队不可自管。Xcode Cloud 并非不好,而是与「每天 500 次、要控队列」的目标错位。

Bitrise 与自托管 Mac Runner 成本直觉

Bitrise 等移动 DevOps SaaS 省去 Agent 运维,按并发套餐收费。对 18 人、双 App、每天约 500 次 Job 的团队,年化费用常高于 3 台 Cloud Mac 订阅,且 Archive 并发仍受平台档位限制。Bitrise 更适合不愿碰 Runner 的初创;愿投入两周搭 Self-hosted Mac Runner 的团队,通常 3~6 个月收回节点成本。若已用 Bitrise,也可只把 L2 迁到专属 release 节点,不必一夜全迁。

Buildkite Mac Agent 的优缺点

Buildkite 把队列放在云端、Agent 放在你机器上,弹性好、Artifact 留存清晰;缺点是多一层编排学习成本,小团队可能觉得「为 3 台 Mac 上大炮」。该客户曾 PoC Buildkite:突发扩容优秀,但最终仍用 GitHub Actions 原生 Self-hosted Runner,因研发只熟一套 YAML。若你已有 Buildkite,三台 Cloud Mac 挂 Agent 的标签策略与本文 fast/archive 拓扑一致。

Cloud Mac 与本地 Mac mini CI 的区别

自建机房 8 台 Mac mini:CapEx 高、折旧、断电与证书机房审计。3 台 Cloud Mac:OpEx 可预测、按日可 PoC、区域可选近 Git 仓库。本地 CI 适合每天单机编译 >14 小时且规格多年不变;Cloud Mac CI 适合峰值波动、外包周、审核季加公证通道。该团队保留 2 台办公 Mac 给开发,重 Xcode CI 全上云,避免「8 台 mini 多数时间空转」。CircleCI 云 macOS 与云 Mac Runner 的 SLO 对比,可延伸阅读 CircleCI 云 macOS vs 云 Mac Runner FAQ

队列 SLO:三台机靠数据扩容,不靠感觉

建议监控:queue_wait_seconds(P95)、run_duration 按 L0/L1/L2 分桶、cache_hit_ratiol2_concurrent(应 rarely > 3)。示例阈值:L1 排队 P95 < 8 分钟;L2 P95 < 25 分钟。连续三天 L2 超标且缓存命中已 > 60%,再谈第四台 release 节点。

缓存:63 机时的关键杠杆

DerivedData 按 branch + Xcode版本 做键;SPM/CocoaPods 锁文件变更时 bust;fast 节点共享只读缓存,release 节点本地 NVMe 存 L2 DerivedData。签名材料走 vault,不进缓存包。缓存键必须含 macOS/Xcode 小版本,否则升级后会出现「命中但链接失败」。

GitHub Actions Self-hosted Runner 最小配置

三台各注册 Runner:macos-fast ×2、macos-archive ×1。L2 务必加 concurrency,防止发版 Job 互 cancel:

GitHub Actions · Release Mac Runner
concurrency:
                group: ios-archive-${{ github.ref }}
                cancel-in-progress: false
                jobs:
                archive:
                runs-on: [self-hosted, macos-archive]
                steps:
                - uses: actions/checkout@v4
                - run: xcodebuild archive -scheme App -archivePath build/App.xcarchive

release 节点用专用 macOS 用户 + Match;重启后先跑解锁脚本再开夜间队列。无人值守细节见 xcodebuild 官方文档 与 Fastlane 文档。

发版日:500 次变成 650 次怎么办

顺序:(1) 停非关键 L0;(2) 托管 Runner 只顶 L1;(3) 按日加第四台 Cloud Mac 48 小时。不要长期把 L2 提到每台 2 并行——会以公证随机失败还债。

何时必须第 4 台

  • L2 排队 P95 连续一周 > 40 分钟,缓存已优化。
  • Monorepo 超 5 个 App 共一台 release,夜间窗口不够。
  • 坚持每个 PR 全量 Archive——应先改 Job 结构,不是先买机器。

反模式

每个 PR 跑 Archive;GitHub Actions Mac Runner 不拆标签;两台 Archive 挤一台 16GB M4;缓存键不含分支;只看出败不看 queue_wait——都会让 3 台看起来「不够用」,从而误判要买 8 台。

FAQ:Google 常抓的短答

500 次 iOS 构建需要几台 Mac?

在 Job 分层(PR / 单测 / Archive 分列)与 DerivedData 缓存到位后,多数团队 3 台 Apple Silicon Cloud Mac 即可:2 台 Fast Mac Runner + 1 台 Release Mac Runner。若 500 次里超过一半是全量 Archive,请先改流水线或规划第 4 台 release 节点。

GitHub Actions Mac Runner 能同时跑几个 Job?

16GB M4 上建议:2 个轻量 Job(PR Build、Unit Test),或 1 个 Archive Job。不要长期「2× Archive」并行。

Archive 为什么不能高并发?

因为内存竞争触发 swap、磁盘队列延迟上升、Keychain 锁codesign 冲突,表现为偶发超时,而不是稳定、可复现的编译错误。

Cloud Mac 比 Xcode Cloud 便宜吗?

长期高频 iOS CI/CD、需要专属 Self-hosted Runner 与密钥常驻的团队,3 台 Cloud Mac 基线池通常比按分钟计费的 Xcode Cloud 更可控。低频发版、零运维优先的团队,仍应优先试 Xcode Cloud。

Bitrise 和 3 台 Cloud Mac 怎么选?

Bitrise 省运维、适合快速上线;每天约 500 次 Job 且要控队列与缓存时,Self-hosted Mac Runner + Cloud Mac 往往年化更低,且 L2 并发由你定义。

VPSSpark:2 Fast + 1 Release 的 Cloud Mac 基线池

若你也在算「500 次/天要买几台 Mac」,建议先用两周日志画出图 1 的分项占比,再按本文模型验证 63 机时是否成立。VPSSpark 云 Mac mini M4 / M4 Pro 支持按日 PoC 与长期订阅,适合挂载 GitHub Actions Self-hosted Mac Runner,把 PR 与 Archive 分到不同队列。

查看 Mac 云主机方案,或在 VPSSpark 首页 选区域,用真实 workflow 跑一轮分桶——再决定 3 台是否够用,而不是先买 8 台。

限时特惠

500 次/天 iOS CI:3 台 Cloud Mac 就够

GitHub Actions Mac Runner · Self-hosted · fast / archive 队列

返回首页
限时优惠 点击查看套餐