CIジョブでは、インストーラーパッケージの構成確認、テストサンプルの読み取り、署名後の配布ディレクトリの検証、2つのディスクイメージの内容比較などでDMGをマウントすることがよくあります。単発の手動実行なら通常は問題になりません。実際に障害が起きるのは、ジョブが並列実行されるとき、スクリプトが異常終了したとき、または前回のジョブがマウント済みボリュームを残したときです。この状態で /Volumes/Product を固定参照するスクリプトを実行すると、即座にエラーになるのではなく、別のジョブの内容を誤って読み取る可能性があります。
デフォルトのマウントが並列実行で破綻する理由
hdiutil attach App.dmg は、イメージ内のボリュームラベルに基づいてマウント名を決定します。同名のボリュームがすでに存在する場合、macOSは番号付きの新しいパスを作成することがあります。それでもスクリプトが元の固定ディレクトリへアクセスし続けると、テスト結果の信頼性が失われます。
もう1つの典型的な問題は、成功時の処理でしか detach を実行しないことです。検証コマンドの失敗、ジョブの停止、後続ステップからの早期リターンのいずれかが発生すると、クリーンアップコマンドは実行されません。長期間再利用されるクラウドMacノードには残留ボリュームが徐々に蓄積し、ボリュームラベルの衝突、ファイルの使用中エラー、作業ディレクトリの誤判定につながります。
マウントポイントはマシン上の共有パスではなく、各ジョブが所有する一時リソースとして扱う必要があります。作成、使用、アンマウント、診断までを同じジョブが担当すべきです。
改善を始める前に、現在の状態を記録します。
/usr/bin/hdiutil info
/bin/df -h
/bin/ls -la /Volumes
hdiutil info では、イメージ、デバイス、マウントパスの対応関係を確認できます。df で分かるのはファイルシステムの使用状況だけであり、イメージとの対応関係の確認には代用できません。
ジョブごとに一意のマウントポイントを割り当てる
マウントディレクトリには、安定していて一意なジョブ識別子を含めます。同じリポジトリでも複数のブランチや再試行ジョブが同時に実行される可能性があるため、リポジトリ名だけでは不十分です。また、識別子に含まれるスラッシュや空白など、パスとして問題になる文字も置換する必要があります。
次のスクリプトは、最初にイメージを検証してから読み取り専用でマウントし、正常終了、コマンド失敗、終了シグナル受信のいずれの場合もアンマウントを試みます。
#!/bin/bash
set -euo pipefail
IMAGE="${1:?usage: mount-image.sh path/to/file.dmg}"
RAW_JOB_ID="${CI_JOB_ID:-local-$$}"
JOB_ID="${RAW_JOB_ID//[^a-zA-Z0-9._-]/_}"
MOUNT="${TMPDIR%/}/sdkmac-image-${JOB_ID}"
ATTACHED=0
cleanup() {
local status=$?
trap - EXIT INT TERM
if (( ATTACHED == 1 )); then
if ! /usr/bin/hdiutil detach "$MOUNT"; then
printf 'unable to detach %s\n' "$MOUNT" >&2
fi
fi
/bin/rmdir "$MOUNT" 2>/dev/null || true
exit "$status"
}
trap cleanup EXIT INT TERM
/usr/bin/hdiutil verify "$IMAGE"
/bin/mkdir -p "$MOUNT"
/usr/bin/hdiutil attach \
-nobrowse \
-readonly \
-mountpoint "$MOUNT" \
"$IMAGE"
ATTACHED=1
test -r "$MOUNT"
/usr/bin/find "$MOUNT" -maxdepth 2 -type f -print
-nobrowse は、マウントしたボリュームが通常のGUIブラウズ対象になるのを防ぎます。-readonly は、確認処理によってサンプルが意図せず書き換えられるのを防ぎます。また、このスクリプトでは rm -rf ではなく rmdir を使用しています。アンマウントに失敗しても、rmdir ならマウントされたままのディレクトリ内に入り込んで内容を削除することがありません。
読み取り専用が適さないケースもある
書き込み動作をテストする場合でも、共有の基準イメージをそのまま書き込み可能にしてはいけません。まず現在のジョブ用にイメージをコピーし、そのコピーがジョブ専用の一時ディレクトリにあることを確認してからマウントします。これにより、失敗したジョブが次回実行時の基準入力を汚染するのを防げます。
| 使用目的 | 推奨モード | 主な確認項目 |
|---|---|---|
| インストーラーパッケージやサンプルの確認 | 読み取り専用マウント | ファイルの存在、権限、ダイジェスト |
| 書き込み処理の検証 | ジョブ専用の書き込み可能コピー | 書き込み結果とアンマウントの完全性 |
| 2つのイメージの比較 | 独立した2つの読み取り専用マウントポイント | パス名の重複防止と固定された比較順序 |
アンマウント失敗時に調査可能な証拠を残す
アンマウントの失敗は、通常 hdiutil 自体のランダムな不具合ではありません。多くの場合、いずれかのプロセスがボリューム内のディレクトリをカレントディレクトリとして使用しているか、ファイルや作業パスを開いたままにしています。原因を隠すように即座に強制アンマウントするのではなく、まず使用中のプロセスを確認します。
/usr/sbin/lsof +D "$MOUNT" 2>/dev/null || true
/usr/bin/hdiutil info
/bin/ps -axo pid,ppid,command
lsof +D は大規模なディレクトリでは時間がかかる可能性があるため、失敗時の処理でのみ実行します。特に、テストプロセス、ログ収集ツール、圧縮ツール、cd でマウントディレクトリへ移動したまま戻っていないシェルを確認してください。通常は、子プロセスの終了を待ち、ファイルハンドルを閉じ、アンマウント前にジョブの作業ディレクトリへ明示的に戻ることで解決できます。
クリーンアップ関数では、元の終了コードも保持する必要があります。アンマウント失敗によって本来のテストエラーが上書きされると、パイプラインにはクリーンアップ異常しか表示されず、最初の失敗原因が失われます。より確実なのは、テストのステータスとクリーンアップのステータスを別々に保存し、両方をジョブサマリーへ記録する方法です。
残留ボリュームを無条件に削除せず監査する
ノードで新しいジョブを開始する前に、このパイプラインの命名規則に従ったマウントディレクトリを確認できます。ただし、名前が似ているという理由だけで削除してはいけません。まず、そのディレクトリが実際にマウントポイントであることを確認し、対応するジョブがまだ実行中か、イメージが現在のワークスペースに属しているかを照合します。
監査結果は、次の3種類に分けることを推奨します。
- 現在のジョブが所有するボリューム:終了トラップで通常どおりアンマウントします。
- ほかのアクティブなジョブが所有するボリューム:記録してスキップし、ジョブをまたいだクリーンアップは禁止します。
- 対応するアクティブなジョブが見つからない残留ボリューム候補:
hdiutil info、使用中のプロセス、作成時のコンテキストを収集し、確認してからアンマウントします。
強制アンマウントは、人手で確認した後の最終手段に限るべきです。ディレクトリの経過時間だけを基準に自動スクリプトを動かすと、長時間かかっていても正常に実行中のジョブを中断する可能性があります。また、ディレクトリの時刻はマウント時刻と同じではありません。ファイルのコピーやメタデータの読み取りによっても、判定に使う時刻情報が変化する場合があります。
イメージ処理をパイプラインの受け入れ条件に含める
再利用可能なディスクイメージ処理では、少なくとも次の項目を確認する必要があります。
- 入力ファイルが存在し、マウント前に
hdiutil verifyが完了している。 - 各ジョブが独立したディレクトリを使用し、イメージのボリュームラベルに依存してパスを生成しない。
- デフォルトは読み取り専用とし、書き込みが必要な場合はジョブ専用のコピーを作成する。
- すべての終了経路にクリーンアップ用のトラップを設定し、子プロセスも終了シグナルを受信できるようにする。
- アンマウント前にマウントディレクトリから移動し、そのボリュームを読み取っているプロセスの終了を待つ。
- アンマウントに失敗した場合は、デバイス、パス、プロセスの情報を保存し、再帰削除を実行しない。
- ジョブ終了後にマウントポイントが消えていること、および並列実行中のほかのジョブを誤ってクリーンアップしていないことを確認する。
SDKMacの専有物理Macノードでも、この方法は対話的なトラブルシューティングと無人パイプラインの両方に適用できます。重要なのはクリーンアップコマンドを増やすことではなく、マウントした各ボリュームに対して明確な所有者、ライフサイクル、失敗時の証拠を定義することです。これを徹底すれば、マシンの再利用や並列実行によってDMGの確認結果が再現困難になるのを防げます。
よくある質問
CIで既定の/Volumes配下の名前を使わない方がよいのはなぜですか?
並列ジョブが同名のイメージを接続すると、macOSが番号を付加します。固定パスを読むスクリプトは別ジョブの内容を参照する可能性があります。
hdiutil detachが失敗した後にマウント先を削除してもよいですか?
削除してはいけません。占有プロセスを確認して通常の解除を再試行します。マウント中の書き込み可能なパスを再帰削除すると、イメージ内のデータを失う恐れがあります。
CIではDMGを読み取り専用でマウントすべきですか?
パッケージやテスト素材を検査する場合は読み取り専用を既定にします。変更が必要なテストだけ、ジョブ固有の書き込み可能な複製を使用します。
継続的インテグレーションにクラウドMacを選ぶ
SDKMac M4とSDKMac M4 Proの構成、リージョン、4種類の料金サイクルを比較して、注文を作成できます。