如果你正在值班,手里是两台(或以上)租来的云 Mac,用 Fastlane 做 iOS 发布,最近出现「编译日志是绿的、TestFlight 里却没有新包」或「传上去的包不对」,这篇就是给你看的。还在纠结要不要上云、要不要加第二台机器,请先看 TestFlight 专机 或 编译上云——这里默认你已经拆成「一台负责编、一台负责传」。
读完这篇,你能带走两件事:① 根据日志判断是证书、制品还是网络哪一类问题;② 对照清单确认两台机器的职责有没有分对,以及该不该继续加机器。
- 编译机:只负责用 Xcode 编译工程,并导出安装包(
.ipa) - 上传机:只负责用发布证书签名,并把
.ipa上传到 TestFlight;苹果后台登记的出口 IP 应对准这台 - Archive / 打出包:Xcode 生成可分发的构建结果;对外交接时我们只用编好的
.ipa文件,不搬整颗工程缓存目录 - match:Fastlane 里统一管理发布证书/描述文件的工具;只应在一台机器上跑
谁可以跳过:只有一台 Mac,或 TestFlight 上传一直正常、还没拆成「编译 + 上传」两台。若你已用多台云 Mac 做发布且链路出了问题,再往下读。
- 证书边界错——发布证书被同步到了编译机,上传机拿不到(最危险)
- 制品边界错——跨机房传
.xcarchive而非可验证的.ipa(最隐蔽) - 网络身份漂移——ASC 白名单仍是旧 IPv4(最玄学)
背景(1 分钟):两台机器之后,常见问题不是「机器慢」
很多团队会租两台云 Mac:一台离亚洲近,白天负责把 App 编译成安装包;另一台放在加拿大,负责 把包交给苹果(TestFlight / App Store)。加第二台往往是为了省时间,但值班里真正耗人的,是两台机器都在管证书、都在传文件——编译那台显示成功,TestFlight 却一直没有新版本。
下面先说大家常踩的错法(模型 B),再说我们用的分法(模型 A) 以及怎么验收。
推荐分法:模型 A(编译只做包,上传只做发布)
一句话:编译机只产出 .ipa 文件;上传机才碰证书和 TestFlight。 不要在两台机器上都跑证书同步(match),否则私钥会落在「错误的那台」上,上传机就会报找不到发布证书。
| 角色 | 编译机(香港) 示例:亚太时区白天编包 | 上传机(加拿大) 固定 IP 交苹果 |
|---|---|---|
| 要做的事 | 拉代码 → Xcode 编译 → 导出 .ipa | 收 .ipa → 用发布证书 → 上传到 TestFlight |
| 不要做 | 不要跑 match、不要上传 TestFlight | 不要重新完整编译整个工程 |
| 交给下一台什么 | 安装包文件 + 本次构建编号(RUN_ID) | 无(终点是 App Store Connect) |
对应到 Fastlane 配置(给工程师对照):编译机的脚本里只有「编译、导出包」相关步骤;上传机的脚本里才有 match、upload_to_testflight 和苹果的 API 密钥。上传机那台在苹果后台登记的出口 IP 只填这一台。
我们的双机跑在 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)
# 香港 · 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?
先别翻完整日志。按下面树走一级即可定位章节(建议收藏本段):
TestFlight 没新 build?
├─ 上传机 RUN_ID ≠ 编译机 RUN_ID(或缺 manifest)
│ → 坑 2 · 制品边界错
├─ 日志含 match / AppStoreDistribution / code signing identity
│ → 坑 1 · 证书边界错
└─ upload 显示成功,ASC 仍无包 / 403 / Invalid Binary(编译机全绿)
→ 坑 3 · 网络身份漂移
| 模式 | 日志关键词(grep) | Release 影响 |
|---|---|---|
| 坑 1 证书边界 | AppStoreDistribution、No matching provisioning、match failed |
阻断:TestFlight / 上架全停 |
| 坑 2 制品边界 | RUN_ID 不一致、Invalid Binary、export_method 冲突 |
高:可能传错包或无法晋级 |
| 坑 3 身份漂移 | 403、upload OK 无 processing、ASC 网络限制 |
间歇阻断:像「玄学」掉包 |
三个失败模式(展开)
坑 1:证书边界错
30 秒判别:编译机 archive 绿;上传机红,且日志在 match / signing 步骤失败。
日志关键词:AppStoreDistribution、Could not find a matching code signing identity、No 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_ID、ipa_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。
日志关键词:403、Successfully uploaded 但无 build、ASC「网络」相关拒绝(编译机日志无异常)。
Release 影响:间歇阻断——有时能传、有时不能,最耗值班耐心。
根因:上传机 IPv4 与 ASC 白名单不一致(第三类隐性故障)。
修复:上传机每日 curl -4 ifconfig.me 对账;漂移即改白名单 + 告警。
如何判断你真的拆对了
门禁、FAQ、值班条目我们合并成下面这张验收表。拆机一周后,每条都应能自动回答「是」:
| 验收项 | 期望结果 |
|---|---|
| RUN_ID 对齐 | 编译机 export 与上传机 upload 日志中 RUN_ID、GIT_SHA、ipa_sha256 一致 |
| 编译机无 Distribution | 编译机钥匙串 / 日志中永不出现 AppStoreDistribution、match step |
| 上传机只做上传 | 上传机 lane 日志只出现 match(或 resign)+ upload_to_testflight,无 ARCHIVE SUCCEEDED |
| 制品形态 | 跨机房文件 < 200MB 的 .ipa + manifest;无 .xcarchive rsync |
| ASC 身份 | 上传机当前 IPv4 = ASC 白名单;编译机 IP 未写入 ASC |
| Runner 隔离 | role=ios-build-hk 与 role=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 补丁 |
最终结论(只看这一段也能执行)
如果你现在同时满足下面三条,请按本段拍板,不必重读全文:
- 已有 ≥ 2 台 Mac runner(或等价多节点 CI)
- Fastlane 链路出现 TestFlight 不稳定 / 不上传(
match failed、processing 卡住、偶发无包) match可能在多台机器上执行过
👉 不要再加机器。
👉 立即做三件事(今天就能开工):
- 停止所有非 upload 节点上的
match/sync_code_signing(签名权只留在 upload 机) - 强制跨机房只传
.ipa+manifest.json(禁止 rsync.xcarchive) - 统一
RUN_ID+GIT_SHA+ipa_sha256,上传机 upload 前必须校验
如果上面三条做不到:你仍在 Stage 2(模型 B)。加机器 = 放大故障,不是修复。 先改 Fastfile / workflow 标签,跑通上文验收表,再谈扩容 build 节点。
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
还会被问到的几句
编译机完全不签名能 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 专机。