開發者使用 Mac 建置 iOS CI 流水線,避開 GitHub Actions 共享 macOS runner 佇列

GitHub Actions 不給力——這句話在 iOS 團隊裡越來越常聽到。問題通常不是 YAML 寫錯,而是發版週 macOS runner 一等就是半小時,真正的 xcodebuild 反而只跑幾分鐘。2026 年不少團隊不再執著於「把整條流水線塞進託管池」,而是改採一套更實際的打法:該在 Linux 上跑的流程不占 Mac,必須上 Mac 的工作直接走獨佔節點。本文會依「痛點 → 三種玩法 → 落地命令 → 避坑提醒」展開;如果你想先理解 Cloud Mac 為什麼能縮短等待時間,可先看 iOS CI 慢與 GitHub Actions 排隊 的入門篇。

一句話:新玩法不是放棄 GitHub Actions,而是把 macOS 建置從共享池裡切出來,用混合 Job、獨佔 Mac 島與發版 burst 彈性,直接砍掉排隊與冷啟動兩大浪費。

「不給力」到底卡在哪?

先把責任分清楚。GitHub Actions 對前後端專案常常夠用,但 iOS / macOS 流水線碰到的是另一種天花板:

  • macOS runner 是稀缺共享資源——發版週、WWDC 後 Xcode 升版週,佇列時間壓過編譯時間很常見
  • Job 結束就銷毀——DerivedData~/Library/Caches/org.swift.swiftpm、Pods 解壓結果難以像 Linux 容器一樣長期保溫
  • 簽章與 TCC 不能完整容器化——鑰匙圈、描述檔、公證票據要綁在可預期的機器上,環境漂移一次就可能半夜救火
  • 日誌比例失衡——建置 9 分鐘、排隊 38 分鐘,產品與營運看到的只有「為什麼又晚了一版」

所以「不給力」往往不是 Actions 壞了,而是把不適合共享池的工作硬塞進共享池。新玩法核心只有一件事:重新劃界,哪些留在託管 runner、哪些必須上獨佔 Mac。

2026 流水線思路變了什麼

過去最常見的作法是「一個 workflow 從頭到尾都跑在 macos-latest」——上手簡單,但在團隊擴大、發版頻率提高後,成本會急速放大。2026 年更主流的是分層編排

層級跑在哪典型任務
快檢層ubuntu-latest 託管 runnerSwiftLint、單元測試(非 UI 模擬器)、授權掃描、危險 diff 提示
重建置層獨佔 Mac(自託管 / Cloud Mac)xcodebuild archive、簽章、公證、上傳 TestFlight
編排層仍在 GitHub Actions觸發條件、審核、制品傳遞、Slack 通知——YAML 平台不用重練

也就是說,編排仍用 Actions,只是macOS 執行器換成你能掌控的機器。這和 Xcode 27 Agent 進 IDE 並不衝突:Agent 幫開發提速,商店鏈路依舊需要穩定的 Mac 建置節點。

玩法一:混合流水線——Linux 快跑,Mac 慢做

最容易落地、對現有倉庫改動最小的策略:PR 與日常 push 先不要碰 macOS runner,只有合併到 main 或打 tag 才觸發 Archive。

建議拆法

  • pull_requestubuntu-latest:lint + 單測 + 覆蓋率門檻
  • push: branches: [main]self-hosted + macos label:Archive + 上傳
  • 夜間定時 Job 在 Mac 島預熱依賴(pod install / resolve),白天 PR 直接消費成果

若單測強依賴 iOS Simulator UI,可保留一條「每週 nightly 全量驗證」Mac Job,避免每個 PR 都燃燒 Mac 分鐘。Windows 主力團隊分工方式可參考 Windows / Linux 主力 + 遠端 Mac 建置島

玩法二:Mac 建置島 + 自託管 Runner

混合流水線解決「不該上 Mac 的別上 Mac」;建置島解決「必須上 Mac 的別排隊」。一台(或多台)獨佔實體 Mac mini 24×7 掛著 actions-runner,把 DerivedData 與依賴快取落在本地 NVMe——Job 不再和整個 GitHub 世界搶 macos-14 配額。

建置島三條紀律

  • 一機一角色:簽章機不要混跑實驗性 Xcode beta,避免鑰匙圈與工具鏈互相污染
  • 快取鍵帶上 Xcode 主版本:升版後主動讓舊 cache 失效,避免髒命中造成偽隨機編譯失敗
  • 並行要隔離路徑:多 Job 同機時禁止共用全域 DerivedData,競態鎖與磁碟打滿可見 自託管 Runner 快取與磁碟 FAQ

沒有機房、也不想先採購硬體的團隊,通常會先用 Cloud Mac 當建置島:按天租賃 M4,SSH 安裝 Runner,發版週開機、離峰退租——邏輯與自建島一致,只是把資本支出轉成營運支出。企業多機資源池可看 企業 Mac CI 資源池選型

玩法三:發版 burst——該加機就加,該退租就退

第三種玩法對應脈衝負載:大版本前一週、節日前封版、客戶 Demo 前夜——建置併發突然變成 3 倍,但隔月又回到低頻。這時候直接買第三台 Mac mini 往往閒置,不如臨時加 Cloud Mac Runner,再用 label 把 burst 流量導向新機。

實務上最常見的操作節奏:

  • 基線 1–2 台常駐 Mac 島承接日常 main 建置
  • 發版窗口租 1–3 台同區 Cloud Mac,裝同版 Runner 並掛 burst label
  • Workflow 裡 runs-on: [self-hosted, macos, burst] 僅在大版本 tag 觸發
  • 窗口結束立即退租,帳單按天結算——可參考 採購、租賃、託管選型指南 做決策矩陣

粗估:M4 16GB 約 $20.6/天。若發版突擊租 3 台 × 5 天,成本約 $309,通常比全組工程師空等佇列、錯過審核窗口更划算——實際價格請以 定價頁 為準。

三種玩法怎麼選?

玩法適合誰主要收益主要成本
混合流水線已在用 Actions、想先降 Mac 分鐘PR 回饋更快、託管帳單下降要盤點哪些測試可脫離 Mac
Mac 建置島每週多次 Archive、對快取命中敏感接近零排隊、環境穩定可控需要維護 Runner、磁碟與憑證治理
發版 burst發版期尖峰很高、平時閒置明顯彈性併發、無閒置資產臨時機必須與常駐機鏡像一致

多數成熟團隊其實是組合拳:日常混合 + 常駐一座建置島 + 大版本 burst,不需要硬性三選一。

落地:Workflow 片段與 Runner 安裝

以下提供可直接修改參數後套用的最小示例(假設 Xcode 16.x / macOS 14 runner)。

混合 Workflow:PR 走 Linux,Release 走自託管 Mac

name: iOS CI
on:
  pull_request:
  push:
    branches: [main]
    tags: ['v*']

jobs:
  fast-check:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: SwiftLint
        run: swiftlint --strict
      - name: Unit tests (no simulator UI)
        run: swift test --parallel

  release-archive:
    if: github.event_name != 'pull_request'
    runs-on: [self-hosted, macos, ios-build]
    steps:
      - uses: actions/checkout@v4
      - name: Restore DerivedData cache
        uses: actions/cache@v4
        with:
          path: ~/ci/DerivedData
          key: dd-${{ runner.os }}-xcode16-${{ hashFiles('**/Package.resolved') }}
      - name: Archive
        run: |
          xcodebuild -scheme MyApp -configuration Release \
            -archivePath $RUNNER_TEMP/MyApp.xcarchive archive

在 Cloud Mac 註冊自託管 Runner

# SSH 登入建置島後(macOS 14+)
mkdir -p ~/actions-runner && cd ~/actions-runner
curl -o actions-runner-osx-arm64.tar.gz -L \
  https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-osx-arm64-2.321.0.tar.gz
tar xzf actions-runner-osx-arm64.tar.gz
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO \
  --token YOUR_TOKEN --labels macos,ios-build --unattended
./svc.sh install && ./svc.sh start
注意:首次簽章與公證建議在 VNC 下完成鑰匙圈解鎖與 TCC 授權,之後 Job 就能用無頭 SSH 穩定跑。憑證輪替時請優先在建置島處理,不要同步改十幾台開發者筆電。

什麼時候先別換?——兩個常見反例

新玩法不是信仰。有兩種情境我會建議先不要急著重構整條流水線:

  • 月建置量只有個位數、且沒有硬性發版時程——託管 macOS 分鐘費通常更低,自託管維運反而更重
  • 團隊目前沒有人能維護建置島——Runner 長期離線、磁碟 95% 無人清理,比排隊更危險;先補齊 on-call 再談擴池

若你屬於「先試一週再決定」陣營,最實際的方式是先租一台 Cloud Mac 掛 Runner,用真實倉庫壓一輪 main 建置,資料通常比會議討論更有說服力。

實作前檢核:把可預期風險先關掉

很多團隊不是策略錯,而是落地順序錯。明明已經選了混合流水線與建置島,卻因為憑證管理、快取目錄、Runner 標籤治理沒先定規範,最後變成「看起來有升級,實際上更難維護」。建議在啟用新流程前,先把以下檢核清單寫進 runbook,讓工程、平台、資訊安全、值班同仁都有一致操作基準。

  • 角色分工先定義:誰負責 Xcode 升版、誰負責憑證輪替、誰有權限移除離線 Runner,避免所有人都能改、也等於沒人負責
  • 標籤命名規則固定:例如 ios-buildreleaseburst,避免 workflow 寫死機器名稱造成後續擴容困難
  • 快取生命週期要明文化:哪些快取跨日保留、哪些只保留發版窗,並定義磁碟高水位(如 80%)時的自動清理策略
  • 安全邊界可審計:簽章憑證、API token、上架帳號權限必須分層,並確保每次變更都能回溯到人與工單
  • 故障演練要排期:至少演練一次「主 Runner 離線」「憑證過期」「Xcode 升版回滾」,確認團隊 30 分鐘內可以恢復主線建置

如果你正在說服管理層,這份檢核清單也很有用:它能把「要不要買機器」從感覺題變成治理題。先把流程跑順,再決定是持續租賃、混合配置,還是逐步自建企業級資源池,通常都比一次性重投資更穩。

常見問題 FAQ

GitHub Actions 對 iOS 專案為什麼常常「不給力」?

macOS 託管 runner 是共享池,發版週佇列可能遠大於建置時間;Job 結束快取又消失;簽章與 TCC 無法完全容器化。關鍵通常是工作負載與資源池不匹配,而非 YAML 語法。

2026 年 iOS 流水線有哪些新玩法?

主流就是三件事:混合流水線(Linux 快檢 + Mac 重建置)、Mac 建置島(自託管獨佔 Runner)、發版 burst(臨時加 Cloud Mac)。編排層照樣可以留在 GitHub Actions。

混合流水線會不會漏掉 iOS 特有問題?

會,所以要保留「合併 main 後必跑 Mac Archive」與「每週 Simulator 全量驗證」兩道閘。PR 層負責快速回饋,不取代發版前驗證。

自託管 Runner 一定要先買 Mac mini 嗎?

不必。脈衝負載可先按天租賃,再看全年利用率決定是否採購常駐節點。先量再買,風險最低。

多台 Runner 並行會互踩快取嗎?

會,如果共用全域 DerivedData 路徑。建議按 Job 切子目錄,必要時用 flock 保護 pod install 區段,並設磁碟水位告警。

Xcode 27 Agent 會取代 Mac CI 嗎?

不會取代。Agent 擅長本地開發與測試編排;Archive、簽章、上架流程仍需在可控 macOS 節點完成。

burst 機器和常駐機環境不一致怎麼辦?

以同版本 Xcode 與同一套初始化腳本(Ansible/shell)統一環境;burst 機只新增 label,不允許「手工特殊配置」。

還值得保留 GitHub 託管 macOS runner 嗎?

值得作為低頻任務兜底(例如每月一次合規掃描)。高頻 Archive 不建議長期押在共享池上。

總結與延伸閱讀

GitHub Actions 不給力,本質是共享 macOS 池追不上 iOS 發版節奏;iOS 流水線新玩法,本質是把快檢、重建置、彈性併發拆開治理。2026 年更值得做的不是急著換 CI 平台,而是在 Actions 編排之上換掉 macOS 執行策略:混合 Job、建置島、發版 burst,按團隊節奏組合就行。