この記事は NTTコミュニケーションズ Advent Calendar 2021 23日目の記事です。
はじめに
こんにちは。デジタル改革推進部の髙田(@mikit_t)です。
業務では社内向けのデータ分析基盤の設計・開発および運用を行なっています。 データドリブン経営を推進するため、社内に散らばる様々なデータを収集・蓄積。データサイエンティストはもちろんのこと、各部署でのデータドリブンな意思決定に貢献できるよう活動しています。
社内のデータ分析コンペティションの環境も我々の分析基盤上で開催しています。 今回はデータ分析については触れませんので、データ分析に興味がある方は 社内でデータ分析コンペティションを開催しました の記事を参照してみてください。
本稿では、オンプレでサーバ運用するにあたって必ず必要になってくる DNSフルリゾルバ のうち、比較的新しい実装の Knot Resolver を紹介します。
書き始めたら大変長くなってしまったので、何回かに分割して掲載していきたいと思います。
DNSフルリゾルバ
おさらい
アプリケーションがホスト名による通信するためには、ホスト名を IPアドレスに変換する必要があります。 一般に、これを名前解決といいます。 名前解決をしてくれるのが「フルリゾルバ」、フルリゾルバに対して名前解決要求を出すのが「スタブリゾルバ」です。 「フルリゾルバ」は OS のネットワーク設定のところに設定する IP アドレスというとわかりやすいかもしれません。
Knot Resolver
チェコ CZ NIC がメンテしている実装で、2014年ごろ登場しました。 シンプルな core と拡張モジュールでの実装となっています。 拡張モジュール開発はユーザも任意に行えます。言語としては C, Lua, Go が使えます。 設定自体も Lua で書きます。
ほかのフルリゾルバ実装ではあまり見かけない、魅力的な特徴は以下の通りです。 かなりモダンな設計になっています。
- シングルスタックでの実装となっており、複雑なスレッドプログラミングをしていない
- キャッシュのバックエンドを永続化できる
- Zero downtime restart
- 複数インスタンスを並列起動することが推奨されている
- インスタンスをひとつずつ順番に再起動することで、ダウンタイムを 0 にできる
- BIND でいう rndc、unbound でいう unbound-control のような管理ツールが用意されていない
- UNIX ドメインソケット経由で、設定の確認・変更を行える
- Prometheus メトリクスのエンドポイントを内蔵している
まだ開発途上の新しいソフトウェアのため、設定項目名がカジュアルに変更されたり、メモリリークの修正が入ったりなどしているのが観測されています。 利用する場合はアップデートの際に ChangeLog をよく読むことをお勧めします。 利用実績としては Cloudflare が利用していると発表しています。
インストール・起動設定
- インストール
コマンドラインについては Ubuntu 20.04 LTS で実施した内容となっています。
https://www.knot-resolver.cz/download/ の通りやっていきます。
# wget https://secure.nic.cz/files/knot-resolver/knot-resolver-release.deb # dpkg -i knot-resolver-release.deb # apt update # apt install -y knot-resolver
Ubuntu 20.04 LTS の公式レポジトリにも Knot Resolver はありますが、3.2.1-3ubuntu2
と古いバージョンのコードベースとなっています。
執筆時点での最新版は 5.4.3
となっています。新しい機能を使うためには、CZNIC 公式レポジトリからのインストールをする必要があります。
- systemd-resolved を止める
ローカルインタフェースの port 53 を listen している systemd-resolved を止めます。
$ sudo systemctl stop systemd-resolved.service $ sudo systemctl disable systemd-resolved.service
systemd-resolved に名前解決要求をするようになっているため、これを無効化します。
$ sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
- 起動設定
パッケージをインストールしてもサービスが有効化されませんので、やっておきます。
$ sudo systemctl enable --now kresd@1.service
- 確認
動いているか確認してみます。
$ systemctl |grep kres kres-cache-gc.service loaded active running Knot Resolver Garbage Collector daemon kresd@1.service loaded active running Knot Resolver daemon system-kresd.slice loaded active active system-kresd.slice
サービス起動OK。ひとつ DNS 名前解決してみましょう。
$ dig engineers.ntt.com @127.0.0.1 ; <<>> DiG 9.16.1-Ubuntu <<>> engineers.ntt.com @127.0.0.1 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26986 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ;; QUESTION SECTION: ;engineers.ntt.com. IN A ;; ANSWER SECTION: engineers.ntt.com. 300 IN A 13.115.18.61 engineers.ntt.com. 300 IN A 13.230.115.161 ;; Query time: 120 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Thu Dec 23 02:20:55 UTC 2021 ;; MSG SIZE rcvd: 78
OK です。
設定
- デフォルト設定
デフォルトの設定ファイルが /etc/knot-resolver/kresd.conf に入っています。
-- SPDX-License-Identifier: CC0-1.0 -- vim:syntax=lua:set ts=4 sw=4: -- Refer to manual: https://knot-resolver.readthedocs.org/en/stable/ -- Network interface configuration net.listen('127.0.0.1', 53, { kind = 'dns' }) net.listen('127.0.0.1', 853, { kind = 'tls' }) --net.listen('127.0.0.1', 443, { kind = 'doh2' }) net.listen('::1', 53, { kind = 'dns', freebind = true }) net.listen('::1', 853, { kind = 'tls', freebind = true }) --net.listen('::1', 443, { kind = 'doh2' }) -- Load useful modules modules = { 'hints > iterate', -- Allow loading /etc/hosts or custom root hints 'stats', -- Track internal statistics 'predict', -- Prefetch expiring/frequent records } -- Cache size cache.size = 100 * MB
--
で始まる行はコメントになります。
内容を見ていきます。
net.listen()
- どのアドレスとポートで、どんなサービスをするかを設定します。
- kind: dns
- 通常の DNS 名前解決のサービスです。
- kind: tls
- DNS over TLS のサービスです。
modules
- ロードするモジュールを指定します。この部分はこのままで特に問題ないです。
hints > iterate
: キャッシュよりもヒントを優先させることを指定しています。stats
: 各種メトリクスを収集します。prometheus エンドポイントを利用する場合は必須の設定です。predict
: キャッシュヒットの効率を高めるため、プリフェッチを行います。
cache.size
- キャッシュに利用するメモリサイズを指定します。
運用において必要となりそうな設定を上げてみます。
- サービスポートの設定
net.listen()
が 127.0.0.1
::1
のみだと自ホストからしか使えません。
オンプレ環境の他の機器からの名前解決要求を受け付けるための IPアドレスとポートを設定します。
net.listen('192.0.2.53', 53, { kind = 'dns' })
- アクセス元制限の設定
net.listen()
でサービス用のアドレスを設定したら、アクセス元制限をする必要があります。
これを書かないと オープンリゾルバ となってしまいますので、特に GIP を net.listen()
で設定する場合には気をつけましょう。
-- ACL modules = { 'view' } view:addr('192.0.2.0/24', policy.all(policy.PASS)) view:addr('127.0.0.1', policy.all(policy.PASS)) view:addr('::1', policy.all(policy.PASS)) view:addr('0.0.0.0/0', policy.all(policy.REFUSE)) view:addr('::0/0', policy.all(policy.REFUSE))
この例では、192.0.2.0/24
とローカルインタフェースからの接続のみ許可、その他は拒否するようにしています。
- log
ログレベルの設定をします。
crit, err, warning, notice, info, debug のいずれかを設定します。デフォルトは notice です。
-- log log_level('debug')
- bogus_log
DNSSEC 検証に失敗したログを出力します。
-- dnssec validation failure logging modules.load('bogus_log')
- nsid
RFC 5001 で定義されている nsid を使うと、複数インスタンスでの運用時、どのインスタンスが答えを返したかがわかるようになり便利です。
-- nsid local systemd_instance = os.getenv("SYSTEMD_INSTANCE") modules.load('nsid') nsid.name(systemd_instance)
- 設定を保存
ここまでの設定を /etc/knot-resolver/kresd.conf
に書いておきます。
Run-time reconfiguration
nc
や socat
を使って UNIX ドメインソケット経由で knot resolver のインスタンスと通信し、インスタンスの設定をライブに確認・変更することができます。
ソケットファイルを確認します。
$ sudo ls -l /run/knot-resolver/control/ total 0 srwxr-xr-x 1 knot-resolver knot-resolver 0 Dec 22 20:13 1
今はインスタンスが 1つしかいないので、ソケットファイルも 1つだけあるのが確認できます。
ソケットファイルを指定して、socat を起動します。
$ sudo socat - UNIX-CONNECT:/run/knot-resolver/control/1 > help() 'help() show this help quit() quit hostname() hostname package_version() return package version user(name[, group]) change process user (and group) log_level(level) logging level (crit, err, warning, notice, info or debug) (snip)
設定内容を確認してみます。
> log_level() 'notice'
ログレベルのデフォルト設定が返ってきました。
キャッシュのクリア
キャッシュのクリアをしてみます。
$ dig engineers.ntt.com @127.0.0.1 (snip) ;; ANSWER SECTION: engineers.ntt.com. 300 IN A 13.115.18.61 engineers.ntt.com. 300 IN A 13.230.115.161
これでキャッシュに engineers.ntt.com
の A レコードが保持されました。TTL は 300秒です。
> cache.clear('com.') { ['count'] = 16, ['round'] = 1, }
com.
配下のキャッシュを削除しました。count
は消したレコードの数です。
再び名前解決を行うと、TTL が 300 の同じ結果が返ってくるはずです。
cache.clear()
は指定された名前空間配下のすべてのキャッシュを消しますが、第二引数に true
を指定すると、その名前だけを削除します。
> cache.clear('com.', true) { ['count'] = 3, ['round'] = 1, }
true
としたため、com.
の 3レコードのみ削除されたことがわかります。
knot-resolver には残念ながら、キャッシュの内容を dump するようなインタフェースはまだ用意されていません
Multiple Instances
インスタンスを2つ追加起動してみます。
$ sudo systemctl start kresd@2.service $ sudo systemctl start kresd@3.service
確認してみます。dig に +nsid をつけて、nsid を要求します。
$ dig engineers.ntt.com +nsid @127.0.0.1 ; <<>> DiG 9.16.1-Ubuntu <<>> engineers.ntt.com +nsid @127.0.0.1 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17096 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ; NSID: 33 ("3") ;; QUESTION SECTION: ;engineers.ntt.com. IN A ;; ANSWER SECTION: engineers.ntt.com. 269 IN A 13.115.18.61 engineers.ntt.com. 269 IN A 13.230.115.161 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Thu Dec 23 03:02:56 UTC 2021 ;; MSG SIZE rcvd: 83
NSID: 33 ("3")
とあるとおり、3番目のインスタンスが返事をしています。
$ dig engineers.ntt.com +nsid @127.0.0.1 ; <<>> DiG 9.16.1-Ubuntu <<>> engineers.ntt.com +nsid @127.0.0.1 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 94 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ;; QUESTION SECTION: ;engineers.ntt.com. IN A ;; ANSWER SECTION: engineers.ntt.com. 278 IN A 13.230.115.161 engineers.ntt.com. 278 IN A 13.115.18.61 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Thu Dec 23 03:02:47 UTC 2021 ;; MSG SIZE rcvd: 78
nsid を返してこない返答もあります。これは最初に起動した、1つ目のインスタンスの返事です。
ソケットファイルも 3つできています。
$ sudo ls -l /run/knot-resolver/control/ total 0 srwxr-xr-x 1 knot-resolver knot-resolver 0 Dec 23 02:06 1 srwxr-xr-x 1 knot-resolver knot-resolver 0 Dec 23 03:02 2 srwxr-xr-x 1 knot-resolver knot-resolver 0 Dec 23 03:02 3
Zero-downtime restarts
複数インスタンスがサービスを分散処理しているのを確認できました。
1つ目のインスタンスに設定を読み込ませるため、再起動してみましょう。
別端末で dig を仕掛けて、名前解決に問題が起きないか確認しておきます。
$ while true; do echo "`date`; `dig engineers.ntt.com @127.0.0.1 +nsid | grep NSID`"; done Thu 23 Dec 2021 03:12:51 AM UTC; Thu 23 Dec 2021 03:12:51 AM UTC; Thu 23 Dec 2021 03:12:51 AM UTC; Thu 23 Dec 2021 03:12:51 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:51 AM UTC; Thu 23 Dec 2021 03:12:51 AM UTC; Thu 23 Dec 2021 03:12:51 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:51 AM UTC; ; NSID: 33 ("3") Thu 23 Dec 2021 03:12:51 AM UTC; ; NSID: 32 ("2")
NSID つき、NSID なし、2種類の応答があります。
$ sudo systemctl restart kresd@1.service
と再起動すると、新しい設定が読み込まれます。
Thu 23 Dec 2021 03:12:53 AM UTC; Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 33 ("3") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 33 ("3") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:53 AM UTC; Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 33 ("3") Thu 23 Dec 2021 03:12:53 AM UTC; ; NSID: 33 ("3") Thu 23 Dec 2021 03:12:54 AM UTC; ; NSID: 31 ("1") Thu 23 Dec 2021 03:12:54 AM UTC; ; NSID: 33 ("3") Thu 23 Dec 2021 03:12:54 AM UTC; ; NSID: 32 ("2") Thu 23 Dec 2021 03:12:54 AM UTC; ; NSID: 32 ("2")
NSID 1 の応答が返ってくるようになりました。
おわりに
これまで紹介してきましたように、Knot Resolver はとてもモダンで、調べれば調べるほど面白いソフトウェアです。が、この記事では、魅力を十分に伝え切れたとは言えません。 Prometheus endpoint によるサーバ状況の可視化と監視、etcd でのキャッシュ内容永続化とインスタンス間の共有、DNSTAP によるクエリログ取得と分析など、まだまだ書きたいトピックがありますので、また時間を見つけて書いていきたいと思います。
それでは、明日の記事をお楽しみに!