TL;DR
- ELBはクライアントからのリクエスト時に、クライアントとバックエンドとのコネクション2つを維持する
- 両方の接続について、アイドルタイムアウト値はデフォルトで60秒となっている
- バランシング先のNginxなどのKeepAlive Timeout値は、ELBのアイドルタイムアウト値より大きくなければならない
- もしELBのアイドルタイムアウト値より小さい場合、504 Gateway Timeoutエラーが返ってくる
- この504エラーはALBのログには書き込まれないので、きちんとALBのステータスコードを監視するべき
ELBのコネクション
図にするまでもないが、ELBにおけるTCPコネクションは以下のようになっている。
KeepAliveとは
まずはKeepAliveについて振り返っておく。KeepAliveとはクライアントとサーバー間での接続が有効であるかを確認するために一定周期で行う通信のことである。
そしてApacheやNginxなどのサーバーには、KeepAlive接続時のリクエスト待ち時間を設定できるKeepAlive Timeoutという項目がある。このKeepAlive Timeoutを60秒にした場合、KeepAlive接続中のクライアントから60秒間リクエストがなければTCPコネクションを切断するようになる。
ELBのアイドルタイムアウトというのも、このKeepAliveTimeoutと同一のものである。
ELBのアイドルタイムアウト値より小さい場合の挙動
もしバランシング先のApacheのKeepAlive Timeoutが、ELBのアイドルタイムアウト値より小さい場合には504エラーが起こりうる。例えばELBのアイドルタイムアウト値を60秒、Apache側のKeepAlive Timeout値が15秒とする。
上記のような場合で、クライアントから15秒間リクエストがない状態を考えてみる。Apache側はKeepAlive Timeout値が15秒なので、ELBとのTCPコネクションを切断する。しかしELB側はKeepAliveTimeout値が60秒以上を想定しているので、KeepAliveによる一定周期の確認前にリクエストが来てしまうと、Apache側から切断されてもう使えないTCPコネクションを利用しようとしてしまう。これによってELBは504 Gateway Timeoutエラーを返すのだ。
KeepAliveの確認の周期はなにかで定められている訳ではないが、1秒ごとに行われるのが一般的だ。(ALB側がどのような周期で確認しているかは分からないが)
ALBのログには記録されない
上記によって起こる504エラーはALBのログとして残らない。そのためきちんとCloudWatchによって、ALBが返すステータスコードを監視しなければならない。
ALBには「HTTPCode_ELB_5XX_Count」と「HTTPCode_Target_5XX_Count」がある。前者はALB自体が返すステータスコードで、後者はターゲットグループつまりアプリケーション側が返すステータスコードを記録している。前者には後者である「ターゲットグループが返すステータスコード」は含まれていないので、両方にアラートを設定して監視するのが望ましい。
結論
サーバーはちゃんと監視しような…。
上の放置してたら1時間に1回ぐらい504エラーが出てた、なんてこともありえる。というか自分がそうだった。