Ich verwende PiHole als rekursiven DNS Server sowie als DHCP Server in meinem Heimnetz. Das bewahrt mich vor unliebsamer Werbung und erlaubt mir, DNS Einträge nach meinem Belieben zu vergeben und mehrere Domains zu verwalten (etwas, dass meine Fritzbox nicht kann). Zwar synchronisiere ich DNS und DHCP Einstellungen zwischen meinen zwei Raspberry Pi’s, bei einem Ausfall meines primären DNS PiHole, muss ich dennoch (abhängig vom Betriebssystem meiner Clients) manuell tätig werden um den zweiten PiHole als primären DNS Server verfügbar zu machen. Hier kommt keepalived ins Spiel.
Das Prinzip von keepalived ist einfach, es wird eine virtuelle IP Adresse (Cluster IP) im Netz bereitgestellt. Zwei (Cluster)-Knoten (meine zwei Raspberries) tauschen sich über ihren Status aus und abhängig davon, wird die virtuelle IP Adresse an einen der beiden Knoten “gebunden”. Der Status kann eine Rückmeldung eines Skripts sein oder schlicht die Erreichbarkeit eines der beiden Knoten. Unseren PiHole DNS Server sprechen wir über diese virtuelle IP an, nachdem keepalived korrekt konfiguriert ist. Eine ausführliche Dokumentation von keepalived findet ihr hier.
Damit PiHole über keepalived abgesichert werden kann, sollten die DNS und gegebenenfalls die DHCP Einstellungen zwischen den beiden Knoten synchronisiert sein. Wir wollen ja in einem Failover Fall keine manuellen Tätigkeiten mehr durchführen. Diese Synchronisierung könnt ihr z.B. mit Grafity-Sync oder wie in meinem Fall über ein selbst geschriebenes Ansible Playbook durchführen. Das Ansible Playbook hat den Charme, dass es neben den DNS Einstellungen auch die DHCP Einstellungen synchronisiert. Eine Anleitung zur Einrichtung des Playbooks, könnt ihr hier finden.
Keepalived installieren
Auf beiden Knoten könnt ihr keepalived folgendermaßen installieren:
apt install keepalived
Um den Status unseres PiHole Dienstes für keepalived zu ermitteln, legen wir ein Skript auf beiden Knoten mit folgendem Inhalt ab:
#!/bin/sh
PIHOLESTATUS=$(ps ef | grep pihole-FTL | grep -v grep)
if [ "$PIHOLESTATUS" != "" ]
then
exit 0
else
exit 1
Das Skript prüft, ob es einen laufenden Prozess mit dem Namen pihole-FTL
gibt und exkludiert unseren eigenen grep
Befehl. Wird keine Rückmeldung erhalten, beendet das Skript mit dem Status 1
(also Fehler). Ansonsten mit Status 0
. Die Skriptdatei bereichtigen wir dann noch zur Ausführung mit chmod +x /usr/local/scripts/check-pihole
.
Keepalived konfigurieren
Nun legen wir die Konfiguration von keepalived auf beiden Knoten ab. Die beiden Konfigurationen unterscheiden sich zwischen der Rolle der beiden Knoten (Master und Backup), ihr solltet sie also nicht vertauschen. Die Konfiguration wird unter /etc/keepalived/keepalived.conf
abgelegt.
Hier die Konfiguration des Master / Primary Knotens:
global_defs {
router_id pihole-dns-01
script_user root
enable_script_security
}
vrrp_script chk_pihole {
script "/usr/local/scripts/check-pihole"
interval 1
weight -100
}
vrrp_instance PIHOLE {
state MASTER
interface eth0
virtual_router_id 55
priority 150
advert_int 1
unicast_src_ip 192.168.1.2
unicast_peer {
192.168.1.3
}
authentication {
auth_type PASS
auth_pass secret
}
virtual_ipaddress {
192.168.1.4/24
}
track_script {
chk_pihole
}
}
Hier die Konfiguration des Backup Knotens:
global_defs {
router_id pihole-dns-02
script_user root
enable_script_security
}
vrrp_script chk_pihole {
script "/usr/local/scripts/check-pihole"
interval 1
weight -100
}
vrrp_instance PIHOLE {
state BACKUP
interface eth0
virtual_router_id 55
priority 140
advert_int 1
unicast_src_ip 192.168.1.3
unicast_peer {
192.168.1.2
}
authentication {
auth_type PASS
auth_pass secret
}
virtual_ipaddress {
192.168.1.4/24
}
track_script {
chk_pihole
}
}
In den Konfigurationen müsst ihr noch ein paar Anpassungen für eure Umgebung durchführen.
auth_pass
: Hier wird ein Passwort angegeben, welches für den Statusaustausch von keepalived verwendet wird. Das Passwort darf maximal 8 Zeichen lang sein.virtual_ipaddress
: Hierbei handelt es sich um die oben erwähnte virtuelle IP Adresse die dem Master / Primary Knoten zugewiesen wird. In meinem Fall habe ich die192.168.1.4
verwendet.unicast_src_ip
undunicast_peer
: Alssrc_ip
gebt ihr die jeweils lokale IP Adresse des jeweiligen Knotens an. Alspeer
wird die IP Adresse des keepalived “Partners” angegeben. In meinem Fall unterhält sich also192.168.1.2
(Master) mit192.168.1.3
(Backup) und umgekehrt.script
: Hierbei handelt es sich um das Skript, dass ihr oben angelegt habt. Solltet ihr das Skript unter anderem Namen oder Pfad abgelegt haben, müsst ihr das entsprechend hier berücksichtigen.priority
: Hier müsst ihr nichts anpassen, es ist jedoch wichtig zu verstehen. Die Priorität gibt an, welchen Knoten im “Normalzustand” welche Rolle inne hat. Sollten beide Knoten funktionstüchtig sein, gewinnt der Knoten mit der höheren Priorität das Rennen und hast die Master Rolle inne.weight
: Gibt das “Gewicht” unseres Prüfskriptes an. Wenn ein Status ungleich0
zurückgeliefert wird, reduziert sich diepriority
unseres Knotens um die Höhe vonweight
. In meinem Fall, reduziert das Prüfskript die Priorität des Knotens um100
.
Nun aktivieren und starten wir den keepalived Dienst auf beiden Knoten:
systemctl enable --now keepalived.service
Anschließend prüfen wir den Status des keepalived Dienstes. Die Ausgabe sollte ungefähr so aussehen:
systemctl status keepalived.service
root@piHole:~# systemctl status keepalived.service
● keepalived.service - Keepalive Daemon (LVS and VRRP)
Loaded: loaded (/lib/systemd/system/keepalived.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2022-01-06 13:44:35 CET; 1s ago
Main PID: 10698 (keepalived)
Tasks: 2 (limit: 2059)
CGroup: /system.slice/keepalived.service
├─10698 /usr/sbin/keepalived --dont-fork
└─10699 /usr/sbin/keepalived --dont-fork
Jan 06 13:44:36 piHole Keepalived[10698]: Command line: '/usr/sbin/keepalived' '--dont-fork'
Jan 06 13:44:36 piHole Keepalived[10698]: Opening file '/etc/keepalived/keepalived.conf'.
Jan 06 13:44:36 piHole Keepalived[10698]: Starting VRRP child process, pid=10699
Jan 06 13:44:36 piHole Keepalived_vrrp[10699]: Registering Kernel netlink reflector
Jan 06 13:44:36 piHole Keepalived_vrrp[10699]: Registering Kernel netlink command channel
Jan 06 13:44:36 piHole Keepalived_vrrp[10699]: Opening file '/etc/keepalived/keepalived.conf'.
Jan 06 13:44:36 piHole Keepalived_vrrp[10699]: Registering gratuitous ARP shared channel
Jan 06 13:44:36 piHole Keepalived_vrrp[10699]: (PIHOLE) Entering BACKUP STATE (init)
Jan 06 13:44:36 piHole Keepalived_vrrp[10699]: VRRP_Script(chk_ftl) succeeded
Jan 06 13:44:36 piHole Keepalived_vrrp[10699]: (PIHOLE) received lower priority (140) advert from 192.168.1.3 - discarding
Failover testen
Soweit so gut, ob piHole auch korrekt funktioniert, können wir testen, indem wir die virtuelle IP Adresse in unserem Browser aufrufen. (Bspw: http://192.168.1.4/admin/
) Wir sollten unseren gewünschten Master Knoten als Ziel zurückbekommen:
Stoppen wir den piHole Dienst auf unserem primären System (systemctl stop pihole-FTL.service
) und rufen die virtuelle IP erneut auf, solltet ihr als Ziel den zweiten piHole Server erreichen.
Lasst uns nachschauen, was keepalived im Hintergrund gemacht hat. Ihr könnt sehen, dass der ehemalige Master Knoten, sich in den Backup Zustand gebracht hat, weil das Prüfscript ein entsprechendes Ergebnis zurückgeliefert hat.
root@piHole:~# systemctl status keepalived.service
● keepalived.service - Keepalive Daemon (LVS and VRRP)
Loaded: loaded (/lib/systemd/system/keepalived.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2022-01-06 13:44:35 CET; 13min ago
Main PID: 10698 (keepalived)
Tasks: 2 (limit: 2059)
CGroup: /system.slice/keepalived.service
├─10698 /usr/sbin/keepalived --dont-fork
└─10699 /usr/sbin/keepalived --dont-fork
Jan 06 13:44:36 piHole Keepalived_vrrp[10699]: (PIHOLE) received lower priority (145) advert from 192.168.1.3 - discarding
Jan 06 13:44:37 piHole Keepalived_vrrp[10699]: (PIHOLE) received lower priority (145) advert from 192.168.1.3 - discarding
Jan 06 13:44:38 piHole Keepalived_vrrp[10699]: (PIHOLE) received lower priority (145) advert from 192.168.1.3 - discarding
Jan 06 13:44:39 piHole Keepalived_vrrp[10699]: (PIHOLE) received lower priority (145) advert from 192.168.1.3 - discarding
Jan 06 13:44:39 piHole Keepalived_vrrp[10699]: (PIHOLE) Entering MASTER STATE
Jan 06 13:57:23 piHole Keepalived_vrrp[10699]: Script `chk_ftl` now returning 1
Jan 06 13:57:23 piHole Keepalived_vrrp[10699]: VRRP_Script(chk_ftl) failed (exited with status 1)
Jan 06 13:57:23 piHole Keepalived_vrrp[10699]: (PIHOLE) Changing effective priority from 150 to 140
Jan 06 13:57:26 piHole Keepalived_vrrp[10699]: (PIHOLE) Master received advert from 192.168.1.3 with higher priority 140, ours 50
Jan 06 13:57:26 piHole Keepalived_vrrp[10699]: (PIHOLE) Entering BACKUP STATE
Sollte uns dieser Test nicht ausreichen, können wir einen ping
auf die virtuelle IP Adresse sowie einen nslookup
Befehl in einer Schleife laufen lassen, während wir den piHole Dienst oder den ganzen Raspberry durchstarten:
while true; do
nslookup mydnsname.home.lab 192.168.1.4
sleep 1
done
Nun bleibt uns noch, die virtuelle IP Adresse als DNS Server auf unseren Clients oder auf unserem DHCP Server zu hinterlegen. In meinem Fall ist piHole auch der DHCP Server. Eine Abweichende IP Adresse als DNS Server in die DHCP Konfiguration zu übernehmen, können wir durch die hier durch die Ablage einer Konfigurationsdatei unter /etc/dnsmasq.d/99-dns-vip.conf
mit folgendem Inhalt erreichen:
dhcp-option=option:dns-server,192.168.1.4
Im Anschluss starten wir piHole neu, damit die DHCP Konfiguration gezogen wird. Sobald sich unsere Clients nun neu bei dem DHCP Server anmelden, bekommen sie unsere virtuelle IP als DNS Server übergeben:
root@piHole:~# pihole restartdns
[✓] Restarting DNS server
Das wars. Von nun an, haben wir (hoffentlich) immer einen aktiven primären DNS Server.
Philip
UPDATE 02.04.2022: alle benötigten Skripte aus diesem Artikel, sind jetzt auch auf meinem Github Repository zu finden.
Hallo,
danke für die tolle Beschreibung. Ich habe aber zwei Fragen als Anfänger.
Was ist mit der router_id gemeint? Der Hostname vom RPi?
global_defs {
router_id pihole-dns-01
script_user root
enable_script_security
}
Was passiert wenn einer der RPi selbst ausfällt? Das Skript arbeitet dann ja nicht mehr?
Danke
Thorsten
Hallo Thorsten,
die
router_id
ist frei von dir wählbar und nicht von dem Hostnamen abhängig. Es sollte nur klar ersichtlich sein, welcher Knoten / Host sich dahinter verbirgt, ansonsten ist die Konfiguration komplizierter als sie sein muss.Zu deiner zweiten Frage. Sollte ein Pi nicht mehr erreichbar sein, wird der zweite Pi die Aufgabe übernehmen. Da sich beide Keepalive Installationen “unterhalten”, wissen sie um ihre Verfügbarkeit. Das heißt aber im Umkehrschluss, dass bei nur zwei Knoten, ein “Split-Brain” passieren könnte so beide Pi sich über das Netzwerk nicht mehr erreichen können und beide die Master Rolle übernehmen wollen. Das wird auf Netzwerkebene schiefgehen und du wirst mal hier und mal da die virtuelle IP angebunden bekommen. Das ist aber in einem Heimnetz eine ziemlich theoretische Annahme und selbst wenn das der Fall ist, hast du generelle Netzwerkprobleme. Ich betreibe dieses Konstrukt jetzt schon sehr lange und hatte noch nie Probleme mit Split Brain. Abhängig davon, wie beide Pi ans Netzwerk angebunden sind, kann das aber der Fall sein.
Gruß Philip
Hallo Philip,
danke für deine Erklärungen und alles läuft bei mir nun auch.
Gruß
Thorsten
Frage: bei mir habe ich zwei PiHole Server hinter einer Firewall in zwei DMZ (172.16.222.0/30 und 172.16.222.4/30). Muss die VIP im selber Netz sein, oder kann die VIP auch in einem anderen Netz liegen? Ich denke nicht, wegen dem Routing, aber vielleicht gibt es eine Idee.
Meine Grundidee ist: ein Prod-Netz sagen wir 192.168.100.0/24, die DNS VIP sollten dann z.B. 192.168.100.254 sein. Dann müsste es ein Routing geben wegen es DNS request auf die 192.168.100.254 gibt, das es dann ein DNS Forward Routing auf die beiden PiHoles in den /30 DMZ Netze gibt. Keepalived macht ja ein vrrp zwischen den PiHoles. Das erlaubt die Firewall schon. Aber ich denke das wird so nicht funktionieren, oder?
Hallo Philip,
läuft super vielen dank dafür
Update: habe eine ähnliche Frage dazu hier gefunden:
https://serverfault.com/questions/760875/keepalived-on-different-subnets
es scheint eine komplexe Lösung zu geben, aber der Grundkontext ist: keepalived funktioniert nur im selben Subnet.
Moin,
besten Dank für diese ausführliche Beschreibung!
Habe es soeben bei mir auf zwei älteren Raspberry PI’s 3 eingerichtet, und alles funktioniert super.
Danke für die Beschreibung. Habe sowas gesucht. Habe bisher nur eine Docker Pi-Hole Instanz und wollte auf zweiter Hardware eine weiter Instanz als Failover einrichten. Cool wäre wenn man die zweite Instanz erst dann tatsächlich starten könnte, wenn die Erste ausfällt, so spart man sich den Strom den die Failover Instanz ständig sinnlos braucht bis sie zum Einsatz kommt, wegen einer Verzögerung der DNS Auflösung bis die zweite Instanz gestartet ist, würde ich mir keine Sorgen machen und damit leben. Hat da jemand eine Lösung???