你的起點

從 KVMFLUX 租用一台專屬雲端 Mac mini M4,你會拿到真實 Apple 硬體上等同 root 的管理權限:16GB 統一記憶體、256GB SSD、預裝 macOS,可透過 SSH 與 VNC 存取。沒有任何共用,也沒有虛擬化,所以 xcodebuild 看到的是完整的 M4,Secure Enclave 的行為也和你桌上那台一樣。

選一個離你的成果儲存最近的區域,而不是離團隊最近。一個 runner 與 Git 託管和 CDN 通訊的頻率,遠高於任何人真正操作它的頻率——涵蓋新加坡、日本、韓國、香港、美國東部與美國西部

第一步 — 先把 SSH 鎖死,再做別的

機器出廠時開啟了密碼登入,方便你第一次進去。第一次工作階段就該結束這種狀態:先推金鑰,再關密碼。

本機終端機
ssh-keygen -t ed25519 -f ~/.ssh/kvmflux_ci -C "ci-runner"
ssh-copy-id -i ~/.ssh/kvmflux_ci.pub admin@<YOUR_MAC_HOST>
在 Mac 上
sudo tee -a /etc/ssh/sshd_config <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
EOF
sudo launchctl kickstart -k system/com.openssh.sshd

對 CI 節點來說還有兩項設定很關鍵:阻止機器休眠,以及沒有接顯示器時不要讓背景常駐程式暫停。

在 Mac 上
sudo pmset -a sleep 0 displaysleep 0 disksleep 0
sudo systemsetup -setrestartfreeze on

保留 VNC 作為救命通道。有些 Xcode 提示視窗——授權條款提示、模擬器首次執行權限——只會在圖形介面裡出現,你需要一條在 SSH 設定出問題時也能進去的路。

第二步 — 安裝 Xcode 工具鏈

跳過 App Store。xcodes 可以非互動式地安裝指定的 Xcode 版本,正是無頭節點需要的。先裝 Homebrew,再裝工具鏈。

在 Mac 上
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install xcodesorg/made/xcodes aria2
xcodes install 16.2 --experimental-unxip
sudo xcodes select 16.2
sudo xcodebuild -license accept
sudo xcodebuild -runFirstLaunch

確認你實際要建置的目標平台。如果流水線要跑模擬器測試,現在就下載執行環境,別等第一個任務時再下載。

在 Mac 上
xcodebuild -downloadPlatform iOS
xcrun simctl list runtimes
xcodebuild -version

加上行動端流水線常用的工具:fastlane 用於 lane 與簽署,如果倉庫帶二進位資產就裝 git-lfs

在 Mac 上
brew install fastlane git-lfs jq
git lfs install --system

第三步 — 把機器註冊為 runner

GitHub Actions、GitLab 和 Buildkite 的模式都一樣:下載一個代理,給它一個限定範圍的 token,把它當作服務運行。這是 Apple Silicon 上 GitHub Actions 版本的做法。

在 Mac 上
mkdir ~/actions-runner && cd ~/actions-runner
curl -o runner.tar.gz -L \
  https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-osx-arm64-2.321.0.tar.gz
tar xzf runner.tar.gz
./config.sh --url https://github.com/your-org/your-app \
  --token <REGISTRATION_TOKEN> \
  --labels macos,arm64,m4 --unattended
./svc.sh install && ./svc.sh start

svc.sh 安裝會把 runner 接進 launchd,這樣重新啟動後即使沒有登入的桌面工作階段也能繼續存活。用標籤來指定目標:

.github/workflows/ios.yml
jobs:
  build:
    runs-on: [self-hosted, macos, m4]
    steps:
      - uses: actions/checkout@v4
      - run: xcodebuild -scheme App -destination \
          'platform=iOS Simulator,name=iPhone 16' test

註冊 token 一小時後失效,且只能用一次——這部分沒問題。runner 的長期憑證儲存在 runner 目錄裡的 .credentials 檔案中,所以讓這個使用者帳戶保持「無趣」:不放個人 SSH 金鑰,不留瀏覽器工作階段。

第四步 — 給 CI 一個它能自己解鎖的鑰匙圈

簽署是大多數自架 Mac 設定最容易卡住的地方。解決方案是一個專用鑰匙圈,由你的 lane 建立、解鎖、用完即丟——這樣登入鑰匙圈永遠不會跳出提示,也不會在任務之間洩漏。

簽署 lane
KEYCHAIN=ci.keychain-db
security create-keychain -p "$KEYCHAIN_PASS" $KEYCHAIN
security set-keychain-settings -lut 3600 $KEYCHAIN
security unlock-keychain -p "$KEYCHAIN_PASS" $KEYCHAIN
security import dist.p12 -k $KEYCHAIN -P "$P12_PASS" \
  -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: \
  -s -k "$KEYCHAIN_PASS" $KEYCHAIN
security list-keychains -d user -s $KEYCHAIN login.keychain-db

set-key-partition-list 這一行是所有人都容易忘掉的——沒有它,codesign 會一直卡著等一個永遠不會出現的圖形密碼提示。如果你用 fastlane,配合一個私有憑證倉庫的 match 能把這整段全部自動化。

快取:把成果留在磁碟上

專屬機器勝過臨時 runner 的核心原因正是狀態可以持久保留。善用這一點,別每個任務都重新下載全世界:

  • DerivedData — 用 -derivedDataPath ~/ci-cache/dd 指向一個固定路徑,漸進式建置時間通常能從幾分鐘降到幾秒。
  • Swift 套件 — 設定 -clonedSourcePackagesDirPath ~/ci-cache/spm,讓依賴解析在任務之間重複使用已有的檢出內容。
  • CocoaPods 與 Homebrew — 兩者預設都快取在 runner 使用者的主目錄下;只是別在建置之間清空工作區。

誠實估算磁碟用量。256GB SSD 上,Xcode 加執行環境大約要占用 40GB,一個活躍專案的快取會逐漸漲到 60–80GB。按計畫清理,而不是等出問題才清理:

每週排程任務
find ~/ci-cache/dd -maxdepth 1 -mtime +14 -exec rm -rf {} +
xcrun simctl delete unavailable
df -h /

如果專案自帶大體量資產,或需要並存多個 Xcode 版本,額外 SSD +1TB 選配只要 $11.7/月,比到發布時排查磁碟寫滿的問題划算得多。

併發:一台 M4 能扛幾個任務?

一台 16GB 記憶體的 M4 處理一個重任務表現很好:一次完整的乾淨建置加模擬器測試套件能用滿所有核心。同時跑兩個基於模擬器的任務對小應用還行,但記憶體壓力上來後兩個都會掉速。

我們經過幾個月流水線值守後總結的經驗規則:

  • 對建置加測試類工作負載,一個 runner 程序一次跑一個任務,是最穩妥的預設設定。
  • 只有在加了第二台機器之後,才把 lint、單元測試和 UI 測試拆成獨立任務——放在同一台機器上排隊只會帶來額外開銷。
  • 夜間任務是白得的算力:把依賴稽核和截圖批次跑排在該區域時區的非工作時段。

當排隊時間成為瓶頸時,加一台租用節點能線性擴充——用同樣的標籤註冊即可,排程器會自動做負載平衡。按日還是按月節點的算帳是另一個話題,我們在租期成本拆解裡算過;相關工作流程也可以在應用場景總覽裡看到。

整套設定,濃縮版

  1. 僅金鑰登入 SSH,關閉睡眠,保留 VNC 作為備援。
  2. xcodes 安裝 Xcode,接受授權,預先下載執行環境。
  3. 把 runner 註冊為帶有意義標籤的 launchd 服務。
  4. 專用 CI 鑰匙圈,設定好分區清單。
  5. 持久化快取並配合排程清理。

整套操作動手時間不到一個小時,結果是一個團隊不再需要操心的建置節點——這正是重點所在。

在真機上試一下

上面每一條指令都是在我們租出去的這台機器上撰寫並驗證過的。租一台,跟著做,如果不合適就取消——無論哪種結果都不涉及硬體帳單。

Mac Mini M4 · 16GB / 256GB
按日$19.3 /天
按週$52.2 /週
按月$96.7 /月
按季$263 /季

專屬硬體,美元計費,隨時可取消。