Replizierter DNS Nameserver mit PowerDNS und Docker

In diesem Blog-Post beschreibe ich, wie man mit wenigen Schritten PowerDNS als Nameserver einrichtet. Alle Dienste werden mittels Docker-Compose als Container betrieben. Als Backend kommt MariaDB mit Master-Slave-Replikation zum Einsatz. Als WebUI setze ich PowerDNS-Admin ein.

Nodes vorbereiten

Für den Test habe ich in der Hetzner-Cloud zwei kleinstmögliche Nodes (CX11, 3€ pro Monat) erstellt und mit Debian 10 initialisiert.

IP Node 1: 116.202.111.94

IP Node 2: 116.202.111.112

Anschließend habe ich Docker und Docker-Compose installiert:

apt install docker.io docker-compose

Docker-Compose für den Master

Die Passworte hier habe ich natürlich für mein Testsystem geändert 😉 Für eigene Installationen empfehle ich das auch zu tun…

---

version: "3"
services:
  mariadb:
    image: bitnami/mariadb:10.4
    volumes:
      - mariadb-data:/bitnami/mariadb
    environment:
      MARIADB_ROOT_PASSWORD: iegie8wohd2Aeta
      MARIADB_DATABASE: pdns
      MARIADB_USER: pdns
      MARIADB_PASSWORD: teiGuna5ophie6a
      MARIADB_REPLICATION_MODE: master
      MARIADB_REPLICATION_USER: replicator
      MARIADB_REPLICATION_PASSWORD: dieB6ahghoothie
    ports:
      - 3306:3306
  pdns:
    image: psitrax/powerdns:v4.2
    environment:
      MYSQL_HOST: mariadb
      MYSQL_DB: pdns
      MYSQL_USER: pdns
      MYSQL_PASS: teiGuna5ophie6a
      MYSQL_DNSSEC: "no"
      MYSQL_AUTOCONF: "true"
    ports:
      - 53:53/tcp
      - 53:53/udp
    command:
      - --api=yes
      - --api-key=faiquahz2haeXie
      - --webserver-address=0.0.0.0
      - --webserver-allow-from=0.0.0.0/0
  admin:
    image: ngoduykhanh/powerdns-admin:0.2.2
    environment:
      SQLALCHEMY_DATABASE_URI: sqlite:////data/pdnsadmin.db
    volumes:
      - admin-data:/data
    ports:
      - 80:80

volumes:
  mariadb-data:
    driver: local
  admin-data:
    driver: local

Ab Zeile 5 wird der MariaDB-Master konfiguriert. Ich verwende das Bitnami-Image, da man dort die Replikation einfach über Umgebungsvariablen aktivieren kann. Der Port 3306 muss vom Slave aus erreichbar sein, für mein Testsetup gebe ich ihn einfach extern frei. Für ein produktives Setup sollte unbedingt ein internes Netz erstellt werden (bei Hetzner mit wenigen Klicks möglich – anlegen des Netzwerks und zuweisen zu den Nodes reicht für das Debian-Standard-Image aus) oder die Kommunikation zwischen den MySQL-Instanzen mittels TLS (schwierig) oder Wireguard (einfach) abgesichert werden. Das Port-Binding ist bei Verwendung eines internen Netzes oder von Wireguard so anzupassen, dass Port 3306 nicht mehr öffentlich erreichbar ist.

Ab Zeile 19 wird PowerDNS konfiguriert. Das verwendete Image initialisiert MySQL gleich beim Start mit dem korrekten Schema. Schema-Upgrades werden derzeit nicht unterstützt! Extern wird der DNS-Port 53 für die Protokolle TCP und UDP freigegeben.

In den Zeilen 32-35 wird der API-Server von PDNS aktiviert und per API-Key gesichert für das Docker-Netzwerk freigegeben.

In den Zeilen 36-43 wird der PowerDNS-Admin konfiguriert. Die Verbindung zu PowerDNS erfolgt später in der grafischen Oberfläche. Im Testsystem gebe ich direkt Port 80 frei, in einem Produktivsystem sollte HTTPs verwendet werden, beispielsweise durch Traefik als Proxy.

Mittels docker-compose up -d kann der Stack nun gestartet werden.

Docker-Compose für den Slave

Auf dem Slave sieht die Konfiguration ähnlich aus. Es wird jedoch auf die WebUI verzichtet:

---

version: "3"
services:
  mariadb:
    image: bitnami/mariadb:10.4
    volumes:
      - mariadb-data:/bitnami/mariadb
    environment:
      MARIADB_REPLICATION_MODE: slave
      MARIADB_MASTER_HOST: 116.202.111.94
      MARIADB_MASTER_ROOT_USER: root
      MARIADB_MASTER_ROOT_PASSWORD: iegie8wohd2Aeta
      MARIADB_REPLICATION_USER: replicator
      MARIADB_REPLICATION_PASSWORD: dieB6ahghoothie
  pdns:
    image: psitrax/powerdns:v4.2
    # mariadb slave is readonly, so we cannot autoconfigure the database
    environment:
      MYSQL_AUTOCONF: "false"
    # all database options must be passed as command args
    # unless https://github.com/psi-4ward/docker-powerdns/blob/master/entrypoint.sh
    # contains an option to disable schema updates only
    command:
    - --gmysql-host=mariadb
    - --gmysql-dbname=pdns
    - --gmysql-user=pdns
    - --gmysql-password=teiGuna5ophie6a
    - --gmysql-dnssec=no
    ports:
      - 53:53/tcp
      - 53:53/udp

volumes:
  mariadb-data:
    driver: local

Da MariaDB hier im Slave-Mode läuft, werden nur das Root-Passwort und das Replication-Passwort des Masters sowie dessen IP angegeben. Der „pdns“-Benutzer wird vom Master automatisch mit synchronisiert.

Das PowerDNS-Image versucht im „AUTOCONF“-Modus, die Datenbank zu initialisieren. Dies ist auf dem Slave weder möglich (read-only) noch nötig (wird ja vom Master repliziert). Da es im Image aktuell keine Option gibt, die Initialisierung abzuschalten, wird AUTOCONF komplett deaktiviert und die Datenbankoptionen werden als Befehlsargumente an PDNS übergeben.

Auch dieser Stack kann mittels docker-compose up -d gestartet werden.

PowerDNS-Admin einrichten

Nun kann der PowerDNS-Admin über die Adresse [TBD] aufgerufen werden. Es erscheint der Login-Dialog mit der Option, einen neuen Benutzer anzulegen, mit dem man sich anschließend einloggen kann.

Signup deaktivieren

Da nicht jeder zufällige Besucher Zugang zur DNS-Verwaltung bekommen soll, wird als nächstes unter Settings -> Authentication das Häkchen bei „Allow users to sign up“ entfernt.

Verbindung zu PowerDNS einrichten

Unter Settings -> PDNS sind die URL, der API-Key und die Version des PowerDNS einzutragen. Die URL ist http://pdns:8081/api, der API-Key wie im Docker-Compose verwendet. Version ist aktuell 4.2.

Im Menüpunkt Administration -> PDNS kann man die Verbindung prüfen – sie funktioniert, wenn dort Daten angezeigt werden.

Testdomain anlegen

Unter „+New Domain“ kann nun eine Testdomain eingerichtet werden. Der „Type“ ist native, da die Replikation im Backend erfolgt. Ich habe als Domain test1.example.com verwendet.

Im Dashboard kann nun mit „Manage“ ein A-Record zur Domain eingetragen werden. Als Name wird @ verwendet, das ist die Domain selbst. Als IP kann 1.2.3.4 eingetragen werden. Nach „Save“ muss die Änderung noch einmal mittels „Apply Changes“ übernommen werden, sonst geht sie verloren.

Nachdem der Record gespeichert wurde, können beide Nameserver direkt mittels „dig“ Befehl abgefragt werden und liefern die IP:

#> dig @116.202.111.94 test1.example.com

;; ANSWER SECTION:
test1.example.com.	3600	IN	A	1.2.3.4

#> dig @116.202.111.112 test1.example.com

;; ANSWER SECTION:
test1.example.com.	3600	IN	A	1.2.3.4

Die Testdomain kann nun wieder gelöscht werden.

Weitere Schritte

Für ein Produktivsystem sollte wie oben schon geschrieben ein Proxy mit HTTPs eingerichtet werden (beispielsweise Traefik) sowie die Kommunikation zwischen den MySQL-Instanzen gesichert werden.

Es sollte auch dringend ein Backup eingerichtet werden. Dies kann auf MySQL-Ebene geschehen, es wäre aber auch ein Dump der Nameserver-Zonen denkbar. Auch die Datenbank der WebUI sollte gesichert werden.

Es ist ebenso sinnvoll, Templates für Zonen zu erstellen und an die eigenen Wünsche anzupassen. Unter Settings -> Basic können noch weitere Optionen passend eingestellt werden.

Für automatische Upgrades der Container kann Watchtower zum Einsatz kommen.

Soll IPv6 unterstützt werden, ist es notwendig, den PDNS-Container auf dem Hostnetzwerk laufen zu lassen. In dem Fall ist es wichtig, die API-Server auf das interne Docker-Netzwerk zu beschränken.

Veröffentlicht unter Linux

2 Gedanken zu „Replizierter DNS Nameserver mit PowerDNS und Docker

  1. Hallo!

    Danke erstmal für die Anleitung 🙂

    Leider scheitere ich derzeit beim Starten. Ich erhalte von dem MariaDB-Container den Fehler „mkdir: cannot create directory ‚/bitnami/mariadb/data‘: Permission denied“. Hast du eine Idee, wie das zu lösen ist?

    Docker Host-System ist ein Debian 10, wie bei dir. Einziger Unterschied ist, dass ich ebenfalls versuche das ganze hinter den Traefik zu schieben. Da das aber eher beim PDNSadmin angesiedelt ist, wüsste ich nicht wieso das Auswirkungen auf den Container haben sollte.

    Danke für einen Tipp 🙂

    1. Hallo Patrick,

      verwendest Du denn das Docker-Compose-File genau so, wie in meinem Post – insbesondere die Volumes? Ich habe es eben noch einmal auf einem frischen System getestet und konnte das Problem nicht nachstellen.
      Solltest Du hingegen statt dem Volume ein Bind-Mount (z.B. hostPath auf Kubernetes oder ein pfad-gebundenes Volume auf Docker) verwenden, ist die Ursache, dass das Verzeichnis dann die falschen Zugriffsrechte erhält. Ein möglicher Lösungsansatz dazu ist unter https://github.com/bitnami/bitnami-docker-mariadb/issues/186#issuecomment-660318927 zu finden.

      Viele Grüße,
      Michael.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*

code