发布前夜,项目里的主 App、今日小组件、Apple Watch 伴侣应用三个 target 一起全量编译,单机跑完要 38 分钟。团队没有常年养一支自建 CI 集群的预算,但发布日确实等不起。后来我们摸索出一个折中方案:发布当天临时租三台云端 Mac mini,组成一支「一日构建农场」,用 SSH 矩阵把任务分发出去,编译结束后统一签名、汇聚产物,全程不到 15 分钟就能拿到三个可上传的包。这篇文章记录这套编排的完整做法。
什么时候值得搭多节点
不是所有项目都需要这么折腾。判断标准很直接:单次全量编译超过 20 分钟,或者一次发布要同时产出多个 target、多种架构的包,才值得临时多开几台节点。如果只是日常写代码、跑单元测试,一台 Mini Standard(24GB 内存)完全够用,常年开三台反而是浪费。
多节点农场的核心思路是「按天租用、按需解散」:发布窗口前一晚下单,发布完成后立即释放,费用只发生在真正需要并行算力的那一天。
农场拓扑与节点分工
我们按各节点的网络路径与资源条件挑选角色。分工前先在控制台确认各节点当前可选的机型档位,把重编译角色排到网络路径和资源都合适的节点上。
| 节点角色 | 套餐 | 芯片/内存 | 位置 | 天价 | 负责内容 |
|---|---|---|---|---|---|
| 主编译节点 | Mini Pro | M4 Pro / 64GB | 美国西部 | $59.3/天 | 主 App target,同时兼签名归拢 |
| 编译节点 A | Mini Standard | M4 / 24GB | 东京 | $40.1/天 | 今日小组件 target |
| 编译节点 B | Mini Standard | M4 / 24GB | 首尔 | $40.1/天 | Watch App target |
三台合计 $139.5/天。作为对比,若全部换成 Mini Pro 则是 $177.9/天——小组件和 Watch App 的编译负载本身不重,用 Standard 档位就够,把预算留给真正吃内存的主 App 编译。
教训:一开始我们把三台节点都放在东京,结果主编译节点和签名节点合并成一台后,rsync 内网互传虽快,但美西节点承担了后续 App Store Connect 上传,反而离用户上传目标更近——签名节点选靠近上传出口的地域,比选靠近开发者的地域更重要。
分发脚本与执行
协调机通常就是主编译节点本身,用一个简单的 Bash 脚本把 target 分发给各从属节点,后台并行执行,最后统一等待:
#!/bin/bash
set -e
NODES=("node-a.internal" "node-b.internal")
TARGETS=("WidgetExtension" "WatchApp")
for i in "${!NODES[@]}"; do
ssh "build@${NODES[$i]}" \
"cd ~/repo && xcodebuild -scheme ${TARGETS[$i]} \
-configuration Release -archivePath build/${TARGETS[$i]}.xcarchive \
archive CODE_SIGNING_ALLOWED=NO" &
done
xcodebuild -scheme MainApp -configuration Release \
-archivePath build/MainApp.xcarchive archive
wait
echo "all targets archived"
关键点是从属节点带上 CODE_SIGNING_ALLOWED=NO,只产出未签名的 .xcarchive,证书完全不需要同步到那两台机器上。主编译节点自己带证书,负责 MainApp 的签名编译,同时等待其他节点的产物。
产物汇聚与集中签名
从属节点编译完成后,用 rsync 把 .xcarchive 推回主节点:
rsync -avz build@node-a.internal:~/repo/build/WidgetExtension.xcarchive ./build/
rsync -avz build@node-b.internal:~/repo/build/WatchApp.xcarchive ./build/
三个 archive 到齐后,在主节点用同一份描述文件和证书统一跑 xcodebuild -exportArchive,再用 fastlane 的 pilot upload 一次性传三个包到 TestFlight。整个签名环节只在一台机器上发生,私钥不落地到从属节点,这一点在检查安全基线时会被反复确认。
下单前建议过一遍自查清单:
- [ ] 三台节点的 macOS 与 Xcode 版本是否一致(避免产物架构差异)
- [ ] 从属节点的 SSH 公钥已提前写入
authorized_keys - [ ] 签名节点选在离 App Store Connect 上传出口较近的区域
- [ ] 每台节点的免费重装额度还够用,发布前不要临时重装
常见问题与边界
多节点编排也有不划算的场景。如果项目只有一个 target,或者全量编译本身就在 10 分钟以内,拆分成多节点反而会因为 SSH 调度、rsync 传输产生额外等待,不如老老实实用一台节点跑完。另外,首尔节点目前没有 Pro/Max 档位,如果三个 target 里有一个内存占用特别高(比如带机器学习模型推理的 target),要提前把它排到东京或美西的 Pro/Max 节点,而不是硬塞进首尔的 Standard 节点。
常见问题
多节点构建农场适合什么规模的项目?
单次全量编译超过 20 分钟,或者需要同时产出多个 target(主 App、Widget、Watch App)与多架构包时才值得搭。日常单人开发用一台 Standard 节点足够,不必为此常年多开机器。
多台节点各自编译,签名证书怎么保证一致不泄露?
只在一个节点(通常是硅谷的签名节点)保留证书与描述文件,其余节点只负责编译产出未签名的 .app/.xcarchive,通过 rsync 集中到签名节点统一签名和上传,私钥不落地到多台机器。
怎么决定哪个节点担任重负载编译角色?
先在控制台确认各节点当前可选的机型档位,再结合团队成员的网络路径与产物上传方向来选:重编译角色放在资源充足、对主要成员延迟低的节点,签名与上传角色放在出口带宽大的节点。