0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ALB 502 エラーの原因を特定する「3層切り分け」定石パターン(AWS アクセスログ × Target Group × EKS Pod)

0
Posted at

TL;DR

  • ALB が返す 502 は、ALB → ターゲット間の 接続失敗・不正レスポンス・接続切断 が主原因です。「インフラ起因」と決めつけがちですが、ターゲット側 (Pod) の不安定(プロセスクラッシュ・半開状態) が ALB 502 として表面化しているケースも多いです。
  • 切り分けは「ALB アクセスログ → Target Group ヘルスチェック → Pod ログ・イベント」の3層が定石。それぞれの層で見るべきフィールド・メトリクス・コマンドが違います。
  • 実例では、断続的な 502 の原因は アプリ例外+Pod 規模不足の複合 でした。インフラ設定変更ゼロ、アプリチーム側の修正のみで収束しています。

環境

項目 内容
クラウド AWS
ロードバランサ Application Load Balancer (ALB)
アプリ実行基盤 EKS on EC2(マネージドノードグループ上でマイクロサービスを稼働)
ルーティング ALB → Target Group(ターゲットタイプ=IP) → Pod(AWS Load Balancer Controller / TargetGroupBinding 想定)
監視 CloudWatch メトリクス + ALB アクセスログ (S3)

EKS on EC2 を前提に書きますが、3層切り分けの考え方は ALB → ECS / EC2 / Lambda 構成でも同じです。

本記事で扱う「3層切り分け」のスコープを先にお示しします。各層で見るフィールド・メトリクス・コマンドが異なります。

figure1_3layer_scope.png

図1: ALB 502 の3層切り分けスコープ(第1層=ALBアクセスログ/第2層=Target Group ヘルスチェック/第3層=Pod ログ・イベント)

起きたこと

ある日、アプリ担当者から「特定の画面で 502 が 断続的に 発生する」と連絡が入りました。監視アラートより先に人検知です。全リクエストが落ちているわけではなく、一部のリクエストだけ 502 を返している、という状況でした。

502 = Bad Gateway は「ALB がターゲットから有効な応答を得られなかった」ことを示すステータスです。AWS 公式仕様上、ALB が 502 を返すのは主に以下のケースです (アプリが正常に 500 / 503 を返した場合は、ALB はそれをそのまま中継するため 502 にはなりません)。

  • ターゲットへの TCP 接続失敗 (TLS ハンドシェイク失敗、SG/NACL 拒否、ターゲット停止中)
  • ターゲットからの 不正な HTTP レスポンス (不正ヘッダ・プロトコル違反)
  • ターゲットとの 接続が応答完了前に切断 (Pod クラッシュ、keep-alive timeout ミスマッチ等)

ALB から先のターゲット = EKS Pod の通信不全」を疑うのは正しい方向ですが、通信不全の根本原因がアプリ層 (Pod プロセスの異常) にある ことも多いです。先入観を持たずに層を分けて確認していきます。

第1層: ALB アクセスログ

最初に見るのは ALB アクセスログ (S3 出力) です。フィールドの組み合わせで「ALB 側で完結している 502 か、ターゲットからの応答異常か」が分かります。

見るべきフィールド

フィールド 意味
elb_status_code ALB がクライアントに返したステータスコード
target_status_code ターゲット (Pod) が ALB に返したステータスコード。- ならターゲットからの応答なし
request_processing_time クライアント受信〜ターゲット送信までの時間
target_processing_time ターゲット送信〜ターゲット応答受信までの時間。-1ターゲットから有効な応答を取得できなかった ことを示す
response_processing_time ターゲット応答受信〜クライアント返信までの時間

パターン別の読み方

ALB が 502 を返すとき、target_status_code は通常 - (有効なHTTPレスポンスを受け取れていない) になります。target_processing_time の値で「接続段階の失敗か、応答の途中切断か」を判別できます。

elb_status_code target_status_code target_processing_time 想定される原因層
502 - -1 ターゲット未到達 or 接続失敗 (TCP接続失敗、TLSハンドシェイク失敗、SG/NACL拒否、ターゲット停止) → 第2層へ
502 - 数値あり 接続後の応答異常 (不正レスポンス、接続途中切断、Podクラッシュ、keep-aliveミスマッチ) → 第2層・第3層へ
504 - 数値あり (大) ALB アイドルタイムアウト超過 (アプリ処理遅延)。502ではないが参考
500 / 503 同じ 500 / 503 数値あり ターゲット自身の 5xx を ALB がそのまま中継。ALB は 502 を生成しない (本記事スコープ外、アプリログを直接調査)

ALB は ターゲットが正常に返した 5xx (500 / 503 等) はそのまま中継しますelb_status_code = 502 が出ているなら、ターゲットから「正常な HTTP レスポンス」を受け取れていない (= 接続/応答段階で何か起きている) と判断できます。

抽出手段

ALB アクセスログ (S3) は Athena でクエリするか、件数が少なければ S3 Select で十分です。Athena の標準的なテーブル定義では elb_status_code / target_status_codeSTRING 型で定義されるため、比較値は文字列リテラル で書きます。

-- Athena 例: 直近1時間の 502 を target_status_code 別に集計
-- AWS 標準 DDL: elb_status_code / target_status_code / time は string 型 (time は ISO8601 文字列)
-- パーティション (year/month/day) を併用してスキャン量を抑えるのが実務的
SELECT target_status_code, COUNT(*) AS cnt
FROM alb_access_logs
WHERE year = '2026' AND month = '06' AND day = '14'  -- 環境の DDL に合わせて調整
  AND elb_status_code = '502'
  AND from_iso8601_timestamp(time) >= current_timestamp - interval '1' hour
GROUP BY target_status_code
ORDER BY cnt DESC;

target_status_code がほぼ - で埋まっていれば、第2層・第3層で「ターゲット側で何が起きているか」を裏取りします。

第2層: Target Group ヘルスチェック履歴

第1層で「ターゲットからの応答なし」が多いと出たら、Target Group のヘルスチェックを確認します。Pod 自体が断続的に Unhealthy 判定されていないかを見る層です。

見るべき CloudWatch メトリクス

メトリクス 確認ポイント
UnHealthyHostCount 一時的にスパイクしているか。断続発生のタイミングと一致するか
HealthyHostCount 想定 Pod 数を下回っていないか
TargetConnectionErrorCount ALB → ターゲット間の TCP 接続失敗数
HTTPCode_ELB_5XX_Count ALB 自身が生成した 5xx の総数 (500 / 502 / 503 / 504 を含む)。発生タイミングの相関確認に有効。502 単独の詳細判定はアクセスログ側で行う
HTTPCode_Target_5XX_Count ターゲットが正常に返した 5xx の総数 (500 / 503 等)。502 の調査では原則使わない

UnHealthyHostCount が断続的にスパイクし、HTTPCode_ELB_5XX_CountTargetConnectionErrorCount が同期して増加していれば、特定の Pod が一時的に落ちて再起動を繰り返している 可能性が高いです。次の層で kubectl ベースで裏取りします。

第3層: Pod ログ・イベント

最後は EKS 側です。Pod 単位で「落ちている事実」と「落ちた理由」を取りに行きます。

確認コマンド

# Pod の現状と再起動回数 / Last State
kubectl describe pod <pod-name> -n <namespace>

# 直前のコンテナのログ (再起動した場合に有効)
# 単一コンテナの Pod ならこれで十分
kubectl logs <pod-name> -n <namespace> --previous

# サイドカーなど複数コンテナを含む Pod では -c <container-name> でコンテナを指定
kubectl logs <pod-name> -n <namespace> --previous -c <container-name>

# Namespace のイベントを時系列で
kubectl get events -n <namespace> --sort-by='.lastTimestamp'

# Deployment / ReplicaSet の Pod 状態
kubectl get pods -n <namespace> -o wide

確認項目

  • アプリ例外: kubectl logs --previous でクラッシュ直前のスタックトレース。HTTP リクエスト処理中の NullPointerException / OutOfMemoryError / DB接続失敗など
  • OOMKilled: describe podLast State: Terminated, Reason: OOMKilled。コンテナのメモリ requests/limits の見直し
  • CrashLoopBackOff: 起動 → クラッシュ → 再起動を繰り返している状態
  • スケジューリング失敗: eventsFailedScheduling (ノードリソース不足) や FailedCreatePodSandBox (CNI / ENI 上限)
  • ReadinessProbe 失敗: Pod が Ready にならず Target Group から外れる

ALB は UnHealthyHostCount が一定数を超えると当該 Pod へのルーティングを止めますが、その判定に至るまでの間、一部のリクエストで 502 が観測されることがあります。これが「断続的 502」の典型的な発生メカニズムの一つです。

実例:断続502 の原因がアプリ例外+規模不足だった

ある EKS on EC2 のマイクロサービス構成で、ALB 502 が断続的に発生したケースです。完全汎用化のため、固有の構成情報は省きます。

切り分けの経路

  1. 第1層 (ALB アクセスログ): 502 のうち相当数で target_status_code = -target_processing_time数値あり (接続後の切断) と -1 (接続自体の失敗) が混在。Pod プロセスがクラッシュするタイミングによって、接続維持できているケースと既にポートが閉じているケースの両方が発生していると推定
  2. 第2層 (Target Group ヘルス): UnHealthyHostCount が時間帯によりスパイク。HTTPCode_ELB_5XX_CountTargetConnectionErrorCount も同期して増加。「一部の Pod が落ちて接続を維持できなくなっている」第1層の推定と整合
  3. 第3層 (Pod ログ・イベント): kubectl logs --previous -c <container> でアプリ側の例外スタックトレースを確認。並行して kubectl get events に Pod の再起動・OOMKilled 系メッセージ。describe podRestarts 回数が増加

当たり

  • アプリケーション層の例外: 特定の入力データで未ハンドリング例外が発生し、ワーカーが死ぬ
  • Pod / ノード規模不足: 健全な Pod に負荷が寄って遅延・タイムアウト、結果として更に Unhealthy 判定が拡大

対処

  • アプリチームへ修正依頼: 例外ハンドリングの追加・該当ロジックの修正
  • インフラ側の設定変更(ALB アイドルタイムアウト / Target Group ヘルスチェック閾値 / HPA 設定など)は実施せず

「インフラを触らずに収束させられた」のは、3層切り分けで原因が アプリ層に閉じている と早い段階で確信できたためです。

まとめ

教訓
ALB 502 は「ターゲットからの正常な応答を得られない」状態。ターゲットが返した 5xx (500/503) は ALB が中継するだけで 502 にはならない
まず elb_status_code = '502'target_status_codetarget_processing_time を確認し、接続段階の失敗か応答段階の異常かを判別する
切り分けは ALB アクセスログ → Target Group ヘルス → Pod ログ・イベント の3層
断続的 502 は一部の Pod / 一部のリクエストだけ落ちているシグナル。レイヤ別ログで当たりをつける
インフラ担当が「アプリ起因」を切り分け切ってアプリチームに渡せると、責任分界が明確になり修正が早い

EKS on EC2 を前提に書きましたが、ALB → ECS / EC2 / Lambda 構成でも、第1層・第2層の考え方は同じです。第3層が ECS タスクログや EC2 アプリログに置き換わるだけです。

同じ AWS マルチリージョン構成で踏んだ別の罠は、姉妹記事 「cannot execute INSERT in a read-only transaction」— Aurora Global Database で踏んだセカンダリ書込みのワナ にまとめています。あわせてどうぞ。

出典・参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?