目次
はじめに
HTTP/3はQUICというトランスポートプロトコルを利用しています。QUICはUDPを利用していますが、QUIC自体はステートフルなプロトコルです。
ステートフルなQUICを、QUICを解釈しないUDPロードバランサでバランシングしようとするにはいくつかの注意問題点があります。今回は簡単に説明し、NLBでも実験をしてみました。
QUICの用語などは以前書いた記事を参照
asnokaze.hatenablog.com
UDPロードバランサ
QUICはステートフルですので、おなじQUICコネクションのUDPパケットは同じサーバに割り振ってやる必要があります
ロードバランサの分散アルゴリズムにはいくつかありますが、下記をとりあげます
ハッシュ方式
送信元IP, 送信元ポート が一緒であれば、同じサーバに振り分けられるため、一見うまくいくように見えます。
しかし、いくつかの注意点があります
ハッシュの再計算
1つめはハッシュの再計算が起こる点です。
UDPロードバランサは、特定のタイミングでハッシュの再計算が発生しえます。例えば、振り分け先のサーバが増減した際や、設定を更新した際に振り分け表がクリアされるロードバランサもあります(*)
ハッシュの計算が変わった場合、既存の通信の振り分け先が変わってしまいます。運良く再計算する前と同じサーバに振り分けられればいいですが、そうでなければStateless resetとなります(QUICパケットは暗号化する必要があり、鍵を失った際の切断方法)。実際は振り分け先の台数に依存しますが、現在扱っている接続のうち多くの接続が切断されることになります。
コネクションマイグレーションが出来ない
2つめはコネクションマイグレーションが出来ない点です。QUICのコネクションマイグレーションは以前書いたとおりです。
asnokaze.hatenablog.com
QUICでは送信元IPや送信元ポートが変わっても通信をそのまま継続できます。そのため、クライアントはそれらが変わったとしてもそのまま通信を継続しようとします。しかし、送信元IPや送信元ポートが変わるとUDPロードバランサによって振り分けられるサーバが変わるため、コネクションを維持できずStateless resetを行うことになります。
クライアントが開始するコネクションマイグレーションはPath validationの失敗するだけで通信は維持できますが、NATリバインディングなどの中間装置によって送信元ポートが変わる場合は先述の通りStateless resetとなります。(追記) NATの変換テーブルから消えてる場合ですので、通信がない時間がある場合に起こります。再接続で問題ないケースが多いでしょう。PINGフレームを送ることで回避もできそうです。
NLBを用いたハッシュの再計算の実験
AWS NLBでUDPロードバランサはハッシュを用いて振り分けを行っています(Docs)、ハッシュの再計算が発生するか確認します。
今回は簡易的に、HTTP/3ではなく単純にUDPを使用して実験を行います。
- client: UDPで指定されたNLBに定期的にメッセージを送ります。(今回は送信元ポートを5つ用意し、並列にメッセージを投げる)
- server: UDPでリッスンし、UDPパケットが届くと、自身のホスト名を返します。
(コード: https://gist.github.com/flano-yuki/4e70d4d38da87018d4ed8c035d27b765 )
今回はEIP付きのNLB(リスナープロトコルはUDP)で、5つのインスタンスをターゲットグループに登録します (手抜きで、全部unhealtyで均等に処理させる)
- ip-10-xxx-yyy-232
- ip-10-xxx-yyy-181
- ip-10-xxx-yyy-225
- ip-10-xxx-yyy-74
- ip-10-xxx-yyy-119
clientを実行すると、5つの送信元ポートでメッセージを送信し受信するのを、数秒おきに行います。5つの送信元ポートは同じ順序で使うため、縦に同じホストが表示されます。
しかし、インスタンスの増減を行ったタイミングで、ハッシュ計算が代わり振り分け先ホストが変わるのが確認できます。
$ cat ./client.rb ip-10-xxx-yyy-225 ip-10-xxx-yyy-232 ip-10-xxx-yyy-181 ip-10-xxx-yyy-74 ip-10-xxx-yyy-181 ip-10-xxx-yyy-225 ip-10-xxx-yyy-232 ip-10-xxx-yyy-181 ip-10-xxx-yyy-74 ip-10-xxx-yyy-181 ip-10-xxx-yyy-225 ip-10-xxx-yyy-232 ip-10-xxx-yyy-181 ip-10-xxx-yyy-74 ip-10-xxx-yyy-181 ip-10-xxx-yyy-225 ip-10-xxx-yyy-232 ip-10-xxx-yyy-181 ip-10-xxx-yyy-74 ip-10-xxx-yyy-181 ip-10-xxx-yyy-225 ip-10-xxx-yyy-232 ip-10-xxx-yyy-181 ip-10-xxx-yyy-74 ip-10-xxx-yyy-181 (timeout) (timeout) (timeout) ip-10-xxx-yyy-74 (timeout) ip-10-xxx-yyy-232 ip-10-xxx-yyy-181 ip-10-xxx-yyy-74 ip-10-xxx-yyy-74 ip-10-xxx-yyy-74 ip-10-xxx-yyy-232 ip-10-xxx-yyy-181 ip-10-xxx-yyy-74 ip-10-xxx-yyy-74 ip-10-xxx-yyy-74
ですので、HTTP/3 (QUIC)をNLBで負荷分散するのは適切とは言えません。
QUICの負荷分散について
QUICのロードバランシングについてはIETFでも議論が行われています。
基本的にはQUICを解釈してロードバランスを行います。
QUICのコネクションIDにサーバの識別子を暗号化して入れておき、それを見て適切なサーバに振り分けるなどの方法が考えられています。
その他にも、間違ったサーバに振り分けられた際に、自分宛てでないパケットを適切なサーバに転送するなども考えられますが、構成が複雑になってしまいそうですね。
ということで、通常はHTTP/3を終端しバックエンドにはHTTP/1.1で振り分けとかを行う構成が普通かなとは思います。(ゆくゆくはマネージドロードバランサも対応してくるでしょう)
QUIC-LBの提案仕様や、コネクションID-awareなNATに関するドキュメントを読むとより深く理解が出来ると思います(読めてない)
- draft-duke-quic-load-balancers-05 - QUIC-LB: Generating Routable QUIC Connection IDs
- draft-duke-quic-natsupp-00 - Network Address Translation Support for QUIC
(*): 一般的なUDPロードバランサすべてに当てはまるわけではないので、表現を変更しました。
以前「UDPロードバランサは、特定のタイミングでハッシュの再計算を行います。例えば、振り分け先のサーバが増減した際や、設定を更新した際にハッシュの再計算が起こります。」
以後「UDPロードバランサは、特定のタイミングでハッシュの再計算が発生するものもあります。例えば、振り分け先のサーバが増減した際や、設定を更新した際に振り分け表がクリアされるロードバランサもあります(*)」