keepalived kann unter anderem dazu dienen, eine oder mehrere IPs zwischen einem oder mehreren Serversystemen umzuschalten. In der Regel ist das zugrundeliegende Protokoll hierbei VRRP, das Virtual Router Redundancy Protocol.
Möchte man nun im Serververbund herausfinden ob localhost gerade im aktiven (ACTIVE), passiven (BACKUP) oder defunktionalen (FAULT) Zustand ist, gibt es hier mehrere Lösungsansätze die alle ihre eigenen Vor- und Nachteile haben.
Mir war es wichtig, mal eine kleine Übersicht zu schaffen um zumindest einige Verfahren genauer zu beleuchten, um anderen Systemadministratoren, die vor der gleichen Aufgabe stehen, die Arbeit etwas zu erleichtern.
TLDR: Siehe „Teil 5 – DBus“.
Teil 1 – keepalived mit Status-Dumping nach Unix-Signal
keepalived bietet die Funktion die aktuellen Statistiken des laufenden Prozesses in eine Datei zu schreiben, nachdem es ein bestimmtes Signal am Unix-Prozess empfangen hat. Da ich die Daten gerne im json-Format haben wollte, wäre dies das Signal 36, welches wie folgt gesendet werden kann:
kill -s $(keepalived --signum=JSON) $(cat /var/run/keepalived.pid)
Daraufhin findet sich in /tmp/keepalived.json nun eine entsprechende Datei, mit allein exportierten Informationen.
Diesen Ansatz kann man weiter in einem Skript verarbeiten um nun ausschließlich den Clusterstatus zu extrahieren:
#!/bin/bash
while getopts ":vs" opt; do
case ${opt} in
v ) verbose=1
;;
s ) single=1
;;
\? ) echo "Usage: cmd [-vs]"
exit 1;
;;
esac
done
# dump
json=$(kill -s $(keepalived --signum=JSON) $(cat /var/run/keepalived.pid) && cat /tmp/keepalived.json)
output=""
# loop over it
for instance in $(echo "$json" | jq -r '. | to_entries | .[].key'); do
release_master=$(echo "$json" | jq -r '.['$instance'].stats.release_master')
become_master=$(echo "$json" | jq -r '.['$instance'].stats.become_master')
if [[ $verbose == 1 ]]; then
iname=$(echo "$json" | jq -r '.['$instance'].data.iname')
output+="$iname "
fi
if [ "$become_master" -gt "$release_master" ]; then
output+="ACTIVE\n"
elif [ "$become_master" -eq "$release_master" ]; then
output+="BACKUP\n"
else
output+="UNKNOWN\n"
fi
done
if [[ $single == 1 && $verbose != 1 ]]; then
if [[ $output =~ ^("ACTIVE\n")+$ ]]; then
echo "ACTIVE"
exit 0
else
echo "UNKNOWN"
exit 1
fi
else
echo -e "$output"
fi
Im Skript werden die Werte „become_master“ und „release_master“ so gegenübergestellt, dass sich daraus der Clusterstatus ACTIVE oder BACKUP ergibt.
Vorteile:
- Clusterstatus ist nahezu aktuell
- kein komplexeres SNMP-Setup nötig
- lediglich Nachinstallation von jq notwendig
Nachteile
- aufgrund der Tatsache, dass Unix-Signale ausschließlich asynchron erfolgen („Fire and forget“), kommt es zu einer Race-Condition mit dem Dumping der Statistiken nach /tmp/keepalived.json
- diese Probleme können durch zusätzliche Checks bzw. inotify behoben werden, was aber die Komplexität weiter erhöht
Teil 2 – SNMP
Ein schlankeres Script kann man nutzen, wenn man auf SNMP als Unterbau zurückgreifen kann. Existiert ein SNMP-Setup beispielsweise bereits, weil es ohnehin schon zum sammeln von Statistiken zum Einsatz kommt, lohnt sich das folgende Script doppelt.
Ansonsten ist das zusätzliche SNMP-Setup mit einem gewissen Mehraufwand und einer Erhöhung der Komplexität am System verbunden. Im Kurzdurchlauf:
apt install snmp snmpd snmp-mibs-downloader
# START /etc/snmp/snmpd.conf
sysLocation Sitting on the Dock of the Bay
sysContact Me <me@example.org>
sysServices 72
master agentx
agentaddress 127.0.0.1,[::1]
rocommunity public localhost
rocommunity6 public localhost
rouser authPrivUser authpriv -V systemonly
includeDir /etc/snmp/snmpd.conf.d
# END
# START /etc/snmp/snmp.conf
# Leere Datei oder wahlweise alles auskommentiert
# END
# START /etc/keepalived/keepalived.conf
global_defs {
enable_snmp_vrrp
enable_snmp_checker
enable_snmp_rfc
enable_snmp_rfcv2
enable_snmp_rfcv3
}
[...]
# END
systemctl start snmpd
systemctl restart keepalived
# TEST
snmpwalk -v2c -c public localhost KEEPALIVED-MIB::version
Wird als Rückgabe die aktuelle keepalived-Version ausgegeben, hat alles funktioniert. Nun kann mit einem vergleichsweise schlanken Bash-Script der Status beispielsweise wie folgt abgerufen werden:
#!/bin/bash
res=$(snmpwalk -v2c -Oe -OQ -Ov -c public localhost KEEPALIVED-MIB::vrrpInstanceState)
if [[ "$res" =~ ^2+$ ]]; then
echo "ACTIVE"
exit 0
elif [[ "$res" =~ ^1+$ ]]; then
echo "BACKUP"
exit 1
elif [[ "$res" =~ ^3+$ ]]; then
echo "FAULT"
exit 2
else
echo "UNKNOWN"
exit 3
fi
Vorteile:
- aktuelle Daten, keine Race-Condition zwischen Unix-Signal und dem Schreiben der Daten
- schlankes Bash-Script möglich, im Beispiel kann sogar Iteration über die Rückgabewerte entfallen
- im Prozess installierter snmpd kann auch für weitere Daten genutzt werden
Nachteile:
- vergleichsweise aufgeblasenes Setup wenn snmpd nicht ohnehin schon verwendet wird
- ggf. gewisse Einarbeitung in snmp-Kommandos und deren Funktionsweise nötig
Teil 3 – keepalived notify Script
Es ist möglich, den State noch über einen anderen Mechanismus zu erlangen. Keepalived bietet die Notify-Funktion, mit der ein Notify-Script bei bestimmten oder bei allen State-Änderungen ausgeführt wird. Mit der Konfigurationseinstellung „notify“ kann für die jeweilige VRRP-Instanz das jeweilige Script konfiguriert werden. Beachtung sei Zeile 4 geschenkt:
vrrp_instance INSTANZ1 {
state BACKUP
interface ens192
notify /usr/bin/kdstate
virtual_router_id 32
priority 100
authentication {
auth_type PASS
auth_pass etst1234
}
virtual_ipaddress {
10.0.0.1
}
}
Das Script in /usr/bin/kdstate sieht in dem Fall so aus:
#!/bin/bash
echo $2 $3 > /tmp/kdstate.$2.txt
Die Parameter, welche keepalived beim Aufruf des Scripts übergibt, haben hierbei die folgende Bedeutung:
- $1 = „GROUP“ or „INSTANCE“
- $2 = name of group or instance
- $3 = target state of transition („MASTER“, „BACKUP“, „FAULT“)
Vorteile:
- beim State-Wechsel wird eine Datei geschrieben aus der die Daten äußerst effizient abgerufen werden können
- diese Lösung kann über das Script noch mit weiteren Aufgaben, abhängig vom jeweiligen State-Change, auf eine sehr einfache Weise kombiniert werden (z. B. Start oder Stopp von Anwendungen)
Nachteile:
- nur wenig nutzbare Daten verfügbar, gerade genug für unseren Zweck
- die Daten werden nur beim State-Change geschrieben, können also unter Umständen (z. B. seltener Absturz des keepalived-Prozesses) veraltet sein und nicht den korrekten Zustand wiederspiegeln
Nachtrag: keepalived bietet auch die Möglichkeit, den Notify-Output in eine FIFO-Pipe zu stecken. In Kombination mit dieser und einem entsprechenden Listener auf der Pipe sind auch noch andere Konstellationen als der schlichte Output in ein File denkbar.
Teil 4 – Prüfung von keepalived-IPs ohne keepalived
Man könnte das auch als „Cheat“ bezeichnen. Hat man alle IPs zu einer Instanz oder eines Interface als Datengrundlage zur Verfügung, kann man schlicht prüfen, ob diese auf dem aktuellen System vorhanden sind. Ist dies der Fall, wäre das System mit hoher Wahrscheinlichkeit aktiv:
#!/bin/bash
(ip a | grep -q "10.0.0.1") || exit 1
(ip a | grep -q "10.0.0.2") || exit 1
echo "ACTIVE"
Vorteile:
- relativ universell (z. B. mit anderen Cluster-Managern) einsetzbar
- relativ schlank, simples Script
Nachteile:
- keine Unterscheidung in BACKUP und FAULT möglich, nur ACTIVE-Prüfung möglich (Script ist aber erweiterbar, z. B. um Gegenstelle/anderen Clusternode über Netzwerk zu prüfen)
- im Fehlerfall (z. B. IP-Konflikt) ist der ausgegebene State eventuell nicht korrekt
- IP-Adressen müssen bekannt sein und vollständig vorliegen, in größeren, komplexeren Deployments kann man mit einem entsprechenden Konfigurationsmanagement entgegnen
Teil 5 – DBus
Seit Version 1.3.0 bietet keepalived die Anbindung an DBus, worüber der keepalived-State mit unterschiedlichen DBus-Clients abgerufen werden kann. In der keepalived.conf muss lediglich der Parameter „enable_dbus“ gesetzt werden:
global_defs {
enable_dbus
}
Anschließend können die Daten wie folgt abgerufen werden:
#!/bin/bash
for instance in $(busctl --list tree org.keepalived.Vrrp1 | grep -E 'IPv(4|6)$'); do
res+=$(busctl --json=short get-property org.keepalived.Vrrp1 \
$instance \
org.keepalived.Vrrp1.Instance State | jq '.data[0]')
done
if [[ "$res" =~ ^[2]+$ ]]; then
echo "ACTIVE"
exit 0
elif [[ "$res" =~ ^[1]+$ ]]; then
echo "BACKUP"
exit 1
elif [[ "$res" =~ ^[3]+$ ]]; then
echo "FAULT"
exit 2
else
echo "UNKNOWN"
exit 3
fi
Eine tolle und gut zu erweiternde Lösung bietet auch dieses Python-Script.
Vorteile:
- DBus-Properties werden aus der Laufzeit der Anwendung geladen, sind also brandaktuell
- keine zusätzliche Installation von Daemons notwendig
- relativ schlankes Script als Check
Nachteile:
- keepalived DBus-Properties sind so organisiert, dass einzeln über alle Kombinationen aus NIC+VRRP-Instanz+IP-Version iteriert werden muss (siehe Beispielscript)
Fazit
Es gibt unzählige, teils verworrene Lösungen um den Status von keepalived-Instanzen abzufragen. Hier wurden nur 5 beleuchtet. Die Methode meiner Wahl ist in Teil 5 beschrieben und stellt eine elegante Antwort für die Frage, wie denn der Status von keepalived-Instanzen abzurufen sei, bereit. Ich wünsche beste Erfolge bei der Implementierung eurer Lösung.