2/11-13に開催されたJANOG57のNOCチームに、サーバーチーム(SV)の学生リーダーとして参加してました。
今回のNOCでの目的の一つに、NOCについての情報や得られた知見を公開する、というのがありました。 まずはSVの構成 各コンポーネントの詳細は担当してくれたメンバーさんが記事を書いてくれるはず。 僕も今後NOCで得られた知見とか色々書いて行けたらと思います。
NOCチームの公式ページは以下です。 メンバーのブログ記事などNOCに関連する情報は公開資料のページに集まるはずなのでぜひ見ていただけると嬉しいです。
サーバーチーム (SV) について
SVではDNS、DHCP、監視基盤(メトリクス等)、ログ収集の構築・運用を担当しました。 これをリーダー2人(学生1人(自分)+大人1人)、メンバー11人(学生8人+大人3人)で構築していました。なかなか大所帯ですね。 SVのメンバーはDNS、DHCP、メトリクス、ログの4つのサブチームに分かれてそれぞれ構築・運用をしてもらいました。
NOCチームには他にも、
- ルーターやスイッチなどL2/L3や対外接続まわりを構築するバックボーンチーム(BB)
- アクセスポイントなど無線周りを構築するアクセスポイントチーム(AP)
- 会場内のケーブルを作成し敷設するケーブルチーム(CB)
などのチームがあります。 各チームの学生リーダーは全員友達だったのでチーム間の調整はしやすかったです。
SVで構築したもの
SVで構築したもの全体の構成図が下図になります。 DNSとアプライアンスを除きほとんどのサービスはクラウドに構築しています。 そのおかげもあって事前に色々作業できたので、ほとんど構築は完了している状態でホットステージを迎えることができました。

それにしても気づいたらなかなかリッチな構成になってました。 今回はさくらインターネットさんのNOCチームだったこともあり、さくらのクラウドのリソースを必要な分はどうぞ、とかなり自由に使わせていただけたのが大きかったなと思います。 ここまで贅沢にクラウドのリソースを使えるNOCもなかなかないと思うので貴重な機会でした。
サーバー群を構築するために使用した Terraform, Ansibleは以下のリポジトリで公開しています。 github.com
ちなみに物理構成図はこんな感じです。たくさんサーバーが建ってたのが分かると思います。実はここにも載っていない補助的なサーバーがまだまだあって、クラウドでは40台を超えるサーバーが動いていました。*1 クラウドのリソースなどはTerraformに残っているので、興味のある方はjanog57-infraリポジトリを確認していただければと思います。

このブログではDNS、DHCPの構成などについて説明します。 今後監視基盤、ログ収集、IaCなどについての記事も書く予定です。
DNS
まずはDNS(フルサービスリゾルバ)についてです。構成のコンセプトは「サービス断を最大限防ぎつつ、latencyを小さくする」です。 短期間のイベントであることとメンバー全員がDNSに慣れているわけではないことを考えると、ハードウェア故障よりも設定ミスなどを意識した冗長構成が必要だと考えていました。 きれいなフォールバック構成は余計設定難易度を上げるだけなので、今回はコンポーネント同士をできる限り独立させ、最悪の場合一部を落としても他のコンポーネントには影響させずにサービスを継続できるような構成を意識しています。 後述しますが、5台のDNSサーバー同士や、各DNSサーバーの中でUnboundとKnot Resolverを独立させているのはこのような背景意識がもとになっています。
全体構成
今回の構成の一番の特徴は各会場とクラウドと合わせて5台のDNSサーバーが動いていていることだと思います。 理由はいくつかあるのですが、一番大きいのはlatencyの観点です。 会場からクラウドまではネットワーク的にも物理的にも一定の距離があるので、クラウドのDNSでは多少のlatencyがかかってしまいます。*2 しかし、現代のインターネットではDNSはほぼ全ての通信で使われており、一つのページを表示するために複数の名前解決が必要な場面も少なくないため、快適にインターネットを使用するにはDNSのlatencyを小さくすることが重要だと考えました。
そこで、DNSのlatencyを削減するため、(さくらの全体リーダーとも相談の上で)さくらさんの本社ラックで眠っていたサーバーを3台を各会場に設置し、そこでDNSサーバーを動かすことにしました。 また、機材的にも電源的にも構築コスト・設定難易度的にも各会場で冗長を組むことは難しかったため、DHCPで複数のDNSサーバーをクライアントに配布し、クライアントのフォールバック機構に頼る構成としました。 配布するDNSサーバーの順番は実際にレスポンスが返ってくるまでの時間を測定した結果 会場 (約5ms) <クラウド (約15ms) <別会場 (約26ms) だったためこの順番にしています。 ここでのlatencyはキャッシュヒットしたときの値です。 dnseval を用いて測定してもらいました。

ちなみにホットステージ期間にカオステストを実施しており、DNSについては「NOCメンバーにprimaryとして配っているDNSサーバーの電源を落とす」という操作をしていました。 各チームのリーダー以外には内容を共有せずに実施していたのですが、DNSがおかしいという報告が上がることも無く問題なく名前解決できていたことを確認しました。 実際はある程度のダウンタイムが発生してしまうと考えていた*3のですが、現代の主要なOS/ブラウザに実装されているフォールバック機構は思っていたよりも優秀でした。
各DNSサーバーの構成
各DNSサーバーは dnsdist + Unbound (+ unbound_exporter) + Knot Resolver で構成しています。 5台とも同じ構成で、後述するAnsibleでデプロイしています。
dnsdistの裏でUnboundとKnot Resolverを動かすようにしたのは、フルリゾルバとして動くメインの2つのソフトウェアについて、実装のバグを踏んでしまったとか、(メンバーもDNSに慣れているわけではないので)設定ミスがあったとかの最悪の場合にもとりあえず落とす、という手段を取ってサービス断を回避できるためです。 実装の多様性を確保しつつ、dnsdistのヘルスチェック機能で一方で不具合が生じてもサービスへの影響を抑えながら切り離せる柔軟性を持った構成です。 ちなみに監視はPrometheusベースのメトリクス監視と、blackbox_exporterのDNSモジュールを用いた死活監視をしていました。*4

パフォーマンスチューニングも結構がんばってました。 負荷試験は resperf でUmbrella Popularity List をテストデータとして使用して行いました。 ドメインの重複が無いためほぼほぼキャッシュが効かないデータですが、これでも定常時2000qps程度、ピークなら3000qpsを耐えれる程度を目指してチューニングしていました。 目標値は RubyKaigi2023のデータ から、max5000人程度(→10000クライアント?)来そうな事を考えて計算した値です。 これまでのNOC経験上5-6割程度はキャッシュヒットするだろうとは思っていたのですが、*5これを再現できるようなテストデータはないので、安全側に倒してPopularity Listで同じ目標値を達成することを目指しました。 今回の構成なら一応複数のDNSサーバーにクエリが分散されるのでさらに余裕あるはずではあるんですが、サービス断を防ぐことを優先してきれいな負荷分散は目指しておらず最悪の場合1台に集中する構成なので、単体でも耐えれる程度を目標値にしています。
実際の話
実際どうだったかというと、実は1日目はJAM BASE 4F-6F用DNSでACLまわりで色々トラブルがありました。 具体的にはv6のprefixが変わっていたのをdnsdistのACLに反映できてなかったとか、ルーター側でオンプレサーバーへのDNSパケットをdenyしてしまっていたとか...... クラウドのDNSは問題なく使える状態だったので、クライアントが無事フォールバックしてくれて名前解決は問題なくできていたのですが、構築後の動作確認が十分ではなかったですね... 他チームの構築が全て完了しないとテストできないので時間が無かったというのもありますが、テストすべきことをリスト化するなりスクリプト書いておくなりしておけば良かったかもな、と思っています。*6 それにしてもL2/L3からL7までが複雑に絡み合うNOCのネットワークにおいて、分業された各チーム間で整合性を保ち続けるのはやっぱり難しいですね。 このような課題をシステムで解決しようとしているのが僕の研究テーマだったりします(Network Verificationという分野です)。
メトリクスデータはまた別の場所で整理して出す予定ですが、ハズレ値を除外すると5台を合計しても1000qpsには収まってそうな感じでした。 3000qps(しかもキャッシュが使えないデータで)はだいぶ過剰だったかもしれない......ともあれ今後のNOCで負荷試験する際に参考にできそうなデータを取得できたので良かったです。 ハズレ値というのは、会期中の10000qpsを超えたタイミングのことです。クエリログは取得していなかったので詳細は不明ですが、メトリクスを確認する限りこのタイミングではキャッシュヒット率が平常時とは大きく異なっていたのでおそらく人為的なクエリ列による何かだろうと推測しています。
DNSを実際どのように構築していったかについてはDNSを担当してくれたメンバーによるブログ連載が始まってるのでぜひ見ていただければと思います。
DHCP
今回のDHCPはただ単にクライアントにIPアドレスを配るだけでなくBBチームやAPチームの運用を支えていました。実はすごいぞDHCP。
DHCPはWi-Fiを使う上で欠かせないコンポーネントです。そのため、DNSと同じくサービス断をできる限り回避するような堅牢な構成にしました。 今回は、クラウド上で事前構築・検証が可能な利点を活かして、gatewayの負荷分散やAPへの固定アドレス配布など他チームの課題を解決する役割も担っていました。
DHCPの構成
DHCPサーバーはクラウド上に2台構築しました。 特徴は片方に不具合が生じても他方が一切の影響を受けずにサービスを継続できる、2台が独立した冗長構成を取ったことです。 アドレス設計をしているBBチームに依頼してDHCPで配るアドレス範囲を広め(半分でも問題ない程度)に用意してもらい、それぞれのサーバーに半分ずつ別の範囲をアドレスプールとして設定するようにしました。 そして、DHCP relay先として2台のDHCPサーバーを設定してもらいました。これにより、通常時は2台がレスポンスを返してクライアントがどちらを採用するか選択、仮に片方が停止してしまっても他方は問題なくレスポンスを返せるような構成となっています。 アドレス範囲を広めに用意してもらっているため、片方しか動いていない状態でも問題なくサービスを継続することができます。
Kea DHCPには複数台を連携させて耐障害性を高めるKea HAという機能がありますが、今回はあえて採用していません。 この選択の背景には以下のような背景や意図があります。
- 連携トラブルの排除:サーバー間のステート同期の失敗による不整合や、スプリットブレインなど、HA特有トラブルの可能性を排除したかった
- 設定ミスへの耐性:物理障害よりも設定ミスに起因するトラブルの方が可能性としては大きいと考え、リッチな機能を用いた冗長構成よりもシンプルな構成を保つ方が結果として堅牢になると判断した
- 十分なアドレス空間:配布するアドレスはプライベートIPアドレスであり十分なアドレス空間があったため、倍の大きさのアドレスプールを確保することが可能だった
- プロトコルを信じた冗長構成:DHCPというプロトコル自体が複数のサーバーが存在することを前提に設計されているため、サーバー側でHA構成を取らなくても問題ない *7

監視・可視化
Kea DHCPの管理・監視は、ISC公式の管理ソフトウェアであるStorkを採用しました。 Storkは設定や状態を確認できトラシューに役立つGUIとPrometheusにKea DHCPのメトリクスを開示するAPIを提供してくれます。 運用では、普段の全体監視ではメトリクスAPIから収集したデータをGrafanaで可視化、個別のトラシュー時などはStorkのWebUIも使って調査、といった具合で使い分けていました。 Kea DHCP と Storkについては以下の記事でも紹介しています。
Kea DHCP + Stork でDHCPサーバーを構築して Grafana + Prometheus で監視してみた - crashRT のブログ
実は一部サブネットでリース数が 1.8 * 1019 程度の非常に大きな値を叩き出す自体が発生していました。
値が264程度なことから、これはKea DHCPが assigned_address_total メトリクスを計算する過程で負の値になってしまい、それが符号無し整数として表示されてしまっているのが原因だと考えています。
公式ツールであるStorkなら解決しているのでは?という淡い期待からStork APIからメトリクスを収集する独自のexporterをメンバーに作ってもらったんですが、結局解決には至りませんでした。
正確な値を知るにはリースを管理するDBを直接叩いて取得するしかなさそうな感じがしています...
試行錯誤の詳細は色々調査してくれたメンバーにブログ化をお願いしているのでどこかで公開されるはずです。
作ってもらったexporterは以下のリポジトリで公開しています。
GitHub - janog57-noc/stork-subnet-exporter · GitHub
client classesを用いたgatewayの分散 (with BB)
今回のコングレのネットワークにはゲートウェイが2台存在しているのですが、この2台でうまく負荷分散させたいという要望がBBチームからありました。
当初は2台のDHCPサーバーにそれぞれ別のアドレスを設定するだけ、というシンプルな構成を想定していましたが、BBによる事前検証の結果、7:3程度まで偏ってしまうことが判明しました。(本番では最大10:1程度まで偏ったサブネットも存在しました)
この偏りを解消するための方法として「Kea HAのload balancing機能を使えばいい感じになるのではないか」という提案もありましたが、前述の通りKea HAでの連携トラブルのリスクを負いたくなかったため採用しませんでした。 色々議論した結果最終的に採用したのが、MACアドレス末尾によってDHCPで通知するgatewayアドレスを変える、という方法です。
具体的にはKeaの client-classes を使い、クライアントの(16進数表記での)MACアドレス末尾が奇数か偶数かを判定し、それに基づいてroutersオプションで配布するアドレスを変えるようにしました。
"client-classes": [ { "name": "Class-MAC-Odd", "test": "match('[13579bdfBDF]', substring(hexstring(pkt4.mac, ''), -1, 1))" }, { "name": "Class-MAC-Even", "test": "match('[02468aceACE]', substring(hexstring(pkt4.mac, ''), -1, 1))" } ],
"option-data": [ { "name": "routers", "data": "{{ subnet.gateway }}" }, {% if subnet.second_gateway is defined %} // コングレ gateway負荷分散 { "name": "routers", "data": "{{ subnet.second_gateway }}", "client-classes": ["Class-MAC-Even"] }, {% endif %}
補足:コンフィグの詳細について
「Class-MAC-Evenに分類された場合のみgatewayアドレスをもう一つのアドレスに変える」という構造にしています。
これにより、仮にクラス分けがうまくできないクライアントが存在した場合でもなんらかのgatewayアドレスは配布されるだけでなく、
gatewayが一つしか存在しないサブネットと記述を共有することができます。
以下のドキュメントを参考にしました。
16. Client Classification — Kea 3.0.2 documentation
このアプローチは確実にgatewayを半々に近い割合でで分散できるだけでなく
- 2台のDHCPサーバーの独立を保てる
- DHCPサーバーが片方落ちてもgatewayの分散は保つことができる
というメリットがあります。 デメリットとしては、トラシュー時にどちらのgatewayを通ったかを確認するにはMACアドレスを確認しなければならない、という点がありますが、シンプルかつ確実に分散できるメリットを取りこの構成としました。
実際会期中もほとんど半々でトラフィックを分散できていたようなので良かったです。 コングレでのgateway分散の話は以下の記事でも説明されているのでこちらもぜひ。
host reservationを用いたAPのアドレスアサイン (with AP)
今回、返却手順の都合によりAPのIPアドレスはDHCPの固定割り当てで設定したいとの要望がAPチームからありました。 そこで、Kea DHCPのhost resrevation機能を用いて、各APのMACアドレスに対してそれぞれ固定でIPアドレスをリースするようにしました。
各APのMACアドレスおよびIPアドレスはNetBoxに登録してもらい、これをもとに固定割り当てを設定することにしました。 APチームによるNetBoxへの登録の話は以下の記事で紹介されています。
NetBoxからのコンフィグ生成は、以下のように行いました。
- メンバーに作ってもらったコードでNetBoxのAPIを叩き各会場のAPのMACアドレスとIPアドレスを取得しYAMLで出力
- 生成されたYAMLをAnsibleの変数として読み込み、Jinja2テンプレートでKeaのコンフィグに落とし込む
メンバーに作ってもらったコードは以下のリポジトリで公開しています。 大量のAPについて手作業でデータを登録するのは労力がかかる割にミスの可能性もあって避けたかったので、いい感じのコードを書いてもらえて助かりました。
GitHub - janog57-noc/ap_mac_ip_mapping: 各APのmac addressとip addressの対応をNetBoxから取得しYAMLにする
APチームから、初期化を省略できたおかげで撤収がスムーズに終わったと言ってもらえたので良かったです。
さいごに
JANOG57 NOCで構築したDNSとDHCPの構成について紹介しました。 なかなか面白い構成だったのではないかと思っています。 会期中も無事安定してサービスを提供できたので安心しました。 この安定稼働は実際に手を動かして構築や地道な検証などをしてくれたメンバーの力があってこそだと思います。メンバーの皆様ありがとうございました!
監視基盤・ログ収集については以下の記事で紹介したので、こちらも見ていただけたら嬉しいです。
*1:載っていないのは他チームが使っていたサーバーとかです
*2:今回さくらのクラウド上に建てた各種サーバーは全て東京第2ゾーンにあります
*3:ある程度ダウンタイムは許容しつつ、クライアント側でのフォールバックが完了すれば再び名前解決できるので、名前解決できない状況が継続するのは避けられる、という設計意図でした
*4:dnsdistとKnot Resolverは最初からPrometheus向けエンドポイントが用意されてるのがやっぱり便利ですね
*5:キャッシュヒット率で参考にしたのは以下の記事に書いた内容です。
*6: sidan を各会場で動かすとかしても良かったかもしれないなあ。コングレで試す程度で終わってしまっていたんですが、もっと本格的に使っても良かった気がします
*7:DHCPには色々な実装が存在する(特にDHCP relay)ので、全ての実装が前提としているはずであり枯れた仕様であるRFC2131を前提とする方が多様な実装との連携がうまくいくのではないか?という理由もあります。DHCPの色々な実装についてはまたどこかでLTか何かしたいなあと考えています。