Ein DNS-Server mit PowerDNS und Docker – Teil 2: Deployment

Nachdem im ersten Teil die Vorbereitungen getroffen für den Betrieb des PowerDNS-Servers getroffen wurden, soll es nun um die tatsächliche Inbetriebnahme, die Bereitstellung der Software, oder neudeutsch, das Deployment gehen. Zunächst müssen Docker und Docker Compose installiert sein. Da Docker öfters die Installationsroutinen ändert, verweise ich an dieser Stelle an die offiziellen Dokumentationsseiten der Installation von Docker und Docker Compose.

Testlauf und Deployment mit Docker Compose

Beim ersten Aufruf von Docker Compose empfehle ich den Start im Vordergrund, d.h. ohne die Option “-d”:

geschke@pankow:~/services/nsbackend1$ docker-compose -f nsbackend1.yml up

Dabei werden die Logs aller Container auf die Standardausgabe geschrieben. Falls es hier zu Problemen kommt und nicht alle Container auf Anhieb erfolgreich gestartet werden können, werden etwaige Fehler direkt dichtbar. Meiner Erfahrung nach handelt es sich in vielen Fällen um Konflikte mit Ports und IP-Adressen, die von unterschiedlichen Containern beansprucht werden, z.B. wenn sich PowerDNS-Server und PowerDNS-Recursor gleichzeitig an den nach außen freigegebenen Port 8081 binden wollen. Damit würden sich beide Container im Wettlauf befinden, wobei der erste gewinnt, und der darauf folgende nicht mehr gestartet wird bzw. dies mit einem Fehler quittiert. Derartige Konflikte lassen sich glücklicherweise sehr leicht erkennen und lösen.

Ebenfalls können Fehlermeldungen sichtbar werden, wenn der PowerDNS-Server-Container versucht, die MariaDB-Datenbank anzusprechen, diese aber noch nicht initialisiert ist bzw. noch nicht existiert. Da dieser Fall häufiger auftritt bzw. nicht garantiert werden kann, dass die Datenbank bereits läuft, wenn der Zugriff durch PowerDNS-Server erfolgt, wurden Vorkehrungen getroffen, d.h. das Start-Skript wartet, bis die Datenbank verfügbar ist und startet den PowerDNS-Server im Anschluss daran.

Sollte der erste Start erfolgreich sein, kann für den Regelbetrieb der Start als Daemon im Hintergrund erfolgen:

geschke@pankow:~/services/nsbackend1$ docker-compose -f nsbackend1.yml up -d
Creating network "nsbackend1_dns_net" with the default driver
Creating nsbackend1_powerdns_recursor_1     ... done
Creating nsbackend1_powerdns_admin_1        ... done
Creating nsbackend1_mariadb_powerdnsadmin_1 ... done
Creating nsbackend1_mariadb_powerdns_1      ... done
Creating nsbackend1_dnsdist_1               ... done
Creating nsbackend1_powerdns_1              ... done

Erste Schritte mit PowerDNS-Admin

Wenn alle Services laufen, sollte PowerDNS-Admin unter der IP-Adresse des Hosts per Web-Browser erreichbar sein:

Ein DNS-Server mit PowerDNS und Docker – Teil 2: Deployment 1

PowerDNS-Admin Login Screen

 

Da die User-Datenbank beim Start leer ist, heißt es, sich einen Account anzulegen. Dazu kann der Login-Screen mit Klick auf “Create an account” erweitert werden:

Ein DNS-Server mit PowerDNS und Docker – Teil 2: Deployment 2

PowerDNS-Admin Login Screen – User anlegen

 

Nach dem Anlegen des Accounts und anschließenden Login befindet man sich im Dashboard von PowerDNS-Admin. Um nicht jedem – auch nicht im heimischen Netzwerk – die Möglichkeit zu geben, sich selbst einen Account anzulegen, ist es empfehlenswert, dieses Feature zu deaktivieren. Dazu ist unter “Settings” -> “Authentication” die entsprechende Option abzuwählen:

Ein DNS-Server mit PowerDNS und Docker – Teil 2: Deployment 3

PowerDNS-Admin Settings: Optionen zur Authentifizierung

 

Nachdem dies erledigt ist, müssen in PowerDNS-Admin noch die Einstellungen für den PowerDNS-Server eingerichtet werden. Der entsprechende Bereich findet sich unter “Settings” -> “PDNS”.

Ein DNS-Server mit PowerDNS und Docker – Teil 2: Deployment 4

PowerDNS-Admin: Wo ist der PowerDNS-Server?

 

Die API befindet sich dank der Docker-internen Namensauflösung unter der URL “http://powerdns:8081/api/v1/”, als API-Key ist die im Docker-Compose-File angelegte Zeichenkette einzugeben.

Migration der Bind-Zonefiles

Damit wären die wichtigsten Schritte zur Konfiguration von und mit PowerDNS-Admin erledigt, natürlich schadet es nicht, sich ein wenig umzuschauen und die Funktionen zu erkunden. Falls man von Grund auf startet und keine vorhandene Domain importieren möchte, kann bereits eine Domain angelegt werden. Da diese Beschreibung davon ausgeht, dass eine Migration von Bind-DNS erfolgt, dürfen die zu importierenden Domains jedoch nicht angelegt werden!

Für den Import wurde im Docker-Compose-File das Volume “/zones/” im PowerDNS-Server-Container eingerichtet. Lokal befindet sich das Verzeichnis unter “./zone“. Die Zonefiles von Bind lassen sich insofern einfach dort hinein kopieren und sind im PowerDNS-Container verfügbar. In meinem Fall wollte ich die (lokale) Domain geschke.net importieren, das dazu gehörige Zonefile lautet “geschke.net.hosts“. PowerDNS-Server bringt dazu das Programm “zone2sql” mit, das Bind-Zonefile ausliest und in SQL-Kommandos umwandelt. Dazu kann man per Kommandozeile in den laufenden Container “einsteigen”:

geschke@pankow:~/services/nsbackend1$ docker exec -it nsbackend1_powerdns_1 bash
root@daec4b5a8f82:/# cd /zones/
root@daec4b5a8f82:/zones# ls
10.11.12.rev  192.168.10.rev  geschke.net.hosts

Wie zu erkennen ist, befinden sich drei Zonefiles in diesem Verzeichnis. Zu Testzwecken kann zone2sql zunächst auch alleine, d.h. ohne direkten Import in die Datenbank aufgerufen werden, in diesem Fall sollte eine Liste der SQL-Kommandos erscheinen:

root@daec4b5a8f82:/zones# zone2sql -gmysql --zone=geschke.net.hosts --zone-name=geschke.net
insert into domains (name,type) values ('geschke.net','NATIVE');
insert into records (domain_id, name, type,content,ttl,prio,disabled) select id ,'geschke.net', 'SOA', 'nsbackend1.geschke.net webmaster.geschke.net 1543407847 10800 3600 604800 38400', 38400, 0, 0 from domains where name='geschke.net';
[...]

Wie zu erkennen ist, wird die Domain in der ersten ausgegebenen Zeile angelegt, daher darf sie nicht bereits in der Datenbank existieren. Zum Import wird einfach diese Ausgabe per Pipe dem MySQL-Client-Programm übergeben:

root@daec4b5a8f82:/zones# zone2sql -gmysql --zone=geschke.net.hosts --zone-name=geschke.net | mysql -u powerdnsuser -h mariadb_powerdns -p powerdns
1 domains were fully parsed, containing 181 records
Enter password:

An dieser Stelle ist das Passwort für die MariaDB für PowerDNS-Server aus der Compose-Datei anzugeben.

Analog können weitere Domains, auch für die rückwärtige Auflösung von Namen in IP-Adressen, importiert werden.

In PowerDNS-Admin sollte die Domain nach dem Import nun ebenfalls auftauchen:

Ein DNS-Server mit PowerDNS und Docker – Teil 2: Deployment 5

Alle Einträge können dort natürlich geändert, gelöscht, ergänzt usw. werden, dazu entweder auf den Domain-Namen oder den Button “Manage” klicken:

PowerDNS-Admin: Verwaltung der Domain-Einträge

 

Der erste DNS-Server läuft somit und kann nach Belieben genutzt werden! Da PowerDNS-Server einen Webserver mitbringt, der unter Port 8081 freigegeben wurde, lassen sich Statistiken und Logs unter der IP-Adresse des Hosts über den Port 8081 erreichen:

Ein DNS-Server mit PowerDNS und Docker – Teil 2: Deployment 6

PowerDNS Built-In Webserver

 

Zweiter (Slave-) DNS-Server und Zonentransfer

Die Geschichte des Zonentransfers ist eine Geschichte voller Missverständnisse… So könnte ich an dieser Stelle starten und von einigen Versuchen erzählen, genau diesen zum Laufen zu bringen. Zunächst bedarf es einen zweiten DNS-Server. Das dazu gehörige Docker-Compose-File ist analog zum ersten aufgebaut. Da die Container auf einem anderen Host gestartet werden, können auch dieselben IP-Adressen genutzt werden. Die Passwörter und API-Keys sollten sich jedoch unterscheiden. Im Folgenden werden die Änderungen im Docker-Compose-File gegenüber dem ersten Nameserver dargestellt:

version: '3.7'
services:
  [...]
  powerdns:
    [...]
    environment:
      PDNS_SLAVE: "true"
      PDNS_ALLOW_NOTIFY_FROM: "127.0.0.0/8, 192.168.10.0/24, 172.16.0.0/12, 172.30.1.0/24"
      [...]
  powerdns_recursor:
    [...]
  dnsdist:
    [...]
    ports:
      - "192.168.10.221:53:53/udp"
      - "192.168.10.221:53:53/tcp"
    [...]

Für PowerDNS-Server muss die Betriebsart als “Slave” gesetzt werden. Die Erlaubnis für die Benachrichtigung über Zonentransfers wurde hier extra weit gefasst, denn die Einschränkung findet mittels dnsdist statt. Diesem Service wird wie eingangs im ersten Teil erwähnt die IP-Adresse 192.168.10.221 zugewiesen. Alle anderen Services und deren Optionen unterscheiden sich nicht im Vergleich zum primären DNS-Server.

Während dnsdist auf dem primären Nameserver die Erlaubnis für AXFR und IXFR erteilt und derartige Anfragen an den PowerDNS-Auth-Server weiterleitet, werden auf dem Slave Notify-Requests der IP-Adressen des Hosts bzw. der sekundären und somit IP-Adresse des Nameservers auf dem Host erlaubt. Die Regeln bzw. Actions lauten somit:

addAction(AndRule({OpcodeRule(DNSOpcode.Notify),
  OrRule({makeRule("192.168.10.104/32"),makeRule('192.168.10.220/32')})}),
  PoolAction('auth'))
addAction(NetmaskGroupRule(recursive_ips), PoolAction('recursor'))
addAction(AllRule(), PoolAction('auth'))

Letztlich sind die hier dargestellten Konfigurationsoptionen die Quintessenz aus etlichen Tests und Versuchen. Beispielsweise dürfen gar nicht alle DNS-Requests an den Auth-Server weitergeleitet werden, denn damit würden die rekursiven Abfragen nicht mehr funktionieren, was eine korrekte Namensauflösung zunichte machen würde. Die hier dargestellte Konfiguration erlaubt Zonentransfers von einem Rechner im Netzwerk, und zwar dem zweiten DNS:

geschke@mickten:~/services/nsbackend2$ dig axfr @192.168.10.220 geschke.net

; <<>> DiG 9.11.5-P4-5.1ubuntu2.1-Ubuntu <<>> axfr @192.168.10.220 geschke.net
; (1 server found)
;; global options: +cmd
geschke.net.            86400   IN      SOA     nsbackend1.geschke.net. hostmaster.geschke.net. 2020032505 10800 3600 604800 38400
*****.geschke.net.      86400   IN      A       192.168.10.**
*****.geschke.net.      38400   IN      CNAME   **********.geschke.net.
[...]

Wenn die Anfrage von einem anderen Host erfolgt, wird dies mit einer Fehlermeldung quittiert:

geschke@moabit:~$ dig axfr @192.168.10.220 geschke.net

; <<>> DiG 9.11.5-P4-5.1ubuntu2.1-Ubuntu <<>> axfr @192.168.10.220 geschke.net
; (1 server found)
;; global options: +cmd
; Transfer failed.

Eine derartige Abfrage kann auch aus dem Inneren der Container stattfinden, zu beachten ist dabei einerseits, dass sich das Tool dig bei Ubuntu im Paket dnsutils befindet und erst noch nachinstalliert werden muss, und dass ggf. die Ports angepasst werden müssen, d.h. Port 5300 für PowerDNS-Server bzw. Port 5301 für PowerDNS-Recursor. Hier ein Beispiel einer AXFR-Anfrage aus dem PowerDNS-Recursor-Container:

root@6ed854f8dd73:/# dig -p 5300 @172.30.1.30 axfr geschke.net

; <<>> DiG 9.16.0-Ubuntu <<>> -p 5300 @172.30.1.30 axfr geschke.net
; (1 server found)
;; global options: +cmd
geschke.net.            38400   IN      SOA     nsbackend1.geschke.net. hostmaster.geschke.net. 2020032001 10800 3600 604800 38400
[...]

Und weiter?

Die DNS-Server sind in Betrieb und funktionieren bis jetzt sehr gut. Die bisherige Installation von Pi-hole nutzt nun PowerDNS, ohne dass ein Unterschied erkennbar wäre. Eine ähnliche Konfiguration von PowerDNS mit Docker läuft übrigens bereits seit längerem auch in der weiten Welt da draußen, sprich dem Internet, und sorgt für die korrekte Namensauflösung meiner Domains, einzig ergänzt durch Nginx-Proxy und dessen Let’s-Encrypt-Companion.

 

7 Gedanken zu „Ein DNS-Server mit PowerDNS und Docker – Teil 2: Deployment“

  1. Moin.
    Super Tutorial, läuft wirklich klasse. Eine Frage hat sich jedoch ergeben.
    Ich würde gern dyn. DNS innerhalb des LAN nutzen und es den Clients ermöglichen, sich selbst am DNS zu registrieren. Dies hat jedoch nicht funktioniert.
    Beim Testen ist mir nun aufgefallen, dass wohl dnsdist die Anfragen frisst – Deaktiviert man diesen nämlich und lässt den PDNS direkt auf Port 53/tcp/udp lauschen, funktionieren die Eintragungen am DNS problemlos.
    Hast du dafür zufällig schon eine Lösung bzw. etwas derartiges mal gebastelt?!
    Danke und LG
    Tobi

    1. Hallo!

      Erstmal vielen Dank! Wie ist denn Dein Anwendungsszenario für dynamisches DNS im internen Netzwerk? Aus meiner Sicht ist dyn. DNS nur ein Workaround oder vielmehr eine Krücke, um die IP-Vergabe der Provider auszutricksen, da sie einem freiwillig keine feste IP zuweisen bzw. falls überhaupt, nur gegen Aufpreis. Um die Frage direkt zu beantworten, dynamisches DNS im LAN habe ich noch nicht gebastelt, daher kann ich Dir ad hoc leider keine Lösung nennen.

      Hier sorgt DHCP auf der OPNsense-Firewall dafür, dass die MAC-Adressen der Clients bzw. VMs auf die jeweiligen IP-Adressen gemappt werden, die dann feste Hostnamen besitzen, die in PowerDNS eingetragen sind. Da sich die MAC-Adressen der VMs ändern bzw. selbst vergeben lassen, könnte man so auch mehrere mit derselben versehen, so dass sie beim Start dieselbe IP-Adresse erhält, wobei natürlich nur jeweils eine davon laufen darf. Darüber hinaus habe ich für einige IP-Adressen auch per CNAME mehrere Hostnamen vergeben, und einige Entwicklungs-Web-Server mit virtual hosts / Server Blocks (Nginx server_name) eingerichtet.

      1. Hallo. Danke für deine Antwort.
        Ich rede nicht von dyn. DNS ala DynDNS.org oder wie sie alle heißen.
        Mir gehts darum, dass ich das DNS für das interne Netzwerk verwenden will.
        Beispiel: Ich habe nen DHCP, der vergibt intern die IPs. Als DNS-Suffix vergebe ich etwa “lan.local”.
        Damit ich im eigenen LAN nicht mit den IPs hantieren muss will ich, dass sich die Clients automatisch beim DNS melden und ihre IP mitteilen (Funktion: dnsupdate in PowerDNS)
        Das funktioniert auch prima, wenn ich, wie oben beschrieben, auf dnsdist verzichte.
        Ich bin inzwischen soweit, dass dnsdist wohl die Anfragen entweder frisst oder an den recursor leitet, anstatt an auth.
        Mir fehlt also die passende Regel, entsprechende Anfragen ans DNS (ich nehme an “Update”) dnsdist mitzugeben um so eine richtige Weiterleitung zu erreichen.
        Ich hoffe, ich habs einigermaßen verständlich formuliert 😉

        Gruß
        Tobi

Schreibe einen Kommentar

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

Tags: