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の通知が来たとき、この記事を思い出して落ち着いて調査に取り掛かってもらえれば、必ず答えは見つかるはずだ。
現場からは以上だ。また次の障害でお会いしよう。
コメント