2026年獨佔遠端 Mac CI:並行 Job 下的鑰匙圈爭用與 codesign 工作階段鎖——分使用者/分鑰匙圈分區與 security/codesign 可復現 SSH 決策矩陣 + FAQ
在獨佔節點上把並行度從 1 拉到 N 時,最先暴露的往往不是 CPU,而是鑰匙圈與工作階段語意:codesign、公證與 security 工具鏈共用同一套憑證儲存與解鎖模型,SSH 無介面工作階段又與 GUI 登入狀態不一致。本文用工程語言對齊「分使用者」與「分鑰匙圈檔案」兩類隔離邊界,並給出可寫入 Runbook 的決策矩陣與排錯清單。
1. 你會先看到什麼現象?
典型表現包括:同一時段僅部分 Job 失敗、重試即過;codesign 報找不到身分或私鑰;errSecInternalComponent/鑰匙圈存取被拒;公證或 notarytool 在流水線 A 成功、流水線 B 間歇失敗。它們往往與「多程序同時碰觸同一把鎖或同一鑰匙圈狀態」同時出現,而不是單純的 Xcode 版本問題。
2. 根因:鑰匙圈、工作階段與「會話語意」差異
macOS 上簽章相關的私鑰與憑證通常存放在使用者鑰匙圈中;login.keychain-db 的解鎖與預設鑰匙圈搜尋路徑,會隨登入工作階段類型變化。透過 SSH 執行的非互動式 shell,往往不等價於已在圖形介面解鎖過的工作階段;而多個並行 Job 若共用同一 OS 使用者、同一預設鑰匙圈、同一套環境變數,就會在「解鎖、搜尋路徑、快取」上產生爭用。
codesign 在存取鑰匙圈項目時,需要能解析到正確的簽章身分與私鑰;若此時鑰匙圈處於半解鎖狀態,或搜尋列表(login/自訂)與建置指令稿假設不一致,就會表現為難以穩定復現的失敗。工程上常將這種「必須序列化或透過分區消除共享」的約束稱為工作階段鎖:不是某個全域互斥量文件,而是憑證與工作階段綁定帶來的事實序列需求。
2.1 報錯 → 可能方向(快速分診)
| 現象或報錯線索 | 優先懷疑 |
|---|---|
| 僅高並行時出現,降並行即消失 | 同使用者/同鑰匙圈爭用;考慮分使用者或簽章佇列 |
| SSH 下失敗,本機 Terminal 成功 | 工作階段與鑰匙圈解鎖路徑不一致;核對 security list-keychains 與預設鑰匙圈 |
| 找不到簽章身分 | 鑰匙圈搜尋列表未包含自訂鑰匙圈;或 codesign 未指定 --keychain |
| 鑰匙圈項目存在但仍拒絕存取 | 項目存取控制(ACL)未包含目前程序/使用者;或鑰匙圈未解鎖 |
3. 兩條隔離軸:分使用者 vs 分鑰匙圈分區
分使用者(多 macOS 帳號):每個 Runner 或每類流水線使用獨立系統使用者,各自擁有獨立的 ~/Library/Keychains 與登入語意。優點是邊界最清晰,適合「真並行」;缺點是帳號與權限治理成本高。
分鑰匙圈分區(單使用者多鑰匙圈檔案):為每條業務線或每個 Repo 準備獨立 .keychain-db,用 security list-keychains -d user/security default-keychain 在 Job 開頭明確設定搜尋順序,並在 codesign 上使用 --keychain 指向該檔案。優點是改動面小於多帳號;缺點是所有 Job 仍共享同一 UID,若指令稿寫壞全域鑰匙圈列表,仍會互相踩踏,需要嚴格的前置/後置還原。
與編譯快取分區類似,獨佔節點上的「簽章可復現」同樣依賴目錄與真源隔離。可延伸閱讀:Xcode 26 遠端 Mac CI 與 Derived Data 策略,以及 遠端 Mac 無頭公證:notarytool、鑰匙圈與 SSH 排錯決策表,便於把「建置產物」與「憑證工作階段」兩條線分開設計。
4. SSH 可復現決策矩陣(security/codesign)
下列矩陣用於在 Runbook 裡做「選路徑」——從最小改動到最強隔離。
| 約束 | 建議策略 | 取捨 |
|---|---|---|
| 可接受簽章序列化 | 在 CI 層用佇列/鎖包裹 codesign+公證步驟 |
吞吐量下降;實作最簡單 |
| 同一使用者、多 Job 真並行 | 每 Job 獨立鑰匙圈檔案+建置前設定 keychain search list+codesign --keychain |
指令稿必須可重入;嚴禁永久改預設鑰匙圈後不還原 |
| 強隔離、長期無人值守 | 每 Runner 獨立系統使用者+launchd 工作階段;SSH 僅作維運入口 | 帳號與憑證分發流程要配套 |
| 必須非互動解鎖 | 鑰匙圈密語透過 CI 機密注入,Job 內 security unlock-keychain -p(搭配最小權限與稽核) |
密語輪換與外洩面需納入威脅模型 |
5. 可復現檢查清單(命令級)
在 SSH 工作階段中,將下列命令納入「失敗時第一條」採集指令稿(注意脫敏後再寫入日誌)。
# 目前使用者與鑰匙圈搜尋路徑 whoami security list-keychains -d user security default-keychain # 解鎖(非互動情境謹慎使用,密語來自金鑰管理) # security unlock-keychain -p "$KEYCHAIN_PASSWORD" /path/to/ci.keychain-db # 驗證簽章身分對建置可見 security find-identity -v -p codesigning # 明確指定鑰匙圈簽章(範例) # codesign --keychain /path/to/ci.keychain-db -s "Developer ID Application: ..." ...
若同一主機上混用 GUI 登入與 SSH 建置,務必在文件中寫明「以哪一類工作階段為真源」,並在 Runner 啟動指令稿裡統一鑰匙圈列表,避免「人在桌面解鎖過、Job 在 SSH 裡找不到」的漂移。
6. FAQ
Q1:為什麼不能只在「鑰匙圈存取」裡點一次「永遠允許」就結束?
圖形介面的授權與無人值守 SSH Job 的程序族不一致;並行 Job 仍可能碰觸不同路徑或 ACL。Runbook 級方案應能在乾淨工作階段裡重複成功,而不是依賴人工點擊。
Q2:codesign 的 --keychain 能替代分使用者嗎?
能緩解很多「搜不到身分」的問題,但不能自動解決所有 OS 級資源爭用。若團隊仍共用一個 UID 且指令稿互相改寫鑰匙圈列表,仍會出現踩踏,需要佇列或帳號級隔離。
Q3:公證(notarytool)也要進矩陣嗎?
需要。notarytool 使用 App Store Connect API 金鑰或 Apple ID 工作階段,應與 codesign 步驟同一套機密與鎖策略,否則會出現「套件已簽但公證間歇失敗」的假陽性。
Q4:獨佔節點上還要避免什麼?
避免多個 Job 同時修改全域 ~/Library/Keychains 中繼資料而不還原;避免把開發者個人登入鑰匙圈與 CI 鑰匙圈混在同一預設搜尋堆疊頂而不加註解。
在獨佔遠端 Mac mini 上,把簽章與並行一起寫進「可維運」
鑰匙圈與 codesign 的問題,本質是長期穩定工作階段+清楚使用者邊界的工程題。Mac mini 上的 macOS 與 Apple Silicon 組合,能把 Xcode 工具鏈、security 命令列與公證流程放在同一套官方支援路徑裡跑通;Apple Silicon 統一記憶體與極低待機功耗,適合作為獨佔 CI 節點 7×24 小時上線,而 Gatekeeper、SIP、FileVault 與系統級鑰匙圈模型疊加,使憑證治理與實體機託管場景天然契合。
若你希望並行 Job 在「不互相踩鑰匙圈」的前提下盡量吃滿 CPU,一台穩定、靜音、長期在線的 Mac mini M4 往往是比拼湊虛擬機更省心的底座;現在即可從首頁瞭解方案,把本文矩陣落進你的 Runbook。