Xcodeホスト型ビルドと自主管理クラウドMacの選び方
チームにはすでに稼働中のiOSパイプラインがあっても、依存関係の追加、Xcodeの切り替え、署名設定の変更があるたびに、同じ議論が繰り返されます。ホスト型ビルドを使い続けるべきか、それとも処理を自主管理クラウドMacへ移すべきか、という問題です。まず比較すべきなのは、1回のビルドが何秒短縮されたかではありません。長期的なコストを左右するのは、環境を再現できるか、キャッシュを制御できるか、失敗時の状態を保存できるか、そしてエンジニアがどれだけの運用作業を担う必要があるかです。
まずワークロードを3種類に分類する
印象だけでプラットフォームを選ばず、最初に直近2週間のジョブを集計します。マージチェック、リリース用アーカイブ、一時的な診断の3種類に分けて記録するのが有効です。マージチェックは通常、実行時間が短く、入力が明確で、失敗しても再実行できます。リリース用アーカイブでは、署名情報、成果物の保存、厳密なバージョン固定が必要です。一時的な診断では、エンジニアが同じ環境に入り、問題を再現できなければなりません。
少なくとも次のデータを記録します。
| 項目 | 記録する内容 | 判断の目的 |
|---|---|---|
| ツールチェーン | macOS、Xcode、Ruby、パッケージマネージャーのバージョン | 環境固定の難易度を判断する |
| 入力 | コミット番号、ロックファイル、ビルドパラメーター | ジョブを再現できるか判断する |
| 状態 | キュー待ち、実行、アップロード、クリーンアップの所要時間 | 真のボトルネックを特定する |
| 失敗時の情報 | ログ、result bundle、アーカイブ成果物 | 障害調査に必要な情報が残るか判断する |
| キャッシュ | パス、サイズ、ヒット条件、無効化方法 | 長期的な効果を判断する |
1回成功しただけでは、プロセスが安定しているとはいえません。同じコミットを少なくとも3回連続で実行し、その後、依存関係には触れずアプリケーションコードだけを変更したコミットでキャッシュの挙動を再検証します。
共通の環境フィンガープリントを作成する
どちらの方式でも、同じ環境確認スクリプトを使用します。xcodebuild -versionだけを出力するのではなく、システムのバージョン、現在のDeveloperディレクトリ、SDK、Ruby、パッケージマネージャーの状態も記録してください。フィンガープリントファイルは各ジョブの通常の成果物として保存し、失敗時にはアプリケーションコードを調べる前に、まずその内容を比較します。
#!/bin/zsh
set -euo pipefail
mkdir -p artifacts
{
sw_vers
uname -m
xcodebuild -version
xcode-select -p
xcrun --sdk iphoneos --show-sdk-version
ruby --version
git --version
} > artifacts/environment.txt
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-derivedDataPath "$PWD/.build/DerivedData" \
-resultBundlePath "$PWD/artifacts/App.xcresult" \
build
DEVELOPER_DIRに、未確認のパスを指定してはいけません。自主管理環境には複数のXcodeを保持できますが、パイプライン側で使用するバージョンを明示的に選択する必要があります。ホスト型環境では、ジョブの開始時に実際のバージョンを検証し、一致しなければ早い段階で終了させます。これにより、アーカイブ段階まで処理が進んでからSDKの差異が判明する事態を避けられます。
キャッシュ、署名、並行実行を個別に検証する
ディレクトリ全体をキャッシュとして移動しない
キャッシュキーには、少なくともXcodeのメジャーバージョン、アーキテクチャ、依存関係ロックファイルのハッシュを含めます。SwiftPM、CocoaPods、ビルド中間ディレクトリは、無効化の条件が異なるため、別々に管理する必要があります。ユーザーディレクトリ全体をまとめて復元してはいけません。古い権限、作業用ファイル、表面からは見えない設定まで新しいジョブへ持ち込むことになります。
自主管理クラウドMacは、大規模な依存関係キャッシュを継続的に保持する必要があるパイプラインに適しています。ただし、リポジトリごとに独立した作業ディレクトリを用意し、キャッシュを削除しても完全なビルドが成功することを定期的に検証しなければなりません。ホスト型ビルドでは、通常、キャッシュのライフサイクルをプラットフォーム側が制御します。したがって、キャッシュは成功の前提条件ではなく、最適化手段として扱うべきです。
署名情報はジョブの実行中だけ参照可能にする
署名情報は、ジョブ開始時に管理されたストレージから注入し、完了後すぐに一時コピーを削除します。ログにパスワード、鍵の内容、完全なファイルパスを出力してはいけません。どちらの方式でも、成功時だけでなく失敗時の分岐でもクリーンアップが実行されることを検証します。
並行実行のテストは2つのジョブから始め、DerivedData、シミュレーター、Keychain、固定ポートを共有していないか確認します。共有された書き込み可能な状態が1つでもあれば、並行数の増加によって散発的な障害が増幅される可能性があります。
障害訓練で復旧可能性を検証する
実際のリリースが失敗してから復旧手順を検証するのでは遅すぎます。破壊的な影響のない訓練を4つ用意できます。存在しないschemeを意図的に指定する、依存関係キャッシュを1つ削除する、テストケースを失敗させる、アーカイブ前にジョブを終了する、という方法です。毎回、終了コード、ログ末尾、result bundle、一時的な署名情報、作業ディレクトリの状態を確認します。
ホスト型ビルドでは、失敗を説明するのに十分なログがあるか、環境を再現できるか、再実行時の入力が完全に一致しているかを重点的に確認します。自主管理クラウドMacではさらに、残存プロセス、マウントされたディレクトリ、ポートが次のジョブを汚染しないことも確認しなければなりません。ジョブごとに一意のディレクトリを使用し、終了フックで子プロセスを回収することを推奨します。
workdir="$(mktemp -d "$PWD/.job.XXXXXX")"
cleanup() {
jobs -p | xargs -r kill 2>/dev/null || true
rm -rf "$workdir"
}
trap cleanup EXIT INT TERM
クリーンアップスクリプトは、範囲が広すぎるワイルドカードによって他のジョブへ影響を与えないよう、最初にテスト用ディレクトリで検証する必要があります。保存が必要なログとビルド成果物は、先に独立したartifactsディレクトリへコピーしてからクリーンアップを実行します。
宣伝文句ではなく制御範囲で判断する
標準的な依存関係を使用しており、ジョブが短く、失敗時にはそのまま再実行でき、チームがマシンの状態を管理したくない場合は、通常、ホスト型ビルドが適しています。複数のXcodeバージョンを固定する、キャッシュを長期的に再利用する、特殊なツールを実行する、実行環境に入って診断する、長時間のジョブを継続して動かすといった要件がある場合は、自主管理クラウドMacのほうが明確な境界を構築しやすくなります。
両者を分担して利用することもできます。マージリクエストでは軽量なテストを実行し、安定ブランチでは自主管理環境を使ってアーカイブと詳細な診断を行う構成です。重要なのは、両方の環境で同じ環境フィンガープリント、ロックファイル、ビルドエントリーポイント、成果物の命名規則を共有することです。互換性のない2本のパイプラインを作ってはいけません。
最終評価では、最速だった1回ではなく、複数回実行した際の成功率、環境差異の数、キャッシュ削除後の復旧可能性、失敗時に残る証拠の完全性、人手で介入する手順の数を確認します。結果はリポジトリ内の意思決定記録に残し、Xcodeのメジャーバージョン、依存関係の仕組み、ジョブ規模が変わった際には再評価することをあらかじめ決めておきます。そうすることで、最初の選択が永続的な前提になることを防げます。
よくある質問
ホスト型ビルドは必ず運用が簡単ですか?
必ずしもそうではありません。標準化された案件には向きますが、Xcode固定、永続キャッシュ、特殊なツール、詳細な障害調査には自主管理環境が適します。
二つのビルド方式を併用できますか?
可能です。マージ検査をホスト型で行い、安定したキャッシュ、複雑な署名、完全なログが必要なアーカイブを自主管理クラウドMacへ分けられます。
選定前に最優先で確認する項目は何ですか?
同じコミット、ロックファイル、コマンドを複数回実行し、成功率、環境差分、キャッシュ、ログの完全性、失敗後の復旧時間を比較します。
1回の注文で専用利用できる物理Mac miniが必要ですか?
M4とM4 Proの2構成、4つのレンタル期間、5つの選択可能なノードを確認し、ワークフローに合ったデバイスを選びましょう。