← 返回开发日记

Fastlane 双云 Mac 架构踩坑:编译与上传拆分后最常见的 3 个失败模式

Runbook · 2026.06.04 · 约 8 分钟阅读 · 可直接贴值班手册

两台云 Mac:香港节点 archive,加拿大节点 Fastlane upload

如果你正在值班,手里是两台(或以上)租来的云 Mac,用 Fastlane 做 iOS 发布,最近出现「编译日志是绿的、TestFlight 里却没有新包」或「传上去的包不对」,这篇就是给你看的。还在纠结要不要上云、要不要加第二台机器,请先看 TestFlight 专机编译上云——这里默认你已经拆成「一台负责编、一台负责传」。

读完这篇,你能带走两件事:① 根据日志判断是证书、制品还是网络哪一类问题② 对照清单确认两台机器的职责有没有分对,以及该不该继续加机器。

先对齐名词(后文会反复用到)
  • 编译机:只负责用 Xcode 编译工程,并导出安装包(.ipa
  • 上传机:只负责用发布证书签名,并把 .ipa 上传到 TestFlight;苹果后台登记的出口 IP 应对准这台
  • Archive / 打出包:Xcode 生成可分发的构建结果;对外交接时我们只用编好的 .ipa 文件,不搬整颗工程缓存目录
  • match:Fastlane 里统一管理发布证书/描述文件的工具;只应在一台机器上跑

谁可以跳过:只有一台 Mac,或 TestFlight 上传一直正常、还没拆成「编译 + 上传」两台。若你已用多台云 Mac 做发布且链路出了问题,再往下读。

TL;DR(30 秒)
双云 Mac 拆分后,瓶颈通常不是性能,而是 Fastlane 的三类边界错误
  1. 证书边界错——发布证书被同步到了编译机,上传机拿不到(最危险)
  2. 制品边界错——跨机房传 .xcarchive 而非可验证的 .ipa(最隐蔽)
  3. 网络身份漂移——ASC 白名单仍是旧 IPv4(最玄学)
下文先给30 秒诊断树,再展开三类故障;文末有验收表、四阶段判断,以及「最终结论」执行块(只看该段也能拍板)。

背景(1 分钟):两台机器之后,常见问题不是「机器慢」

很多团队会租两台云 Mac:一台离亚洲近,白天负责把 App 编译成安装包;另一台放在加拿大,负责 把包交给苹果(TestFlight / App Store)。加第二台往往是为了省时间,但值班里真正耗人的,是两台机器都在管证书、都在传文件——编译那台显示成功,TestFlight 却一直没有新版本。

下面先说大家常踩的错法(模型 B),再说我们用的分法(模型 A) 以及怎么验收。

推荐分法:模型 A(编译只做包,上传只做发布)

一句话:编译机只产出 .ipa 文件;上传机才碰证书和 TestFlight。 不要在两台机器上都跑证书同步(match),否则私钥会落在「错误的那台」上,上传机就会报找不到发布证书。

两台云 Mac 各干什么(模型 A)
角色 编译机(香港) 示例:亚太时区白天编包 上传机(加拿大) 固定 IP 交苹果
要做的事拉代码 → Xcode 编译 → 导出 .ipa.ipa → 用发布证书 → 上传到 TestFlight
不要做不要跑 match、不要上传 TestFlight不要重新完整编译整个工程
交给下一台什么安装包文件 + 本次构建编号(RUN_ID无(终点是 App Store Connect)

对应到 Fastlane 配置(给工程师对照):编译机的脚本里只有「编译、导出包」相关步骤;上传机的脚本里才有 matchupload_to_testflight 和苹果的 API 密钥。上传机那台在苹果后台登记的出口 IP 只填这一台

编译机 · 只管打包容器 上传机 · 只管交苹果 编译 → 导出 .ipa 不碰证书 · 不上传 带构建编号 证书 + 上传 TestFlight 固定公网 IP 苹果后台登记此机 .ipa → 两台之间只传安装包文件,不传整颗工程缓存
模型 A:编译机只交付 .ipa;上传机才做证书与 TestFlight

我们的双机跑在 Hashvps 独立 Mac mini 上,选它的主要原因是需要固定 IP + 机房角色隔离(upload 机 IP 写进 ASC,build 机不必)。

常见错法:模型 B(两台都在「帮倒忙」)

模型 B 就是职责没分开,我们第一周也这么干过。表面上是两台机器分工,实际上:

  • 两台都跑证书工具 match → 发布用的私钥留在编译机上,上传机拿不到 → TestFlight 一直失败
  • 把整颗工程构建缓存目录(.xcarchive)拷到另一台再打包 → 两台配置不一致 → 传上去的包苹果不认
  • 两台各自导出一遍安装包 → 导出选项不一致 → 日志写成功,TestFlight 却没有可用版本

若你们流水线像上面任意一条,再加第三台 Mac 通常没用,要先按模型 A 把「谁编译、谁上传、谁管证书」分清楚。

可观测性:怎样确认 build 与 upload 是同一次构建

双机最容易出的运营事故是「传了昨晚的 IPA」。我们在编译机、上传机日志里强制打印同一组字段:

  • RUN_ID(流水线单次 ID,如 hk-build-88421
  • GIT_SHA(短 commit)
  • 制品路径 + SHA256(写在 build/manifest.json
编译机成功 · 上传机成功(同一 RUN_ID)
# 香港 · build
[15:11:42]: Successfully exported: ./build/MyApp-20260604-1502.ipa
[15:11:42]: RUN_ID=hk-build-88421  GIT_SHA=a3f91c2  SHA256=9f2a…

# 加拿大 · upload(数小时后仍可对齐)
[02:22:01]: RUN_ID=hk-build-88421  GIT_SHA=a3f91c2  ipa_sha256=9f2a…
[02:24:18]: Successfully uploaded package to App Store Connect

值班规则很简单:上传机日志里没有相同 RUN_ID,一律按制品错配处理,不要先查 ASC。

30 秒诊断:TestFlight 没新 build?

先别翻完整日志。按下面树走一级即可定位章节(建议收藏本段):

值班诊断树(复制到 Runbook)
TestFlight 没新 build?
 ├─ 上传机 RUN_ID ≠ 编译机 RUN_ID(或缺 manifest)
 │     → 坑 2 · 制品边界错
 ├─ 日志含 match / AppStoreDistribution / code signing identity
 │     → 坑 1 · 证书边界错
 └─ upload 显示成功,ASC 仍无包 / 403 / Invalid Binary(编译机全绿)
       → 坑 3 · 网络身份漂移
三类故障 · 一眼对照(值班用)
模式 日志关键词(grep) Release 影响
坑 1 证书边界 AppStoreDistributionNo matching provisioningmatch failed 阻断:TestFlight / 上架全停
坑 2 制品边界 RUN_ID 不一致、Invalid Binaryexport_method 冲突 :可能传错包或无法晋级
坑 3 身份漂移 403、upload OK 无 processing、ASC 网络限制 间歇阻断:像「玄学」掉包

三个失败模式(展开)

坑 1:证书边界错

30 秒判别:编译机 archive 绿;上传机红,且日志在 match / signing 步骤失败。

日志关键词:AppStoreDistributionCould not find a matching code signing identityNo matching provisioning profiles

Release 影响:阻断级——TestFlight 与 App Store 上传无法继续,直到私钥回到上传机。

现象:编译机 archive 绿,上传机 upload 红;TestFlight 无新 build。

典型日志
Could not find a matching code signing identity for type 'AppStoreDistribution'
❌  Lane upload failed

根因:模型 B——编译机仍跑 match(appstore),Distribution 私钥不在上传机。

修复:编译机删除一切 match;上传机独占 match(rotate 仅上传机、独立窗口)。见 match readonly

坑 2:制品边界错

30 秒判别:对比编译机 / 上传机日志的 RUN_IDipa_sha256;或 ASC 报 Invalid Binary 而编译机无 signing 错误。

日志关键词:RUN_ID 不一致、Invalid Binary、双机出现两次 export_ipa / ExportOptions

Release 影响:——可能上传旧包、错 commit,或 processing 卡死;不一定立刻红 pipeline。

根因:跨机房传 .xcarchive 或双机各自 export。跨机房最小单位是可核对 SHA 的 IPA,不是 archive 目录。

修复:编译机单次 export_ipa + manifest.json;上传机只 upload,不再 export。

坑 3:网络身份漂移

30 秒判别:Fastlane 写 upload 成功;编译机、上传机 signing 全绿;TestFlight 仍无包或 ASC 403。

日志关键词:403Successfully uploaded 但无 build、ASC「网络」相关拒绝(编译机日志无异常)。

Release 影响:间歇阻断——有时能传、有时不能,最耗值班耐心。

根因:上传机 IPv4 与 ASC 白名单不一致(第三类隐性故障)。

修复:上传机每日 curl -4 ifconfig.me 对账;漂移即改白名单 + 告警。

如何判断你真的拆对了

门禁、FAQ、值班条目我们合并成下面这张验收表。拆机一周后,每条都应能自动回答「是」:

双机拆分验收(模型 A)
验收项 期望结果
RUN_ID 对齐 编译机 export 与上传机 upload 日志中 RUN_IDGIT_SHAipa_sha256 一致
编译机无 Distribution 编译机钥匙串 / 日志中永不出现 AppStoreDistributionmatch step
上传机只做上传 上传机 lane 日志只出现 match(或 resign)+ upload_to_testflight,无 ARCHIVE SUCCEEDED
制品形态 跨机房文件 < 200MB 的 .ipa + manifest;无 .xcarchive rsync
ASC 身份 上传机当前 IPv4 = ASC 白名单;编译机 IP 未写入 ASC
Runner 隔离 role=ios-build-hkrole=ios-upload-ca 标签分离,workflow needs 串联

升级判断:你现在在哪一阶段?

排障解决「今晚怎么救火」;阶段判断解决「下周该不该改架构」。对照下面四档,看你是该维持、该纠偏,还是该进入 role split。

Stage 1:单机 Fastlane

特征:一台 Mac runner;match + build + upload 在同一 Fastfile、同一钥匙串。

结论:不需要双机。先优化磁盘、Derived Data、上传窗口;若仅亚太编得慢,再考虑加 build 节点,而不是先拆 upload。

Stage 2:双机但未 role split(模型 B)

特征:已有两台(或多台)Mac;多台都跑 match / export / upload;日志里常出现本文三类边界错误。

结论:加第三台机器不会解决问题,只会提高证书漂移和制品错配概率。下一步是收拢到模型 A,不是扩容 runner 数量。

Stage 3:Role split(模型 A · 本文目标)

特征:build / upload 职责隔离;跨机房只传带 RUN_ID 的 IPA;match 只存在于 upload 节点;上传出口 IP 固定。

结论:✔ 验收表能稳定勾满 → 可以横向加 build runner(亚太多编)或加固 upload 专机,做稳定 CI farm。固定出口、分节点订机可参考 Hashvps 套餐(建议先锁 upload 机 ASC IP,再扩 build)。

Stage 4:Multi upload node(进阶)

特征:多 upload 区域(如 US / EU / APAC);ASC API Key、IP 策略按区分;制品晋级与合规审计独立成线。

结论:已进入分发系统范畴,不是「再多一台 CI Mac」——需要制品库、策略与审计,超出本篇 Runbook。

阶段 → 动作(决策闭环)
阶段 你现在最该做的
Stage 1维持单机;优化构建与上传时段
Stage 2停止加机;实施模型 A + 跑通验收表
Stage 3横向扩展 build;巩固 upload 专机与监控
Stage 4单独立项:多区域分发与合规,而非 Fastlane 补丁
升级触发器(承认问题已发生)
如果你的 CI 已经出现「TestFlight 偶发不出包 + 多机 runner + match 参与多节点」,说明你已经进入必须做 role split 的阶段,而不是继续扩容 CI runner。阅读到这里仍卡在 Stage 2,请按上文诊断树 + 模型 B 反模式改 lane,不要先买第三台 Mac。

最终结论(只看这一段也能执行)

如果你现在同时满足下面三条,请按本段拍板,不必重读全文:

  • 已有 ≥ 2 台 Mac runner(或等价多节点 CI)
  • Fastlane 链路出现 TestFlight 不稳定 / 不上传match failed、processing 卡住、偶发无包)
  • match 可能在多台机器上执行过

👉 不要再加机器。

👉 立即做三件事(今天就能开工):

  1. 停止所有非 upload 节点上的 match / sync_code_signing(签名权只留在 upload 机)
  2. 强制跨机房只传 .ipa + manifest.json(禁止 rsync .xcarchive
  3. 统一 RUN_ID + GIT_SHA + ipa_sha256,上传机 upload 前必须校验

如果上面三条做不到:你仍在 Stage 2(模型 B)加机器 = 放大故障,不是修复。 先改 Fastfile / workflow 标签,跑通上文验收表,再谈扩容 build 节点。

上传机 Fastfile(模型 A · 节选)
lane :upload_only do |options|
  verify_run_id!(options) # 对照 manifest.json
  match(type: "appstore", readonly: true)
  upload_to_testflight(ipa: options[:ipa_path], api_key_path: "asc_api_key.json")
end
变更纪律
拆 lane 当周不要 rotate match。证书变更 = 独立事件,先停双 lane,只在上传机操作。

还会被问到的几句

编译机完全不签名能 archive 吗? 取决于工程配置;我们用的是编译机 export 出待上传机做最终 app-store 签名的 IPA 路径(或编译机仅编译校验、上传机 resign)。关键是:match 只在一台机器上发生

match 快过期了怎么办? 只在上传机、非 readonly 窗口 rotate;编译机停用至 profile 稳定。

和出差 Runbook 的区别? 出差文讲人在现场的 Wi‑Fi;本篇讲机房角色与 Fastlane 边界,可照抄验收表。

系列说明:本文为系列 ① Runbook:双云 Mac + Fastlane 排障(TestFlight 不上传 / match failed / iOS CI 多机问题)。后续可拆:② Build·Sign·Distribute 架构篇、③ 模型 B 反模式专题;选型见 TestFlight 专机

延伸阅读

双机角色、固定出口与节点规格见 香港套餐首页定价;评估「要不要加 upload 专机」仍建议先看 TestFlight 专文。

Hashvps

卡在 Stage 2?先 role split,再加机器

upload 专机固定 IPv4,适合 ASC 白名单;build 节点可按亚太需求横向扩展。

查看方案
限时优惠