【実務・中級編】 ALBにおけるHTTPステータスコード502 (Bad Gateway) の発生原因とトラブルシューティング – クラウド&コンテナネットワーク実践ガイド

ALBの「502 Bad Gateway」を極める:深夜の呼び出しを回避する実務的トラブルシューティング

「またお前か……」。深夜3時、Slackの通知音とともに飛んできたCloudWatch Alarmの文字を見て、溜息をついた経験はないだろうか。そう、AWS Application Load Balancer(ALB)における 502 Bad Gateway は、SREにとって最も胃を痛めるエラーの一つだ。

教科書には「ターゲットが不正なレスポンスを返した際に発生」と書かれているが、現場の泥臭い戦場では、その一言では片付けられない。本記事では、ALBがなぜ502を吐くのか、その内側で何が起きているのかをパケットレベルで解き明かし、即座に原因を切り分けるための「現場の勘所」を伝授する。

—

1. 502 Bad Gatewayの正体:ALBは「郵便局の窓口」

ALBは単なるL7プロキシだ。クライアントとターゲット(EC2やFargate、Lambda)の間に立ち、TCPコネクションを終端し、HTTPリクエストを中継する。

ALBが 502 を返すとき、それは「私は正しく仕事をしたけれど、奥のターゲットが期待通りに返事をくれなかった」という悲鳴に近い。具体的には、ALBがターゲットへリクエストを投げた直後、以下のような事象が起きたことを意味する。

  • TCP RST(リセット)の受信: ターゲットがコネクションを突然切断した。
  • 不完全なHTTPレスポンス: レスポンスヘッダーが途中で途切れた。
  • ALBがターゲットを解釈できない: HTTPの仕様を逸脱したフォーマットで応答が返ってきた。

—

2. 実務で遭遇する「3大原因」と調査の初動

原因1:ターゲットのタイムアウトとプロセス停止

最も多いのが「リクエスト処理中にターゲットのプロセスが死んだ」パターンだ。例えば、PHPの php-fpm やNode.jsのイベントループが、重い処理でブロックされて応答不能になり、カーネルレベルでTCP切断が発生するケースだ。

【調査の第一歩】
まずはターゲット側のログ(access.log や error.log)を確認し、ALBからのリクエストが到達しているか確認する。もしログに記録すらされていないなら、ネットワーク経路やALBのセキュリティグループ、あるいはOSレベルの接続制限(net.core.somaxconn 等の枯渇)を疑うべきだ。

原因2:Keep-Aliveの不整合

ALBとターゲット間ではHTTP Keep-Aliveが多用される。ALB側が「このコネクションはまだ使える」と判断して再利用しようとした瞬間、ターゲット側のWebサーバー(NginxやApache)が先にタイムアウトでコネクションを閉じていると、ALBは「あれ?切れてる」となり、即座に502を返す。

【解決策のヒント】
ターゲットサーバー(Nginx等)の keepalive_timeout 設定が、ALBのアイドルタイムアウト(デフォルト60秒)よりも短く設定されていることを確認してほしい。

# Nginxの設定例: ALBよりも余裕を持たせるのが鉄則
keepalive_timeout 65s; 
# クライアントからの要求を待つ時間をALBより長く設定する

原因3:レスポンスヘッダーの肥大化

ALBには許容されるレスポンスヘッダーのサイズ制限がある。これをオーバーフローするような巨大なCookieやカスタムヘッダーをターゲットが返すと、ALBは処理を放棄し502を返す。

—

3. 手を動かして再現する:デバッグ手法

現象が起きている最中に「何が起きているのか」を特定するために、以下のデバッグ手法を使いこなそう。

A. curlでターゲットを直撃する

ALBを経由せず、直接ターゲットのIP(または内部DNS)に対してリクエストを投げ、ヘッダーを覗くのが基本だ。

# ターゲットに直接リクエストを送り、HTTPヘッダーと応答コードを検証する
curl -Iv -H "Host: example.com" http://<ターゲットのプライベートIP>:80/api/resource

B. Pythonで「中途半端な切断」をシミュレートする

ALBがどう反応するかを確認するために、わざとレスポンスを途中で切るサーバーを作ってみるのも有効だ。

import socket

# 不完全なレスポンスを返す簡易サーバーの例
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8080))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = conn.recv(1024)
    # レスポンスヘッダーの途中で強制終了させる
    conn.send(b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n")
    conn.close() # ここでALBは502を生成する

—

4. SRE的思考:再発防止のために

502を撲滅するために、インフラエンジニアとして取り組むべきことは以下の3点だ。

1. ALBアクセスログの分析:
ALBのアクセスログにある target_status_code が -(ハイフン)になっている場合、ターゲットがレスポンスを返さずに切断したことを意味する。ここを起点に、「どのターゲットで頻発しているか」をAthenaでクエリして相関関係を突き止めよう。

2. ヘルスチェックの厳格化:
「死んでいるのにリクエストを送り続ける」のが最も質が悪い。ターゲットが正常に処理できない状態なら、即座にヘルスチェックで切り離せるよう、アプリケーションの /health エンドポイントを実装する際には、DB接続チェックなども含めた「Deep Health Check」を推奨する。

3. タイムアウト値の最適化:
Proxy Idle Timeout は安易に大きくすれば良いものではない。長すぎる設定は、ターゲットのメモリリークを隠蔽し、障害を長引かせる可能性がある。

最後に:エラーは「システムの健康診断」

502 Bad Gatewayは、システムが抱える「無理」が可視化されたサインだ。焦ってALBの設定をいじる前に、まずターゲットのOSリソース(dmesg や top)を眺め、アプリケーションのログを追いかけてほしい。

ネットワークのトラブルシューティングは、パケットという「見えないもの」を論理的に追いかける知的なパズルだ。次に502の通知が来たとき、この記事を思い出して落ち着いて調査に取り掛かってもらえれば、必ず答えは見つかるはずだ。

現場からは以上だ。また次の障害でお会いしよう。

コメント

タイトルとURLをコピーしました