若你正在值班,手上是兩台(或以上)租用的雲 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 機不必)。香港節點規格可對照 香港 Mac mini 方案。
常見錯法:模型 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 · 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 · 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 與 GitHub Actions 計費 窗口,再考慮加 build 節點。
結論:不需要雙機。若僅亞太編得慢,再考慮加 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 專機。固定出口、分節點訂機可參考 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 專機。