クラウドMac CIのスリープを制御して無人ビルドを安定させる

クラウドMac CIのスリープを制御して無人ビルドを安定させる

夜間のアーカイブが途中で止まり、翌朝にはタイムアウトの記録しか残っていない。リモートデスクトップで接続し直すと、Mac自体は問題なく操作できる。このような障害を、すぐにネットワークの問題と判断してはいけません。クラウドMacで無人CIを運用するには、まずシステムスリープ、ディスプレイスリープ、リモートセッションの終了、Runnerプロセスの終了を切り分ける必要があります。見た目は似ていても、修正すべき箇所はそれぞれ異なります。

中断がどのレイヤーで発生したかを確認する

設定を変更する前に、失敗したジョブの最終出力、終了コード、ホストの起動時刻を記録します。ビルドコマンドの途中でログが突然途切れていても、システムの起動時刻が変わっていなければ、スリープとプロセスのライフサイクルを引き続き調査します。起動時刻が変わっている場合は再起動イベントとして調査し、スリープ防止設定で問題を覆い隠してはいけません。

症状 優先して確認する項目 よくある結論
画面は消えるが、SSHとビルドは正常 displaysleep ディスプレイ出力だけがスリープしている
SSHとジョブが同時に応答しなくなる sleep、電源ログ システムがスリープした可能性がある
ターミナルを閉じるとジョブが終了する Runnerの起動方法 プロセスが対話セッションに依存している
ホストの起動時刻が変わっている 再起動履歴、ジョブログ システムが再起動した

最初に、システムへ変更を加えずベースラインを収集します。

mkdir -p "$HOME/ci-audit"
date > "$HOME/ci-audit/power-baseline.txt"
pmset -g custom >> "$HOME/ci-audit/power-baseline.txt"
pmset -g assertions >> "$HOME/ci-audit/power-baseline.txt"
pmset -g sched >> "$HOME/ci-audit/power-baseline.txt"
sysctl -n kern.boottime >> "$HOME/ci-audit/power-baseline.txt"

pmset -g customでは電源供給条件ごとの設定、pmset -g assertionsでは現在スリープを防止しているプロセス、pmset -g schedでは既存のスケジュールイベントを確認できます。sleep 0という1行だけを抜き出して判断してはいけません。電源プロファイルと、実際に有効なアサーションの両方を確認する必要があります。

ディスプレイスリープとシステムスリープを分けて考える

Mac miniにはバッテリーがないため、主にAC電源接続時の設定を確認します。ディスプレイスリープでコンパイルが止まることはなく、CIのために仮想ディスプレイ出力を常時点灯させる必要もありません。無人ジョブに実際に影響するのは、システムがスリープするかどうかと、Runnerが有効なプロセスとして動作し続けるかどうかです。

継続的なビルド専用のノードでは、元の出力を保存したうえで、AC電源接続時のシステムスリープを無効にできます。

sudo pmset -c sleep 0 disksleep 0 powernap 0
pmset -g custom

ここではdisplaysleepを変更していません。画面が消えるかどうかは、バックグラウンドビルドとは無関係だからです。コマンドが正常終了しただけで設定変更が完了したと判断せず、実行後に設定を再取得して確認してください。人がリモートデスクトップを利用するMacでもある場合は、チームの利用時間帯を先に検討します。CIの実行頻度が低いなら、システムポリシーを恒久的に変更するよりも、ジョブの実行中だけスリープを防止する方法が適しています。

電源設定で制御できるのは、Macを稼働状態に保てるかどうかです。ターミナルに依存する一時プロセスを、自動的にバックグラウンドサービスへ変えることはできません。

caffeinateで単一のビルドをラップする

caffeinateを使うと、子プロセスが存続している間だけ電源アサーションを作成できます。システム全体でスリープを無効にする方法より影響範囲が明確で、定期アーカイブ、長時間テスト、一度限りの依存関係ビルドに特に適しています。

共通のラッパースクリプトを作成します。

sudo install -d -m 0755 /usr/local/bin
cat <<'EOF' | sudo tee /usr/local/bin/ci-awake >/dev/null
#!/bin/zsh
set -euo pipefail
if (( $# == 0 )); then
  exit 64
fi
exec /usr/bin/caffeinate -ims "$@"
EOF
sudo chmod 0755 /usr/local/bin/ci-awake

Runner側でさらにバックグラウンド実行記号を追加せず、ラッパースクリプトに対象コマンドを直接引き継がせます。

/usr/local/bin/ci-awake /usr/bin/xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  build

-iはアイドル時のシステムスリープを防止し、-mはディスクのアイドルスリープを防止し、-sはAC電源接続中のシステムスリープを防止します。対象コマンドが終了すると、ラッパープロセスとともにアサーションも解除されます。ビルド中にpmset -g assertionsを実行し、該当するアサーションが出力されていることを確認できます。

Runnerをリモートセッションから切り離す

SSHやグラフィカルセッションを閉じたときにビルドが終了する場合、確認すべき中心はpmsetではなくRunnerの親プロセスです。ターミナルから手動で起動したプロセスは、セッション終了シグナルを受け取る可能性があります。また、作業ディレクトリ、環境変数、一時的なキーチェーンコンテキストが失われることで失敗する場合もあります。

常時稼働させるRunnerはlaunchdで管理します。少なくとも次の項目を確認してください。

  1. local.ci.runnerのような固定のサービスラベルを使用し、ログインするたびに重複起動しないようにする。
  2. 作業ディレクトリには絶対パスを使用し、ターミナル起動時のカレントディレクトリに依存しないようにする。
  3. PATH、ツールチェーンの選択、キャッシュディレクトリを明示的に設定し、対話型shellの設定を継承しないようにする。
  4. 標準出力と標準エラーを別々のログファイルへ書き込み、制御可能なローテーションポリシーを設定する。
  5. Runnerが終了シグナルを受け取ったときに実行中の子プロセスも終了させ、ビルドディレクトリを占有し続ける孤児プロセスを残さないようにする。

ユーザー単位のサービスは、次のコマンドで確認できます。

launchctl print "gui/$(id -u)/local.ci.runner"
pgrep -afil 'runner|xcodebuild'

ユーザーがログインしていない状態でもサービスを動かす必要がある場合は、システム単位のLaunchDaemonを使用し、launchctl print system/local.ci.runnerで確認します。ユーザー単位とシステム単位のインスタンスを同時に残してはいけません。2つのRunnerが同じ作業ディレクトリを取り合う可能性があります。

証拠に基づいて本番運用を検証する

設定が完了したら、以前に中断が発生した時間を超えて動作するテストジョブを実行します。テスト中はリモートデスクトップとSSHクライアントを意図的に閉じますが、Runnerは停止しないでください。再接続後に、ジョブの終了コード、成果物の時刻、サービスプロセス、電源アサーションを確認します。

再び中断した場合は、すぐに次の情報を収集します。

date
pmset -g assertions
pmset -g log | tail -n 120
sysctl -n kern.boottime
last reboot | head

最終的な受け入れ条件は4つです。ディスプレイスリープがジョブに影響しないこと、リモートセッションを閉じてもRunnerが動作し続けること、ビルド中にcaffeinateのアサーションを確認できること、ジョブ終了後にアサーションが自動的に解除されることです。いずれかを満たさない場合は、対応するレイヤーに戻って対処し、電源設定をむやみに追加し続けないでください。

XcodeVM上の専有クラウドMacで安定した無人CIを実現するには、2つの役割を明確に分ける必要があります。システムの電源ポリシーはノードを稼働状態に保ち、launchdとジョブラッパーはビルドプロセスを人の操作するセッションから切り離します。両者を分けて設定し、それぞれの証拠を収集できるようにしておけば、次回の中断時には曖昧なタイムアウト記録ではなく、原因を特定できる情報が残ります。

よくある質問

ディスプレイのスリープを無効にすればシステムも停止しませんか?

いいえ。displaysleepは画面出力だけを制御します。システムのsleep値と現在有効な電源アサーションをpmsetで個別に確認する必要があります。

CI用Macではスリープを常に無効にすべきですか?

継続的なCI専用の物理ノードなら、元の設定を保存したうえで無効化できます。実行頻度が低い場合はcaffeinateでジョブの実行中だけ抑止する方が明確です。

リモート接続を閉じるとビルドが終了するのはなぜですか?

Runnerが対話シェルの子プロセスになっている可能性があります。launchdの独立サービスとして起動し、終了コードと電源ログも確認してください。

XcodeVM クラウドMac

現在のワークロードに適した専有物理マシンを選択

メモリ、ストレージ、ノード、課金期間を確認してから注文設定に進みます。すべてのノードは365日継続稼働しています。実際の可用性はコンソールのリアルタイム表示をご確認ください。

プランを選んでレンタルする