はじめに
SENSYN Robotics(センシンロボティクス)の中山です。
Webアプリやそのインフラ周りと、Web側とドローンの接続を行うデバイスドライバ的な部分を担当しています。
今回はAzure Application GatewayでHTTPの502 Bad Gatewayが出たときの話です。
Azure Application Gateway
Microsoft Azureで提供されている、HTTP・HTTPSのレイヤー7ロードバランサです。AppGWと略すことが多いです。AWSのApplication Load Balancer(ALB)みたいなものですね。マネージドサービスなので、管理が楽です。
ロードバランサなので、ユーザーに公開されるIPアドレスはAppGWのものになります。SENSYN RoboticsのサービスはKubernetes上のコンテナとして動いているのですが、そのコンテナ群のフロントエンドとして機能します。
図にするとこんな感じ↓。
Web browser <--HTTPS--> AppGW <--HTTP--> containers on Kubernetes
502 Bad Gateway
サービスの提供を開始した頃、いくつかのリクエストに対して502 Bad Gatewayが返るという問題が発生しました。テストしてみると、大量1のリクエストを送ったときに発生するようです。
当初は、コンテナ側の負荷上昇が原因でAcceptキュー(backlog)が溜まり、時間内にHTTP接続を受け付けられなかったことで接続タイムアウトしたのだと考えていました。
しかし、tcpdumpで調査してみると、そもそもTCPの接続要求(SYN)が来ていないように見えます。AppGWが502を返すまでの時間が非常に短いことからも、backlog溢れによる接続タイムアウトという可能性は低そうでした。
HTTP1.1
AppGWのドキュメントを見てみると、バックエンドとHTTP1.1で通信するとあります。
バックエンド サーバー プールへの通信は、HTTP/1.1 で行われます。
HTTP1.0とHTTP1.1の違いといえば、
A. 接続の再利用
B. パイプライン
C. Chunked response
D. キャッシュ制御の標準化
といった辺りです。今回は接続で問題が起きているので、Aの接続の再利用が関係している可能性が高そうです
仮説
HTTP1.1で追加された機能である接続の再利用をAppGWが利用している2とすると、以下のシーケンスで問題が起きそうです。
- AppGWがリクエストを受け付ける
- AppGWがバックエンドに対してTCP接続する
- TCP接続を通してHTTPのリクエストをバックエンドに投げる
- バックエンドがAppGWにレスポンスを返す
- AppGWがクライアントにレスポンスを返す
- AppGWはTCP接続を切らない
- バックエンドは一定時間使われていないTCP接続を切断しようとする
- 切断中にAppGWがリクエストを受け付ける
- AppGWが切断中のTCP接続にリクエストを送ろうとする
- 送れないのでBad Gatewayエラーを返す
AppGWはまだ接続中だと思っているTCP接続が、バックエンド側では切断されている、という状況です。バックエンド側で切るのが早すぎるのが問題です。
解決
バックエンド側が一定時間使われていないと判断する閾値を変更しました。具体的にはKeep-Aliveのタイムアウトを5秒から3分にしています。
問題の起きていたバックエンドはNode.jsで書かれているので、以下のようなコードにしました。
var server = http.createServer(app).setTimeout(config.timeout.server());
server.keepAliveTimeout = config.timeout.server(); // デフォルトは5秒
この修正を入れてテストしてみると、きれいに502エラーが消えました。問題解決です。
まとめ
- Azure Application Gatewayで502 Bad Gatewayが起きたときは、バックエンド側のKeep-Aliveが原因のケースがある
- tcpdumpを使うと原因を切り分けやすい