當 CI 控制端持續在雲端 Mac 上執行版本檢查、擷取程式碼、啟動建置及收集產出時,如果每一條命令都建立新的 SSH 連線,就會重複進行公開金鑰協商、驗證與工作階段初始化。單次延遲未必明顯,但串接數十條短命令後,握手時間便會占據可觀比例。SSH 內建的 ControlMaster 可讓後續命令重複使用已驗證的 TCP 連線,但通訊端隔離、失效偵測與結束清理也必須一併妥善處理。
先確認最佳化目標
連線複用適合「同一個 CI 任務在幾分鐘內反覆存取同一台 Mac」的情境。如果任務只執行一次耗時較長的建置,最佳化握手的效益有限;如果控制端會依序執行十多條短命令,複用通常更有價值。
先記錄未啟用複用時的連線過程:
time ssh -o BatchMode=yes build-mac 'sw_vers -productVersion'
ssh -vv -o BatchMode=yes build-mac 'true' 2>ssh-debug.log
檢查偵錯記錄中是否反覆出現金鑰交換與驗證階段。不要只比較單次命令可能受偶發因素影響的耗時;至少應連續執行多次,並分別衡量「建立連線」與「執行遠端命令」所花費的時間。
ControlMaster 最佳化的是建立連線的成本,不會加快 Xcode 編譯、相依性解析或檔案傳輸本身。
建立最小複用設定
在 CI 控制端的 ~/.ssh/config 中,為建置主機設定專用別名。%C 會對連線參數進行雜湊,避免使用者名稱與主機名稱過長,導致 Unix Socket 路徑建立失敗。
Host build-mac
HostName mac.example.internal
User ci-runner
BatchMode yes
ControlMaster auto
ControlPersist 10m
ControlPath ~/.ssh/control/%C
ConnectTimeout 15
ServerAliveInterval 30
ServerAliveCountMax 3
StrictHostKeyChecking yes
UserKnownHostsFile ~/.ssh/known_hosts_ci
準備目錄時,務必限制其權限:
install -d -m 700 "$HOME/.ssh/control"
chmod 600 "$HOME/.ssh/known_hosts_ci"
ssh build-mac 'printf "%s\n" ready'
ssh -O check build-mac
第一條命令會建立主連線,ssh -O check 應回傳主程序的狀態。ControlPersist 10m 表示最後一個工作階段結束後,主連線最多再保留十分鐘,並不代表任務可以無限期依賴該連線。
固定主機身分
不要為了方便而設定 StrictHostKeyChecking=no。應透過受控管道取得主機公開金鑰,寫入 CI 專用的 known_hosts,並在節點重建或金鑰輪替時遵循明確的更新流程。連線複用只會減少後續握手次數,不會改變首次連線的信任邊界。
為平行任務隔離通訊端
共用 Runner 最常見的問題,是所有任務都使用 ~/.ssh/control/%C。如果兩條管線同時連線至相同主機,就可能複用對方建立的主連線;其中一個任務執行退出操作時,也會中斷另一個任務。
更穩妥的做法,是為每個任務建立獨立目錄,並透過命令列覆寫 ControlPath:
set -euo pipefail
job_id="${CI_JOB_ID:-local-$$}"
control_dir="${TMPDIR:-/tmp}/ssh-control-${job_id}"
install -d -m 700 "$control_dir"
ssh_opts=(
-o ControlMaster=auto
-o ControlPersist=10m
-o "ControlPath=${control_dir}/%C"
-o BatchMode=yes
)
ssh "${ssh_opts[@]}" build-mac 'xcodebuild -version'
ssh "${ssh_opts[@]}" build-mac 'git --version'
任務識別碼必須取自可信任的管線上下文,並限制為較短的字串。不要直接將分支名稱串接到路徑中,因為斜線、空格或過長的名稱都可能導致通訊端建立失敗。
偵測失效連線並安全重建
網路切換、遠端重新啟動或控制程序異常退出後,通訊端檔案可能仍然存在,但其對應連線已無法使用。執行業務命令前應先檢查連線;如果檢查失敗,請刪除目前任務自己的通訊端目錄,再重新建立連線。
if ! ssh "${ssh_opts[@]}" -O check build-mac >/dev/null 2>&1; then
rm -rf "$control_dir"
install -d -m 700 "$control_dir"
ssh "${ssh_opts[@]}" build-mac 'true'
fi
此處只能刪除目前任務所建立的目錄,不能使用 rm -rf ~/.ssh/control/* 清理共用目錄。也要避免無條件重試建置命令:連線中斷時,遠端命令可能早已啟動。安全的做法是只自動重試具等冪性的檢查,再透過程序、鎖定檔或建置編號確認原始任務的狀態。
留意伺服器端的工作階段上限
一條主連線可以承載多個邏輯工作階段,但數量並非沒有限制。平行命令過多時,伺服器端可能拒絕新的 channel。應先限制單一任務的 SSH 平行處理量,再評估是否需要調整伺服器端政策,而不要將提高工作階段上限視為預設的修正方式。
在退出階段進行確定性清理
正常結束時,使用控制命令關閉主連線;即使異常退出,也仍須刪除任務目錄。Shell 指令碼可以透過 trap 統一處理:
cleanup() {
ssh "${ssh_opts[@]}" -O exit build-mac >/dev/null 2>&1 || true
rm -rf "$control_dir"
}
trap cleanup EXIT INT TERM
最終驗收應涵蓋四個項目:連續命令確實使用同一條主連線;兩個平行任務使用不同目錄;遠端重新啟動後能夠重建連線;任務取消後不會留下通訊端。SetMini 上的自動化任務也應遵循相同原則:在控制台確認目前可選的設定後,將連線層視為可偵測、可重建、可清理的基礎設施,而不是永遠不會失效的隱藏狀態。
常見問題
ControlPersist 設定得越久越好嗎?
不是。CI 通常可先設定為 5 至 15 分鐘,只需涵蓋同一工作內的連續命令;保留過久會增加陳舊通訊端與跨工作誤用的風險。
多個並行工作可以共用同一個 ControlPath 嗎?
不建議。每個流水線工作應建立權限為 700 的獨立通訊端目錄,結束時執行 ssh -O exit,再刪除該目錄。
連線複用會略過 SSH 主機金鑰驗證嗎?
不會。建立主連線時仍應啟用 StrictHostKeyChecking=yes,並透過受控的 known_hosts 檔案固定目標主機身分。
將工程步驟放進隨時可連線的雲端 Mac
從兩種 Apple Silicon 配置與四個資料中心中選擇,資源不與其他租戶共享;實際可用狀態以控制台即時回傳為準。