要点: ECS タスクが ALB ターゲットグループに登録されると、3 つの独立したヘルスチェック機構がタスクの生死を判定します。ECS のヘルスチェック猶予期間(Grace Period)は起動時間を確保しますが、ALB 側のチェック間隔・閾値・ポートと整合していなければ機能しません。1 か所でもずれるとタスクがトラフィックを受ける前に再起動ループに陥ります。

問題: タスクが準備完了前に停止される

ALB 配下に新しい ECS サービスをデプロイしたとします。コンテナの起動に 30 秒かかるのに、20 秒で ECS がタスクを異常と判定して置き換えを開始します。置き換えたタスクも同じ運命をたどり、サービスは RUNNING と DRAINING を繰り返して安定しません。

この現象の原因は、3 つのヘルスチェック機構がそれぞれ独立して動作し、ECS が最初に受け取った異常シグナルに即座に反応するためです。

  1. ALB ターゲットグループのヘルスチェック — ロードバランサーが設定された間隔でターゲットにプローブを送信する。
  2. ECS サービスのヘルスチェック — ECS スケジューラーがロードバランサー配下のタスク状態を評価する。
  3. コンテナヘルスチェック — Dockerfile の HEALTHCHECK またはタスク定義の healthCheck で定義するオプションの仕組み。

ヘルスチェック猶予期間は、起動時にこれらのシグナル評価を一時停止する ECS の設定です。3 層のヘルスチェックがどう連動するかを理解することが、再起動ループ回避の鍵になります。

第 1 層: ALB ターゲットグループのヘルスチェック

ECS タスクがターゲットグループに登録されると、ALB がヘルスチェックプローブの送信を開始します。これらの設定は ALB リスナーではなくターゲットグループに属します。

主要パラメータ

パラメータ デフォルト 推奨初期値 説明
プロトコル HTTP HTTP or HTTPS ヘルスチェックのプロトコル
パス / /health or /healthz ALB がリクエストを送るエンドポイント
ポート traffic-port traffic-port プローブ先のポート(次節で詳説)
間隔(Interval) 30 秒 10〜30 秒 ターゲットごとのプローブ間隔
タイムアウト 5 秒 5 秒 レスポンスの待ち時間
正常閾値(Healthy threshold) 5 2〜3 正常判定に必要な連続成功回数
異常閾値(Unhealthy threshold) 2 2〜3 異常判定に必要な連続失敗回数
成功コード 200 200–299 正常と判定する HTTP ステータスコード

ターゲットが正常になるまでの時間

計算式は以下のとおりです。

$$T_{healthy} = Interval \times Healthy\ Threshold$$

デフォルト設定(30 秒間隔 × 閾値 5)では、ターゲットが正常と判定されるまで 150 秒かかります。正常閾値を 2、間隔を 10 秒にすれば 20 秒まで短縮できます。

ターゲットが異常になるまでの時間

$$T_{unhealthy} = Interval \times Unhealthy\ Threshold$$

デフォルト(30 秒 × 2)では、異常ターゲットがローテーションから外れるまで 60 秒かかります。この間、ALB は障害中のターゲットにもトラフィックを送り続けます。

第 2 層: ヘルスチェックポート — 混乱しやすい設定

ヘルスチェックポートは、ALB がプローブを送信する先を決定します。3 つの選択肢があります。

  1. traffic-port(デフォルト)— アプリケーショントラフィックと同じポートでプローブします。最もシンプルで一般的な設定です。
  2. 固定ポート番号 — コンテナが専用のヘルスエンドポイントを別ポートで公開する場合に使います(例: サイドカーや管理用ポート)。
  3. オーバーライドポート — コンテナが複数ポートでリッスンし、プライマリ以外をヘルスチェック対象にしたい場合に使います。

専用ヘルスチェックポートを使うべきケース

  • アプリが 443/8080 でトラフィックを処理し、8081 の軽量 /health エンドポイントでヘルスを返す場合。
  • サイドカーコンテナ(例: Envoy)がメインアプリの代わりにヘルスチェックを処理する場合。
  • トラフィックポート上の認証やレートリミットをバイパスしてヘルスチェックを通したい場合。

落とし穴: ECS 動的ポートマッピングとのミスマッチ

動的ポートマッピング(ECS がランダムなホストポートを割り当てる方式)を使用している場合、ターゲットグループは ECS 連携経由でマッピングされたポートを自動検出します。この場合にヘルスチェックポートをハードコードすると、ALB が間違ったポートにプローブを送り、ヘルスチェックが常に失敗します。動的ポートマッピングでは、明確な理由がない限り必ず traffic-port を使ってください。

第 3 層: ECS ヘルスチェック猶予期間

healthCheckGracePeriodSecondsECS サービスに設定します(タスク定義やターゲットグループではありません)。この設定は ECS スケジューラーに次のことを伝えます:

「タスクが RUNNING 状態に入りロードバランサーに登録された後、指定した秒数の間はすべてのヘルスチェック失敗を無視してください。」

猶予期間中の挙動:

  • ALB ヘルスチェックの失敗では ECS はタスクを置き換えません
  • コンテナ HEALTHCHECK の失敗も ECS に無視されます。
  • ALB は内部的にターゲットの状態を追跡し続けるため、異常なターゲットにはトラフィックを送りません。

猶予期間が切れるとどうなるか

猶予期間が終了すると、ECS は通常どおりヘルスシグナルを評価し始めます。この時点で ALB がまだターゲットを異常と報告している場合、ECS は即座にタスクをドレインして置き換えます。

設定を誤ると再起動ループが発生します:

$$Grace\ Period < T_{healthy} \Rightarrow \text{ALB が正常判定する前にタスクが停止される}$$

猶予期間が 30 秒なのに ALB の正常判定まで 150 秒(デフォルト設定)かかる場合、タスクはトラフィックを一度も受けることなく常に停止されます。

推奨の計算式

猶予期間は少なくとも以下を満たすように設定してください:

$$Grace\ Period \geq (Interval \times Healthy\ Threshold) + \text{コンテナ起動時間} + \text{バッファ}$$

例えば、コンテナの起動に 20 秒、ALB の間隔が 10 秒、正常閾値が 3 の場合:

$$Grace\ Period \geq (10 \times 3) + 20 + 10 = 60\ \text{秒}$$

一般的な Web サービスでは 60〜120 秒が適切です。起動時にデータベースマイグレーションやキャッシュウォームを行うアプリケーションでは 180〜300 秒が必要な場合もあります。

第 4 層(オプション): コンテナヘルスチェック

Docker の HEALTHCHECK 命令、または ECS タスク定義の healthCheck ブロックで、コンテナ内部でコマンドを定期実行します。指定回数連続で失敗すると、Docker がコンテナを UNHEALTHY とマークします。

{
  "healthCheck": {
    "command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
    "interval": 15,
    "timeout": 5,
    "retries": 3,
    "startPeriod": 30
  }
}

startPeriod の重要性

startPeriod はコンテナレベルの猶予期間です。この期間中のヘルスチェック失敗はリトライ回数にカウントされません。アプリケーションの初期化が完了するまでの時間を十分にカバーするよう設定してください。

重要: ECS のヘルスチェック猶予期間とコンテナの startPeriod は独立しています。ECS は両方のシグナルを評価します。猶予期間が 60 秒でも startPeriod が 0 の場合、起動が遅いコンテナは ECS が ALB のステータスを確認する前に Docker から UNHEALTHY とマークされる可能性があります。

すべてを連携させる

起動に約 15 秒かかる一般的な Web アプリケーションで、3 層のヘルスチェックを整合させる設定例です。

ターゲットグループのヘルスチェック

プロトコル:       HTTP
パス:            /health
ポート:          traffic-port
間隔:            10 秒
タイムアウト:     5 秒
正常閾値:        3
異常閾値:        3
成功コード:       200

正常判定までの時間: 10 × 3 = 30 秒

ECS サービス

healthCheckGracePeriodSeconds: 60

猶予期間(60 秒) > 正常判定時間(30 秒)+ 起動時間(15 秒)= 45 秒 ✓

コンテナヘルスチェック(タスク定義)

{
  "command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
  "interval": 10,
  "timeout": 5,
  "retries": 3,
  "startPeriod": 45
}

startPeriod(45 秒)> 起動時間(15 秒)、リトライで起動中の一時的な失敗を吸収 ✓

よくある間違いと修正方法

1. 猶予期間が短すぎる

症状: タスクが RUNNING → DRAINING → RUNNING を繰り返す。ECS イベントに service X has reached a steady state が表示されない。

修正: healthCheckGracePeriodSeconds(Interval × Healthy Threshold) + 起動時間 より大きくする。

2. 正常閾値が高く間隔が長い

症状: デプロイに 5 分以上かかる。ALB が新しいターゲットを正常と判定するのを ECS が待ち続ける。

修正: 正常閾値を 2 に下げ、間隔を 10 秒に短縮する。

3. ヘルスチェックパスが 3XX や 401 を返す

症状: アプリケーションは動作しているのに、ALB がすべてのターゲットを異常と判定する。

修正: ヘルスチェックパスが認証・リダイレクト・CORS ヘッダーなしで 200 を返すことを確認する。

4. 動的ポートマッピングでヘルスチェックポートが間違っている

症状: ALB ヘルスチェックがタイムアウトし、ターゲットがすべて initial 状態のまま。

修正: ヘルスチェックポートをハードコードではなく traffic-port にする。

5. コンテナの startPeriod が未設定

症状: 起動中に Docker がコンテナを UNHEALTHY とマーク。猶予期間が残っていても ECS がタスクを置き換える。

修正: コンテナヘルスチェックの startPeriod を起動時間以上に設定する。

デバッグチェックリスト

タスクの再起動が止まらない場合、以下の順番で確認してください:

  • [ ] ECS サービスイベントで has started 1 tasks / has begun draining connections のパターンを確認する。
  • [ ] ECS サービスの healthCheckGracePeriodSeconds が十分に大きいか確認する。
  • [ ] ターゲットグループのヘルスチェック設定(パス、ポート、間隔、閾値)を確認する。
  • [ ] ヘルスエンドポイントがローカルで 200 を返すか確認する(curl http://localhost:PORT/health)。
  • [ ] コンテナヘルスチェックを使用している場合、startPeriod が起動時間をカバーしているか確認する。
  • [ ] CloudWatch でターゲットグループの HealthyHostCountUnHealthyHostCount を確認する — 両方 0 の場合はターゲットが登録されていない。
  • [ ] TargetResponseTimeHTTPCode_Target_5XX_Count で、起動遅延とアプリケーションエラーを切り分ける。

まとめ

  • ECS ヘルスチェック猶予期間、ALB ターゲットグループのヘルスチェック、コンテナ HEALTHCHECK は 3 つの独立した仕組みです。すべての整合性が必要です。
  • 猶予期間は常に (ALB 間隔 × 正常閾値) + コンテナ起動時間 より長く設定してください。
  • ALB ヘルスチェックのポートは、明確な理由がない限り traffic-port を使用してください。
  • コンテナヘルスチェックには startPeriod を設定し、初期化中の誤検知を防いでください。
  • 再起動ループのデバッグでは、ECS サービスイベントとターゲットグループのヘルス状態を並行して確認してください。根本原因はこの 2 つの間のギャップにあることが多いです。