Auto Scaling Groupのヘルスチェック完全ガイド — EC2タイプとELBタイプの使い分けと落とし穴

「インスタンスは起動しているのに、ASGが勝手に終了させる」——この現象に直面したとき、最初に疑うべきはヘルスチェックの設定だ。EC2タイプのヘルスチェックはインスタンスの存在しか見ていない。アプリケーションがクラッシュしていても、OSが起動していれば「正常」と判断される。Auto Scaling Groupのヘルスチェック設定を正しく理解することは、不要なインスタンス終了を防ぐだけでなく、本当に異常なインスタンスを確実に置き換えるためにも不可欠だ。

TL;DR — ヘルスチェックタイプの選択基準

状況推奨タイプ理由
ELBを使用していないEC2ELBタイプはロードバランサーなしでは機能しない
ELBを使用、アプリ死活監視が必要ELBアプリレイヤーの応答をヘルスチェックに反映できる
ELBを使用、インスタンス起動直後に終了されるELB + ウォームアップ期間の確認ヘルスチェックグレースピリオドの設定ミスが原因のことが多い
カスタムアプリ監視が必要EC2 + カスタムスクリプトASGのカスタムヘルスチェックAPIで外部から不健全マークを付与

Auto Scaling Groupのヘルスチェックの仕組み

ASGのヘルスチェックを理解するには、誰がヘルス状態を判定するのかを明確にする必要がある。ASG自身がインスタンスをポーリングするわけではない。ASGは複数のソースからヘルス情報を受け取り、それを元に終了・置換の判断を行う。

graph LR EC2SVC["EC2サービス
システムステータス監視"] -->|"Impaired / Stopped"| ASG["Auto Scaling Group
ヘルス判定エンジン"] TG["ターゲットグループ
アプリHTTPチェック"] -->|"Unhealthy"| ASG CUSTOM["外部スクリプト
SetInstanceHealth API"] -->|"Unhealthy"| ASG ASG -->|"不健全と判定"| ACTION["インスタンス終了
新インスタンス起動"] style ASG fill:#FF9900,color:#fff style ACTION fill:#d9534f,color:#fff
  1. EC2ヘルスチェック: EC2サービスがインスタンスのシステムステータスとインスタンスステータスを監視。running以外の状態(stoppedterminatedimpairedなど)になると、ASGは不健全と判断する。
  2. ELBヘルスチェック: ロードバランサーのターゲットグループが設定されたパス・ポートに対してHTTPリクエストを送信し、その結果をASGに伝える。ターゲットがunhealthyになると、ASGはそのインスタンスを不健全と判断する。
  3. カスタムヘルスチェック: 外部システムやスクリプトがSetInstanceHealth APIを呼び出し、ASGに直接ヘルス状態を通知する。

重要なのは、ELBタイプを選択しても、EC2タイプのチェックは無効にならないという点だ。ELBタイプは既存のEC2チェックに追加される。どちらかが不健全と判断すれば、ASGはインスタンスを終了・置換する。

EC2ヘルスチェックタイプの詳細

デフォルト設定のEC2タイプは、EC2のシステムステータスチェックとインスタンスステータスチェックを監視する。具体的には、EC2コンソールで確認できる「2/2 checks passed」の状態が維持されているかどうかだ。ハードウェア障害やネットワーク接続の問題はここで検出される。

ただし、このチェックはOSレイヤーより上を見ない。Nginxが落ちていても、Javaプロセスがクラッシュしていても、EC2インスタンス自体がrunning状態であれば「健全」と判定される。アプリケーションの死活監視としては不十分だ。

# 現在のASGヘルスチェック設定を確認する
aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names my-asg \
  --query 'AutoScalingGroups[0].{HealthCheckType:HealthCheckType,GracePeriod:HealthCheckGracePeriod}' \
  --output table

ELBヘルスチェックタイプへの切り替えと注意点

ELBタイプに切り替えると、ロードバランサーのターゲットグループが実施するヘルスチェック結果がASGの判断に組み込まれる。アプリケーションが/healthエンドポイントで200を返せなければ、ASGはそのインスタンスを置換する。これは本来望ましい動作だ。

しかし、ここで最も多くの誤解が生まれる。ELBタイプに切り替えた直後に「インスタンスが起動してすぐ終了される」という問題が発生するケースがある。原因の大半はヘルスチェックグレースピリオドの設定不足だ。

sequenceDiagram participant ASG as Auto Scaling Group participant EC2 as EC2インスタンス participant APP as アプリケーション participant TG as ターゲットグループ ASG->>EC2: インスタンス起動 EC2->>APP: OS起動 → アプリ起動中 Note over ASG,TG: グレースピリオド中(ヘルスチェック無視) APP-->>TG: /health → 503(起動中) TG-->>ASG: Unhealthy(無視) APP-->>TG: /health → 200(起動完了) TG-->>ASG: Healthy Note over ASG: グレースピリオド終了後に評価開始 ASG->>EC2: InService確定

ヘルスチェックグレースピリオドの役割

インスタンスが起動してからアプリケーションが完全に立ち上がるまでには時間がかかる。グレースピリオドはその猶予時間だ。この期間中、ASGはヘルスチェックの失敗を無視する。グレースピリオドが短すぎると、アプリが起動途中なのにASGが「不健全」と判断して終了させてしまう。

グレースピリオドは「インスタンスが起動してから」ではなく、「InServiceステートに遷移してから」カウントが始まる。ユーザーデータスクリプトが長い場合、InService遷移自体が遅れることがあるため、実際のアプリ起動時間とは異なるタイミングになる点に注意が必要だ。

# ELBヘルスチェックタイプに変更し、グレースピリオドを300秒に設定する
aws autoscaling update-auto-scaling-group \
  --auto-scaling-group-name my-asg \
  --health-check-type ELB \
  --health-check-grace-period 300
# 変更後の設定を確認する
aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names my-asg \
  --query 'AutoScalingGroups[0].{HealthCheckType:HealthCheckType,GracePeriod:HealthCheckGracePeriod,Instances:Instances[*].{ID:InstanceId,Health:HealthStatus,State:LifecycleState}}' \
  --output json

「インスタンスが終了される」問題の診断手順

ASGが意図せずインスタンスを終了させている場合、原因は複数の層に存在する可能性がある。以下の手順で系統的に切り分ける。

Step 1: ASGアクティビティ履歴でTerminationの理由を確認する

まず終了の理由を特定する。ASGはインスタンスを終了するたびにアクティビティ履歴にその理由を記録する。ここを見ずに設定変更するのは、エラーログを見ずにコードを直すようなものだ。

aws autoscaling describe-scaling-activities \
  --auto-scaling-group-name my-asg \
  --max-items 20 \
  --query 'Activities[?StatusCode==`Failed` || contains(Description, `Terminating`)].{Time:StartTime,Desc:Description,Cause:Cause}' \
  --output table

出力のCauseフィールドに「Instance failed ELB health checks」や「Instance failed EC2 health checks」などの理由が記録されている。ここで原因が特定できれば、以降のステップを絞り込める。

Step 2: 対象インスタンスのヘルス状態をASGとELBの両側から確認する

Step 1でELBヘルスチェック失敗が原因と判明した場合、次はELBのターゲットグループ側のヘルス状態を確認する。ASGが「不健全」と判断していても、ELBのターゲットグループ側の詳細を見ないと根本原因はわからない。

# ASG側のインスタンスヘルス状態を確認
aws autoscaling describe-auto-scaling-instances \
  --query 'AutoScalingInstances[?AutoScalingGroupName==`my-asg`].{ID:InstanceId,Health:HealthStatus,State:LifecycleState}' \
  --output table
# ターゲットグループのARNを取得する
aws elbv2 describe-target-groups \
  --query 'TargetGroups[*].{Name:TargetGroupName,ARN:TargetGroupArn}' \
  --output table
# ターゲットグループ内の各インスタンスのヘルス状態と理由を確認する
aws elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/1234567890abcdef \
  --query 'TargetHealthDescriptions[*].{ID:Target.Id,State:TargetHealth.State,Reason:TargetHealth.Reason,Desc:TargetHealth.Description}' \
  --output table

ReasonフィールドがTarget.FailedHealthChecksであれば、アプリケーションがヘルスチェックリクエストに応答していない。Elb.InternalErrorであればELB側の問題だ。

Step 3: ターゲットグループのヘルスチェック設定を確認する

ターゲットグループのヘルスチェック設定が実際のアプリケーションの応答と一致していない場合、正常なインスタンスが不健全と判定され続ける。ヘルスチェックパス、ポート、成功レスポンスコードの設定を確認する。

aws elbv2 describe-target-groups \
  --target-group-arns arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/1234567890abcdef \
  --query 'TargetGroups[0].{Path:HealthCheckPath,Port:HealthCheckPort,Protocol:HealthCheckProtocol,Interval:HealthCheckIntervalSeconds,Threshold:HealthyThresholdCount,Matcher:Matcher}' \
  --output json

よくある設定ミスは、ヘルスチェックパスが/のままで、アプリが/healthしか用意していないケース。あるいはアプリが200以外のステータスコード(例:301リダイレクト)を返しているのに、成功コードが200のみに設定されているケースだ。

Step 4: グレースピリオドとインスタンス起動時間の整合性を確認する

Step 1〜3で設定上の問題が見つからない場合、タイミングの問題を疑う。インスタンスの実際の起動時間(ユーザーデータスクリプトの実行時間を含む)とグレースピリオドを比較する。

# インスタンスの起動時刻とASGのInService遷移時刻を比較する
aws ec2 describe-instances \
  --instance-ids i-0123456789abcdef0 \
  --query 'Reservations[0].Instances[0].{LaunchTime:LaunchTime,State:State.Name}' \
  --output table
# CloudWatch Logsでユーザーデータスクリプトの完了時刻を確認する(ロググループが設定されている場合)
aws logs get-log-events \
  --log-group-name /var/log/cloud-init-output \
  --log-stream-name i-0123456789abcdef0 \
  --limit 50 \
  --query 'events[-10:].{Time:timestamp,Msg:message}' \
  --output table

アプリの起動に120秒かかるのにグレースピリオドが60秒であれば、ELBタイプに切り替えた瞬間から問題が発生する。グレースピリオドはアプリの実際の起動時間より余裕を持たせた値に設定する。

実際の障害パターン — 誤診から正しい診断へ

本番環境でよく見るパターンを一つ紹介する。

症状: ELBタイプに切り替えた翌日から、ASGが断続的にインスタンスを終了・置換し始めた。CloudWatchのメトリクスを見るとCPUやメモリに異常はない。アプリチームは「アプリは正常に動いている」と言う。

最初の誤診: グレースピリオドが短すぎると判断し、300秒から600秒に延長した。しかし問題は解消しなかった。

実際の原因: ターゲットグループのヘルスチェック設定を確認すると、ヘルスチェックのタイムアウトが2秒に設定されていた。アプリの/healthエンドポイントはDBへの疎通確認を含んでおり、ピーク時に3〜4秒かかることがあった。タイムアウト超過でヘルスチェックが失敗し、閾値(2回連続失敗)に達するとターゲットがunhealthyになり、ASGがインスタンスを置換していた。

修正: ヘルスチェックタイムアウトを5秒に延長し、/healthエンドポイントからDBチェックを切り離して軽量化した。

グレースピリオドを疑う前に、ターゲットグループのヘルスチェック設定そのものを確認する——これが正しい診断順序だ。

# ターゲットグループのヘルスチェックタイムアウトを更新する
aws elbv2 modify-target-group \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/1234567890abcdef \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3

カスタムヘルスチェックの活用

ELBを使用していない、またはELBのヘルスチェックでは検出できないアプリケーション固有の異常(例:キューの詰まり、外部依存サービスへの接続断)を監視したい場合は、SetInstanceHealth APIを使ったカスタムヘルスチェックが有効だ。

# 外部スクリプトやモニタリングシステムからインスタンスを不健全としてマークする
aws autoscaling set-instance-health \
  --instance-id i-0123456789abcdef0 \
  --health-status Unhealthy \
  --should-respect-grace-period

--should-respect-grace-periodを指定すると、グレースピリオド中はこのAPIによる不健全マークが無視される。起動直後の誤検知を防ぐために通常は指定すべきだ。

診断に必要なIAMポリシー

上記のCLIコマンドを実行するには、以下の権限が必要だ。このポリシー例では簡潔さのためにワイルドカードを使用している箇所があるが、本番環境では最小権限の原則に従い、可能な限りリソースARNを特定して制限することを推奨する。

🔽 IAMポリシー例(クリックして展開)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ASGReadAccess",
      "Effect": "Allow",
      "Action": [
        "autoscaling:DescribeAutoScalingGroups",
        "autoscaling:DescribeAutoScalingInstances",
        "autoscaling:DescribeScalingActivities",
        "autoscaling:UpdateAutoScalingGroup",
        "autoscaling:SetInstanceHealth"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ELBReadAccess",
      "Effect": "Allow",
      "Action": [
        "elasticloadbalancing:DescribeTargetGroups",
        "elasticloadbalancing:ModifyTargetGroup"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ELBTargetHealthAccess",
      "Effect": "Allow",
      "Action": [
        "elasticloadbalancing:DescribeTargetHealth"
      ],
      "Resource": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/1234567890abcdef"
    },
    {
      "Sid": "EC2ReadAccess",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances"
      ],
      "Resource": "*"
    }
  ]
}

Auto Scaling Groupヘルスチェックの設定まとめと次のステップ

Auto Scaling Groupのヘルスチェックタイプを変更することは、問題の根本原因ではなく症状への対処になりがちだ。ELBタイプへの切り替えは、アプリケーションレイヤーの監視を強化する正しいアプローチだが、グレースピリオドとターゲットグループのヘルスチェック設定が整合していなければ、かえって問題を悪化させる。

設定変更の前に、必ずASGアクティビティ履歴でTerminationの理由を確認すること——これが最短の診断経路だ。

関連する設定として、Auto Scalingのライフサイクルフックを使ったインスタンス起動時の初期化制御や、スケールインポリシーの設定も合わせて確認することを推奨する。詳細はAWS公式ドキュメントのAuto Scaling ヘルスチェックの概要を参照してほしい。

用語集

用語説明
ヘルスチェックグレースピリオドインスタンスがInServiceステートに遷移してからASGがヘルスチェック結果を評価し始めるまでの猶予時間(秒)
ターゲットグループELBがリクエストをルーティングするインスタンスの集合。ヘルスチェック設定はターゲットグループ単位で管理される
SetInstanceHealth APIASGのインスタンスに対して外部からヘルス状態を設定するAPI。カスタムヘルスチェックの実装に使用する
InServiceステートASGのライフサイクルにおいて、インスタンスが正常にトラフィックを受け付けられる状態
Unhealthy Thresholdターゲットグループがターゲットを不健全と判定するまでに必要な連続失敗回数

Related Posts

コメント

このブログの人気の投稿

EC2 SSH接続タイムアウトの原因と修正方法 — セキュリティグループのインバウンドルール完全ガイド

S3パブリックアクセス拒否の原因と解決策:バケットレベルの「Block Public Access」が優先される仕組み

EC2インスタンスIDをメタデータから取得する方法 — IMDSv2が安全な理由