PowerDNSでキャッシュDNSと権威DNSをDockerで構築する

各種設定ファイル解説

今回利用するOSSは以下の通りです。今回はDNSSEC対応は行わない(正直よく分かってない)。

  • キャッシュDNS:PowerDNS Recursor
  • 権威DNS:PowerDNS Authoritative Server
  • DNS設定用GUI:powerdnsadmin/pda-legacy
  • DB:PostgreSQL
  • Digテスト:Ubuntu

私の構築環境は以下の通りです。

  • Docker Desktop for Windows
  • Windows ネットワーク:172.16.1.0/24
  • Recursorの設定
    • “local”や”home”等は内部DNS(“auth”)で解決する
    • 他のドメインは”8.8.8.8″に解決してもらう
    • digコマンドによるテストは”dig_test”で行う
  • “auth”の設定
    • “webapp”のWeb画面からゾーン情報の追加設定を行う
      (その際Composeファイル記載の”Auth”のPDNS_AUTH_API_KEYキーが必要になる)

Docker Composeファイルは以下の通り。Digコマンドによる設定テストは”dig_test”で行っています。

ですが注意点があります。

  1. 各コンテナで時刻が同期されなければならない
  2. DNSSECの無効化
  3. ”healthcheck”による”db”依存のサービスの遅延起動
  4. “webapp”の原因不明なエラー

1. 各コンテナで時刻が同期されなければならない

Recursorに対してDigコマンドでリクエストを送信した際にエラーが派生しました。エラー内容については忘れましたが調べてみると時刻が同期されていない場合に発生した旨のサイトを見つけたので対応しました。

対応内容は”cap_add”で”SYS_TIME”を指定することです。
ちなみにAuthへ直接問い合わせた場合には発生しなかったので、RecursorからAuthへのforwardする際に同期が必要ということだと考えています。

2. DNSSECの無効化

DNSSECをデフォルト設定のままにした場合にエラーが発生しました。ログを見る限り設定どおりforwardに設定したアドレス(Auth)に陸枝エストしゾーン情報(IPアドレス)を取得できました。

にもかかわらずエラーになったのはDNSSECによるcheckに引っかかったことが原因なようです。(正直DNSSECが何かもよく分かっていません。ポイズン対策と書いてあった気がしますが。)
今回は自宅で構築した上時間がかかってしまっていたのでOffにしました。

3. ”healthcheck"による"db"依存のサービスの遅延起動

DBの調整が済む前に先に”Auth”や”webapp”が起動するとエラーでコンテナが落ちます。

対策としてDBで”healthcheck”セクションでDBの準備OK条件を設定して、DBが必要な各コンテナの”depends_on”で”condition: service_healthy”を設定しています。

4. "webapp"の原因不明なエラー

正直これは原因が分かっていません。

ただきっかけは利用するDBをSQLiteからPostgreSQLに変更した時だったかと記憶しています。(PostgreSQLに変えた理由はSQLiteでセッションに関するトランザクション・エラーが発生したことです。)

現象としてはWebアプリは起動し画面の参照自体は問題なく行えるのですが、ゾーンの追加でエラーが発生したのです。Web画面には”400″or”402″のエラーが表示され、以下のメッセージがログに表示されていました。
“requests.exceptions.MissingSchema: Invalid URL ‘…’: No schema supplied. Perhaps you meant …”

ここで問題なのがこの原因が分かっていないことと、コンテナ軍の削除&再起動を繰り返しているうちにこのエラーが解消されてしまうことです。

恐らくですが先の”db”の”healthcheck”で指定しているコマンドは”auth”のデータベース設定の準備ができていることは確認しているものの、”webapp”のデータベースが準備できていることが確認できていないことが原因かと思われます。
想定通りなら”healthcheck”のコマンド(条件)を変更するか、タイムアウト時間を延ばせば解消するのではないかと睨んでいます。検証はしていません。

docker-compose.yml

version: '3.0'

# Common
x-common-timezone: &COMMON_TZ
  "Asia/Tokyo"

services:
  # キャッシュDNS
  recursor:
    image: powerdns/pdns-recursor-master
    container_name: pdns_recursor
    # restart: always
    environment:
      PDNS_RECURSOR_API_KEY: ABC_DE_F123
      DEBUG_CONFIG: yes
      TZ: *COMMON_TZ
    ports:
      # - "53:53"
      # - "53:53/udp"
      - "8082:8082"
    volumes:
      - ./recursor/recursor.conf.yml:/etc/powerdns/recursor.conf:ro
    networks:
      powerdns:
        ipv4_address: 172.23.0.2
    cap_add:
      - SYS_TIME

  # 権威DNS
  auth:
    image: powerdns/pdns-auth-master
    container_name: pdns_auth
    # restart: always
    environment:
      # webappで入力するAPIキー
      PDNS_AUTH_API_KEY: ABC_DE_F123
      DEBUG_CONFIG: yes
      TZ: *COMMON_TZ
    ports:
      - "8081:8081"
    volumes:
      - auth_varlib:/var/lib/powerdns/
      - ./auth/pdns.conf:/etc/powerdns/pdns.conf:ro
    networks:
      powerdns:
        ipv4_address: 172.23.0.3
    depends_on:
      db:
        condition: service_healthy
    cap_add:
      - SYS_TIME

  # "requests.exceptions.MissingSchema: Invalid URL '...': No schema supplied. Perhaps you meant ..."
  # 上記エラーはボリュームを含むコンテナ軍の削除&起動を繰り返すといつか解消される(powerdns_dbのhealthcheck値を伸ばすと良いかも?)
  webapp:
    image: powerdnsadmin/pda-legacy:latest
    container_name: powerdns_admin
    # restart: always
    ports:
      - "9191:80"
    logging:
      driver: json-file
      options:
        max-size: 50m
    environment:
      GUNICORN_TIMEOUT: 60
      GUNICORN_WORKERS: 2
      GUNICORN_LOGLEVEL: DEBUG
      SQLALCHEMY_DATABASE_URI: 'postgresql://powerdnsadmin:powerdnsadmin_password@powerdns_db/powerdnsadmindb'
      TZ: *COMMON_TZ
    volumes:
      - powerdns_web:/data
    networks:
      powerdns:
        ipv4_address: 172.23.0.4
    depends_on:
      db:
        condition: service_healthy
    cap_add:
      - SYS_TIME
    
  db:
    image: postgres:16.3
    container_name: powerdns_db
    expose:
      - 5432
    environment:
      POSTGRES_DB: powerdb
      POSTGRES_ROOT_PASSWORD: redminep
      POSTGRES_USER: powerdns
      POSTGRES_PASSWORD: powerpass
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
      TZ: *COMMON_TZ
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./postgresql/:/docker-entrypoint-initdb.d/
    networks:
      powerdns:
        ipv4_address: 172.23.0.5
    cap_add:
      - SYS_TIME
    healthcheck:
      test: ["CMD", "psql", "-U", "powerdns", "powerdb"]
      interval: 2s
      timeout: 10s
      retries: 3
      start_period: 8s

  dig_test:
    image: ubuntu
    command: bash -c "apt update -y && DEBIAN_FRONTEND=noninteractive  TZ=Asia/Tokyo apt-get install -y dnsutils curl tzdata && bash"
    tty: true
    environment:
      TZ: *COMMON_TZ
    networks:
      powerdns:
        ipv4_address: 172.23.0.6
    dns: 172.23.0.2
    cap_add:
      - SYS_TIME

volumes:
  powerdns_auth:
    driver: local
  auth_varlib:
  powerdns_web:
    driver: local
  db_data:

networks:
  powerdns:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.23.0.0/16
          gateway: 172.23.0.1

recursor.conf.yml

recursor:
    forward_zones:
    - zone: local
      forwarders:
      - 172.23.0.3:53
      recurse: true
      # notify_allowed: true
    
    - zone: home
      forwarders:
      - 172.23.0.3:53
      recurse: true
      # notify_allowed: true
    
    - zone: home.home
      forwarders:
      - 172.23.0.3:53
      recurse: true
      # notify_allowed: true

    - zone: 0.23.172.in-addr.arpa
      forwarders:
      - 172.23.0.3:53

    forward_zones_recurse:
    - zone: .
      forwarders:
      - 8.8.8.8
      # recurse: true
      # notify_allowed: true

    include_dir: /etc/powerdns/recursor.d

incoming:
  listen:
  - 0.0.0.0
  - '::'

  port: 53

  allow_from:
  - 172.16.1.0/24
  - 172.23.0.0/16

# DNSSECサーバは今回動作させえていない
dnssec:
  validation: off

# logging:
#   loglevel: 7

#   common_errors: true
#   quiet: false
#   trace: yes

pdns.conf

api=yes
webserver=yes
include-dir=/etc/powerdns/pdns.d
# launch=gsqlite3
# gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
local-address=0.0.0.0
local-port=53
# security-poll-suffix=
setgid=pdns
setuid=pdns
primary=no
secondary=no
# DNSゾーン転送を無条件で許可するIPアドレスを書く(PowerDNSキャッシュサーバのIPとRTX830のIP)
allow-axfr-ips=172.23.0.0/16,172.16.1.0/24
allow-dnsupdate-from=172.23.0.0/16
version-string=unknown

# Log
# loglevel=7
# log-dns-queries=yes
# log-dns-details=yes

# PostgreSQLs
launch=gpgsql
gpgsql-host=powerdns_db
gpgsql-port=5432
gpgsql-dbname=powerdb
gpgsql-user=powerdns
gpgsql-password=powerpass
# gpgsql-extra-connection-parameters=
# gpgsql-prepared-statements=yes
gpgsql-dnssec=no

default_schema.sql

このファイルはPostgreSQLで使用されます。Composeファイルでマウントされているディレクトリに配置されているSQLファイルやスクリプトは自動で実行されます。

この機能を利用して”auth”に必要なテーブル等を構築しています。

CREATE TABLE domains (
  id                    SERIAL PRIMARY KEY,
  name                  VARCHAR(255) NOT NULL,
  master                VARCHAR(128) DEFAULT NULL,
  last_check            INT DEFAULT NULL,
  type                  TEXT NOT NULL,
  notified_serial       BIGINT DEFAULT NULL,
  account               VARCHAR(40) DEFAULT NULL,
  options               TEXT DEFAULT NULL,
  catalog               TEXT DEFAULT NULL,
  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

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


CREATE TABLE records (
  id                    BIGSERIAL PRIMARY KEY,
  domain_id             INT DEFAULT NULL,
  name                  VARCHAR(255) DEFAULT NULL,
  type                  VARCHAR(10) DEFAULT NULL,
  content               VARCHAR(65535) DEFAULT NULL,
  ttl                   INT DEFAULT NULL,
  prio                  INT DEFAULT NULL,
  disabled              BOOL DEFAULT 'f',
  ordername             VARCHAR(255),
  auth                  BOOL DEFAULT 't',
  CONSTRAINT domain_exists
  FOREIGN KEY(domain_id) REFERENCES domains(id)
  ON DELETE CASCADE,
  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

CREATE INDEX rec_name_index ON records(name);
CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX recordorder ON records (domain_id, ordername text_pattern_ops);


CREATE TABLE supermasters (
  ip                    INET NOT NULL,
  nameserver            VARCHAR(255) NOT NULL,
  account               VARCHAR(40) NOT NULL,
  PRIMARY KEY(ip, nameserver)
);


CREATE TABLE comments (
  id                    SERIAL PRIMARY KEY,
  domain_id             INT 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,
  CONSTRAINT domain_exists
  FOREIGN KEY(domain_id) REFERENCES domains(id)
  ON DELETE CASCADE,
  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

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


CREATE TABLE domainmetadata (
  id                    SERIAL PRIMARY KEY,
  domain_id             INT REFERENCES domains(id) ON DELETE CASCADE,
  kind                  VARCHAR(32),
  content               TEXT
);

CREATE INDEX domainidmetaindex ON domainmetadata(domain_id);


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

CREATE INDEX domainidindex ON cryptokeys(domain_id);


CREATE TABLE tsigkeys (
  id                    SERIAL PRIMARY KEY,
  name                  VARCHAR(255),
  algorithm             VARCHAR(50),
  secret                VARCHAR(255),
  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);

power_admin.sh

このファイルも”default_schema.sql”と同様にPostgreSQLで使用されます。

この機能を利用して”webapp”のデータベースやユーザを構築しています。

#!/bin/bash

createuser powerdnsadmin  -U powerdns
createdb -E UTF8 -l en_US.UTF-8 -O powerdnsadmin -T template0 powerdnsadmindb 'The database for PowerDNS-Admin'  -U powerdns
psql  -U powerdns powerdnsadmindb <<EOL
ALTER ROLE powerdnsadmin WITH PASSWORD 'powerdnsadmin_password';
EOL

psql -U powerdnsadmin powerdnsadmindb <<EOL
\q
EOL

コメントを残す