DockerでBINDを試してみた

普段私はDNSサーバの開発や運用に携わっているのですが、仕事の傍らDockerを勉強しています。
DockerでBINDを試してみたので、備忘録も兼ねて共有しようと思います。

キャッシュ、権威DNSの構成も試しています。

#実行環境
私が試した環境は以下の通りです。

- Windows 10  
- WSL2
- Docker Desktop

DockerがインストールされていればMacでもLinuxでも動くはずです。

#まずは1台の構成
###Dockerfileの用意
ディレクトリの構成は以下となっています。

```
bind/
  ├ Dockerfile
  ├ named.conf 
  └ example.com 
```

```text:Dockerfile
FROM centos:7

RUN yum -y update
RUN yum install -y bind
RUN /usr/sbin/rndc-confgen -a -b 512 -k rndc-key
RUN chmod 755 /etc/rndc.key

EXPOSE 53/UDP
EXPOSE 53/TCP

COPY named.conf /etc/bind/
COPY example.com /etc/bind/master/

CMD ["/usr/sbin/named", "-c", "/etc/bind/named.conf", "-g", "-u", "named"]
```

```text:named.conf
include "/etc/rndc.key";
controls {
    inet 127.0.0.1 allow { 127.0.0.1; } keys { "rndc-key"; };
};

acl "access-list" {
    127.0.0.1;
    172.17.0.0/16;
};

options {
    directory "/etc/named/";
    pid-file "/run/named/";
    dump-file "/var/named/named_dump.db";
    statistics-file "/var/named/named.stats.log";

    zone-statistics yes;
    version "";
    masterfile-format text;
    recursive-clients 10000;
    tcp-clients 10000;

    allow-recursion { access-list; };
    allow-query { access-list; };
    allow-query-cache { access-list; };
};

view "internal" {
    recursion yes;

    zone "example.com" IN {
        type master;
        file "/etc/bind/master/example.com";
    };
};
```

```text:example.com
$TTL 900
@     IN  SOA example.com. postmaster.example.com. (
        2020062101      ; Serial Number
        1800            ; Refresh
        900             ; Retry
        1209600         ; expire
        900             ; minimum
        )
;
      IN    NS  example.com.
      IN    A   1.2.3.4
www   IN    A   5.6.7.8
```

named.confの設定はほぼ最小限にしています。

###用意したファイルの説明
- BINDパッケージ

今回はBINDはyumでインストール。古いバージョンですが、試すだけなのでこれでも大丈夫です。

- Dockerfile内のEXPOSE

通信を想定するポートを指定します。DNSでは53ポートを使用します。

- named.conf, example.comといったファイルのコピー

BINDの設定に必要なファイルをイメージに追加します。
COPYするファイルはDockerfileを置いたディレクトリ内のファイルを指定します。

- BINDの起動

普通はsystemctlを使って起動するのですが、コンテナだと残念ながらできないようなので、直接`named`を指定して起動します。

###イメージのビルド
```
$ docker build -t bind .
```
途中警告みたいなものがでるかもしれませんが、気にしなくても大丈夫です。

```
$ docker images
REPOSITORY              TAG           　 IMAGE ID            CREATED             SIZE
bind                    latest         　b634adb26126        About an hour ago   412MB
```
###
イメージができたことを確認します。

###ビルドしたイメージを利用してコンテナを起動
```
docker run -dit -p 53:53 -p 53:53/udp --name bind bind:latest
```
ここでは `-p` でポートマッピングを設定しています。

```
$ docker ps 
b81999d14571        bind:latest         "/usr/sbin/named -c …"   About an hour ago   Up About an hour         0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp   bind
```
コンテナが起動できていることを確認できれば成功です。

###digで動作確認
digコマンドはドメインをDNSサーバに問いかけて、IPアドレスを教えてくれるコマンドです。
このコマンドを用いて動作確認を行いたいと思います。

ホスト側で以下のコマンドを打ってみてください

```
$ dig @localhost example.com

; <<>> DiG 9.16.1-Ubuntu <<>> @localhost example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58542
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 95bb6b3867347d2a659e18f05fa40fa7705d1af930ac0b3b (good)
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            900     IN      A       1.2.3.4

;; AUTHORITY SECTION:
example.com.            900     IN      NS      example.com.

;; Query time: 10 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Nov 05 23:43:51 JST 2020
;; MSG SIZE  rcvd: 98
```
`example.com` で設定したIPアドレス `1.2.3.4` が表示されていることがわかります。
ちなみに `www.example.com` を問い合わせても設定したアドレス `5.6.7.8` が表示されています。

```
$ dig @localhost www.example.com

; <<>> DiG 9.16.1-Ubuntu <<>> @localhost www.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17044
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: fc8594f5fe0fda2a5b28b3f65fa41035d0243f359a253da7 (good)
;; QUESTION SECTION:
;www.example.com.               IN      A

;; ANSWER SECTION:
www.example.com.        900     IN      A       5.6.7.8

;; AUTHORITY SECTION:
example.com.            758     IN      NS      example.com.

;; ADDITIONAL SECTION:
example.com.            758     IN      A       1.2.3.4

;; Query time: 10 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Nov 05 23:46:13 JST 2020
;; MSG SIZE  rcvd: 118
```

#キャッシュ、権威DNSの構成を試す
先程は1台のDNSサーバのみでしたが、実際の運用だとクライアントからの問い合わせを受けて、クライアントの代わりに問い合わせを行うキャッシュDNSとゾーン情報を管理している権威DNS 2つの役割を持つDNSサーバを用います。

この構成もDockerで試してみたいと思います。

###コンテナ起動
まずは先程作成したコンテナを削除しましょう

```
$ docker rm bind
```

今度は2台のコンテナを作成します。

```
$ docker run -dit -p 53:53 -p 53:53/udp --name bind_cache bind:latest
$ docker run -dit --name bind_auth bind:latest
```
クライアントからの応答を受けるのはキャッシュDNSだけなので、キャッシュDNSにはポートマッピングの設定を入れます。
権威DNSはクライアントとは直接通信しないので設定していません。

一度 `bind_cache`, `bind_auth` のIPアドレスを確認しましょう。

```
$ docker inspect bind_cache | grep IPAddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.2",
                    "IPAddress": "172.17.0.2",

$ docker inspect bind_auth | grep IPAddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.3",
                    "IPAddress": "172.17.0.3",
```
`bind_cache` の方は `172.17.0.2`、`bind_auth`の方は `172.17.0.3` というIPアドレスであることがわかりました。

###キャッシュDNSの設定
キャッシュDNSの `named.conf` を編集するために以下のコマンドを入力してコンテナの中に入ります。

```
$ docker exec -it bind_cache /bin/bash
```
vi などで `view internal` の部分を以下のように編集します

```text:/etc/bind/named.conf
・
・
・
view "internal" {
    recursion yes;

    zone "." {
        type forward;
        forwarders { 172.17.0.3; };
        forward only;
    };
};
```
`forwarders` に先程確認した権威DNSのIPアドレスを入力します。

###権威DNSの設定
今度は権威DNSの `named.conf` を見てみましょう。

```
$ docker exec -it bind_auth /bin/bash
```
同様にvi などで `named.conf` を編集します
`acl access-list` というところに注目してください。

```text:/etc/bind/named.conf
include "/etc/rndc.key";
controls {
    inet 127.0.0.1 allow { 127.0.0.1; } keys { "rndc-key"; };
};

acl "access-list" {
    127.0.0.1;
    172.17.0.0/16;
};
・
・
```

`acl access-list` では通信を許可するIPアドレスを指定します。
先程確認したキャッシュDNSのIPアドレスは `172.17.0.2` でした。 今回は初期設定で `172.17.0.0/16` を許可しているのでこのままでも問題ないですが、キャッシュDNSのIPアドレスを指定しておきましょう。

```text:/etc/bind/named.conf
include "/etc/rndc.key";
controls {
    inet 127.0.0.1 allow { 127.0.0.1; } keys { "rndc-key"; };
};

acl "access-list" {
    127.0.0.1;
    172.17.0.2;　// 172.17.0.2に変更
};
・
・
```

以上で設定は完了です。

設定を反映するために再起動しておきましょう。
2つコンテナが起動していることが確認できれば成功です。

```
$ docker restart bind_auth
$ docker restart bind_cache 

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS
                          NAMES
f0b2f14e26f2        bind:latest         "/usr/sbin/named -c …"   About an hour ago   Up 6 minutes        53/tcp, 53/udp                           bind_auth
b81999d14571        bind:latest         "/usr/sbin/named -c …"   2 days ago          Up 6 minutes        0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp   bind_cache
```

もし起動していない場合は、`named.conf` の設定がうまく行っていないと考えられます。（;のつけ忘れなど, `docker logs` を使えばエラーの内容が見れると思います）
その場合は、以下のコマンドで起動できなかったコンテナを一度削除して、コンテナを作成するところからやった方が楽かもしれません。

```
$ docker logs bind_XXX
$ docker rm bind_XXX または docker rm [コンテナID]
```

###digで動作確認
先ほどと同様にdigコマンドを用いて動作確認を行います。

```
$ dig @localhost example.com

; <<>> DiG 9.16.1-Ubuntu <<>> @localhost example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58542
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 95bb6b3867347d2a659e18f05fa40fa7705d1af930ac0b3b (good)
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            900     IN      A       1.2.3.4

;; AUTHORITY SECTION:
example.com.            900     IN      NS      example.com.

;; Query time: 10 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Nov 05 23:43:51 JST 2020
;; MSG SIZE  rcvd: 98
```
`example.com` で設定したIPアドレスが表示されていることがわかります。

ちなみに権威DNSで定義ていないドメインも引くことができます。

```
$ dig @localhost www.google.com

; <<>> DiG 9.16.1-Ubuntu <<>> @localhost www.google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53253
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 9

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 3181d4115c9c0426b42bd02e5fa6a8a6a04cb4d481be1221 (good)
;; QUESTION SECTION:
;www.google.com.                        IN      A

;; ANSWER SECTION:
www.google.com.         300     IN      A       172.217.26.36

;; AUTHORITY SECTION:
google.com.             171777  IN      NS      ns1.google.com.
google.com.             171777  IN      NS      ns4.google.com.
google.com.             171777  IN      NS      ns3.google.com.
google.com.             171777  IN      NS      ns2.google.com.

;; ADDITIONAL SECTION:
ns4.google.com.         171777  IN      A       216.239.38.10
ns2.google.com.         171777  IN      A       216.239.34.10
ns1.google.com.         171777  IN      A       216.239.32.10
ns3.google.com.         171777  IN      A       216.239.36.10
ns4.google.com.         171777  IN      AAAA    2001:4860:4802:38::a
ns2.google.com.         171777  IN      AAAA    2001:4860:4802:34::a
ns1.google.com.         171777  IN      AAAA    2001:4860:4802:32::a
ns3.google.com.         171777  IN      AAAA    2001:4860:4802:36::a

;; Query time: 120 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Nov 07 23:01:10 JST 2020
;; MSG SIZE  rcvd: 335
```

#感想
思っていたよりも簡単にできました。
実運用で使えるのかどうかはわかりませんが、色々試してみたい思います。
次はキャッシュDNSにUnboundを使ってみようと思います。