Dockerで代替DNSの動きを確認してみた

# 目次
- 経緯・確認したいこと
- 環境
- ネットワーク構成
- ディレクトリ（プロジェクト）
- Dockerfile
- Docker Compose
- BINDの設定
- 検証
- まとめ
- お片付け

# 経緯・確認したいこと
先日、参加しているオンラインコミュニティで  **「どのような事象だったら代替DNSにクエリー（問い合わせ）されるのか？」** という質問をいただいた。
ケースとしては二つ考えられる。

1. 優先DNSサーバが障害でDNS応答不可
2. 優先DNSサーバ応答ありだけど名前解決不可

Dockerのコンテナを活用して動きを確認してみることにした。

# 環境
- OS: Windows 11 Pro 22H2 WSL2
- WSL2: Ubuntu 22.04.2 LTS
- Docker Engine:
    - Client: 24.0.4
    - Server: 24.0.4

# ネットワーク構成
BINDでDNSサーバーを2台、Ubuntuのクライアントマシンを1台設置する。
Dockerfileでbind、Ubuntuのベースとなるコンテナイメージを作成し、Docker Composeでネットワークを明示的に指定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320647/f4aac52b-24d7-418b-7746-8a7d34199b22.png)

# ディレクトリ（プロジェクト）
BINDとUbuntuごとにDockerfileを用意した。
```
project-bind
├── Dockerfile_bind
├── Dockerfile_ubuntu
└── docker-compose.yml
```
# Dockerfile
```Dockerfile:Dockerfile_bind
FROM ubuntu/bind9:latest

RUN apt-get update -y
RUN apt-get install vim iputils-ping -y
```
BINDの設定ファイルを変更できるようにvim、ネットワーク疎通確認用にpingをインストールした。

```Dockerfile:Dockerfile_ubuntu
FROM ubuntu:latest

RUN apt-get update -y
RUN apt-get install vim iputils-ping dnsutils -y
```
同様にvim、ping そして名前解決確認できるようにdigも追加インストールした。

# Docker Compose
```yaml:docker-compose.yml
version: '3'
services:

  # BIND build
  bind-base:
    build:
      context: .
      dockerfile: Dockerfile_bind
    image: bind-base
    profiles:
      - extra

  # Ubuntu build
  ubuntu-base:
    build:
      context: .
      dockerfile: Dockerfile_ubuntu
    image: ubuntu-base
    profiles:
      - extra

  # DNSServer1(bind)
  bind01:
    image: bind-base:latest
    networks:
      fixed_compose_network:
        ipv4_address: 10.0.0.11

  # DNSServer2(bind)
  bind02:
    image: bind-base:latest
    networks:
      fixed_compose_network:
        ipv4_address: 10.0.0.12

  # Client
  Client01:
    image: ubuntu-base:latest
    tty: true
    networks:
      fixed_compose_network:
        ipv4_address: 10.0.0.20
```
ベースイメージはビルドのみで使用したい。そのため`docker compose up -d` でコンテナ起動しないように `profiles` を設定した。

## build
ベースイメージをビルドするコマンド。
```shell
$ docker compose --profile extra build
```
イメージができていることを確認する。
```shell-session
$ docker images
REPOSITORY    TAG       IMAGE ID       CREATED      SIZE
ubuntu-base   latest    f5752fc36de3   6 days ago   227MB
bind-base     latest    0c7378c82095   7 days ago   237MB
```
## コンテナ起動
コンテナを起動するコマンド。
```shell-session
$ docker compose up -d
[+] Running 4/4
 ✔ Network bind_fixed_compose_network  Created          0.1s
 ✔ Container bind-bind01-1             Started          0.8s
 ✔ Container bind-Client01-1           Started          0.9s
 ✔ Container bind-bind02-1             Started
```
` docker ps ` で３つのコンテナが実行中であることを確認する。

# BINDの設定
2つのBINDにゾーンファイルを作成していく。

## bind01
まずは以下コマンドでBINDコンテナにアクセスする。
```shell
docker exec -it "bind01のコンテナID" /bin/bash
```
vimで `named.conf.default-zones` を編集する。
このファイルに解決するドメインとゾーンファイルを登録する。
```shell
vim /etc/bind/named.conf.default-zones
```

```css:named.conf.default-zones
zone "." {
        type hint;
        file "/usr/share/dns/root.hints";
};

// be authoritative for the localhost forward and reverse zones, and for
// broadcast zones as per RFC 1912

zone "localhost" {
        type master;
        file "/etc/bind/db.local";
};

zone "127.in-addr.arpa" {
        type master;
        file "/etc/bind/db.127";
};

zone "0.in-addr.arpa" {
        type master;
        file "/etc/bind/db.0";
};

zone "255.in-addr.arpa" {
        type master;
        file "/etc/bind/db.255";
};
// add test.pizza zone file
zone "test.pizza" {
        type master;
        file "/etc/bind/db.test.pizza";
};
```

登録したゾーンファイル `db.test.pizza` を作成する。
デフォルトで存在する `db.local` をコピーして作成する。

```shell
cp /etc/bind/db.local /etc/bind/db.test.pizza
```

コピー後はvimで編集する。

```css:db.test.pizza
;
; BIND data file for local loopback interface
;
$TTL    604800
@       IN      SOA     ns1.test.pizza. postmaster.test.pizza. (
                              2         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;
@       IN      NS      ns1.test.pizza.
ns1       IN      A       10.0.0.11
www       IN      A       10.0.0.100
```
BINDの設定を反映させるには named デーモンを再起動させる必要がある。
Dockerだとshell上でnamedデーモンを再起動させるとコンテナが停止しまうため
ここではshellからexitでいったん抜けて、コンテナ自体を再起動（restart）させる。

```shell
docker restart "bind01のコンテナID"
```
実際にDNSサーバーにクエリーを投げて名前解決できるか確認する。
クライアントのコンテナのBashにアクセスする。
```shell
docker exec -it "Clinet01のコンテナID" /bin/bash
```

Ubuntuの問い合わせ先DNSサーバーを指定する時は "/etc/resolv.conf"を編集する。
```shell
vim /etc/resolv.conf
```

`nameserver` に bind01のIPアドレスを指定する
```txt:/etc/resolv.conf
nameserver 10.0.0.11
```
設定できたら、`nslookup` で www.test.pizza を問い合わせをしてみる。
```shell
nslookup www.test.pizza
```
ゾーンファイルに登録したAレコードの結果(10.0.0.100)が表示されればクエリーの肯定応答となる。
```shell
nslookup www.test.pizza
Server:         10.0.0.11
Address:        10.0.0.11#53

Name:   www.test.pizza
Address: 10.0.0.100
```

## bind02

bind01同様に設定していく。
今回代替DNSの動きを確認するため、Aレコードは以下のようにbind01と異なるものを登録する。

```css:db.test.pizza
;
; BIND data file for local loopback interface
;
$TTL    604800
@       IN      SOA     ns1.test.pizza. postmaster.test.pizza. (
                              2         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL
;
@       IN      NS      ns2.test.pizza.
ns2       IN      A       10.0.0.12
www       IN      A       10.0.0.100
www2       IN      A       10.0.0.101
```

クライアントにセカンダリ（代替）DNSの指定先として bind02(IP:10.0.0.12)を指定する。
```txt:resolv.conf
nameserver 10.0.0.11
nameserver 10.0.0.12
```

# 検証1: 優先DNSサーバが障害でDNS応答不可
優先DNSサーバー（bind01）の障害を見立てて、bind01コンテナを停止し、クライアントでwww.test.pizzaに問い合わせをしてみる。
bind01コンテナを停止するコマンド
```shell
docker stop "bind01のコンテナID"
```
クライアントで問い合わせた結果
```shell-session
root@client01:/# nslookup www.test.pizza
;; communications error to 10.0.0.11#53: timed out
;; communications error to 10.0.0.11#53: timed out
;; communications error to 10.0.0.11#53: timed out
Server:         10.0.0.12
Address:        10.0.0.12#53

Name:   www.test.pizza
Address: 10.0.0.100
```
優先DNSサーバーと通信できないためタイムアウトが表示されるが、そのあと代替えDNSに問い合わせし、肯定応答が返ってきた。
よって、**優先DNSサーバーと通信（TCP/UDP53）できないときは代替DNSサーバーに問い合わせする**動きを確認できた。

# 検証2: 優先DNSサーバ応答ありだけど名前解決不可
今度は 代替DNSサーバーだけに登録している www2.test.pizza を問い合わせして検証してみる。
停止していた優先DNSサーバー（bind01）を起動させる。
```shell
docker start "bind01のコンテナID"
```
クライアントで問い合わせた結果
```shell-session
root@client01:/# nslookup www2.test.pizza
Server:         10.0.0.11
Address:        10.0.0.11#53

** server can't find www2.test.pizza: NXDOMAIN
```
優先DNSサーバーにはwww2.test.pizzaのレコードがないため否定応答が返ってきて、代替DNSサーバーに再度問い合わせすることはなかった。

# まとめ

- 優先DNSサーバーが応答できない（通信できない）ときに代替DNSサーバーへ問い合わせする。
- 優先DNSサーバーが否定応答だからといって、次のDNSサーバに問い合わせたりはしない。


# お片付け
Dockerでの動きを確認できたら、検証でつかったコンテナ、イメージは以下コマンドで削除できます。
```shell
docker compose down --rmi all
```