Aktionen

Zabbix Standorte Hostgruppen bei Nichterreichbarkeit automatisch in den Wartungsmodus setzen oder daraus entfernen

Aus znilwiki

Versionen:

  • 20./21.07.2017 erste Version




1 Problemstellung

Eine interessante Problemstellung in Zabbix war für mich schon immer die folgende:

  • An einem Standort steht der Zabbix-Server (hier als SoYouStart bezeichnet)
  • über z.B. eine VPN Verbindung ist ein zweiter Standort angebunden (hier DGHBCS)
  • dort läuft ein Proxy-Server mit mehreren Hosts die überwacht werden (oder die Hosts werden direkt überwacht)

ClipCapIt-170620-212420.PNG

Fällt nun die Verbindung zwischen den Standorten aus, so erhält man in der Regel Trigger über

  • die ausgefallene Verbindung
  • Geräte die sich nicht mehr anpingen lassen
  • Hosts die nicht mehr erreichbar sind

Befinden sich also 20 Host dort so bekommt man schnell 40 bis 50 ausgelöste Trigger - obwohl ja eigentlich nur die Verbindung weg ist.


Kein Problem, Zabbix kann ja Dependencys (Abhängigkeiten) bei den Triggern.

Jupp. Und was heißt das in der Praxis?


Methode 1: Wir setzen an den Triggern an

  • Wir setzen einen Ping + Trigger auf die Firewall am anderen Standort - nicht per Template sondern an einem Host direkt
  • Klappt der Ping z.B. 4 mal hintereinander nicht, löst dieser Trigger aus
  • Für die Hosts am anderen Standort nehmen wir nicht unsere normalen Templates sondern klonen diese einmal für diesen Standort
  • In den neuen, geklonten Templates bearbeiten wir alle Trigger die auf Verbindungsausfälle empfindlich reagieren würden:
    • Wir setzen diesen Trigger in Abhängigkeit auf den Trigger mit der Firewall
    • Fast alle Trigger die von Items abhängig sind die per Zabbix Agent gesammelt werden Melden bei Nicht-Erreichbarkeit per Standard nicht!
    • Alternativ können wir die Abhängigkeit auch direkt in den Trigger-Formel machen (... and {trigger-firewall}=0)


Ergebnis:

  • Die Trigger an den Hosts lösen nicht aus wenn die Verbindung wegbricht


Vorteile:

  • Es funktioniert
  • Die Trigger der Hosts lösen nicht aus und sind auch in der Weboberfläche somit nicht zu sehen.
  • Man könnte auch was mit Macros basteln ... die dann an jedem Host gepflegt werden müssten

Nachteile:

  • Bei Änderungen an den Templates muss man diese Änderungen auch an allen geklonten Kopien vornehmen
  • Hey, wir haben hier 100 Standorte an einem Zabbix-Server - was ist denn das für eine Arbeitsbeschaffungsmaßnahme???
  • Timing muss stimmen - der Firewall-Trigger muss zuerst auslösen




Methode 2: Wir machen es per Action

  • Wir setzen einen Ping + Trigger auf die Firewall am anderen Standort - nicht per Template sondern an einem Host direkt
  • Klappt der Ping z.B. 4 mal hintereinander nicht, löst dieser Trigger aus
  • Wir ignorieren das die Trigger in der Weboberfläche auslösen und verhindern nur das eine Email rausgeht:
    • In der Action bei den Conditions setzen wir die Bedingung '"Trigger name not like trigger-firewall"'


Vorteile:

  • ENTFÄLLT


Nachteile:

  • Es funktioniert nicht! Man kann mit den Conditions keine Wenn-Dann Bedingungen bauen
  • Dadurch würde nur verhindert das der eine Firewall-Trigger die Action auslöst, der Rest kommt einfach normal weiter



Gerne nehme ich an dieser weitere Lösungen von euch an und werde diese hier Beschreiben!
Einfach per Email an Bernhard@znil.de !

Hier folgt nun mein Lösungsvorschlag.



2 Lösungsbeschreibung

Mein Lösungsvorschlag arbeitet nun wie folgt

  • Wir legen eine Hostgruppe für den Standort an. In diesen kommen alle Hosts aus diesem Standort
  • Wir legen einen Dummy-Host an für die Erreichbarkeit des Standortes. Der Host kann für alle Standorte genutzt werden. Dieser Dummy-Host darf nur nicht Mitglied in einer der Standortgruppen sein.
  • Auf dem Dummy-Host richten wir wieder mindestens ein Item (Ping auf etwas internes im Standort, z.B. Firewall-Interface) mit dem zugehörigen Trigger ein
  • Und nun legen wir für jeden Standort eine(!) Action ein die nur auf diesen Trigger hin auslöst
  • Die Action macht dann - sofort - folgendes:
    • Eine Email versenden das der Standort nicht erreichbar ist
    • Die Hostgruppe für den Standort in den Wartungsmodus setzen
  • Wenn die Verbindung wieder steht, sprich der Trigger wieder ausgeht, dann macht die Action per Recovery operations folgendes
    • ggf. wieder eine Email versenden
    • Die Hostgruppe wieder aus den Wartungsmodus entfernen


Vorteile:

  • Die anderen Host Trigger lösen nicht aus bzw. werden gleich wieder deaktiviert
  • Trigger die vorher ausgelöst waren sind hinterher auch noch ausgelöst
  • Trigger die während dessen auslösen erzeugen keine Benachrichtigungen (da Host in Maintenance)
  • Keine Änderungen an den Templates notwendig
  • Nur einmal pro Standort ein Item, einen Trigger und eine Action bauen
  • Einrichtung der Action ab Zabbix 3.2 einfach


Nachteile:

  • Ersteinrichtung unter Zabbix 3.0 oder kleiner recht umständlich
  • Timing muss stimmen - die Action muss vor den anderen Actions auslösen. Funktioniert am besten wenn es eine Verzögerung bei den anderen gibt (Email erst nach x Minuten versenden)




3 Umsetzung




3.1 Benutzer anlegen

Erstellt einen neuen Superadmin Benutzer in Zabbix, das Passwort am besten nur Buchstaben und Zahlen.

Benutzer : setmaintenance
Passwort : abc123
Gruppen  : Zabbix administrators
User type: Zabbix Super Admin

Falls Ihr LDAP-Authentifizierung nutzt sollte der Benutzer in die "noLDAP" Gruppe.



3.2 Skript hinterlegen

Wir hinterlegen im Externalscripts und im Alertscripts Ordner auf dem Zabbix-Server ein Skript welches Maintenance periods" für Host groups erstellen und löschen kann.

nano /usr/lib/zabbix/externalscripts/zabbix_main_group.pl

Inhalt:

#!/usr/bin/perl
 
use strict;
use Getopt::Std;
use vars qw/ %opt /;
 
my $url = "http://localhost/api_jsonrpc.php"; # change <zabbix server> to your zabbix server
my $apiuser="username"; # API User's Username
my $apipassword="password"; # API User's password
my $maintenanceid;
 
#############
# Begin main
#
init();
my $groupname = $opt{s};
my $duration = $opt{d} || 10800;
my $maintname = $opt{n}; 
 
 
# Authenticate against Zabbix for API Maintenance addition
my $auth = newrequest($url, $apiuser, $apipassword);
my $groupid = getgroupid($groupname);
 
if($opt{r}){
    print "Removing maintenance for group $groupname\n";
    getmaintid($groupid,$maintname);
    exit(0);
}else{
    print "Adding maintenance for group $groupname\n";
    addmaint($groupid,$duration,$maintname);
}
exit(0);
 
#########################
# Get command line input
#
sub init(){
    my $opt_string = 'hrs:d:n:';
    getopts( "$opt_string", \%opt ) or usage();
    usage() if $opt{h};
    usage() if !$opt{s};
    usage() if !$opt{n};
}
#####################
# Print script usage
#
sub usage(){
    print STDERR << "EOF";
usage: $0 [-hr] -s groupname [-d duration] -n name
 -h           : This (help) message   
 -r           : Remove maintenance for specified host 
 -s groupname : Hostgroupname in Zabbix
 -d           : Duration of maintenance in seconds. Leave blank to use default.
                  300 =  5 minutes
                 1800 = 30 minutes
                 3600 =  1 hour
                10800 =  3 hour (default)
 -n name      : unique identifier
 
-s and -n are required
 
example: $0 -s groupname -d 3600 -n backup
example: $0 -s groupname -r -n backup
EOF
    exit;
}
 
 
#############################################
# Zabbix API requests require authentication
#
sub newrequest {
    my ($url, $user, $password) = @_;
 
    my $authenticate = qq(curl -k -s -i -X POST -H 'Content-Type: application/json-rpc' -d '{
      "params": {
          "password": "$password",
          "user": "$user"
      },
      "jsonrpc": "2.0",
      "method": "user.login",
      "id": 0
    }'  $url | grep result | head -n 1 | cut -d '"' -f 8);
    my $auth = `$authenticate`;
    chomp($auth);
    return $auth
 
}
 
###################################################
# Subroutine to query Zabbix to get host id
#
sub getgroupid{
    my $groupname = shift;
    my $process = qq(curl -k -s -i -X POST -H 'Content-Type: application/json-rpc' -d '{
    "params": {
        "filter": {
            "name": "$groupname"
        }
    },
    "jsonrpc": "2.0",
    "method": "hostgroup.get",
    "auth": "$auth",
    "id": 2 }' $url);
    my $res = `$process`;
    chomp($res);
 
    # print "$res \n\n";
    my @output = split(/,/,$res);
    my $x=0;
    foreach(@output){
        if (($output[$x] =~ m/\"name\"/)&&($output[$x] =~ m/$groupname/)){
            $output[$x-1] =~ s/\[\{//g;
            $output[$x-1] =~ s/"//g;
            $output[$x-1] =~ s/groupid://g;
            $output[$x-1] =~ s/result://g;
            $output[$x-1] =~ s/\{//g;
            $groupid = $output[$x-1];
        }
        $x++;
    }
    if(!$groupid){
       print "WARNING - $groupname not found in maintenance for Zabbix.\n";
       exit(1);
    }
    print "Group ID: ".$groupid."\n";
    return $groupid
}
 
 
###################################################
# Subroutine to query Zabbix to get maintenance id
#
sub getmaintid{
    my $groupid = shift;
    my $maintname = shift;
    my $process = qq(curl -k -s -i -X POST -H 'Content-Type: application/json-rpc' -d '{
    "params": {
        "output": "extend",
        "selectHosts": "refer",
        "selectGroups": "refer",
        "groupids": "$groupid"
    },
    "jsonrpc": "2.0",
    "method": "maintenance.get",
    "auth": "$auth",
    "id": 2 }' $url);
 
    my $res = `$process`;
    chomp($res);
 
    my @output = split(/,/,$res);
    my $x=0;
    foreach(@output){
        if (($output[$x] =~ m/\"name\"/)&&($output[$x] =~ m/ID $maintname/)){
            $output[$x-1] =~ s/\[\{//g;
            $output[$x-1] =~ s/"//g;
            $output[$x-1] =~ s/maintenanceid://g;
            $output[$x-1] =~ s/result://g;
            $output[$x-1] =~ s/\{//g;
            $maintenanceid = $output[$x-1];
            remmaint($maintenanceid);
        }
        $x++;
    }
    if(!$maintenanceid){
       print "WARNING - $groupname not found in maintenance for Zabbix.\n";
       exit(1);
    }
}
 
 
#################################################
# Subroutine to add maintenance window to Zabbix
#
sub addmaint{
    $groupid = shift;
    $duration = shift;
    $maintname = shift;
    my $start = time();
    my $end = ($start + $duration);
    # my $auth = newrequest($url, $apiuser, $apipassword);
   my $process = 'curl -k -s -i -X POST -H \'Content-Type: application/json-rpc\' -d "{
    \"jsonrpc\":\"2.0\",
    \"method\":\"maintenance.create\",
    \"params\":[{
        \"groupids\":[\"'.$groupid.'\"],
        \"hostids\":[],
        \"name\":\"000_Trigger Maintenance Mode with ID '.$maintname.' - '.$start.'\",
        \"maintenance_type\":\"0\",
        \"description\":\"000_Trigger Maintenance Mode for '.$groupname.' set by Action\",
        \"active_since\":\"'.$start.'\",
        \"active_till\":\"'.$end.'\",
        \"timeperiods\": [{
            \"timeperiod_type\": 0,
            \"start_date\": \"'.$start.'\",
            \"period\": '.$duration.'}]
        }],
    \"auth\":\"'.$auth.'\",
    \"id\":3}" '.$url; 
    my $res = `$process`;
    chomp($res);
 
    my @output = split(/,/,$res);
 
    foreach(@output){
    if ($_ =~ m/\"error/){
            print "$_\n";
        exit(1);
        }
        print "$_\n" if ($_ =~ m/\"result/);
    }
 
}
#################################################
# Subroutine to remove maintenance window from Zabbix
#
sub remmaint{
    $maintenanceid = shift;
    my $process = qq(curl -k -s -i -X POST -H 'Content-Type: application/json-rpc' -d '{
    "jsonrpc":"2.0",
    "method":"maintenance.delete",
    "params":["$maintenanceid"],
    "auth":"$auth",
    "id":2}' $url);
 
    my $res = `$process`;
    chomp($res);
 
    my @output = split(/,/,$res);
 
    foreach(@output){
        print "$_\n" if ($_ =~ m/\"error/);
        print "$_\n" if ($_ =~ m/\"result/);
    }
}

Bei dem Skript handelt es sich um eine Abwandlung des Beispiels von https://www.zabbix.org/wiki/Perl_script_to_add/remove_Maintenance
Ich habe die Host Version auf Host groups angepasst.

Ihr müsst am Anfang den Benutzernamen und das Passwort anpassen.
Falls Ihr die Webseite des Zabbix-Servers über

http://servername/zabbix

erreicht müsst Ihr auch den localhost Eintrag entsprechende anpassen - und ggf. auch https statt http setzen.

Nun machen wir das Skript noch Ausführbar:

chmod +x /usr/lib/zabbix/externalscripts/zabbix_main_group.pl

Und wir verlinken es noch in den Alertscripts Ordner:

ln /usr/lib/zabbix/externalscripts/zabbix_main_group.pl /usr/lib/zabbix/alertscripts/zabbix_main_group.pl

Damit können wir das Skript auch als media type benutzen, das brauchen wir in Zabbix-Version vor 3.2 (also 3.0 und kleiner)



3.3 Dummy-Host

Wir erstellen einen neuen Host der die Items und Trigger für die Verbindungsprüfungen zu den Standorten beherbergt,
außer dem Namen müssen wir nichts eintragen:
ClipCapIt-170620-224613.PNG
Wir Ihr seht baue ich auch gleich eine neue Gruppe dazu.

Für diesen Host erstelle ich nun ein neues Item:
ClipCapIt-170620-225004.PNG
Wie Ihr seht trage ich die IP direkt in den Key ein - so kann ich den Host für mehrere Standorte nutzen.
Als Update interval (in sec) habe ich 40 Sekunden gewählt - damit liegen ich unter meinen sonst üblichen 60 Sekunden.

Jetzt der Trigger dazu:
ClipCapIt-170620-225307.PNG
Die Expression

{Dummy-Standorte:icmpping[192.168.99.1].max(240)}=0

löst aus wenn der Maximalwert der 4 Minuten bei 0 liegt. Bei einem Abfrageintervall von 40 Sekunden sind das 5 bis 6 Pings die hintereinander nicht geklappt haben.
Sobald ein Ping wieder klappt (=1) geht der Trigger sofort wieder aus - der Durchschnitt liegt dann wieder über 0. Wir haben also eine "Anzugsverzögerung" aber keine "Abfallverzögerung".

Important.png
Hinweis: Natürlich könnt Ihr auch mehrere Items in dem Trigger verknüpfen - z.B. in dem Ihr die Firewall und einen Switch dort anpingt - oder was Ihr euch als Kriterium ausdenkt.




3.4 Host Gruppe für den Standort erstellen

Wir brauchen eine Gruppe in der alle Host des Standort sind - aber NICHT unser Dummy-Host!
Bei mir ist das die Gruppe

Ort: DGHBCS

ClipCapIt-170621-224723.PNG



3.5 Action unter Zabbix 3.2 oder höher

Die Action sieht wie folgt aus:
ClipCapIt-170621-000736.PNG
Der Name der Action sollte tunlichst keine Sonderzeichen etc. enthalten!
Dieser Action reagiert also nur wenn der eine extra angelegte Trigger auslöst.

Bei Operations fügen wir einen neuen Schritt wie folgt hinzu:
ClipCapIt-170620-234239.PNG
Das Command ist:

/usr/lib/zabbix/externalscripts/zabbix_main_group.pl -s "Ort: DGHBCS" -d 2592000 -n "{ACTION.NAME}"

Den Namen der Host group, hier "Ort: DGHBCS", müsst Ihr anpassen.
Die Dauer - Parameter -d - entspricht 30 Tagen
Und {ACTION.NAME} setzt den Namen dieser Action ein. Da kann auch ein eigener begriff rein - Hauptsache er ist auch beim Recovery operations gleich
Auf Add klicken - dann sollte es so aussehen:
ClipCapIt-170620-232500.PNG


Nun noch die Recovery operations
ClipCapIt-170620-232723.PNG
Das Command ist:

/usr/lib/zabbix/externalscripts/zabbix_main_group.pl -s "Ort: DGHBCS" -r -n "{ACTION.NAME}"

statt -d 2592000 also -r


3.6 Action unter Zabbix 3.0 oder niedriger

Important.png
Hinweis: Unter den älteren Zabbix Versionen gibt es in der Action diese klare Trennung von Operations und Recovery operations noch nicht. Wir zweckentfremden deshalb die Möglichkeit für PROBLEM und OK unterschiedliche Nachrichten setzen zu können - und setzen die Skript Parameter dort hinein. Unser Skript arbeitet dann als Media type und bekommt die Parameter aus den Text-Feldern

Wir erstellen zunächst ein neues Media types:
ClipCapIt-170621-131158.PNG
mit folgenden Inhalt:
ClipCapIt-170621-131337.PNG
Damit das geht muss das Skript auch im Verzeichnis alertscripts hinterlegt sein (was wir oben schon gemacht haben)
Dann brauchen wir einen Benutzer bei dem wir das Skript / unseren neuen Media type hinterlegen - statt Email.
Da können wir entweder einen neuen Dummy-Benutzer nehmen oder den zuvor erstellten Benutzer für das Skript:
ClipCapIt-170621-131717.PNG
ClipCapIt-170621-131745.PNG

Nun können wir die Action erstellen:
ClipCapIt-170621-132210.PNG
Bei Default Message UND(!) Recovery Message kommt der Name der Gruppe hinein die in den Wartungsmodus versetzt werden soll. Ohne Leerzeichen oder neue Zeile am Ende!!!
Das Feld Default Subject bleibt leer. Das Feld Recovery Subject enthält nur

-r

Unter Conditions wählen wir wieder den Host und den Trigger:
ClipCapIt-170621-133405.PNG

Und unter Operations wie folgt:
ClipCapIt-170621-133618.PNG
Also Nachricht an unseren Dummy-User über das Skript.

Wie funktioniert das?

  • Bei Auslösung der Action wird der Name der Gruppe aus dem Nachrichtentext übergeben. Subject ist leer, damit wird nichts übergeben und die Maintenance von 30 Tagen wird eingerichtet
  • Geht der Trigger wieder aus, wird eine Recovery Message gesendet. Das ist der gleiche Befehl, nur das diesmal aus dem Subject ein -r übergeben wird - für Remove und somit wird die Maintenance wieder gelöscht.




4 Der Test

Zunächst prüfen wir unter Latest data ob der Ping denn klappt:
ClipCapIt-170620-232947.PNG
nun ändere ich die IP einmal Testweise auf eine nicht erreichbare, nach etwas Warten sieht es nun so aus:
ClipCapIt-170620-233228.PNG
Jetzt warten wir noch 4 Minuten bis der Trigger auch auslöst:
ClipCapIt-170620-233851.PNG

Wir wechseln zu Maintenance und siehe da, da ist unser Eintrag:
ClipCapIt-170621-001038.PNG
Im Detail:
ClipCapIt-170621-001104.PNG
ClipCapIt-170621-001125.PNG
ClipCapIt-170621-001147.PNG


Ok, Gegenprobe - ich korrigiere die IP wieder:
ClipCapIt-170620-235356.PNG
Damit sollte der Trigger wieder auf Ok gehen:
ClipCapIt-170620-235448.PNG
und somit auch die Maintenance period wieder verschwinden.

...
Ich wollte hier jetzt kein Bild posten auf dem nichts zu sehen ist - die Maintenance ist wirklich weg!



5 Hinweise / Verbesserungen

Das Skript setzt die Hostgruppe in den Wartungsmodus mit Datensammlung.
Naja, es gibt ja keine Verbindung, also kann ja sowieso nichts kommen.
Ich schätze mal: Jein!

Wenn es einen Zabbix-Proxy gibt so sammelt dieser per Default bis zu einer Stunde die Daten - und übermittelt diese dann auf einen Schlag sobald wieder eine Verbindung besteht.
Die Daten würden also nachgereicht.
Ich vermute also das alle sonstigen Störungen die Trigger auslösen dann nach der Verbindungswiederherstellung durchschlagen und die Alarme kommen.
Damit sind nur Bedingungen die während der Verbindungsunterbrechung neu auftreten.
Ich nicht genau sagen wie Zabbix das behandelt. Es wird einen kurzen Moment geben wo Daten vom Proxy kommen und der Maintenance/Wartungsmodus gerade am abschalten ist.

Alternativ könnte man auch die Datensammlung während des Wartungsmodus deaktivieren: Sucht im Skript die Zeile

\"maintenance_type\":\"0\",

Die 0 bedeudet "With data collection". Ändert die 0 auf eine 1 und wir haben "Without data collection":

\"maintenance_type\":\"1\",

Nun würde die Daten im ersten Moment verworfen - ob alle kann ich nicht sagen.
Der Wartungsplan wird ja gelöscht, Zabbix weis nicht mehr das es diesen gab.
Aber damit sind alle Daten auf jeden Fall "neu" und sollten bei Bedarf Trigger auslösen.

Wie sich "nodata" Trigger verhalten müsste man noch austesten.


Ohne Zabbix-Proxys sind die Daten weg und es sollte keine Probleme (außer mit "nodata") geben.



6 Kommentare


Kommentar hinzufügen
znilwiki freut sich über alle Kommentare. Sofern du nicht anonym bleiben möchtest, trage deinen Namen oder deine Email-Adresse ein oder melde dich an. Du kannst das Feld auch einfach leer lassen.