Azure Batch などのプール型ジョブ実行サービスを使っていると、ある日突然「ノードが Unusable(使用不可)になってプールから消える」「タスクは Active のまま一向に進まない」という事態に遭遇することがあります。本記事では、実際にアプリケーションファイルの破損が原因で発生した事例をベースに、原因の見つけ方と復旧・再発防止策を、現場ですぐ使えるレベルまで掘り下げて解説します。
現象の概要:ノードが Unusable → プール離脱 → タスクが詰まる
まず、今回のトラブルの典型的な症状を整理します。Azure Batch を例にしていますが、他の「プール型ジョブ実行サービス」でもほぼ同じ構造で発生し得る問題です。
- 自動スケーリングで新しいプールノードが起動する
- ノードは 起動直後に Unusable(使用不可)状態 になる
- 少しするとそのノードは Leaving pool / Left pool(プールから離脱) となり消える
- しかしサービス側に 明確なエラーメッセージが見当たらない
- タスクはすべて Active(実行待ち)のまま で、Running に進まない
つまり、プールとしては「ノードを増やそう」としているのに、起動したノードがすべて不健康判定されて消えてしまい、実行可能なノードが 0 台、タスクだけが Active で積み上がっていく状態です。
| 項目 | 状態 | ポイント |
|---|---|---|
| ノード状態 | Unusable → Leaving pool | 起動後すぐに使用不可判定され、自動的にプールから退役 |
| タスク状態 | Active のまま | 割り当て可能なノードがいないため Running に進まない |
| ポータルのエラー | 目立ったエラーなし | 原因がノード内部(起動タスクやアプリケーション)のことが多い |
結論:原因は破損したアプリケーションファイル
今回のケースでは、プールノードに配布していたアプリケーションファイル(アーティファクト)が途中で破損していたことが根本原因でした。
- ノード起動時、起動タスク(Start Task)がアプリケーションを展開・初期化 しようとする
- しかし、配布元ストレージ上のファイルが破損しており 解凍・実行に失敗
- 起動タスクが 正常終了(exit code = 0)できず、ノードは不健康と判断
- 結果として、ノードは Unusable → プールから離脱 という挙動になる
破損したパッケージを 健全なビルド成果物に差し替え・再配布 したところ、すべてのノードが正常に起動し、Active で滞留していたタスクも自動的に消化されました。
Azure Batch におけるノード状態のイメージ
Azure Batch のノードは、ざっくりと以下のような状態を遷移します(簡略化)。
| 状態 | 意味 | 今回のポイント |
|---|---|---|
| Creating / Starting | VM の作成・OS 起動中 | ここはまだアプリケーションは関与しない |
| Starting(起動タスク実行中) | Start Task を実行して環境準備 | ここで失敗すると Unusable へ |
| Idle / Ready | タスク割り当て可能 | 本来はここまで来てほしい |
| Running | タスク実行中 | ここまで進まない場合はその前のフェーズに問題 |
| Unusable | ノードが正常に動作していないと判断 | 起動タスク失敗やエージェント障害など |
| Leaving pool / Leaving | プールから退役中 | Unusable のノードは自動的にここに移ることがある |
今回のように Unusable → Leaving pool という流れになっている場合、ほぼ確実に「ノード起動直後の何か(起動タスクやエージェント)が失敗している」と考えるのが近道です。
すぐに取るべき対応:実務向けチェックリスト
現場ですぐ試せる「最初の 1〜2 時間でやるべきこと」を、チェックリスト形式でまとめます。
| 優先度 | 対応内容 | 目的 |
|---|---|---|
| 高 | 配布物(アプリケーションパッケージ)の健全性確認 | 破損・差し替えミスがないかを最初に疑う |
| 高 | ノード初期化ログ(起動タスクのログ)確認 | 何が失敗しているのかを具体的に掴む |
| 中 | 影響範囲の切り戻し(ロールバック & ノード再作成) | サービス影響を最小限に抑えつつ復旧 |
| 中 | ヘルスチェックの追加実装 | 次回同様の問題が起きたときに「すぐ気付ける」状態にする |
配布物の健全性確認
まず最優先で、配布しているアプリケーションファイルやコンテナイメージが壊れていないかを確認します。
- ビルド成果物の元ファイル(CI の出力など)
- 配布ストレージに置いている実体(Blob Storage の .zip 等)
- プールノードが 実際にダウンロードしたファイル
これらのハッシュ値(SHA-256 など)を突き合わせると、途中で破損していないかを確認できます。
PowerShell でのチェックサム確認例(Windows ノード)
# ローカルのビルド成果物
Get-FileHash .\app_build.zip -Algorithm SHA256
# Blob Storage からダウンロードしたファイル
Get-FileHash C:\batch\app\app_from_blob.zip -Algorithm SHA256
Linux ノードでのチェックサム確認例
# ローカルビルド成果物側
sha256sum app_build.tar.gz
# ノード側にダウンロードされたファイル
sha256sum /mnt/batch/app/app_from_blob.tar.gz
値が 1 文字でも違えば、どこかのタイミングでビットが変わっています。この時点で配布パッケージを健全版に差し替えることが、もっともシンプルかつ効果的な対処です。
アプリケーションパッケージ/コンテナのバージョンロールバック
直近で以下のような操作をしていないかも確認してください。
- アプリケーションパッケージの最新版を発行した
- 既存のバージョンを上書きしてしまった
- コンテナのタグ
latestを差し替えた
怪しい場合は、直近で正常に動いていた 一つ前のバージョンにピン留めしてロールバック するのが安全です。
| 状況 | 推奨アクション | 注意点 |
|---|---|---|
| latest タグを書き換えた | 安定版タグ(例:v1.2.3)に変更して再デプロイ | latest の上書き運用は極力避ける |
| 既存パッケージを上書きした | 新しいバージョン ID を付けて再アップロード | 「同名で中身が違う」は事故の温床 |
ノード初期化ログの確認(起動タスクログ)
配布物に問題がなさそうでも、起動タスクがどこでコケているのか をログで確認しない限り、原因は特定できません。Azure Batch なら、ポータルや CLI からノードごとのファイルを参照できます。
主に確認すべきログは次のとおりです(実際のパスは構成によって多少異なります)。
| 種別 | 代表的なパス / 取得方法 | 見るべきポイント |
|---|---|---|
| 起動タスク stdout | stdout.txt(StartTask ディレクトリ) | どこまで処理が進んだか、想定通りのメッセージが出ているか |
| 起動タスク stderr | stderr.txt | 解凍エラー、権限エラー、パス誤りなどのエラーメッセージ |
| ノードエージェントログ | Azure Batch Agent のログファイル | 起動タスクの失敗やノードの状態変化の記録 |
| OS イベントログ(Windows) | イベントビューア(Application / System) | .NET 例外、サービス起動失敗など |
特に重要なのは、起動タスクの 終了コード です。正常終了は 0 で、それ以外の値は何らかの失敗を意味します。
- 終了コードが 0 以外なら:起動タスクのスクリプトを詳細にレビュー
- ログ自体が出ていないなら:スクリプトが呼ばれていない/権限不足の可能性
影響の切り戻し:ロールバックとノード再作成
原因のあたりが付いたら、並行してサービス影響の最小化を図ります。
- 破損したバージョン を参照しないよう、プールの設定を 安定版に差し替え
- すでに影響を受けているノードは reimage または delete → 再作成
- Active のタスクは 再キュー するか、別プールに 一時退避
| 操作 | 目的 | 注意点 |
|---|---|---|
| ノードの reimage | OS やローカルディスクを初期状態に戻す | ノードのローカルファイルは失われる |
| ノードの削除・再作成 | 完全に作り直してクリーンな状態に | スケーリング設定と整合性を取る |
| タスクの再キュー | Unusable なノードに割り当てられたタスクを別ノードで実行 | 状態が中途半端なタスクは Idempotent になっているか要確認 |
ヘルスチェックの追加:起動直後に「壊れていないか」を検証
再発防止の観点では、起動タスクの中に簡単なヘルスチェックを仕込んでおくと効果的です。
- 依存ファイルが すべて存在 するか
- 重要なバイナリの ハッシュ値が期待通り か
- 設定ファイルが 正しいフォーマット になっているか
これらをチェックし、問題があれば 明示的なエラーメッセージをログに出したうえで終了コード ≠ 0 を返す と、原因の特定が一気に楽になります。
簡易ヘルスチェック(Linux Bash の例)
#!/bin/bash
set -e
APP_DIR="/mnt/batch/app"
EXPECTED_HASH="xxxxxxxx...(期待する SHA-256)"
if [ ! -f "$APP_DIR/app_main" ]; then
echo "[ERROR] app_main not found in $APP_DIR" >&2
exit 10
fi
ACTUAL_HASH=$(sha256sum "$APP_DIR/app_main" | awk '{print $1}')
if [ "$ACTUAL_HASH" != "$EXPECTED_HASH" ]; then
echo "[ERROR] app_main hash mismatch. expected=$EXPECTED_HASH actual=$ACTUAL_HASH" >&2
exit 11
fi
echo "[INFO] Health check passed."
exit 0
簡易ヘルスチェック(Windows PowerShell の例)
$ErrorActionPreference = "Stop"
$AppPath = "C:\batch\app\app_main.exe"
$ExpectedHash = "xxxxxxxx...(期待する SHA-256)"
if (-not (Test-Path $AppPath)) {
Write-Error "[ERROR] $AppPath not found."
exit 10
}
$actual = (Get-FileHash $AppPath -Algorithm SHA256).Hash
if ($actual -ne $ExpectedHash) {
Write-Error "[ERROR] Hash mismatch. expected=$ExpectedHash actual=$actual"
exit 11
}
Write-Output "[INFO] Health check passed."
exit 0
このようなヘルスチェックを導入しておけば、配布物の破損や差し替えミスが起きたときに、ログを一目見ただけで原因にたどり着ける ようになります。
破損ファイルが原因だった実例の流れ
実際にあったケースを簡略化して追ってみます。
- 新バージョンのアプリケーションをビルドし、.zip を Blob Storage にアップロード
- プールの起動タスクで、その .zip をダウンロードして展開するよう設定
- アップロード中に何らかの要因(ネットワーク切断など)で一部のブロックが欠落
- 一見ファイルサイズはそれっぽく見えるが、内部的に壊れている状態に
- ノード起動後、起動タスクが .zip を解凍しようとして失敗
- 起動タスクが非 0 終了コードで終了 → ノードは Unusable と判定
- 自動スケールが再度ノードを追加するが、同じ壊れたファイルを取りに行くため、ノードが次々に Unusable になる
| 調査ステップ | ログ/事実 | わかったこと |
|---|---|---|
| ポータルでノード状態確認 | 起動後すぐ Unusable → Leaving pool | 起動時の処理に問題がありそう |
| 起動タスクの stderr を確認 | 「unexpected end of file」「archive is corrupted」などのメッセージ | .zip(アプリケーションパッケージ)が破損していると推定 |
| ローカル成果物と Blob のファイルを比較 | SHA-256 が一致しない | アップロード中またはストレージ上での破損が確定 |
| 健全なビルドを再アップロード | ハッシュ一致、起動タスクも成功 | ノードが正常に Ready となり、タスク実行が再開 |
このように、「ノードが Unusable になる」という現象自体はインフラ側の問題のように見えますが、実は アプリケーションパッケージの破損というアプリ側の問題 が根っこにある、ということは少なくありません。
再発防止のベストプラクティス
同じような Azure Batch プールノードの Unusable 問題を繰り返さないために、押さえておきたいベストプラクティスを整理します。
アーティファクトに署名・チェックサムを付与し、CI/CD で検証
- ビルド成果物に対して SHA-256 や署名 を付与
- デプロイ前に、CI/CD パイプラインで アップロードされたファイルのハッシュを自動検証
- 一致しない場合はデプロイを 即座に失敗させる
これにより、「壊れたファイルが本番ストレージに置かれる」事態を事前にブロックできます。
配布ストレージをバージョン管理・イミュータブル化
ストレージの運用方法も重要です。
- 同じファイル名を上書きせず、必ずバージョン付きのパス(例:v1.0.0/app.zip) を使う
- 削除・上書き禁止の イミュータブルストレージ を活用する
- ステージング → 本番 という二段階リリースで、ステージング環境での検証が終わるまで本番には流さない
- 必要に応じて ブルー/グリーンデプロイ を導入し、どちらのプールも正常に動作することを確認してから切り替える
起動タスクは冪等設計+リトライを標準に
起動タスクは、ネットワークやストレージの一時的なエラーでも失敗しやすい部分です。以下のような設計を心掛けると、Unusable への移行を減らせます。
- 何度実行しても結果が破綻しないよう 冪等(idempotent)に設計
- ダウンロードや展開処理には リトライ(再試行) を組み込み、一時エラーで即失敗させない
- 途中まで展開されたファイルを クリーンアップするロジック を用意
- すべてのステップで 具体的なログメッセージ を残す
Unusable / 起動タスク失敗率を監視し、アラートを設定
Azure Batch であれば、メトリックやログを Azure Monitor / Log Analytics から取得し、次のような指標でアラートを設定できます。
- Unusable ノードの数が一定以上 に増えたら通知
- Start Task 失敗率(exit code ≠ 0)が一定閾値を超えたら通知
- Active タスク数が急増し、Running のタスクが増えない場合に通知
これにより、「気づいたときには数時間分のジョブが詰まっていた」という事態を避けられます。
環境要因(権限・パス・容量・ウイルス対策)も監視に組み込む
配布物そのものだけでなく、ノード環境の変化にも注意が必要です。
- アプリ配置先ディレクトリの アクセス権限 変更
- ディスクの 空き容量不足
- ウイルス対策ソフトやセキュリティ製品による ファイルの隔離・検疫
- プロキシ設定や証明書の更新・期限切れ
これらが原因で「ファイルはあるが読めない/実行できない」という事象が発生し、その結果 Unusable になることもあります。監視項目に取り入れておきましょう。
類似症状の切り分けポイント
最後に、配布物の破損以外でよくある「ノードが Unusable になる」パターンと、その切り分けポイントをまとめます。
| 想定原因 | 症状の特徴 | 優先的に確認するポイント |
|---|---|---|
| ストレージの一時的な読み取り失敗 | 再試行すると成功することがある/時間帯によって揺らぐ | ネットワークログ、ストレージのスロットリング、リトライ有無 |
| SAS トークンの期限切れ | 一定日時を境に突然すべてのノードが失敗し始める | 起動タスクのエラー内容(403 Forbidden 等)、SAS の有効期限 |
| 依存パッケージ未インストール/バージョン不整合 | 特定ライブラリ読込時に例外発生、テスト環境では再現しない | インストールスクリプトの有無、バージョン固定状況、パスの違い |
| 証明書/プロキシ設定不備 | 外部サイトやプライベートレジストリへのアクセスだけが失敗 | SSL/TLS エラー、プロキシ環境変数、信頼されたルート証明書 |
| パス誤り・権限不足・改行コード差異 | スクリプトだけが実行できない/特定 OS だけで失敗 | スクリプトのパス、実行権限(Linux の x ビット)、改行コード(CRLF vs LF) |
| 解凍・展開ツールの不備 | zip/unzip コマンドが見つからない、サポートされない形式 | 使用しているツールの有無・バージョン、コンテナイメージの中身 |
簡易チェックシート例
トラブルシュート時に、次のようなチェック項目を順番に潰していくと効率的です。
| チェック項目 | 確認結果 | メモ |
|---|---|---|
| 配布物と元ビルドのハッシュは一致しているか | Yes / No | |
| 起動タスクの stdout/stderr に明確なエラーは出ているか | Yes / No | |
| 起動タスクの終了コードは 0 か | 0 / 非 0 | |
| SAS トークンや認証情報の有効期限は切れていないか | Yes / No | |
| 依存ライブラリ・ランタイムのバージョンは想定通りか | Yes / No | |
| ノードのディスク空き容量・権限に問題はないか | Yes / No |
まとめ:まず配布物を疑い、ログで裏取りする
プール型のジョブ実行サービス(Azure Batch など)で、ノードが起動直後に Unusable(使用不可) になり、そのまま プールから離脱 してしまう問題は、一見するとインフラの不具合のように感じられます。しかし、実際には今回のような アプリケーションファイルの破損 や 起動タスクの不具合 が根本原因であることも非常に多いです。
対応の基本的な流れは次のとおりです。
- 配布物の破損有無を最初に疑う(チェックサム・バージョンロールバック)
- ノード初期化ログ(起動タスクの stdout/stderr)を確認 し、具体的なエラーを掴む
- 必要に応じて ノードを再作成/reimage し、Active タスクを再キュー
- ヘルスチェック・監視・バージョン管理 を強化して再発を防ぐ
特に、起動タスクでのヘルスチェックとアーティファクトのバージョン固定(ピン留め)は、「次に同じことが起きたときにすぐ分かる」「そもそも壊れたものを本番に出さない」ための強力な武器になります。Azure Batch プールノードの Unusable 問題に悩まされている場合は、ぜひ本記事のチェックリストやサンプルをベースに、環境に合わせた対策を組み込んでみてください。

コメント