Icinga2: EventCommands und automatisiertes Neustarten von Diensten

Ja ich lebe noch und möchte seit Langem mal wieder etwas zu Icinga schreiben. Derzeit bin ich sehr stark in ein relativ großes Monitoring-Projekt eingebunden. Außerdem hält mich mein Studium und der Sommer davon ab, hier regelmäßig aktiv zu sein. Ich hoffe selbst, dass ich bald wieder frequenter zum Schreiben komme.

Ihr könnt mich natürlich trotzdem jederzeit per E-Mail erreichen. Jetzt aber wieder zum Thema...

Was ist überhaupt ein Eventhandler?

Kommen wir erstmal etwas allgemeiner zu Eventhandlern. Was ist das überhaupt?

Mit Icinga2 gibt es die Möglichkeit (wie damals auch schon unter Icinga1 oder Nagios), per Eventhandler automatisiert Aktionen als Reaktion auf ein Checkergebnis auszulösen.

Das wird unter Icinga2 mithilfe eines EventCommands gemacht, welches einfach an einen entsprechenden Service gehängt wird, um dann bei einer Statusänderung ausgeführt zu werden.

Ein einfacher Anwendungsfall ist das Neustarten von Diensten. Es gab in der Vergangenheit die ein oder andere alte Windows-Möhre, welche diverse Dienste einfach abstürzen ließ und diese auch nicht mehr aus eigener Kraft starten konnte. Selbstverständlich werden diese Dienste bereits per Icinga überwacht. Doch was tun, wenn der Ernstfall eintritt? Per RDP auf die Maschine und per Hand neu starten? Daran sollte man in der heutigen automatisierten IT-Welt gar nicht mehr denken.

Für so etwas gibt es das Event-Feature.

Eine Prise Objekte

Folgende Icinga-Ressourcen werden für einen funktionieren Eventhandler benötigt:

  • EventCommand-Objekt
  • Host-Objekt
  • Service-Objekt (ohne "Apply for"-Zuweisung)
  • auszuführender Eventhandler bzw. Script, welches selbst definierte Aktionen vornimmt

Hint: Zum Zeitpunkt der Erstellung dieses Beitrags funktioniert die Zuweisung eines EventCommands nicht per Apply-for-Konstrukt und anschließender Definition der Variable event_command im Host-Dictionary.

EventCommands können Host- oder Servicechecks zugewiesen werden. Sie werden bei jeder Checkausführung mit ausgeführt, allerdings nur wenn mindestens eine der folgenden Bedingungen wahr ist:

  • der Host/Service befindet sich im SOFT-State
  • der Host/Service wechselt gerade in den HARD-State
  • der Host/Service erholt sich von einem SOFT- oder HARD-State in den Status OK bzw. UP

Ihr seht also, das EventCommand wird entgegen dem Verhalten bei Notifications nicht erst nach dem Statuschange im nächsten HARD-State ausgeführt. Man sollte also in seinem Script einen exakteren Zustand prüfen und wie in diesem Beispiel den Service-State mit übergeben (siehe Script: '0'=OK, '>0' nicht OK). Hierzu sei auch nochmals auf die wirklich tolle Doku von den Icinga-Kollegen verwiesen. Dort gibt es auch noch gute Beispiele für z.B. einen automatisierten httpd-Neustart unter Linux per SSH.

Bash Bashing

Ich habe ein kleines Bash-Script (Anmerkungen sind sehr willkommen) für den Neustart des Windows-Dienstes per RPC geschrieben, denn mein Icinga läuft ja auf Linux. Das Script wird dann anschließend als Icinga-Plugin vom EventCommand aufgerufen.

Hier erstmal das Script:

vim /usr/local/etc/nagios/plugins/restart_win_service.sh
:set paste
#!/bin/bash
#
# restarts a remote windows service via rpc

# define usage function
usage()
    {
        echo "usage $0 (-h <help>) -H <host_address> -u <user_id> -p <user_password> -n <service_name> -s <service_state_id>"
    }

# exit when option string does not begin with a '-'
if [[ ! $@ =~ ^\-.+ ]]
then
  usage
fi

# execute getopt on the arguments passed to this program, identified by the special character $@
OPTS=$(getopt -n "$0" -o hH:u:p:n:s: -- "$@")

# bad arguments, something has gone wrong with the getopt command.
if [ $? -ne 0 ];
then
    exit 1
fi

# little magic, necessary when using getopt.
eval set -- "$OPTS"

# Now goes through all the options with a case and using shift to analyse 1 argument at a time.
# $1 identifies the first argument, and when we use shift we discard the first argument, so $2 becomes $1 and goes again through the case.
# go through all options
while true;
do
    case "$1" in
        -h)
            usage
            shift;;
        -H)
            # We need to take the option of the argument "-h"
            HOST_ADDRESS=$2
            shift 2;;
        -u)
            USER_ID=$2
            shift 2;;
        -p)
            USER_PASSWORD=$2
            shift 2;;
        -n)
            SERVICE_NAME=$2
            shift 2;;
        -s)
            SERVICE_STATE_ID=$2
            shift 2;;
        --)
            shift
            break;;
    esac
done

if [ ! $SERVICE_STATE_ID == 0 ]
then
    net rpc service start "$SERVICE_NAME" -I $HOST_ADDRESS -U "$USER_ID%$USER_PASSWORD" > /dev/null 2> /dev/null
fi

Fragen, Anregungen wie immer gern in die Kommentare. Das Script ist mit Sicherheit noch verbesserungsfähig. Ich habe die wichtigen Stellen - großzügig wie ich bin - mit Kommentaren versehen.

Die CLI-Optionen sammle ich mit getopt ein. Die eigentliche Aktion passiert im letzten Block, wo ein simpler parametrisierter RPC-Befehl abgesetzt wird. Die Optionen legt ihr wie bei anderen Plugins auch in der Icinga-Logik an den verschiedenst möglichen Stellen (Service, EventCommand, Host) als Custom-Vars ab.

Noch eben ausführbar machen:

chmod +x /usr/local/etc/nagios/plugins/restart_win_service.sh

Mehr Objekte, jetzt auch in ausführlich

Jetzt kommen die Host-, Service- und EventCommand-Objekte zur Ausführung des EventCommands.

Host:

object Host "win" {
    import "generic-host"

    address = "172.21.22.24"

    vars.distro = "Windows"
}

Service:

apply Service "WIN-Service: Icinga2" {
    import "generic-service"

    check_command = "by_snmp_win_service"
    event_command = "rpc_restart_win_service"

    vars.service_name = "Icinga 2"
    vars.community_id = "opencomm"

    assign where host.vars.distro == "Windows"
}

EventCommand:

object EventCommand "rpc_restart_win_service" {
    import "plugin-event-command"

    command = [ CustomPluginDir + "/restart_win_service.sh" ]

    arguments = {
        "-H" = "$rpc_host_address$"
        "-u" = "$rpc_user_id$"
        "-p" = "$rpc_user_password$"
        "-n" = "$rpc_service_name$"
        "-s" = "$rpc_service_state_id$"
    }

    vars.rpc_host_address = "$address$"
    vars.rpc_service_state_id = "$service.state_id$"
    vars.rpc_user_id = "admin"
    vars.rpc_user_password = "zapelwup"
    vars.rpc_service_name = "Icinga2"
}

Hier bitte nicht vergessen, die Konstante CustomPluginDir in der constants.conf zu setzen. Außerdem müssen natürlich die Rechte des Scripts passen, denn es wird ja vom Icinga-Prozess (unter Debian logischerweise nagios) ausgeführt.

Test

Testen könnt ihr dann ganz einfach, indem ihr auf der Windows-Gurke testweise den besagten Dienst ausschaltet und wartet bis der Check in den SOFT-State wechselt. Ihr könnt vorher noch das Debuglog einschalten und die Ausführung mit tail verfolgen:

icinga2 feature enable debuglog
systemctl reload icinga2
tail -f /var/log/icinga2/debug.log | grep "Executing event handler"

Und so ähnlich sollte dann das Log aussehen:

[2016-07-16 20:34:49 +0200] notice/Checkable: Executing event handler 'rpc_restart_win_service' for service 'win!WIN-Service: Icinga2'

[2016-07-16 21:16:48 +0200] notice/Process: Running command '/usr/local/etc/nagios/plugins/restart_win_service.sh' '-H' '172.21.22.24' '-n' 'Icinga2' '-p' 'zapelwup' '-s' '3' '-u' 'Administrator': PID 22643

Nicht vergessen, das Debuglog wieder zu deaktivieren sobald ihr fertig seid. Sonst macht sich schnell eure Platte bemerkbar :)

icinga2 feature disable debuglog
systemctl reload icinga2

Was kommt das kommt

Interessant wird das Ganze erst so richtig wenn man es weiter denkt. Mir fallen spontan folgende weitere Anwendungsfälle ein:

  • HDD bei Schwellwert automatisch aufräumen (z.B. APT-Cache löschen)
  • doppelte DHCP-Server erkennenen und beseitigen
  • IP-Adresskonflikte erkennen und auflösen
  • Bestellvorgang auslösen wenn das Lager droht zur Neige zu gehen (das ist übrigens gängige Praxis)
  • bei zu hoher Temperatur Last von der Maschine nehmen

Wie ihr seht, können Eventhandler, richtig eingesetzt, sehr nützlich sein.

Vielleicht habt auch ihr Beispiele die ihr gern zeigen oder sehen würdet? Lasst es mich wissen.