Ubuntu Weekly Recipe

第864回レコード登録なしに名前解決を行える⁠ワイルドカードDNS「nip.io」セルフホストする

ワイルドカードDNSの必要性

意味を持った名前でサーバーにアクセスできる名前解決は、インターネットの根幹を支える重要な技術です。特に最近のWebアプリには、名前解決ができることを前提としているものも多く、ドメインのないLAN内にテスト環境を立てるのが面倒、といった経験のある方も多いのではないでしょうか。KubernetesのIngressなどで、よくこの問題に当たりがちです。

LAN内でお手軽に名前解決を実現したいのであれば、最初の候補に挙がるのがAvahiでしょう。Avahiを使えば、DNSを用意せずとも、ホスト名からIPアドレスを引くことができます。サーバーのホスト名が「www」であれば、⁠www.local」でそのサーバーにアクセスできるという具合です。単に「クライアントがサーバーに到達できればよい」のであれば、これで十分ですが、これでは困るシーンもあります。具体的には、1台のサーバー内に複数のバーチャルホストを起動したい場合です。例えばあるひとつのWebアプリが、⁠www.example.jp」「api.example.jp」のふたつのエンドポイントを要求することもよくあるでしょう。あるいはLXDコンテナで複数のアプリを動かして、その前段にリバースプロキシを置きたい場合も、そのサーバーに到達できる複数の名前を用意する必要があります。

これを実現する手段は、大きく以下の2つです。

  1. /etc/hostsファイルを用意する
  2. ローカルDNSにレコードを登録する

1はもっともシンプルかつ原始的な方法です。ですがすべてのクライアントに同じ内容のhostsファイルを配る必要があり、漏れがあるとアクセスできません。また内容を更新するのにも手間がかかります。2は第834回で紹介した方法です。本物のDNSサーバーを用意するためもっとも確実ですが、企業のシステムであれば「インフラチームにDNSレコード登録を依頼する(そして待たされる⁠⁠」という作業が発生してしまい、これもまた開発効率を落とす原因となってしまいます。

そこで登場するのがワイルドカードDNSです。ワイルドカードDNSでは、DNSサーバーの変更なしに、任意のホスト名を自動的にIPアドレスへ名前解決できます。

nip.ioとは

有名なワイルドカードDNSサービスに、nip.ioがあります。百聞は一見にしかずということで、具体例を見てみましょう。

サーバーのIPアドレスが、192.168.1.1だとします。以下のようにhostコマンドで名前解決してみましょう。

$ host 192.168.1.1.nip.io
192.168.1.1.nip.io has address 192.168.1.1

IPアドレス、192.168.1.1が返ってきました。次は先頭に任意の名前を追加してみましょう。

$ host www.192.168.1.1.nip.io
www.192.168.1.1.nip.io has address 192.168.1.1

こちらもIPアドレス、192.168.1.1が返ってきました。次は名前とIPアドレスの各オクテットを、ドットではなくハイフンで区切ってみましょう。

$ host www-192-168-1-1.nip.io
www-192-168-1-1.nip.io has address 192.168.1.1

やはりIPアドレス、192.168.1.1が返ってきました。IPアドレスは16進数でも指定できます。

$ host www.c0a80101.nip.io
www.c0a80101.nip.io has address 192.168.1.1

例によってIPアドレス、192.168.1.1が返ってきました。

つまりnip.ioは、以下のフォーマットのFQDNを、名前に含まれるIPアドレスとして返すDNSサービスです。

<任意の名前>[.-]<IPアドレス>.nip.io

これをワイルドカードDNSと呼びます。任意のIPアドレスを、正規のDNSの応答として名前解決できるため、テスト用にちょっとドメインがほしいという場合に便利なサービスです。ですが、以下のような点が気になる方もいるでしょう。

  • 第三者のサービスを使うのは社内ポリシー的に問題
  • プライベートIPアドレスを、インターネットを経由して名前解決するのに抵抗がある
  • nip.ioではなく自社ドメインを使いたい

実はnip.ioはApacheライセンス2.0で公開されているオープンソースソフトウェアです。そのためLAN内でのセルフホストも可能となっています。前フリが長くなりましたが、社内ローカルなnip.ioを、自社ドメインで構築しようというのが今回の本題です。

nip.ioの動作原理

nip.ioはPowerDNSの権威サーバーと、Pipe BackendPythonスクリプトで構成されています。PowerDNSはDNSクエリとその応答を、標準入出力を介して外部プログラムとやり取りできます。これがPipe Backendです。外部プログラムを間に挟むことで、DNS応答を動的に生成できるというわけです。nip.ioはこの仕組みを使って、クエリ内のサブドメインからIPアドレスを抽出しています。

コンテナイメージのビルド

nip.ioはDockerで動かすのが一番簡単です。そこでまずDockerを以下のコマンドでインストールしてください。

$ sudo apt install -U -y docker.io

ソースコードをGitHubからクローンします。

$ cd
$ git clone https://github.com/exentriquesolutions/nip.io.git
$ cd nip.io

リポジトリにDockerfileが用意されていますので、以下のコマンドでビルドします。ここではイメージに「nipio-local」という名前をつけています。

$ sudo docker build -t nipio-local .

イメージがビルドされていることを確認しておきましょう。

$ sudo docker images
REPOSITORY    TAG       IMAGE ID       CREATED              SIZE
nipio-local   latest    f964349992b5   About a minute ago   63.9MB
alpine        3.7       6d1ef012b567   6 years ago          4.21MB

設定ファイルの用意

nip.ioの動作には、PowerDNSとPipe Backend用のふたつの設定ファイルが必要となります。どちらもサンプルがリポジトリに含まれており、前者がpdns/pdns.conf、後者がnipio/backend.confです。基本的に前者を変更する必要はありません。

ここでは後者のみを編集します。まずサンプルのbackend.confをホームディレクトリにコピーしてから、このファイルを書き換えていきましょう。

$ cp ~/nip.io/nipio/backend.conf ~/

書き換えるポイントを解説します。

「domain」には、nip.ioが名前解決するドメインを指定します。ここでは例として「nip.example.jp」としましたが、自社で利用しているドメインに変更してください。なおこの権威サーバーはパブリックに公開するわけではなく、あくまでLAN内での名前解決のみに使用します。そのためこのドメインを委任するNSレコードを親ドメインに設定する必要はありません。そのため使われていないサブドメインを、適当に設定してしまって構わないでしょう。

# main domain
# domain=nip.io.example
domain=nip.example.jp

「ttl」には、デフォルトのTTLを指定します。開発目的の場合、あまりに長いTTLを設定すると逆に不便でしょうから、適当に短い値に変更しておきましょう。ここでは300秒としました。

# default ttl
# ttl=432000
ttl=300

[SOA]セクションは、SOAレコードに関する設定です。⁠hostmaster」にはホストマスターのメールアドレスを、⁠ns」にはネームサーバーを指定します。先ほどdomainに指定したドメインと揃えてください。

# Hostmaster email address
hostmaster=hostmaster@nip.example.jp
# Name server
ns=ns1.nip.example.jp

[nameservers]セクションは、NSレコードに関する設定です。先ほどdomainに指定したドメインのサブドメインとしてns1/2のふたつを設定し、それぞれIPアドレスは127.0.0.1としておきましょう。

# nameservers
[nameservers]
ns1.nip.example.jp=127.0.0.1
ns2.nip.example.jp=127.0.0.1

[whitelist]セクションでは、nip.ioが名前解決できるIPアドレスのレンジを指定します。LAN内で使用しているサブネットに合わせて設定するとよいでしょう。筆者の環境では「172.16.0.0/16」のプライベートIPアドレスを使用しているため、以下のように設定しました。この状態のnip.ioは、⁠www-172-16-1-1.nip.example.jp」の問い合わせに対しては「172.16.1.1」を返しますが、レンジ外である「www-192-168-1-1.nip.example.jp」の名前解決は拒否します。

# whitelisted ranges (optional)
[whitelist]
loopback = 127.0.0.0/8
private_net_172_16 = 172.16.0.0/16

[blacklist]セクションでは、名前解決を拒否するIPアドレスを個別に指定できます。whitelistに指定したレンジの中でも、⁠このアドレスだけは使いたくない」といった場合に設定してください。筆者は特に設定する必要を感じなかったため、全体をコメントアウトしました。

# blacklisted ips (optional)
# [blacklist]
# some_description = 10.0.0.1

最終的なbackend.confは以下のようになりました。

[main]
# main domain
domain=nip.example.jp

# default ttl
ttl=300

# default IP address for non-wildcard entries
ipaddress=127.0.0.1

# Indicates whether this response is authoritative, this is for DNSSEC.
auth=1

# Scopebits indicates how many bits from the subnet provided in the question.
bits=0


# SOA
[soa]
# serial number
id=1
# Hostmaster email address
hostmaster=hostmaster@nip.example.jp
# Name server
ns=ns1.nip.example.jp
# Refresh
refresh=10800
# Retry
retry=3600
# Expiry
expiry=604600
# Minimum TTL
minimum=3600


# nameservers
[nameservers]
ns1.nip.example.jp=127.0.0.1
ns2.nip.example.jp=127.0.0.1


# whitelisted ranges (optional)
[whitelist]
loopback = 127.0.0.0/8
private_net_172_16 = 172.16.0.0/16

nip.ioの起動

設定ファイルが完成したら、コンテナを起動します。以下のコマンドを実行してください。

$ sudo docker run \
  -p 10053:53/tcp \
  -p 10053:53/udp \
  -v ~/backend.conf:/opt/nip.io/backend.conf \
  --rm \
  --name nipio \
  -d \
  nipio-local

ポイントは2点です。まずコンテナ内のPowerDNSは当然、TCP/UDPの53番ポートで通信を待ち受けます。ですがホストの53番ポートを使ってしまうと、この後で起動するUnboundとバッティングしてしまいます。そこでホスト側のポートは10053を割り当てています。

もうひとつは、コンテナ内でのnip.ioの設定ファイルです。先ほど編集したbackend.confを、コンテナ内の/opt/nip.io/backend.confにマウントしています。

なおDockerfileを確認するとわかりますが、pdns.confとbackend.confは、コンテナイメージのビルド時にコピーされ、コンテナ内に埋め込まれています。そのためリポジトリ内にあるサンプルを直接書き換えた上でビルドを行えば、起動時のマウント処理は不要になります。ただし筆者は、コンテナイメージ内に個別の設定を埋め込むのは避けるべきだと考えているため、起動時にファイルを渡す方式を採用しました。

またnip.ioのコンテナはデータを持たないため、停止時に削除する「--rm」オプションと、デーモンとしてバックグラウンドで起動する「-d」オプションを合わせて指定しています。

それでは動作を確認してみましょう。127.0.0.1の10053番ポートに対して、名前解決を試してみてください。意図通りのIPアドレスが返ってこれば成功です。

$ host -p 10053 www-172-16-1-1.nip.example.jp 127.0.0.1
Using domain server:
Name: 127.0.0.1
Address: 127.0.0.1#10053
Aliases: 

www-172-16-1-1.nip.example.jp has address 172.16.1.1

またWhitelistにないレンジの名前解決が失敗することも確認しておきましょう。この例では「172.16.0.0/16」以外の名前解決を試みると、結果は返ってこず、nip.ioのコンテナのログには「Not Whitelisted: IPアドレス」が記録されます。

$ host -p 10053 www-192-168-1-1.nip.example.jp 127.0.0.1
Using domain server:
Name: 127.0.0.1
Address: 127.0.0.1#10053
Aliases: 

$ sudo docker logs nipio
(...略...)
May 21 07:04:11 Coprocess: Not Whitelisted: 192.168.1.1

Unboundとの連携

さてこれで「nip.example.jp」ドメインのIPアドレスを解決する、ワイルドカードDNSサーバーが構築できました。ですが実際に運用するには、このドメイン宛ての名前解決要求が、このコンテナに届かなくてはなりません。インターネットに公開している通常のDNS権威サーバーであれば、example.jpのDNSサーバーに、委任用のNSレコードを登録すればよいのですが、今回はLAN内だけで運用するDNSサーバーですので、この方法は取れません。そこで第834回と同じく、ローカルキャッシュサーバーとしてUnboundを利用します。

Unboundには、⁠ある特定のドメインへの名前解決要求は、指定したDNS権威サーバーへ問い合わせる」という仕組みが用意されています。これをstub-zoneと呼びます。実際に設定してみましょう。第834回を参考にして、Unboundをインストールしてください。そして/etc/unbound/unbound.conf.d/unbound.confを、以下の内容で作成します。

server:
    interface: 自分自身のIPアドレス
    port: 53
    access-control: 127.0.0.0/8 allow
    access-control: LANのネットワークアドレス/サブネットマスク allow
    use-syslog: yes
    log-queries: yes
    hide-version: yes
    hide-identity: yes
    do-not-query-localhost: no

forward-zone:
    name: "."
    forward-addr: フォワード先の(プロバイダが提供している)プライマリDNS
    forward-addr: フォワード先の(プロバイダが提供している)セカンダリDNS

stub-zone:
    name: "nip.example.jp."
    stub-addr: 127.0.0.1@10053

基本的には第834回で紹介した設定とほぼ同じです。異なる点は、server句に、localhostへのクエリ送信を許可する「do-not-query-localhost: no」を追加することと、末尾にstub-zone句を追加する点です。なおstub-zoneのnameは、nip.ioで使用しているドメイン名に書き換えてください。設定ができたらUnboundを再起動します。

$ sudo systemctl restart unbound.service

以後このUnboundは、通常の名前解決要求はプロバイダのDNSサーバーにフォーワドしつつ、nip.example.jpドメインの名前解決要求だけは、コンテナ内のnip.ioに問い合わせます。図にすると、以下のようになります。

あとは各クライアントが、キャッシュDNSサーバーとしてこのUnboundを使うように設定しましょう。DHCPでアドレスを配ってしまうのが簡単ですね。


今どきの開発現場では、VMやコンテナを頻繁に作っては消すことでしょう。LAN内で自由なホスト名を、それも複数のホスト名を、DNSサーバーをいじることなく、今すぐ名前解決したい。そんな時にはワイルドカードDNSが便利です。既にUnboundでローカルゾーンを運用しているような環境であれば、nip.io用のスタブゾーンをひとつ追加してあげるだけで、いろいろな作業が捗ることでしょう。

おすすめ記事

記事・ニュース一覧