PowerDNS + PowerDNS-Admin で Docker 上に権威 DNS サーバを構築する

# PowerDNS + PowerDNS-Admin で Docker 上に DNS サーバを構築する

## 概要

DNS サーバ実装である PowerDNS と、その管理用サードパーティ製 Web GUI である PowerDNS-Admin を使って権威 DNS サーバを構築します。

筆者は DNS の専門家ではないため、詳しい専門家の方から見て用語の誤り等ございましたらコメントでご指摘いただけますと幸いです。

## 使用する技術等

- Docker, Docker Compose
- [PowerDNS](https://www.powerdns.com/)
- [PowerDNS-Admin](https://github.com/PowerDNS-Admin/PowerDNS-Admin)
- SQLite
    - PowerDNS ではバックエンドとして各種 RDB を使用することができます
    - PowerDNS-Admin も DB を使用するようですが、 SQLite を使用できます
    - 管理が楽なので SQLite を使うことにしました

## 目標

- PowerDNS によって DNS 権威サーバを建立する
- PowerDNS-Admin によって GUI でレコードを管理できる

## 背景

### PowerDNS

PowerDNS とは、 PowerDNS 社によって開発された OSS の DNS サーバ実装です。  
BIND とは異なり、権威サーバ (Authoritative Server) とリゾルバ (Recursor) でコンポーネントが別れています。

特に大規模なサービスプロバイダ向けとなっているようで、パフォーマンスを売りにしています。

### PowerDNS-Admin

PowerDNS-Admin は、 PowerDNS を GUI で管理するためのサードパーティ製ソフトウェアです。

ドメインの作成やレコードの追加を GUI 上で行うことができます。  
この GUI がいい感じだったので、これと組み合わせて PowerDNS を使うことにしました。

## 手順

### 1. 使用するバージョン等を確認する

PowerDNS, PowerDNS-Admin は両方共公式で Docker での実行をサポートしています。

それぞれ、対応してるバージョンを Docker Hub を見て確認します。

- [PowerDNS の Docker Hub](https://hub.docker.com/u/powerdns)
- [Power-Admin の Docker Hub](https://hub.docker.com/r/ngoduykhanh/powerdns-admin)

PowerDNS については、マイナーバージョンごとにイメージが別れているようなので、新しそうなものを選びます。  
また、今回作りたいのは権威サーバのみなので、 pdns-auth-XX の中から選びます。  
Recursor は pdns-recursor-XX です。

PowerDNS-Admin は、他のソフトウェアでもよく見るようにバージョンごとにタグをつけて管理する形になっています。  
このため Tags のタブからバージョンを確認します。

### 2. 各種設定ファイルを作成する

使用するイメージが決まったら、あとはいろいろとファイルを作成するだけです。

まずは Docker Compose の compose.yaml です。  
各種ポートはとりあえずローカルでの実験用で設定しています。

```compose.yaml
services:
  auth:
    image: powerdns/pdns-auth-48:4.8.1
    ports:
      - "8053:53"
      - "8053:53/udp"
    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
    env_file: .env

  admin:
    image: ngoduykhanh/powerdns-admin:v0.3.0
    ports:
      - "8080:80"
    volumes:
      - type: bind
        source: ./data/powerdns-admin/admin.sqlite
        target: /data/admin.sqlite
    environment:
      - "SQLALCHEMY_DATABASE_URI=sqlite:////data/admin.sqlite"
      - GUNICORN_TIMEOUT=60
      - GUNICORN_workers=2
      - GUNICORN_LOGLEVEL=INFO
```

次は .env です。 PowerDNS の API キーを入れるためだけに使っています。

```.env
PDNS_AUTH_API_KEY=<YOUR-TOKEN-STRING>
```

PowerDNS の設定を行う pdns.conf を作成します。  
Docker Compose によるバインドを通じて、コンテナ内の /etc/powerdns/pdns.d/pdns.conf に配置されます。  
使用する DB の設定などがあるので、任意で書き換えます。

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

### 3. DB をセットアップする

今回は SQLite を使うことにしたので、 .sqlite ファイルを作成します。  
作成しないと PowerDNS も PowerDNS-Admin もうまく起動しません。  

また、 PowerDNS のものには初期化 SQL を投入する必要があります。  
以下の SQL を pdns.schema.sql といった名前でローカルに保存しておきます。
[SQLite の使用に関する公式ドキュメント](https://doc.powerdns.com/authoritative/backends/generic-sqlite3.html#setting-up-the-database) からコピペしましたが、更新されている可能性もあるため改めてそちらを参照することをおすすめします。

<details>
<summary>SQLite の初期化 SQL</summary>

```pdns.schema.sql
PRAGMA foreign_keys = 1;

CREATE TABLE domains (
  id                    INTEGER PRIMARY KEY,
  name                  VARCHAR(255) NOT NULL COLLATE NOCASE,
  master                VARCHAR(128) DEFAULT NULL,
  last_check            INTEGER DEFAULT NULL,
  type                  VARCHAR(8) NOT NULL,
  notified_serial       INTEGER DEFAULT NULL,
  account               VARCHAR(40) DEFAULT NULL,
  options               VARCHAR(65535) DEFAULT NULL,
  catalog               VARCHAR(255) DEFAULT NULL
);

CREATE UNIQUE INDEX name_index ON domains(name);
CREATE INDEX catalog_idx ON domains(catalog);


CREATE TABLE records (
  id                    INTEGER PRIMARY KEY,
  domain_id             INTEGER DEFAULT NULL,
  name                  VARCHAR(255) DEFAULT NULL,
  type                  VARCHAR(10) DEFAULT NULL,
  content               VARCHAR(65535) DEFAULT NULL,
  ttl                   INTEGER DEFAULT NULL,
  prio                  INTEGER DEFAULT NULL,
  disabled              BOOLEAN DEFAULT 0,
  ordername             VARCHAR(255),
  auth                  BOOL DEFAULT 1,
  FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE INDEX records_lookup_idx ON records(name, type);
CREATE INDEX records_lookup_id_idx ON records(domain_id, name, type);
CREATE INDEX records_order_idx ON records(domain_id, ordername);


CREATE TABLE supermasters (
  ip                    VARCHAR(64) NOT NULL,
  nameserver            VARCHAR(255) NOT NULL COLLATE NOCASE,
  account               VARCHAR(40) NOT NULL
);

CREATE UNIQUE INDEX ip_nameserver_pk ON supermasters(ip, nameserver);


CREATE TABLE comments (
  id                    INTEGER PRIMARY KEY,
  domain_id             INTEGER NOT NULL,
  name                  VARCHAR(255) NOT NULL,
  type                  VARCHAR(10) NOT NULL,
  modified_at           INT NOT NULL,
  account               VARCHAR(40) DEFAULT NULL,
  comment               VARCHAR(65535) NOT NULL,
  FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE INDEX comments_idx ON comments(domain_id, name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);


CREATE TABLE domainmetadata (
 id                     INTEGER PRIMARY KEY,
 domain_id              INT NOT NULL,
 kind                   VARCHAR(32) COLLATE NOCASE,
 content                TEXT,
 FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE INDEX domainmetaidindex ON domainmetadata(domain_id);


CREATE TABLE cryptokeys (
 id                     INTEGER PRIMARY KEY,
 domain_id              INT NOT NULL,
 flags                  INT NOT NULL,
 active                 BOOL,
 published              BOOL DEFAULT 1,
 content                TEXT,
 FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE INDEX domainidindex ON cryptokeys(domain_id);


CREATE TABLE tsigkeys (
 id                     INTEGER PRIMARY KEY,
 name                   VARCHAR(255) COLLATE NOCASE,
 algorithm              VARCHAR(50) COLLATE NOCASE,
 secret                 VARCHAR(255)
);

CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
```
</details>

保存できたら、実際に SQLite ファイルの作成と初期化をします。

```
mkdir -p ./data/powerdns-{auth,admin}
touch ./data/powerdns-admin/admin.sqlite
sqlite3 ./data/powerdns-auth/pdns.sqlite < ./pdns.schema.sql
```

### 4. 起動する

```
docker compose up
```

コンテナを起動して http://localhost:8080 にアクセスすると、 PowerDNS-Admin の Web GUI を見ることができます。

![Screenshot from 2023-08-09 20-14-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508344/de2162a0-26bf-0a31-7ec0-c1954a13557f.png)

また、まだ何のゾーン設定も入っていないため確認できませんが、 PowerDNS Authoritative Server 自体も起動しています。

### 5. 設定する

ここから、 PowerDNS-Admin を操作して PowerDNS との連携やドメイン・レコードの追加を行います。

UI や公式ドキュメントに従えば難しくないと思うので、割愛します。

雑ですが、手順としては以上です。  
実際に本番環境で使っていく際には、ポート設定や DB 設定を変えることになるでしょう。

## まとめ

PowerDNS と PowerDNS-Admin を使って、 GUI でドメイン/レコードの管理を行える権威 DNS サーバを作成しました。

走り書きのため読みにくい箇所ありましたら恐縮です。
また、誤り・不足等あればご指摘いただけると幸いです。

いじょうです。