仕事で社内DNSサーバーを更新する機会があったのですが、BiNDはセキュリティが心配だったのでUnboundとNSDを使って構築しました。その手順のメモです。

ちなみに、セカンダリーDNSが設定されていない端末がいくつもあったので、セカンダリーDNSを別IPで立てるのではなく、マスターとスレーブをVRRP (keepalived) で冗長化する構成にしています。普通はあまり無い構成だと思いますが、思いの外うまくいった感じがするので併せて記録しておきます。

環境

プラットフォーム-

IPアドレス

使用ポート

DNSの基礎と今回の構成

コンテンツサーバーとキャッシュサーバー

DNSサーバーには「コンテンツサーバー」と「キャッシュサーバー」の2種類があります。「コンテンツサーバー」は自分が管理するドメインに対する名前解決問い合わせに回答するサーバーです。名前解決に関する情報はゾーンファイルというファイルに書かれていて、ゾーンファイルはこのコンテンツサーバーにあります。

「キャッシュサーバー」は名前解決の回答をキャッシュしておいて、後で同じドメインに対する問い合わせが来た時に、キャッシュしておいた情報を使って回答します。問い合わせられたドメインの情報がキャッシュに無い場合、キャッシュサーバーはコンテンツサーバーか他のキャッシュサーバーに問い合わせを行い、結果をキャッシュします。なのでキャッシュサーバーはゾーンファイルを持ちません。

重要な点は、「キャッシュサーバー」は自分の知らないドメインに対する問い合わせが来たら他のサーバーに問い合わせを転送できますが、「コンテンツサーバー」はそれができません。コンテンツサーバーは管理するドメイン毎にゾーンファイルを持ちますが、それ以外のドメインについては何も知らないため、問い合わせが来たとしてもすぐに「名前解決できない」と回答してしまうのです。

そのため、まずクライアントPCのネットワーク設定では「キャッシュサーバー」をDNSサーバーとして設定しておきます。そしてキャッシュサーバー側では、

という設定を行うことになります。

コンテンツサーバーのゾーン転送

コンテンツサーバーはドメインの名前解決情報を管理するサーバーのため、障害等で止まってしまうと、ドメイン全体が使えなくなってしまいます。ドメインはWebサイトだけでなく、メールやクラウドサービスにも使われていますので、障害が発生しても止まらないよう、冗長化構成を取る必要があります。

そのために、コンテンツサーバーには「ゾーン転送」という機能があります。これは、プライマリとセカンダリの2台のサーバーがあった場合に、プライマリサーバーにあるゾーンファイルを、定期的にセカンダリサーバーにコピーするというものです。ゾーンファイルを定期的にコピーすることで、もしプライマリサーバーが止まってしまっても、セカンダリサーバーが引き継ぐことができます。

VRRPによる冗長化

DNSサーバーは通常、クライアントPCにプライマリとセカンダリの2つのDNSサーバーのIPアドレスを設定し、クライアントPC側でプライマリが落ちていたらセカンダリを使う、という切り替えを行います。

ただ今回は既にあるDNSサーバーを入れ替えるという作業だったのですが、もともとセカンダリサーバーが無かったため、多くのクライアントPCにはプライマリDNSサーバーのIPアドレスしか登録されていませんでした。

全てのPCを設定しに回るのは大変だったため、今回はVRRPという仕組みを使って冗長化しました。

VRRPは、仮想IPアドレスを1つ決めておき、まずプライマリサーバーにその仮想IPを設定しておきます。セカンダリサーバーは定期的にプライマリサーバーが動作しているか確認し、もしプライマリサーバーが停止しているのを検知したら、プライマリサーバーから仮想IPアドレスを奪って自分のIPとして設定します。

こうすることで、クライアントPCからは同じIPアドレスと通信しているように見えますが、仮想IPを持っているサーバーはプライマリからセカンダリに切り替わっているので、障害が発生しても停止せずにサーバーを動作させることができるようになります。

今回必要なFirewall設定

上記を踏まえ、今回必要なFirewallの設定は以下のようになります。

  1. クライアントPCからの名前解決の問い合わせの許可 (ポート 53)
  2. キャッシュサーバーからコンテンツサーバーへの名前解決問い合わせ (ポート 30053)
  3. プライマリ ⇔ セカンダリのゾーン転送のための通信
  4. プライマリ ⇔ セカンダリのVRRPのための通信

インストール (プライマリとセカンダリ共通)

プライマリとセカンダリそれぞれにNSD、Unbound、keepalivedをインストールします。

shell

$ sudo apt install -y nsd unbound keepalived

ログ設定 (プライマリとセカンダリ共通)

初めにログの設定も行っておきます。NSD、Unbound、keepalivedともに、ログは全てsyslogに出力されます。Ubuntuのデフォルト設定ではそれらのログは書き出されませんので、それぞれ別のファイルに出力されるよう設定します。

shell

# NSDのログ設定
$ sudo nano /etc/rsyslog.d/41-dns-nsd.conf
----------
:syslogtag, contains, "nsd" -/var/log/nsd.log
& stop
----------

# Unboundのログ設定
$ sudo nano /etc/rsyslog.d/42-dns-unbound.conf
----------
:syslogtag, contains, "unbound" -/var/log/unbound.log
& stop
----------

# keepalivedのログ設定
$ sudo nano /etc/rsyslog.d/43-dns-keepalived.conf
----------
:syslogtag, contains, "Keepalived" -/var/log/keepalived.log
& stop
----------

# rsyslog を再起動し設定を反映
$ sudo systemctl restart rsyslog

プライマリサーバーの設定

NSDの設定

NSDはまずFirewallの設定をしてから、設定ファイルとゾーンファイルを構成します。

Firewallでは以下の通信を許可しています。

  1. 同じサーバーにインストールされたキャッシュサーバーからの通信
  2. セカンダリサーバーとのゾーン転送通信

shell

$ sudo ufw allow from 127.0.0.1 to any port 30053  # <-- 1
$ sudo ufw allow from 192.168.11.63 to any port 30053  # <-- 2

次にNSDの設定ファイルを記述します。

NSDでは暗号化鍵を設定することでゾーン転送通信を暗号化することができます。暗号化鍵は “key” セクションに記述し、”secret” がその鍵の元になります。”secret” は自分で作成する必要があり、「英数字の文字列で長さが4の倍数」であればどんな値でも設定できます。

/etc/nsd/nsd.conf

server:
  port: 30053
  hide-version: yes
  zonesdir: "/etc/nsd/zones"  # ゾーンファイルが置かれるディレクトリ

remote-control:
  control-enable: no

zone:
  name: "example.com"  # 管理するドメイン
  zonefile: "example.com.zone"  # ゾーンファイル名
  notify: 192.168.11.63@30053 MYKEY  # ゾーン転送で送る側の設定
  provide-xfr: 192.168.11.63 MYKEY  # ゾーン転送で送る側の設定

key:
  name: "MYKEY"
  algorithm: hmac-sha256
  secret: "英数字、長さが4の倍数の文字列"

次にゾーンファイルを作成します。ゾーンファイルの中身はそれぞれ異なるため割愛しますが、配置するディレクトリやファイル名は上記の設定ファイルに書かれた通りにする必要があります。

(上記の例では “/etc/nsd/zones/example.com.zone” がゾーンファイルとなります)

Unboundの設定

Unboundも同様にFirewallを設定してから設定ファイルを記述します。

shell

# Firewallの設定
$ sudo ufw allow 53

# 設定ファイルのサンプルをコピーし編集する
$ cd /etc/unbound/unbound.conf.d
$ sudo cp /usr/share/doc/unbound/examples/unbound.conf .
$ sudo nano /etc/unbound/unbound.conf.d/unbound.conf

/etc/unbound/unbound.conf.d/unbound.conf

port: 53

interface: 192.168.11.61  # 仮想IP
interface: 192.168.11.62  # プライマリサーバーのIP

hide-identity: yes
hide-version: yes
hide-trustanchor: yes

do-not-query-localhost: no

remote-control:
  control-enable: no

stub-zone:
  name: "example.com"
  stub-addr: 127.0.0.1@30053

forward-zone:
  name: "."
  forward-addr: [パブリックDNSのIPアドレス]

上記の設定で、”stub-zone” のところにコンテンツサーバーへ転送するドメインを指定します。今回は “example.com” への問い合わせが来た場合は、ローカルのコンテンツサーバーに転送しています。

“forwad-zone” では、stub-zoneで設定したドメイン以外の名前解決をするときは、パブリックDNSへ問い合わせを転送するよう設定しています。”name” にピリオド (“.”) を指定すると、”全てのドメイン” を指定することができます。

keepalived の設定

最後にVRRPで冗長化するためのkeepalivedの設定を行います。

shell

# Firwallの設定
$ sudo ufw allow from 192.168.11.63 to any port 112  # セカンダリとの通信
$ sudo ufw allow from 192.168.11.63 to any port 51  # セカンダリとの通信

# 設定ファイルを編集
$ sudo nano /etc/keepalived/keepalived.conf

/etc/keepalived/keepalived.conf

global_defs {

# 1. アラート通知メール設定
  notification_email {
    alert@example.com
  }
  notification_email_from alert@example.com
  smtp_server [SMTPサーバー]
  smtp_connect_timeout 60
}

# 2. VRRP死活監視設定
vrrp_script check_dns {
  script "/usr/bin/dig @localhost www.example.com -p 30053"
  user nobody nogroup
  interval 10
  weight 0
  rise 1
  fall 1
}

# 3. VRRP設定
vrrp_instance VI {
  state MASTER
  interface ens160
  virtual_router_id 201
  priority 100
  advert_int 1
  garp_master_delay 5
  unicast_peer {
    192.168.11.63
  }
  track_script {
    check_dns
  }
  virtual_ipaddress {
    192.168.11.61/24
  }
}

少し長いですが、上記では3つの設定を行っています。

1. アラート通知メール設定

VRRPの処理でエラーが発生した時に通知メールを送信するための設定です。メールの送信先アドレス、送信元アドレス、SMTPサーバーなどを指定します。

2. VRRP死活監視設定

VRRPの死活監視は通常Pingなどが使われますが、カスタマイズすることもできます。

今回はDNSクエリへの応答が無かったら停止したと見なしてセカンダリへフェイルオーバーしたいため、digコマンドでローカルのコンテンツサーバーが応答を返すかをチェックしています。

3. VRRP設定

最後にVRRPの設定です。

“state” はサーバーの最初の地位を表します。”MASTER” は初めにプライマリになるサーバーで、”BACKUP” は最初セカンダリになるサーバーです。”MASTER” が落ちるとセカンダリが “MASTER” に昇格します。

“unicast_peer” は、VRRP通信する他のサーバーを設定します。keepalivedはデフォルトではマルチキャストでVRRP通信を行いますが、今回はその他にkeepalivedサーバーを立てていないので、unicast_peerを指定して特定のサーバー以外とは通信しないようにしています。

“track_script” には上記2で指定したスクリプトを設定します。

最後に “virtual_ipaddress” で仮想IPアドレスを設定します。

再起動

最後に設定を反映させるためにそれぞれのサービスを再起動します。Unboundは keepalived (の仮想IP) と NSD が起動していないとエラーになりますので、再起動する順番に気を付けてください。

shell

$ sudo systemctl restart keepalived
$ sudo systemctl restart nsd
$ sudo systemctl restart unbound

それぞれのログを確認し、正常に起動していればプライマリサーバーの設定は完了です。

セカンダリサーバーの設定

セカンダリサーバーも設定内容はほぼプライマリサーバーと同じですが、いくつか違う点もあるので以下に簡単に記述します。

NSDの設定

shell

# Firewallの設定
$ sudo ufw allow from 127.0.0.1 to any port 30053
$ sudo ufw allow from 192.168.11.62 to any port 30053  # プライマリのIPを許可

# NSDの設定
$ sudo mkdir /etc/nsd/zones
$ sudo chown nsd:nsd /etc/nsd/zones
$ sudo chmod 755 /etc/nsd/zones
$ sudo nano /etc/nsd/nsd.conf

/etc/nsd/nsd.conf

server:
  port: 30053
  hide-version: yes
  zonesdir: "/etc/nsd/zones"
  zonefiles-write: 5

remote-control:
  control-enable: no

zone:
  name: "example.com"
  zonefile: "example.com.zone"
  allow-notify: 192.168.11.62 MYKEY  # ゾーン転送で受け取る側の設定
  request-xfr: 192.168.11.62@30053 MYKEY  # ゾーン転送で受け取る側の設定

key:
  name: "MYKEY"
  algorithm: hmac-sha256
  secret: "英数字、長さが4の倍数の文字列"  # プライマリと同じ値を設定

セカンダリサーバーでは、ゾーンファイルはゾーン転送により作成されるため、改めてここで作る必要はありません。

Unboundの設定

shell

# Firewallの設定
$ sudo ufw allow 53

# Unboundの設定
$ cd /etc/unbound/unbound.conf.d
$ sudo cp /usr/share/doc/unbound/examples/unbound.conf .
$ sudo nano /etc/unbound/unbound.conf.d/unbound.conf

/etc/unbound/unbound.conf.d/unbound.conf

port: 53

# この部分がセカンダリとプライマリで異なります
interface: 0.0.0.0
interface-automatic: yes

hide-identity: yes
hide-version: yes
hide-trustanchor: yes

do-not-query-localhost: no

remote-control:
  control-enable: no

stub-zone:
  name: "example.com"
  stub-addr: 127.0.0.1@30053
forward-zone:
  name: "."
  forward-addr: [パブリックDNSのIPアドレス]

セカンダリサーバーはまだ仮想IPを持っていない (今はプライマリサーバーが持っている) ので、Unboundの “interface” 設定に仮想IPを指定することができません。

そこでセカンダリサーバーでは、 “interface” に “0.0.0.0” を設定し、”interface-automatic” に “yes” を設定することで、Unboundが自動的にIPアドレスの変更 (仮想IPの取得) を検知し、通信を許可することができるようにします。

keepalivedの設定

shell

# Firewallの設定 (プライマリのIPアドレスを許可)
$ sudo ufw allow from 192.168.11.62 to any port 112
$ sudo ufw allow from 192.168.11.62 to any port 51

# keepalivedの設定
$ sudo nano /etc/keepalived/keepalived.conf

/etc/keepalived/keepalived.conf

global_defs {
  notification_email {
    alert@example.com
  }
  notification_email_from alert@example.com
  smtp_server [SMTPサーバーのIPアドレス]
  smtp_connect_timeout 60
}

vrrp_script check_dns {
  script "/usr/bin/dig @localhost www.example.com -p 30053"
  user nobody nogroup
  interval 10
  weight 0
  rise 1
  fall 1
}

vrrp_instance VI {
  state BACKUP
  interface ens160
  virtual_router_id 201
  priority 90
  advert_int 1
  garp_master_delay 5
  unicast_peer {
    192.168.11.62
  }
  track_script {
    check_dns
  }
  virtual_ipaddress {
    192.168.11.61/24
  }
}

keepalivedの設定もほぼプライマリと同じですが、以下2点だけ異なります。

  1. “state” に “BACKUP” を指定
  2. “unicast_peer” にプライマリのIPアドレスを指定

再起動

最後に設定を反映するためにサービスを再起動します。

shell

$ sudo systemctl restart keepalived
$ sudo systemctl restart nsd
$ sudo systemctl restart unbound

それぞれのログを確認し、サービスが正常に起動していればセカンダリサーバーの設定も完了です。

最後の確認

VRRPによる仮想IPのフェイルオーバーと、ゾーン転送が正常に行われるか確認します。

プライマリサーバーのNSDを停止すると、VRRPによって仮想IPがセカンダリサーバーに移るか確認できます。

shell

$ sudo systemctl stop nsd

“ip” コマンドや “ifconfig” などでIPアドレスを確認し、仮想IPがセカンダリサーバーの方に移っていればOKです。keepalived のログにも記載されますので確認してみましょう。

ゾーン転送は、ゾーンファイルを更新すると自動的に行われます。プライマリ、セカンダリ両方のログにゾーン転送処理の結果が記録されますので確認しましょう。

以上でNSD、Unbound、keepalivedを使ったDNSサーバーの構築は完了です。