Docker+DNS入門　その1：CoreDNSを用いた権威DNSサーバ構築

これから仕事でDNS周辺技術を利用しそうなので，勉強のためにローカルにDNSサーバを構築して動かしてみました．
DNS関連で詳しい記事はたくさんありますが，自分のやりたかったこと(ローカルにキャッシュDNSと複数の権威DNSを立てる)がまとめられている記事がなかったので公開しました．
※docker&DNSの初心者が実装したとりあえず動くだけのものです．信頼しないでください．

## 対象者
<a id="#Chapter1"></a>
- DNSの基礎的な知識はあるが，実装したことがない方

## 実行環境
- Windows10
- WSL2 (Ubuntu 22.04)
- Docker v2.21.0 (注: Docker desktop ではありません)
    - 環境構築は[Ubuntu22.04へDockerとDocker Compose v2 をインストール (Docker on WSL2 without Docker Desktop)](https://qiita.com/kujiraza/items/a8236f65e2c46735ee91)が参考になります．
- CoreDNS v1.11.1

## システムの全体像
Docker環境に3つのDNSサーバを構築します．
2つの権威DNSサーバがそれぞれ管理するドメインに対して，自前のキャッシュDNSサーバが名前解決を行います．
権威DNSサーバ1は`example.org`のドメインを管理し，そのサブドメイン`"home".example.org`の管理を権威DNSサーバ2に委譲します．
本記事ではまず最初にCoreDNSを用いて権威DNSサーバを構築します．
キャッシュDNSサーバの構築手順は次回の記事 [Docker+DNS入門　その2：Unboundを用いたキャッシュDNSサーバ構築](https://qiita.com/miwamoto/items/7981c390ac876a7fac5e)で説明します．

<img width="800" alt="ファイル名" src="https://i.imgur.com/UBJinaD.png">

## CoreDNS とは
- CoreDNSはGo言語で実装された軽量で柔軟性のあるDNSです．
- CoreDNSはデフォルトではほとんど機能を持たず，必要な機能をプラグインとして追加することでカスタマイズできます．
- 追加するプラグインは設定ファイル (Corefile) に記述します．
## 用意するファイル
```sh:ディレクトリ構造
$ tree -a dns_test/
dns_test/
├── compose.yml
├── .env
├── example_auth
│   ├── Corefile
│   └── zone
│       └── db.example.org
└── home_example_auth
    ├── Corefile
    └── zone
        └── db.home.example.org
```

## 権威DNSサーバ1 (example.orgサーバ) の設定 
1. CoreDNSの設定ファイルを作成
    ```sh:./example_auth/Corefile
    example.org {
        file /etc/coredns/zone/db.example.org
        log
        reload 5s
    }
    ```
    - プラグインの説明
        - example.orgドメインの問い合わせを受け付けます．
        - `file {ファイルパス}`により，ファイルに記述したゾーン情報を参照します．
        - `log`により，CoreDNS実行中のログ情報が標準出力されます．
        - `reload 5s`により，5秒ごとにCorefileをリロードします．
2. Corefileが読み込むzoneファイル
    ```zone:./example_auth/zone/db.example.org
    $TTL 2d    ; default TTL for zone
    $ORIGIN example.org. ; base domain-name
    @         IN      SOA   ns1.example.org. noc.example.org. (
                                    2022090400 ; serial number
                                    12h        ; refresh
                                    15m        ; update retry
                                    3w         ; expiry
                                    2h         ; minimum
                                    )
    ; name server resource record for the domain
                   IN      NS      ns1.example.org.
    home           IN      NS      ns1.home
    ; domain hosts includes NS records defined above
    ns1            IN      A       172.19.0.21
    ns1.home       IN      A       172.19.0.31
    ```
    `home           IN      NS      ns1.home`により，`*.home.example.org`のドメイン解決を別のサーバに委譲しています．

## 権威DNSサーバ2 (home.example.orgサーバ) の設定
1. CoreDNSの設定ファイル(Corefle)を作成
    ```conf:./home_example_auth/Corefile
    home.example.org {
        file /etc/coredns/zone/db.home.example.org
        log
        reload 5s
    }
    ```
2. Corefileで読み込むzoneファイルを作成
    ```conf:./home_example_auth/zone/db.home.example.org
    $TTL 2d    ; default TTL for zone
    $ORIGIN home.example.org. ; base domain-name
    @         IN      SOA   ns1.home.example.org. noc.home.example.org. (
                                    2022090400 ; serial number
                                    12h        ; refresh
                                    15m        ; update retry
                                    3w         ; expiry
                                    2h         ; minimum
                                    )
    ; name server RR for the domain
                   IN      NS      ns1.home.example.org.
    ; domain hosts includes NS records defined above
    ns1            IN      A       172.19.0.31
    mail           IN      A       172.19.0.32
    www            IN      A       172.19.0.33
    ftp            IN      A       172.19.0.34
    ```

## dockerの設定
1. CoreDNS の docker imageをpull
    - https://hub.docker.com/r/coredns/coredns
    ```
    docker pull coredns/coredns
    ```
2. コンテナネットワークを作成
    ```
    docker network create --subnet=172.19.0.0/16 dns_test
    ```
3. compose.ymlを作成
    ```yml:./compose.yml
    services:
        example_auth:
            image: coredns/coredns:1.11.1
            container_name: example_auth
            restart: on-failure
            volumes:
                - './example_auth:/etc/coredns'
            networks:
                dns_test:
                    ipv4_address: $ipv4_example_auth
            command: -conf /etc/coredns/Corefile -dns.port $dns_port
        home_example_auth:
            image: coredns/coredns:1.11.1
            container_name: home_example_auth
            restart: on-failure
            volumes:
                - './home_example_auth:/etc/coredns'
            networks:
                dns_test:
                    ipv4_address: $ipv4_home_example_auth
            command: -conf /etc/coredns/Corefile -dns.port $dns_port
    
    networks:
        dns_test:
            external: true
    ```
    - compose.yml内の変数は`./.env`に記述
    ```conf: ./.env
    dns_port=53
    ipv4_example_auth=172.19.0.21
    ipv4_home_example_auth=172.19.0.31
    ```
### compose.yml の中身の説明
- オプションについての説明は以下が詳しいです．
    - 公式 [Compose specification | Docker Documentation](https://docs.docker.com/compose/compose-file/)
    - 日本語まとめ記事 [Docker Compose - docker-compose.yml リファレンス - Qiita](https://qiita.com/zembutsu/items/9e9d80e05e36e882caaa)
- service:について
    - volumes:で指定したパスにボリュームをマウントします．
        - このパス以下に作成したデータはコンテナを破棄しても削除されません．
        - `'./docker-dns/example_auth:/etc/coredns'`とすると，コンテナ側の`/etc/coredns`にボリュームが用意され，かつホスト側の`./docker-dns/example_auth`に紐付けられます．
        - つまり`./docker-dns/example_auth`配下の設定ファイルをコンテナ側が参照できるようになります．
    - (service:配下の) networks:でコンテナが所属するネットワークならびに割当IPアドレスを指定します．
        - 先ほど作成したネットワーク dns_testに所属させます．
        - `ipv4_address: $ipv4_example_auth`により，サブネット (172.19.0.0/16) 内の適当なアドレスを割り当てます．
    - `command: -conf /etc/coredns/Corefile -dns.port $dns_port`により以下を決めています．
        - CoreDNSの設定ファイル(Corefile)の読み込み
        - 権威DNSサーバの稼働するポートを53番に指定 (デフォルトでも53番ですが)
- networks:について
    - `external: True`により，dns_testに所属するコンテナが他のネットワークとも通信可能となります．

## 動作確認
### 権威DNSサーバの起動
```compose.yml```があるディレクトリで```docker compose up```を実行します．
```:Ubuntu-22.04 (WSL2)
~/dns_test$ docker compose up
[+] Running 2/2
 ✔ Container example_auth       Created                              0.1s 
 ✔ Container home_example_auth  Cre...                               0.1s 
Attaching to example_auth, home_example_auth
home_example_auth  | home.example.org.:53
home_example_auth  | [INFO] plugin/reload: Running configuration SHA512 = ~~~
home_example_auth  | CoreDNS-1.11.1
home_example_auth  | linux/amd64, go1.20.7, ae2bbc2
example_auth       | example.org.:53
example_auth       | [INFO] plugin/reload: Running configuration SHA512 = ~~~
example_auth       | CoreDNS-1.11.1
example_auth       | linux/amd64, go1.20.7, ae2bbc2
```
Corefileでlogを有効化したことでログが標準出力されています．起動したことが確認できます．

### 権威DNSサーバによる名前解決
- ホスト側で172.19.0.21(example.orgサーバ)を指定して，DNSクエリを投げて，名前解決できていることを確認します．
    - +nocookieにより，DNS cookieを利用せず，最新の設定内容が得られます．
```:Ubuntu-22.04 (WSL2)
$ dig +nocookie @172.19.0.21 ns1.example.org

; <<>> DiG 9.18.18-0ubuntu0.22.04.1-Ubuntu <<>> +nocookie @172.19.0.21 ns1.example.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9388
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;ns1.example.org.               IN      A

;; ANSWER SECTION:
ns1.example.org.        172800  IN      A       172.19.0.21

;; AUTHORITY SECTION:
example.org.            172800  IN      NS      ns1.example.org.

;; Query time: 0 msec
;; SERVER: 172.19.0.21#53(172.19.0.21) (UDP)
;; WHEN: ~~~
;; MSG SIZE  rcvd: 115
```
- 同様に172.19.0.31 (home.example.orgサーバ) を指定して，DNSクエリを投げて，名前解決できていることを確認します．
```:Ubuntu-22.04 (WSL2)
$ dig +nocookie @172.19.0.31 www.home.example.org

; <<>> DiG 9.18.18-0ubuntu0.22.04.1-Ubuntu <<>> +nocookie @172.19.0.31 www.home.example.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61428
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;www.home.example.org.          IN      A

;; ANSWER SECTION:
www.home.example.org.   172800  IN      A       172.19.0.33

;; AUTHORITY SECTION:
home.example.org.       172800  IN      NS      ns1.home.example.org.

;; Query time: 0 msec
;; SERVER: 172.19.0.31#53(172.19.0.31) (UDP)
;; WHEN: ~~~
;; MSG SIZE  rcvd: 135
```

## 補足
ここまでの実装では，example.orgサーバからhome.example.orgサーバへの委譲を利用できません．
具体的には，ホストが`www.home.example.org`のIPアドレスを権威DNSサーバ1 (example.orgサーバ) へ問い合わせた時，サーバ1はそのIPアドレスを管理しているhome.example.orgサーバの情報を知らせるのみで，ホストもサーバ1もそれ以上は問い合わせない (反復問い合わせをしない) 状態です．

## 次回の話
次回の記事  [Docker+DNS入門　その2：Unboundを用いたキャッシュDNSサーバ構築](https://qiita.com/miwamoto/items/7981c390ac876a7fac5e)では，ホストに代わって反復問い合わせを行うキャッシュDNSサーバ(フルサービスリゾルバ)を，Docker+Unboundで実装します．
これによって，example.orgサーバからhome.example.orgサーバへの委譲を利用できます．