本セッションの登壇者
セッション動画
よろしくお願いいたします。テーマはDB、CDN、キャッシュみたいな話なんですけど、そのうちのDBというものをやめてみた話です。
自己紹介です。
ISUCON本では第5章「データベース」、今日やめるという話をするデータベースの章を担当しております。
名前解決でネットの基盤を支えるDNS
本題に入る前に一般的なWebサービスの構成です。ISUCON本の1章にも出てきますが、この図で矢印で済まされているところに結構大事なものが実は挟まっています。
それが名前解決です。Webサービスの要素を接続するほとんどのところに名前解決が関係します。
実はこのDNSというのは非常に重要なコンポーネントですよというのが、まず最初の話です。
DNSはインターネットにおける重要な基盤技術の1つだというのは、間違いないと思っています。DNSがなかったらIPアドレスを覚えなければなりません。そんなことは人間にはかなり難しいでしょうし、DNSがあるからWebが発展したとも言えるんじゃないかと思います。Webサービスとパフォーマンスにとっても、非常に重要な基盤技術になっています。DNSを切り替えることで、ロードバランスやスケールアウトが実現できたり、1台落ちたので別のサーバーに切り替える(フェイルオーバー)といった可用性の向上ができます。また、ここには書いていませんが、グローバルサーバーロードバランシング(GSLB)といって、ユーザーに近いところのサーバーをDNSで返して、全世界でロードバランスするみたいなこともできています。
「DNSと私」ということで、思い返すとDNSとは結構いろんな関わり方をしてきました。
前職でConsulというミドルウェアを使うようになって、Consul DNSという機能を利用していました。OrchestratorというツールとConsulを組み合わせて、MySQL Primaryのフェイルオーバーを実現したり、サーバー全台にローカルのDNSキャッシュサーバーとしてunboundを入れて利用したり、AWSとGCPをVPNなどで繋げたマルチクラウドの名前解決を動的なクエリ書き換えで実現してきました。現職では権威DNSサービスの開発運用をしています。
権威DNSサービスとは何かということですが、さくらのクラウドではDNSアプライアンスという名前でやっております。Amazon Route53やGoogleではCloud DNSと呼ばれていて、コントロールパネルやAPIによってゾーンの作成だとか、DNSの各種レコードの操作が可能です。昔はDNSサーバーも自前でやっていたかと思いますが、DNSサーバーの管理をサービス提供者に任せることで、脆弱性対策、攻撃対策を含めた面倒なDNSサーバー管理の手間を省くことができます。この攻撃対策というのが次の話になります。
DNS水責め攻撃とは
最近ちょっと一部界隈で話題なのがDNSの水責め攻撃です。ランダムサブドメイン攻撃というほうが攻撃の形をイメージしやすいかなと思います。簡単に説明すると、攻撃対象のドメインに対して、ランダムに生成したサブドメインを頭にくっつけて問い合わせを行うことで、DNSの機能停止や機能低下を狙う攻撃です。
じゃあ実際、どんなのがやってくるのかというと、こちらが攻撃の記録です。TCPダンプで取得してドメインの部分をexample.comに書き換えています。攻撃にはランダムな文字列、意味ありげな単語の組み合わせで来ているものもあります。また、大文字小文字が混ざっているものもありますが、これはGoogle Public DNS仕様のキャッシュポイズニングを防ぐためにこういったクエリが来るということがわかっています。もう1つ、真ん中あたりに、example.com にドットが1つじゃなくて、2つ3つと増えているものがあります。ドットの間をラベルというんですけれども、これが増えることもあります。
攻撃者はオープンリゾルバやPublic DNSに対して大量のランダムサブドメインの問い合わせを発生させます。そうすると、ランダム文字列なのでGoogleでさえDNSキャッシュサーバーにキャッシュが存在しません。その結果、権威DNSサーバーに大量の問い合わせがやってきてDoSとなってしまいます。攻撃のアクセス元を調べると、やはりGoogleやCloudflareからの攻撃が大きくなっています。
さくらのクラウドのDNSサービスでの事例では、2022年の8月にサービスに影響するものが初めて発生しました。それ以来、現在に至るまで断続的に1日数回、攻撃の規模は大小ありつつ継続しています。下のグラフのように800万クエリ/分が数時間から3日間継続したこともあります。3日間この800万クエリが継続すると、Route53だとどれくらいのコストがかかるか調べてみると、100万円ぐらいなんですね。それで、狙いがどこにあったのかみたいなところを調べたりもしました。最大4000万クエリ/分で、このグラフの左側なんて無のところからいきなり立ち上がって、だいたい5000クエリ/分と突然1万倍になるという攻撃がやってきています。
サーバー負荷が爆上がり
このDNSアプライアンスサービスが影響を受けた理由の1つは、やはり大量のクエリがやってきたことでサーバーの負荷が上がってしまうことです。もう1つ大きな問題としてあったのが、DNSサーバーのバックエンドとしてMySQLを利用していたことです。データベースをなぜ使うかというと、お客様のゾーンをたくさんお預かりするのでそれを管理しやすいためですが、反面、負荷が上がりやすい問題があります。
どんな構成になっていたのかを示す図です。一番右側のAPIサーバーからリクエストを受けて、内部のAPIがMariaDBを更新して、それぞれのDNSサーバーにレプリケーションをして、各DNSサーバーのPowerDNSはそれぞれのMariaDBを参照するという構成になっていました。これ自体は非常に安定した構成ですし、それ以前に何か問題が起きるということはありませんでした。
再度、影響を受けた理由ですが、攻撃が来ていない時の通常の名前解決では、DNSサーバー側のキャッシュが有効に働いて、アクセスが5~10分の1に減るので、データベースの負荷があまり大きくありません。しかし、水責め攻撃ではランダムな文字列なので、キャッシュがまったくヒットしない状況が生まれて、SQLが都度発行されます。さらに残念なことに、DNSでは最長マッチを行わないといけないのでリクエスト中にドット、つまりラベルが増えると、その分、クエリが一番長いのはどれかを探すため、最悪N+1が起きて負荷が上がって少ないクエリでも落ちてしまうみたいなことがありました。
そこで、どのような対策をしていたかというと、まずはクラウドでインスタンスの停止起動だけでスケールアップが可能なので、スケールアップをしました。さらにPowerDNSのアップデートで性能が上がり、新しいパラメータなどを入れてさばけるコネクション数やデータベースへの接続数をチューニングしました。また、MariaDBのチューニングや、dnsdistというものをインストールしてフィルタリングを行いました。
まずMariaDBのチューニングです。いくつかやったんですけど、そもそも有効なものはまったくありませんでしたという話になります。
もう1つ、対策の中でこのdnsdistというのも入れました。dnsdistというのは、PowerDNSと同じ開発元がOSSとしてリリースしているDNSのプロキシーソフトウェアです。PowerDNSの前に、別のサーバーとして動かしてDNSのロードバランシングやリクエストのフィルタ、レートリミットなどを行うことができます。これは同じサーバー上にPowerDNSのポートを変えて導入しました。
こちらが設定例です。下から4行目の (3, 16) の3は「3 QPS以上であれば」という意味です。16という数字はIPv4のIPアドレスのレンジの/16にあたるので、ある程度のネットワークをまとめてGoogleからのIPの/16から3 QPS来ていたらドロップします。
じゃあ、これを全部のゾーンに対してやっておけば問題ないじゃないかという話ですが、すべてのゾーンに適用するのは、ゾーン数が多くて設定が爆発するので不可能です。もう1つ、ゾーンにお客様が「*.example.com」もすでに設定していたら、設定自体ができません。そのため、攻撃があった時に攻撃の内容を見て判断するという運用でやっていますので、これも銀の弾丸ではないですね。
ですので、根本のパフォーマンスを強化する必要がやはりあります。そこでRDBMSをやめました。
DBをやめたら起こること
PowerDNSではいくつかのバックエンドが使えるので、その性能評価をしています。一番左側のグラフでラベルが0個の時はキャッシュヒットするので変わりません。MySQL、LMDBというKVSを使ったバックエンド、BIND Zone fileのバックエンドでは性能が変わってきます。MySQLに対して、BIND Zone fileバックエンドでは、ラベルが1個の時は6倍、2個以上になると10倍以上の性能差があります。
なので、このBIND Zone fileバックエンドを使いました。昔から利用されるBINDのゾーンファイルを読み込み、オンメモリで処理するバックエンドで、SQLが都度発行されることがないので早いです。わかりやすいですね。
今、Route53のAPIを叩いてゾーンファイルを設定する、Terraformでシュッとするみたいなことをやっているとまったく見る機会がないと思うんですけれど、ゾーンファイルはこのようなテキストファイルになっています。
これをサーバーに設定する上でゾーンファイルをサーバー間でコピーする必要があります。コピーした先でそれが更新されていれば再読み込みしたり、あるいはお客様がゾーンを消したならPowerDNSからも破棄する必要がある場合もあります。さらにレプリケーションのような高速な反映も求められます。マニュアルに書いてあるんですけど、DNSの反映は15秒から1分以内に行われますと書いてあるので、これがSLOになります。将来的にはスケールアウトしたいし、クラウドのマネージドサービスなどを使えたら実装が楽だというのもいろいろ考えたんですけど、最終的には使わずに自分で実装しました。
RDBMSの代わりを工夫して実装
簡単に仕組みはこんな感じで動いています。APIからMariaDBに対するSQLが少しまだ残ってはいるんですけど、(1) で更新時にゾーンファイル群のtarアーカイブを作っています。これはデータベースのアトミック操作と同じような形で、中途半端な状態で転送されないように、別名ファイルで作成して、mvで元のファイル名にするみたいな感じでやっています。(2) のところはちょっと変わったアイデアなんですけど、各DNSサーバーのworkerは、If-Modified-Sinceが付いたHTTPリクエストを送っています。サーバーはこのリクエストを受け付けて、普通のサーバーは304
を返したり、コンテンツが更新されていれば200 OK
で返しますが、ここでは更新されるまでファイルの更新日付をポーリングしつつ、レスポンスを遅延/ブロッキングするということをやっています。これによって、ポーリングしている最中に実際にお客様が操作をしてゾーンファイルが更新されれば、ポーリングを解いてレスポンスが返る。それによってworkerからサーバーに対するリクエストの頻度を下げて負荷を削減しつつ、更新があれば一瞬で反映される仕組みを作りました。(3) については、受け取ったゾーンファイルを展開してリロードを行う仕組みで、珍しいことはしていません。このようにzone file バックエンド化しつつ、MySQLのレプリケーションのように一瞬で更新が反映されるようなことを実現しました。
まとめになります。DNSはあまり意識されませんがうまく使うとできることがたくさんあると思います。水責め攻撃の対策は難しくて、攻撃対策に限らずあらゆるWebアプリケーションの中で適材適所でRDBMS/DBをやめる選択もあるんじゃないかと思います。そうするとアトミックな更新、レプリケーション、バックアップなどRDBMSがやっていてくれたことを他のミドルウェアで実現したり、自分で実装する必要があるんじゃないかなというところで、今回の話がちょっとでも参考になればいいかなと思います。
おまけ
今、さくらインターネットではエンジニア採用を強化しています。「大規模計算資源のインフラというものを一緒に作りませんか?」ということで、よろしくお願いいたします。
以上になります。