シナリオの境界
cron 扇出は、(1) エントリを単一の運用アカウントが管理し、(2) 各ジョブが短く冪等、(3) クリティカル区間を flock と壁時計上限で囲み、(4) 分単位のジッターを許容できるときに妥当です。複数スカッドが投入し、CI と人間が優先度・公平性を争う段階では、cron だけでは「誰が CPU を取っているか」が属人化します。
Nomad 型(HashiCorp Nomad そのものでも、同じ概念のオーケストレータでも可)は、「アーカイブはホストあたり1本」「SwiftPM 解決は2並列まで」のように制約キーごとの最大並列を宣言し、再起動・再配置を HCL 等に残せる点が価値です。Mac は実行端末、スケジューラは退避しやすい OSに置くのが現実的です。境界の線引きはシンプルで、同一の可変ディレクトリをドキュメント化されたロック領域なしに触れる経路が2本あるなら、すでに「キュー+ロック」の設計対象です。
キューモデル対照表
深夜のオンコールがcrontab を12個読まずに状態を復元できるかが、運用コントラクトです。
| 観点 | cron 扇出+ラッパ | Nomad 型の軽量キュー |
|---|---|---|
| 初動遅延 | 周期に依存(多くは60秒)。周期短縮はヘルド増。 | キュー駆動なら p50 は数十秒〜1分台も現実的。 |
| 並行制御 | 手作り:flock/Redis カウンタ等、ドリフトしやすい。 |
宣言的:count・spread・affinity で上限が一箇所。 |
| 先奪・隔離 | 自前シグナルなければ暗黙知に依存。 | 再起動・再スケジュール・優先クラスと割当ログ。 |
| 状態・観測性 | ログ分散、相関 ID が揃いにくい。 | ジョブ履歴、job/alloc ラベルでメトリクス化しやすい。 |
| 典型故障 | 静かな重複+キャッシュ破損。 | 制約モデル誤りによる飢餓(HCL で修正可能)。 |
実行可能な設定要点(コピー用)
cron:単一レーン・非ブロッキング flock 拒否ではなく待ちを使い分け、壁時計は timeout で必ず殺す。
# crontab 断片(buildlane ユーザのみ)
*/1 * * * * buildlane /usr/bin/flock -n /var/run/meshmac/build.lane.lock \
timeout 45m /usr/local/bin/run-ci-dequeue.sh >>/var/log/meshmac/dequeue.log 2>&1
dequeue 先頭で待ち深度を検査:Redis リストや SQLite キュー長が 20 を超えたら exit 77 でページ(クライアントを吊り下げない)。
Nomad:物理アーカイブ1本に対応する periodic には prohibit_overlap = true。実処理は run-ci-dequeue.sh 内で Mac へ SSH/Runner API。
job "meshmac-mac-build" {
datacenters = ["dc1"]
type = "batch"
periodic {
cron = "*/2 * * * *"
prohibit_overlap = true
}
group "lane" {
count = 1
task "dequeue" {
driver = "exec"
config { command = "/usr/local/bin/run-ci-dequeue.sh" }
resources { cpu = 500 memory = 512 }
restart {
attempts = 3
interval = "30m"
delay = "30s"
mode = "delay"
}
}
}
}
権限とロック
スケジューラは並べるだけで、macOS の権限境界は別問題です。非対話ビルドは buildlane 等の専用アカウントに寄せ、コード署名はそのキーチェーンだけに閉じます。root の LaunchDaemon からは sudo -u buildlane で Xcode を起動してください。
- ロック粒度:Pods/npm キャッシュ/シミュレータ枠など共有資源ごとに
.lock。ディスク全体を1ロックにするのは意図的直列化のときだけ。 - メタデータ:ロック旁に
job_id・commit・runner_nameの JSON を置き、オンコールが「まだ誰の所有か」を読めるようにする。 - 先奪後の掃除:割当 kill 時は
trapで部分.xcarchiveとロック解放。スケジューラが再試行してもディスクが孤児で埋まらないようにする。
ビルド衝突の検収チェックリスト
- ☐ 同一チェックアウトでドライラン2本が書き込み競合なし(または flock 区間120秒以内)。
- ☐ CI と手動が同じ dequeue 入口のみ通る(バイパス cron なし)。
- ☐ ロック待ちの中央値が社内 SLA 内、p95 > 15分ならアラート。
- ☐ 実行中ジョブ kill 後にゾンビ flockが残らない(PID 確認と解除手順が Runbook にある)。
- ☐ 夜間アーカイブ5連続後も空き容量 > 15%。満たせなければレーン分離またはノード追加。
タイムアウトと退避パラメータ
以下は出発点です。p95 ビルド時間の実測で上書きしてください。
| パラメータ | cron+シェル基準 | Nomad 型基準 |
|---|---|---|
| ジョブ壁時計 | timeout 45m 軽量、重いアーカイブは 90m。 |
kill_timeout を同水準に。Xcode で 30s 既定は避ける。 |
| ロック待ち | パッケージマネージャは flock -w 180、TO 時はメトリクス付きで失敗。 |
待ちはワーカー側。再起動は delay = "30s" 未満でビジーループにしない。 |
| 再試行退避 | 指数:30s→60s→120s 上限、係数 0.8–1.2 のジッター。 |
reschedule で max_delay = "5m"、delay_function = "exponential"。 |
| 待ち深度上限 | レーンあたり pending 20、拒否は HTTP/exit で意味のあるコードに。 | 投入側+キューで強制(Nomad は SLA を推測しない)。 |
bash 5+ のジッター例:sleep $((30 + RANDOM % 30))。各分岐で attempt・next_backoff_s・reason をログに残し、ダッシュボード検索を単調に保ちます。
FAQ
- Mac 本体に Nomad が無いとダメ?
- いいえ。制御面を Linux に置き Mac を実行端末にするのが一般的です。ブランドより宣言的な重複禁止と履歴が目的です。
- cron の方が簡単なのに、なぜキューが爆発する?
- 導入時の簡単さと、定常運用の簡単さは一致しません。扇出に中央の深度上限がないと隠れバックログが増え、周期を詰めると同じロックへ群がります。
- flock だけで Nomad は不要?
- 直列化には有効ですが、優先度・履歴・複数ホスト間の整合は別レイヤです。第二台目を足すならスケジューラまたは単一の dequeue デーモンに寄せた方が説明が楽です。
cron は単一レーンの合理的ハックになり得ますが、深度・タイムアウト・ロック領域を本番コード同様に管理する前提です。Nomad 型は制約キーと再起動ポリシーをチームの実態に合わせるほど効きます。どちらでも macOS 権限と flock ドメインは省略できません。p95 待ちが検収を超えるなら、無理に並列を上げず多ノード化や共有ビルド専用リソースの追加を検討してください。トップでプールの前提を確認し、購入・プランはログイン不要で比較できます。接続手順はヘルプセンターを参照してください。