PowerDNSとPowerDNS-AdminをDockerで動かして、Kubernetesの外部DNSサーバーにする(1)

毎度、ググっても出てこない小ネタを取り扱っております。
本記事は個人的な見解であり、筆者の所属するいかなる団体にも関係ございません。

# 0. はじめに

「インターネットにはまずDNSサーバー」と誰かが言ったかもしれませんが、DNSサーバーはそれだけ重要なものです。
オンプレで動かすKubernetesでも重要で、特にIngressリソースとかで動かす時に、自動的にDNSサーバーにホスト名を登録し、TLS証明書を発行する必要があります。

その時に自動的にレコードをDNSに登録できるDNSサーバーが必要です。

Kubernetesには、ExternalDNSというIngressリソースのホスト名をDNSサーバーに登録する仕組みがあります。

kubernetes-sigs/external-dns: Configure external DNS servers (AWS Route53, Google CloudDNS and others) for Kubernetes Ingresses and Services 
https://github.com/kubernetes-sigs/external-dns

しかし、これで利用できるDNSサーバーは、クラウドなどのDNSサービスばかりでオンプレで利用できるDNSサーバーは多くありません。

ExternalDNSからオンプレミスで利用できるDNSサーバーは以下の4つです。
- PowerDNS
- CoreDNS
- RFC2136
- Pi-hole

しかし、RFC2136はBINDで動きますが、今更BINDとか見たくありません。。
CoreDNSはetcdでの連携が必要で、etcdを外にさらすのも気が引けます。
Pi-holeはRaspberry PiのAd Blockingツールですが、そこまでの機能は不要です。

となると、PowerDNS一択になるわけで、こいつを動かすしかないという結論になります。

::: note 
本記事は、OpenAIのChatGPT GPT-4oの力を借りて作成しています。

:::

# 1. PowerDNSの概要

**PowerDNS**は、オープンソースのDNSサーバーで、動的なDNSセットアップや高可用性のニーズに応える強力な機能を持っています。PowerDNSは大きく2つに分かれており、**権威サーバー**（Authoritative Server）は権威あるDNS応答を提供し、**リカーサー**（Recursor）はクライアント側のDNS解決を行います。このガイドでは、Dockerを使用して**PowerDNS権威サーバー**をSQLiteバックエンドでセットアップする方法を説明します。

主な機能としては：
- MySQL、PostgreSQL、SQLiteなど、複数のバックエンドデータソースに対応。
- DNSSECとの統合による安全なドメインネームサービス。
- REST APIによる簡単な管理が可能。

# 2. Dockerを使用したUbuntuでのPowerDNSのセットアップ

## ステップ1: Dockerをインストール
まずはDockerとDocker Composeをインストールします。

```bash
sudo apt update
sudo apt install -y docker.io
```

Dockerが自動的に起動するようにし、インストールを確認します。

```bash
sudo systemctl start docker
sudo systemctl enable docker
docker --version
```

## ステップ2: Docker Composeのインストール
Docker Composeがデフォルトでインストールされていない場合は、次のコマンドでインストールします。

```bash
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

## ステップ3: Ubuntu のスタブリゾルバーを止める

Ubuntuのスタブリゾルバーを止めます。

そのためには、`/etc/systemd/resolved.conf`を編集して、`DNSStubListener=no`を追加します。

```bash
sudo sed -i 's/^#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
```

resolv.confも編集します。

```bash
cd /etc
sudo ln -s ../run/systemd/resolve/resolv.conf resolv.conf
```

その後、サービスを再起動します。

```bash
sudo systemctl restart systemd-resolved
```

53番ポートのListenが止まっているか確認します。

```bash
sudo ss -tuln | grep 53
```

## ステップ4: PowerDNSのセットアップ
PowerDNS用の設定ディレクトリを作成します。

```bash
mkdir ~/powerdns && cd ~/powerdns
```

`docker-compose.yml`ファイルを作成し、SQLiteをバックエンドに設定します。

```yml:
services:
  auth:
    image: powerdns/pdns-auth-master:latest
    container_name: pdns_auth
    environment:
      - PDNS_AUTH_API_KEY
    volumes:
      - type: bind
        source: ./data/powerdns-auth/pdns.sqlite
        target: /var/lib/powerdns/pdns.sqlite
      - ./pdns.d/pdns.conf:/etc/powerdns/pdns.d/pdns.conf
    ports:
      - "53:53/udp"
      - "53:53/tcp"
      - "8081:8081"

volumes:
  powerdns:
    driver: local
```

## ステップ5: PowerDNSの起動
Docker Composeを実行し、PowerDNSを起動します。

```bash
docker-compose up -d
```

サービスが実行中かどうかを確認します。

```bash
docker ps
```

### PowerDNSのAuthorityが起動したか確認します。

まだ何もレコードを入れていないので、何も返ってきませんが、確認しておきましょう。

```bash
dig @127.0.0.1 example.com +short
```

以下のように何も返ってこなければOKです。

```
; <<>> DiG 9.18.1-1ubuntu1-Ubuntu <<>> @127.0.0.1 example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 29047
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

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

;; Query time: 1 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Thu Nov 21 09:22:29 UTC 2024
;; MSG SIZE  rcvd: 40
```

docker-compose.ymlの検証ができたので、一旦、PowerDNSのAuthorityを停止します。

```bash
docker compose down
```

# 3. PowerDNSのデータベースの初期化とPowerDNS起動設定

PowerDNSのデータベースのディレクトリを作成します。
```
mkdir -p ./data/powerdns-{auth,admin}
touch ./data/powerdns-admin/admin.sqlite
```

PowerDNSのデータベースのスキーマをダウンロードします。

```
wget https://github.com/PowerDNS/pdns/blob/master/modules/gsqlite3backend/schema.sqlite3.sql
```

スキーマーを適用して、pdns.sqliteファイルを作ります。
```
sqlite3 ./data/powerdns-auth/pdns.sqlite < ./schema.sqlite3.sql
```

sqlite3コマンドがインストールされていない場合は、インストールします。

```
sudo apt install -y sqlite3
```

pdns.dディレクトリを作成します。

```
mkdir ./pdns.d
```

`./pdns.d/pdns.conf`を編集します。

```io:./pdns.d/pdns.conf
local-address=0.0.0.0,::
launch=gsqlite3
gsqlite3-dnssec
gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
include-dir=/etc/powerdns/pdns.d
primary=yes #
secondary=no #
version-string=powerdns
api=yes
allow-axfr-ips=
```

APIキーファイルを保存するファイル `.env`ファイルを作ります。
パスワードは適時変更してください。

```yml:.env
PDNS_AUTH_API_KEY=password
```

# 4. PowerDNS APIを使用してDNSレコードを追加する

PowerDNSのAuthorityを起動します。

```
docker compose up -d
```

## 4-1. API接続確認

SQLiteデータベースに直接レコードを追加することも可能ですが、簡単な方法として**PowerDNS API**を使用するのがおすすめです。

まず、API接続でエラーが出ないか確認します。

```
curl -H 'X-API-Key: password' http://localhost:8081/api/v1/servers | jq
```

以下のように返ってきたらOKです。
```
[
  {
    "autoprimaries_url": "/api/v1/servers/localhost/autoprimaries{/autoprimary}",
    "config_url": "/api/v1/servers/localhost/config{/config_setting}",
    "daemon_type": "authoritative",
    "id": "localhost",
    "type": "Server",
    "url": "/api/v1/servers/localhost",
    "version": "5.0.0-alpha0.1988.master.g28654477d",
    "zones_url": "/api/v1/servers/localhost/zones{/zone}"
  }
]
```

## 4-2. DNSのZoneの追加

APIを使用してDNSレコードを追加するのに次の`curl`コマンドを使用します(他の方法もあります)。passwordは適時、PDNS_AUTH_API_KEYで指定したパスワードに変更してください。

```bash
curl -sk -H 'X-API-Key: password' -H "Content-Type: application/json" -X POST \
-d '{
  "name": "example.jp.",
  "kind": "Native",
  "masters": [],
  "nameservers": [
    "ns1.example.jp.",
    "ns2.example.jp."
  ]
}' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones
```

ゾーンが作成されたか確認します。

```
curl -H 'X-API-Key: password' http://127.0.0.1:8081/api/v1/servers/localhost/zones | jq .
```

::: note
jqが入っていない場合は、`sudo apt install -y jq`でインストールしてください。
:::

以下のようにZoneが作成されれば成功です。

```json
[
  {
    "account": "",
    "catalog": "",
    "dnssec": false,
    "edited_serial": 2024112601,
    "id": "example.jp.",
    "kind": "Native",
    "last_check": 0,
    "masters": [],
    "name": "example.jp.",
    "notified_serial": 2024112601,
    "serial": 2024112601,
    "url": "/api/v1/servers/localhost/zones/example.jp."
  }
]
```

## 4-3. レコードの追加

以下のコマンドでmail.example.jp のAレコードを追加します。
注意点は、メソッドが`PATCH`で、URLがゾーンのURLになっているところです。

```bash
curl -sk -X PATCH -H 'X-API-Key: password' -H "Content-Type: application/json" \
-d '{
    "rrsets": [
      {
        "name": "mail.example.jp.",
        "type": "A",
        "changetype": "REPLACE",
        "ttl": 86400,
        "records": [
          {
              "content": "192.168.9.9",
              "disabled": false
          }
        ]
      }
    ]
}' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.jp.
```

入ったかどうか確認します。

```json
{
  "account": "",
  "api_rectify": true,
  "catalog": "",
  "dnssec": true,
  "edited_serial": 2024112602,
  "id": "example.jp.",
  "kind": "Master",
  "last_check": 0,
  "master_tsig_key_ids": [],
  "masters": [],
  "name": "example.jp.",
  "notified_serial": 2024112602,
  "nsec3narrow": false,
  "nsec3param": "",
  "rrsets": [
    {
      "comments": [],
      "name": "mail.example.jp.",
      "records": [
        {
          "content": "192.168.9.9",
          "disabled": false
        }
      ],
      "ttl": 86400,
      "type": "A"
    },
---後略---
```

# 5. DNSを引いてみる

mail.example.jpレコードが追加されているはずなので、digコマンドでテストしてみます。

```
dig @localhost mail.example.jp +short
```

以下のIPアドレスが返ってくればOKです。
```
192.168.9.9
```

# 6. つづく

ちょっと長くなったので、一旦ここまででこの記事は終わりにします。