ラウンドロビンDNSを理解する
(blog.hyperknot.com)- OpenFreeMap の運営者は、複数地域の VPS を同じサブドメインの A レコードにまとめて ラウンドロビン DNS を構成し、ブラウザと Cloudflare が実際にどのサーバーを選ぶのかを実験した
- 別途 ロードバランサー を使わずに負荷分散と障害回避を期待できるが、実際の結果はクライアントのアドレス並べ替えやリトライ方法に大きく左右される
- 米国・欧州・シンガポールの VPS 3台でテストした結果、Chrome と Firefox は起動時にランダムなサーバーを固定する傾向があり、Safari と curl は繰り返しリクエストした後、近い EU サーバー に収束した
- 一部のサーバーがオフラインになると、ブラウザと curl はすばやく代替サーバーへ切り替えたが、Cloudflare プロキシ経由のリクエストはクライアント IP ごとに決めた origin を使い続けるため、521 エラー が発生する可能性がある
- Cloudflare がオフラインの origin やレイテンシの低いサーバーを適切に選べない場合、ラウンドロビン DNS ベースの分散は、ユーザーの場所と関係なく遅いサーバーにつながる可能性がある
ラウンドロビン DNS の基本アイデア
- 一般的な VPS ベースの Web サイトは、DNS プロバイダーで 1つの A レコード を追加し、特定の IP にトラフィックを送る
- ラウンドロビン DNS は、同じサブドメインに複数のサーバー IP を指定する方式
- 例は
rr-direct.hyperknot.comとrr-cf.hyperknot.comに複数の A レコードを設定した構成
- 例は
- この構成では、複数のサーバーへ負荷を分散し、オフラインのサーバーを避ける動作を期待できる
- ほとんどの DNS プロバイダーで、別途ロードバランサーなしに設定できるため、シンプルでほぼ無料に近いアプローチ
- Cloudflare のようなサービスのロードバランシング機能は、コスト負担が大きくなる可能性がある
クライアントはどの基準でサーバーを選べるのか
- 関連標準として RFC 8305 Happy Eyeballs と RFC 6724 が登場する
- RFC 8305 のアドレス並べ替えセクション は、状態を持つクライアントが各アドレス経路の予想往復時間(RTT)の記録を持っているなら、より低い RTT のアドレスを優先する宛先アドレス選択ルールを追加すべきだと説明している
- 実験者はこれを次の動作として理解した
- サーバーがオンラインかオフラインかを確認する
- オンラインのサーバーを ping 時間 を基準に並べ替える
実験構成
- 世界の3地域に VPS を作成した
- 米国
- 欧州
- シンガポール
- Cloudflare にはプロキシされた A レコード3件と、プロキシされていない A レコード3件を設定した
- 各サーバーは nginx で同じレスポンス構造を提供する
- すべてのパスへのリクエストは
color.pngに書き換えられる /serverは/etc/hostnameをtext/plainとして返す
- すべてのパスへのリクエストは
color.pngは 1px の PNG ファイルで、サーバーごとに色が異なる- US: 緑
- EU: 青
- SG: 赤
- ホスト名は
test-eu、test-us、test-sgで区別される - テスト地点が欧州のため、最も近い EU サーバー が選ばれるのが期待される動作
- HTML テストページ は 10x10 グリッドにランダムな画像を埋め、サーバー選択結果を可視化する
すべてのサーバーがオンラインのときのクライアント別の動作
- Chrome は複数の場所の中からややランダムに1つを選んだ後、一度選択したサーバーにとどまる傾向を示した
- 数時間後に選択を再評価する
- テストでは、最も遅いシンガポールサーバーに数時間固定されることもあった
- HTTP/2 を使わない場合は、2つのサーバーの間でランダムに選び、パターンを作ることもある
- Firefox も Chrome と似た動作をする
- 起動時にランダムな場所を選ぶ
- ブラウザを再起動すると、別のランダムな場所を選ぶことがある
- Safari は常に最も近いサーバーを正しく選択する
- サーバーが一時的にオフラインになってから復帰しても、数回リロードすれば再び EU サーバーを見つける
- curl も近いサーバーへ補正される
- 初回実行ではそうならない場合があるが、コマンドを2回実行すると必ず最も近いサーバーへ移動する
- 例では最初のリクエストが
test-us、次のリクエストがtest-euに変わった
Cloudflare プロキシ経由時の動作
- Cloudflare はクライアント IP を基準にランダムな場所を選び、その後も同じ場所を使い続ける
- 観察された動作は
client_ip_hash modulo server_numに似ている - 自宅 IP では、どんな操作をしても Cloudflare が米国サーバーへ接続した
curl https://rr-cf.hyperknot.com/serverの結果はtest-usになる
- モバイルホットスポットでは常に EU サーバーへ接続した
- 複数の VPS で同じ curl コマンドを実行すると、各 VPS は世界中のランダムな場所に接続されるが、常に同じサーバーを使う
- 例の結果は
test-sg
- 例の結果は
一部のサーバーがオフラインのときの違い
- 米国サーバーで
service nginx stopにより nginx を停止した後、動作を確認した - Chrome、Firefox、Safari、curl はいずれもオフラインのサーバーを検出し、別のサーバーを選択する
- 読み込み中にサーバーを落としても、1秒未満で補正されるほど 代替接続 が高速に動作する
- Cloudflare はオフラインのサーバーを検出できない
- クライアント IP に対して一度決めたサーバーへ、オンラインかどうかに関係なくアクセスし続ける
- そのサーバーがオフラインなら、ユーザーはエラーを受け取る
- curl の結果は
error code: 521
Cloudflare に対する疑問と限界
- Cloudflare がオフラインの origin を検出できない動作は、ネットワークの バグ である可能性が高いと見ている
- Cloudflare ドキュメントの zero downtime failover を根拠に、ブラウザや curl のように動作すべきだと判断した
- 少なくともオフラインのサーバーは検出されるべき
- Safari のように、最も低いレイテンシのサーバーを選択できればなお良い
- 現在の動作では、米国サーバー1台とニュージーランドサーバー1台を置いた場合、米国ユーザーの50%がニュージーランドサーバーからレスポンスを受ける可能性がある
- Safari ユーザーは、Cloudflare を使わない場合より Cloudflare を使う場合のほうが遅くなる可能性がある
- 関連する HN の議論 には Cloudflare の CEO と CTO が回答した
- 実験を継続運用するため、世界中の3つの VPS コストなしで HTTPS とラウンドロビン DNS をサポートするサーバーレスプラットフォームがあるかどうかも質問している
1件のコメント
Hacker Newsのコメント
うーん、権威DNSチームにここで何が起きているのか説明してほしいと頼んでおいた。
確かな答えが得られたらHNで知らせる。コードを見てから何年も経っているし、その間に多くの人が変更を加え続けてきた :-)
推測では、著者がブログで触れているように、クライアントIPとバックエンドサーバーの親和性を維持しようとする挙動に関係していそうだ。核心的な問いは「バックエンドサーバーが落ちたら、その親和性を破るべきなのか」だが、さらに分かったら自分のコメントへの返信として残すつもり。
この問題に対する初期の解決策の一つがSRV DNSレコードだった。MXレコードに似ているが、メールだけでなくすべてのサービスに適用しようとした方式だ。
MXレコードとSRVレコードでは、クライアントが試行するサーバー一覧と優先順位を指定でき、SRVには負荷分散用の
weightパラメータもあった。しかしSRVは、事実上すべての標準プロトコルに割り込んで全クライアントにSRVを確認させるという政治的争いを避けるため、そのプロトコル標準がSRVの利用を明記している場合にのみ使うよう定められた。そのため、技術的にはHTTPクライアントはSRVを使えなくなった。その後HTTP/2やそれ以降のHTTP標準を作る際にも、Googleなどから出た不適切な論理のせいで、新しいHTTPプロトコルにSRVを明記できなかった。SRVは新規開発では事実上死んでおり、一部の古い標準でだけ使われているようだ。新しい負荷分散の解決策はHTTPSとSVCB DNSレコードに見える。理解しているところでは、TLS 1.3ハンドシェイクを早く開始して往復回数を減らすため、DNSに追加パラメータを入れたがっていた人たちが標準化したものだ。SVCBレコードタイプはHTTPSと同じだが、SRVのように汎用化された形式だ。HTTPSとSVCBにはSRVやMXの優先順位パラメータがあるが、SRVの
weightパラメータはない。標準は公開され、一部のブラウザにはサポートが入ったようだが、全員が有効化しているわけではない。近い将来、ブラウザが実際にどうするか見守る必要がある。GeoDNSをanycastと併用、または代替として使うCDNでルーティング問題を引き起こし得るCNAME flatteningの小細工を要求しない。どこかのプラットフォームがapexドメインではなく
wwwサブドメインを使うよう勧めているのを見たことがあるなら、理由はこれであり、AkamaiがGeoDNSを使っているためHTTPSレコードの標準化を推進した理由の一つでもある。人々がよくドメインapexでWebサイトをホストしたがることを考えると、こうしたレコードがないのは特に痛い。ただしDNSSECに依存できないなら、MXスタイルのレコードを安全に使うのは難しいかもしれない。
DNS負荷分散には、本当に厄介な境界ケースがある。GoのHTTP/2クライアントがラウンドロビンDNSを使う状況を扱ったことがあり、問題が起きた。
GoのHTTP/2クライアントは最初に接続できたサーバーを再利用し続け、DNSを再解決しない。このため、プールに新しいサーバーを追加しても、クライアントが新しいサーバーを発見できないことがある。
特に病的なケースは、すべてのバックエンドが落ちた後、最初のバックエンド1台だけが復旧すると、クライアントが全員そのサーバーに固定されて移動しなくなる状況だ。他のサーバーが復旧しても、すでに最初のサーバーに接続しているため、新規接続するクライアントはほとんどいない。
grpc-goでも似た問題が起きる。gRPCのDNSリゾルバはバックエンド接続が切れたときだけ再解決する。そのため、gRPCクライアントが1台のホストに集中して張り付き、そのまま居座ることがある。サーバー側でMAX_CONNECTION_AGEを設定し、一定時間が経過したら定期的にクライアントを切断して、クライアントにDNSを再解決させるべきだという提案もある。サービスディスカバリにもっと良い標準的な解決策があればと思う。結局できる最善策は、仮想IPベースのリクエスト単位ロードバランサを実装し、ロードバランサにヘルスチェックをさせることのようだ。しかしこれも、問題を仮想IPを実装するシステムへ押し出しているだけだ。ルーティングシステムはバックエンドより相対的に静的だと仮定し、そこから利益が生まれると見ているようだ。
ベアメタルではこれをどうやっているのか気になる。AWS/GCPなどには内部ロードバランサがあることは知っているが、これを実装する秘訣が何なのか知りたい。関連するブログ記事やホワイトペーパーのおすすめも歓迎。
「サーバーが1台オフラインならどうなるだろう? 米国サーバーを止めてみよう:
service nginx stop」とあるが、こういうテストをしてはいけない。クライアントは接続拒否を見て次のIPへ進む。しかし実際には、サーバーがまったく応答しないこともあれば、接続を受け入れたまま沈黙することもある。
その場合はクライアント側のタイムアウトに頼ることになり、信頼性を高めるためのラウンドロビンDNSは突然ずっと魅力の薄いものに見えてくる。
サービスを止めるのは計画作業であり、その場合は先にDNSを更新して対処できる。
SIG_STOPやip/nftablesのDROPのほうが、はるかに現実的なテストだ。「ご覧のとおり、すべてのクライアントがこれを正しく検出し、代替サーバーを選択します」という部分が、厄介な核心です。信頼性はクライアント側で決まります
例えば
systemd-resolvedは、かつては可能な限り技術的に正しく振る舞うとして、常に最も低い IP アドレスを返していました。DNS ラウンドロビンは明確に定義されていないのだから、常に最も低い IP を返しても間違いではない、という理屈でした。騒動の後に変更されましたが、私の知る限り Debian 11 はその動作に固定されていたか、長い間そうでした。また、リトライ動作がひどい、あるいはまったくないアプリケーションも数多く扱っています。「接続拒否が1つ出た。全部キャンセルして終了し、二度と試さないでおこう」というように動作します。するとリクエスト全体の20〜30%が燃えて消えます。
他に選択肢がないなら、受け入れられる解決策です。記事で述べているように、ブラウザのように数回のリトライが設定された品質の高い HTTP クライアントがあるなら、DNS ラウンドロビンはヘルスチェックなどを備えた実際のロードバランサを見つける用途としては問題なく、100%の成功率を提供できます。
しかし DNS ラウンドロビンはロードバランサではなく、ロードバランサのほうが優れています。
以前働いていた場所には、数億件のレコードと60秒 TTL を持つ内部 DNS サーバーがあり、顧客からの着信接続をネットワーク内部の正しいリソースへつなぐカスタム内部ルーティングシステムに使われていました。実際に素晴らしいものでした。ルーティング変更は DDNS 更新と同じくらい簡単で、
NOTIFYによってすべての下位サーバーへ変更をプッシュし、全体に反映されるまでの平均遅延は60秒未満でした。そのおかげで、より複雑なツールを作りやすくなり、単一サーバーからデータセンター全体まで、ボタン1つでサービスから外せるコントロールパネルも作れました。そのシステムにも明らかに荒い部分はありましたが、あの種のシステムとしては高速で、覗き込みやすく、比較的防弾に近いものでした。
フェイルオーバーも同じです。あるリージョンが落ちたら、トラフィックを他のリージョンに均等に広げたいのでしょうか、それとも次に近い隣接リージョンに集中させたいのでしょうか。その動作が重要なら、トラフィック管理の制御権は自分で保持すべきで、他人に渡してはいけません。
今では、その「他に選択肢がない」という状況に至るずっと前に選ぶべき別の解決策があります。
「複数のサーバーに負荷を分散し、どのサーバーがオフラインかを自動的に検出してオンラインのサーバーを選択できる」という表現における DNS の自動オフライン検出について、慎重に揚げ足を取るなら、素のラウンドロビン DNS は負荷分散にしか使えません。
クライアントに賢いロジックを入れない限り、可用性状態の検出という面で自動的に起こることは何もありません。記事の導入部はこの点をある程度述べてはいますが、意味を把握するために何度も読む必要がありました。公平に言えば、私の理解力の問題かもしれません。その後で記事の残りを読んでみると、すべてその賢いロジックに関する内容でした。
ブラウザが選んだ 1/N のサーバーレコードが利用不能な場合、プロトコルレベルで自動復旧やリトライは起こりません。
付け加えると「関連するお楽しみ」として、Java の DNS TTL [1] と
.equals()[2] の動作も忘れないでください。[1] https://stackoverflow.com/questions/1256556/how-to-make-java...
[2] https://news.ycombinator.com/item?id=21765788(5年前、コメント168件)
TTL を無視するクライアントもありますが、かなりまれです。
サーバーが落ちると、世界中に分散・キャッシュされた IP アドレスが残り、人々がそのアドレスへアクセスするのを防げない
https://www.cloudflare.com/learning/dns/glossary/round-robin...
負荷分散にもコストがあり、ロードバランサーが接続を微妙に、あるいは露骨に壊してしまう問題もある。あるプロバイダーでは、ロードバランサーの可用性が私たちのホストより悪かったこともある
クライアントを制御できるなら、プラットフォームの DNS API を呼び出して IP リストを取得し、適切にシャッフルして巡回する方式も妥当だ。DNS が壊れた場合に備えて、安定して割り当てられた IP をいくつかクライアントのバイナリに入れておけるなら、さらによい。ただし DNS はたいてい壊れておらず、クラスタを更新するたびに新しい設定やバイナリを配布せずに、運用上の変更に使いやすい
クライアントがブラウザなら、デフォルトの動作はかなりよい。通常は IP を順番に使うので問題になり得るが [1]、それ以外ではリトライ動作がよい。接続拒否が返るとすぐ別の IP を試し、タイムアウトすると少なくともいくつかの別の IP を試す。理想的ではないので、ブラウザにはロードバランサーを使うだろうし、可能なら少なくとも初回のページ読み込みにはそうするだろう。WebSocket などには DNS ラウンドロビンと、ある程度賢い JS クライアントロジックを使うこともできる。それでも、サイト全体に DNS ラウンドロビンを使うこと自体は可能だ
クライアントがブラウザでもなく、自分で制御もできないなら、幸運を祈るしかない
ときには、誰かが DNS キャッシュリゾルバを作る際に TTL フィールドを秒ではなく日数として解釈している、と想定しなければならない点は 100% 認める。そのようなリゾルバの背後にいるクライアントは、DNS を更新するときに問題が起きる。しかしロードバランサーが DNS 名の背後にあり、そのアドレスを変えなければならない時が来れば、その時に同じ問題に直面することになり、その時点では経験もないだろう
[1] RFC の一つは、OS API が応答をプレフィックス一致に基づいてソートすべきだと提案している。IP プレフィックスが階層的で、ネットワーク距離が最も近いサーバーを選ぶ代理指標になるなら筋が通るかもしれない。しかし現実には、数値的に隣接する
/24がネットワーク上で隣接していないことは多い。サーバーアドレスが大きく散らばっている場合、一部のクライアント IP のトラフィックが、数値的に似たサーバー IP 側へ偏るのを見ることがあるもちろん、誰かがローカル DNS を誤って設定したり、質の悪いクライアントを使ったりすることは避けられない。壊れた設定を持つ人々には障害を受け入れてもらうか、同じデータセンター内の別サーバーへ IP を再割り当てする必要がある
こんにちは。Cloudflare の CTO です。Cloudflare のすべての無料アカウントに変更をデプロイし、有料アカウントと同じ動作になるようにしました
ここで述べられていた問題は修正され、すべてのアカウント種別で Zero Downtime Failover が動作するはずです。もう一度テストしてもらえますか?
記事にまとめてくれてありがとうございます。この動作を全員向けに変更できてうれしいです
記事もそれに合わせて更新する。無料アカウントにも適用してくれて感謝しているし、素晴らしい結果だ
これのダークなリミックス版が fast flux hosting で、多くの bulletproof hosting プロバイダーが使っている方式だ
https://unit42.paloaltonetworks.com/fast-flux-101/
ゼロダウンタイム・フェイルオーバー は Pro 以上の機能だと言及してもよいかもしれない
以前、オリジンサーバー保護のドキュメントがプランレベル別に分かれていた頃も、そのように文書化されていたと記憶している。なので、動作やリトライが異なって見える可能性がある