Unterschiedliche Ansätze um aus keepalived den aktuellen State/Clusterstatus zu extrahieren

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.

Schreibe einen Kommentar

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