頭を悩ませないDNSのスケーリング
DNSインフラは、ネットワークの静かな基盤です。トラフィックの急増や局所的なDDoS攻撃によって、リカーシブ(再帰)リゾルバーのCPU使用率が100%に達するまでは、その存在を意識することはありません。私は数年間、手動のフェイルオーバーや厳格なIPtablesルールに苦労してきましたが、dnsdistを本番環境に導入したことで状況が一変しました。単にトラフィックを分散させるだけでなく、DNSスタック全体に対するプログラム可能なシールドを手に入れることができたのです。
dnsdistは、プロトコルを認識するプロキシだと考えてください。IPとポートしか見ない標準的なレイヤー4ロードバランサーとは異なり、dnsdistは実際のDNSヘッダーを検査します。クエリタイプ、ドメイン名、フラグを理解できるのです。これにより、リクエストの実際の内容に基づいてトラフィックをルーティングできます。この機能のおかげで、標準的なBIND構成ならクラッシュしていたであろう50,000 QPS(Queries Per Second)のフラッド攻撃から、私のインフラを救うことができました。
クイックスタート:5分で稼働開始
Bind、PowerDNS、Unboundなどのバックエンドリゾルバーがある場合、その前段にdnsdistを配置してトラフィックの交通整理をさせることができます。ここでは、Ubuntu 22.04または24.04で基本的なインスタンスを稼働させる方法を紹介します。
1. インストール
デフォルトのリポジトリにもdnsdistは含まれていますが、最新のセキュリティパッチ(バージョン1.9.xなど)を適用するために、公式のPowerDNSリポジトリを使用することをお勧めします。クイックなラボテストであれば、標準リポジトリで問題ありません。
sudo apt update
sudo apt install dnsdist -y
2. 基本設定
設定は /etc/dnsdist/dnsdist.conf に記述します。ここでは、ポート53でリッスンし、Luaベースの構文を使用して2つのアップストリームリゾルバーにクエリを分散するシンプルなバランサーをセットアップします。
-- 全てのインターフェースでリッスン
setLocal('0.0.0.0:53')
-- ヘルスチェック付きのバックエンドサーバーを定義
newServer({address="8.8.8.8", name="google-primary", checkName="google.com."})
newServer({address="1.1.1.1", name="cloudflare-secondary", checkName="google.com."})
-- リアルタイム監視用のWebインターフェースを有効化
setWebserverConfig("0.0.0.0:8083", "セキュアなパスワード")
3. 起動
サービスを再起動して変更を適用します:
sudo systemctl restart dnsdist
sudo systemctl enable dnsdist
dig @127.0.0.1 google.com でテストしてみましょう。dnsdistが2つのアップストリーム間でシームレスにクエリを転送するようになります。
真の魅力:インテリジェントなトラフィック処理
dnsdistの真の力は、ディープパケットインスペクション(DPI)にあります。本番環境では、トラフィックを「プール」に分割することがよくあります。ミッションクリティカルな内部ドメインは優先度の高いサーバープールにルーティングし、一般的なインターネット検索はパブリックリゾルバーに送信するといった運用が可能です。
スマートな負荷分散ポリシー
デフォルトの leastOutstanding ポリシーは画期的です。サーバーを盲目的に交互に切り替えるのではなく、dnsdistは各バックエンドに対して現在処理中のクエリ数を追跡します。重い再帰ルックアップなどが原因で1つのサーバーの応答が遅くなった場合、dnsdistはそのレイテンシを検知し、新しいクエリをより高速なノードに振り替えます。その他のオプションには以下があります:
- firstAvailable: プライマリ/バーストシナリオに最適です。
- wrandom: スペックの異なるサーバー間での重み付けランダム分散。
- roundrobin: 古典的な順次分散。
ライブコンソール
インタラクティブコンソールは、私のお気に入りのトラブルシューティングツールです。dnsdist -c を実行して、ライブのLua環境に入ります。設定ファイルを触ることなく、リアルタイムでクエリの多いドメインを確認したり、悪用されているIPをブロックしたりできます。これは、計器なしで飛行するのと、DNSトラフィックの完全なフライトレーダーを持っているのとの違いのようなものです。
DNSファイアウォールの構築
dnsdistはエッジに位置するため、悪意のあるトラフィックを遮断するのに最適な場所です。ここでフィルタリングを行うことは、重い再帰エンジンにクエリを到達させるよりも、CPUサイクル的に大幅に低コストです。
1. IPごとのレート制限
特定のクライアントがリソースを独占するのを防ぐために、スロットリングをかけることができます。このルールは、各ユニークIPを毎秒10クエリに制限します。それを超えるものは即座にドロップされます。
-- 10 QPSを超えるIPからのトラフィックをドロップ
addAction(MaxQPSIPRule(10), DropAction())
2. 「ANY」クエリの無効化
攻撃者はDDoS攻撃の増幅(アンプ)に ANY クエリを頻繁に使用します。実際に ANY 応答を必要とする正当なアプリケーションはほとんどないため、1行でこれらを無効化できます。
-- 増幅攻撃を防ぐため、すべてのANYタイプクエリをブロック
addAction(QTypeRule(DNSQType.ANY), DropAction())
3. 即時のドメインブラックリスト登録
フィッシングドメインやボットネットのC2サーバーを特定した場合、ゲートウェイでシンクホール化できます。私はこれを利用して、既知の悪意のあるドメインが解決される前にブロックし、500人以上のユーザーを保護してきました。
-- 特定の悪意のあるドメインへのリクエストをドロップ
addAction("malware-callback.com", DropAction())
本番環境での安定運用のヒント
大規模にdnsdistを運用するには、信頼性のための追加ステップがいくつか必要です。高トラフィックのクラスターを管理することで学んだ内容を共有します。
堅牢なヘルスチェック
サーバーが単に「起動している」かどうかを確認するだけでは不十分です。checkName を使用して、サーバーが実際に有効なDNSレコードを返していることを確認してください。バックエンドが SERVFAIL を返し始めた場合、dnsdistは数秒以内に自動的にローテーションから外します。
パケットキャッシュの有効化
パケットキャッシュは非常に強力です。繰り返されるクエリをメモリから提供することで、バックエンドの負荷を40%以上削減できます。私のテストでは、再帰ルックアップが20〜50msかかるのに対し、キャッシュされた応答は1ms未満で返されました。
-- 10,000件のユニークな応答をキャッシュ
pc = newPacketCache(10000)
getPool(""):setCache(pc)
ダッシュボード
内蔵のWebダッシュボードでは、キャッシュヒット率やレイテンシ分布を視覚的に把握できます。異常を察知するために不可欠です。不正アクセスを防ぐため、管理用VPNのIPからのみアクセスできるように制限してください。
dnsdistを導入することで、DNSは静的なサービスから、プログラム可能で回復力の高いゲートウェイへと進化します。軽量でありながら、控えめなハードウェアでも数百万のクエリを処理でき、現代のウェブの予測不可能性を乗り切るために必要な細かい制御を提供してくれます。

