1. Einleitung
Check-Plugins sind in Python geschriebene Software-Module, die auf der Checkmk-Instanz ausgeführt werden und die Services eines Hosts erstellen und auswerten.
Checkmk umfasst über 2000 fertige Check-Plugins für alle nur denkbare Hardware und Software. Diese werden vom Checkmk-Team gepflegt und jede Woche kommen neue dazu. Daneben gibt es auf der Checkmk Exchange weitere Plugins, die von unseren Anwendern beigesteuert werden.
Und trotzdem kann es immer wieder vorkommen, dass ein Gerät, eine Anwendung oder einfach nur eine bestimmte Metrik, die für Sie wichtig ist, noch von keinem dieser Plugins erfasst wird — vielleicht auch einfach deshalb, weil es sich dabei um etwas handelt, das in Ihrer Firma entwickelt wurde und es daher niemand anders haben kann. Im Artikel zur Einführung in die Programmierung von Erweiterungen für Checkmk erfahren Sie, welche Möglichkeiten Sie haben.
Dieser Artikel zeigt Ihnen, wie Sie echte Check-Plugins für den Checkmk-Agenten entwickeln können — mit allem was dazugehört. Für SNMP-basierte Check-Plugins gibt es einen eigenen Artikel.
1.1. Die Check-API-Dokumentation
Für die Programmierung der Check-Plugins gibt es seit der Checkmk-Version 2.0.0 eine neu entwickelte Check-API. Wir zeigen Ihnen, wie Sie diese Check-API für die Plugin-Programmierung nutzen können.
Über die Checkmk-Benutzeroberfläche haben Sie jederzeit Zugriff auf die Dokumentation der Check-API: Help > Developer resources > Check plugin API reference. Wählen Sie im neuen Browserfenster in der linken Navigationsleiste BASE > Agent based API ("Check API") aus:
Hinweis für Benutzer der bis zur Version 1.6.0 gültigen Check-API: Falls Sie noch Check-Plugins haben, die mit der alten API entwickelt wurden, sollten Sie diese bald auf die neue Check-API migrieren. Zwar wird die alte Check-API für eine Übergangszeit weiterhin unterstützt — aber auch diese Zeit wird einmal enden. Den einmaligen Aufwand zur Migration machen die Vorteile der neuen Check-API wett, denn diese ist konsistenter, logischer, besser dokumentiert und zukunftssicher. Im Blogpost zur Migration von Check-Plugins informieren wir Sie ausführlich über die notwendigen Schritte.
1.2. Voraussetzungen
Wenn Sie Lust haben, sich mit dem Programmieren von Check-Plugins zu befassen, benötigen Sie folgendes:
Kenntnisse in der Programmiersprache Python.
Erfahrung mit Checkmk, vor allem was das Thema Agenten und Checks betrifft.
Übung mit Linux auf der Kommandozeile.
1.3. Begriffe
Zwar ist hier immer die Rede von einem Check-Plugin und davon, dieses zu schreiben. Aber genau genommen benötigen Sie immer zwei Plugins, das Agentenplugin auf dem überwachten Host und das Check-Plugin auf dem Checkmk-Server. Beide müssen geschrieben und aufeinander abgestimmt werden. Nur so funktionieren sie hinterher reibungsfrei.
2. Ein Agentenplugin schreiben
Wer sich für die Programmierung von Check-Plugins in Checkmk interessiert, hat mit großer Wahrscheinlichkeit auch schon einmal einen Checkmk-Server aufgesetzt. Haben auch Sie dies bereits gemacht, haben Sie dabei als Einstieg vermutlich auch Ihren Checkmk-Server selbst als Host überwacht.
Im Folgenden gehen wir von einem beispielhaften Szenario aus, in dem Checkmk-Server und überwachter Host identisch sind. So ist es uns möglich über Livestatus-Abfragen vom Host Informationen über Host-Gruppen zu erhalten, die der Checkmk-Server zur Verfügung stellt.
Im beschriebenen Beispiel gehen wir von einem Unternehmen mit mehreren Standorten aus:
Jeder dieser Standorte wird in Checkmk durch eine Host-Gruppe abgebildet.
Jeder Standort hat sein eigenes Service-Team.
Damit bei Problemen jeweils das richtige Service-Team verständigt werden kann, gilt, dass jeder Host einem Standort — also auch einer Host-Gruppe — zugewiesen sein muss. Das Ziel in diesem Beispiel ist nun, eine Prüfung aufzusetzen, mit der kontrolliert werden kann, ob für keinen Host vergessen wurde, eine Host-Gruppe zuzuweisen.
Das Ganze läuft in zwei Schritten:
Informationen für das Monitoring aus dem Host auslesen. Darum geht es in diesem Kapitel.
Schreiben eines Check-Plugins in der Checkmk-Instanz, welches diese Daten auswertet. Das zeigen wir im nächsten Kapitel.
Und los geht’s …
2.1. Informationen auslesen und filtern
Am Anfang jeder Plugin-Programmierung steht: die Recherche! Das bedeutet, dass Sie herausfinden müssen, wie Sie überhaupt an die Informationen kommen, die Sie für die Überwachung brauchen.
Für das gewählte Beispiel nutzen wir die Tatsache, dass der Checkmk-Server zugleich der Host ist. Damit genügt zunächst ein Abruf der Statusdaten via Livestatus, also der in Tabellen organisierten Daten, die Checkmk über die überwachten Hosts und Services im flüchtigen Speicher vorhält.
Melden Sie sich als Instanzbenutzer an und fragen Sie die Informationen zu den Host-Gruppen mit folgendem Befehl ab:
OMD[mysite]:~$ lq "GET hostgroups"
action_url;alias;members;members_with_state;name;notes;notes_url;num_hosts;num_hosts_down;num_hosts_handled_problems;num_hosts_pending;num_hosts_unhandled_problems;num_hosts_unreach;num_hosts_up;num_services;num_services_crit;num_services_handled_problems;num_services_hard_crit;num_services_hard_ok;num_services_hard_unknown;num_services_hard_warn;num_services_ok;num_services_pending;num_services_unhandled_problems;num_services_unknown;num_services_warn;worst_host_state;worst_service_hard_state;worst_service_state
;Hamburg;myhost11,myhost22,myhost33;myhost11|0|1,myhost22|0|1,myhost33|0|1;Hamburg;;;3;0;0;0;0;0;3;123;10;0;10;99;0;14;99;0;24;0;14;0;2;2
;Munich;myhost1,myhost2,myhost3;myhost1|0|1,myhost2|0|1,myhost3|0|1;Munich;;;3;0;0;0;0;0;3;123;10;0;10;99;0;14;99;0;24;0;14;0;2;2
;check_mk;localhost;localhost|0|1;check_mk;;;1;0;0;0;0;0;1;66;0;0;0;4;0;1;4;61;1;0;1;0;1;1
Die erste Zeile der Ausgabe enthält die Spaltennamen der abgefragten Tabelle hostgroups
.
Als Trennzeichen fungiert das Semikolon.
In den folgenden Zeilen folgen dann die Inhalte sämtlicher Spalten, ebenfalls durch Semikolons getrennt.
Die Ausgabe ist bereits für dieses kleine Beispiel relativ unübersichtlich und enthält Informationen, die für unser Beispiel nicht relevant sind.
Generell sollten Sie zwar die Interpretation der Daten Checkmk überlassen.
Allerdings kann eine Vorfilterung auf dem Host den Umfang der zu übertragenden Daten reduzieren, wenn diese gar nicht gebraucht werden.
Also schränken Sie in diesem Fall die Abfrage auf die relevanten Spalten (Columns
) ein — auf die Namen der Host-Gruppen (name
) und die in den Gruppen befindlichen Hosts (members
):
OMD[mysite]:~$ lq "GET hostgroups\nColumns: name members"
Hamburg;myhost11,myhost22,myhost33
Munich;myhost1,myhost2,myhost3
check_mk;localhost
Die Livestatus-Schnittstelle erwartet alle Befehle und Header in jeweils einer eigenen Zeile.
Die daher notwendigen Zeilenumbrüche machen Sie durch \n
kenntlich.
In diesem Beispiel existieren aktuell drei Host-Gruppen: zwei Gruppen für die Standorte sowie die Gruppe check_mk
.
Diese enthält einen Host namens localhost
.
Die Host-Gruppe check_mk
stellt eine Besonderheit innerhalb der Host-Gruppen dar.
Sie haben diese nämlich nicht selbst angelegt.
Und Sie können auch keinen Host aktiv in diese Gruppe aufnehmen.
Woher also stammt diese Host-Gruppe?
Da in Checkmk per Definition jeder Host einer Gruppe angehören muss, weist Checkmk jeden Host, den Sie keiner Gruppe zuweisen, der „speziellen“ Gruppe check_mk
zu.
Sobald Sie einen Host einer Ihrer eigenen Host-Gruppen zuweisen, wird er aus der Gruppe check_mk
entfernt.
Auch gibt es keinen Weg, einen Host erneut der Host-Gruppe check_mk
zuzuweisen.
Genau diese Eigenschaften der Gruppe check_mk
werden nun für unser Beispiel genutzt:
Da jeder Host einem Standort zugeordnet sein soll, müsste die Host-Gruppe check_mk
leer sein.
Ist sie nicht leer, so besteht Handlungsbedarf, sprich darin befindliche Hosts müssen den Host-Gruppen und damit den Standorten zugeordnet werden.
2.2. Den Befehl in den Agenten einbauen
Bis jetzt haben Sie sich mit dem lq
-Befehl als Instanzbenutzer die Informationen anzeigen lassen.
Das ist hilfreich, um sich einen Einblick in die Daten zu verschaffen.
Damit Sie diese Daten vom Checkmk-Server aus abrufen können, muss der neue Befehl jedoch Teil vom Checkmk-Agenten auf dem überwachten Host werden.
Theoretisch könnten Sie nun direkt den Checkmk-Agenten in der Datei /usr/bin/check_mk_agent
editieren und diesen Teil einbauen.
Das hätte aber den Nachteil, dass Ihr neuer Befehl bei einem Software-Update des Agenten wieder verschwindet, weil die Datei ersetzt wird.
Besser ist es daher, ein Agentenplugin zu erstellen.
Alles was Sie dafür brauchen, ist eine ausführbare Datei, die den Befehl enthält und im Verzeichnis /usr/lib/check_mk_agent/plugins/
liegt.
Und noch eins ist wichtig: Die Daten können nicht einfach so ausgegeben werden. Sie brauchen noch einen Sektions-Header (section header). Das ist eine speziell formatierte Zeile, die den Namen des neuen Agentenplugins enthält. An diesem Sektions-Header kann Checkmk später erkennen, wo die Daten des Agentenplugins beginnen und die des vorherigen aufhören. Am einfachsten ist es, wenn Sektions-Header und Check-Plugin den gleichen Namen tragen — auch wenn dies nicht verpflichtend ist.
Also brauchen Sie jetzt erst einmal einen sinnvollen Namen für Ihr neues Check-Plugin.
Dieser Name darf nur Kleinbuchstaben (nur a-z, keine Umlaute, keine Akzente), Unterstriche und Ziffern enthalten und muss eindeutig sein.
Vermeiden Sie Namenskollisionen mit vorhandenen Check-Plugins.
Wenn Sie neugierig sind, welche Namen es schon gibt, können Sie diese in einer Checkmk-Instanz auf der Kommandozeile mit cmk -L
auflisten lassen:
OMD[mysite]:~$ cmk -L
3par_capacity agent HPE 3PAR: Capacity
3par_cpgs agent HPE 3PAR: CPGs
3par_cpgs_usage agent HPE 3PAR: CPGs Usage
3par_hosts agent HPE 3PAR: Hosts
3par_ports agent HPE 3PAR: Ports
3par_remotecopy agent HPE 3PAR: Remote Copy
3par_system agent HPE 3PAR: System
3par_volumes agent HPE 3PAR: Volumes
3ware_disks agent 3ware ATA RAID Controller: State of Disks
3ware_info agent 3ware ATA RAID Controller: General Information
3ware_units agent 3ware ATA RAID Controller: State of Units
acme_agent_sessions snmp ACME Devices: Agent Sessions
acme_certificates snmp ACME Devices: Certificates
Die Ausgabe zeigt nur die ersten Zeilen der sehr langen Liste. Durch die Verwendung von Präfixen ist die Zugehörigkeit vieler Check-Plugins hier bereits gut zu erkennen. Auch für Ihre eigenen Check-Plugins empfiehlt sich daher die Verwendung von Präfixen. Die zweite Spalte zeigt übrigens an, wie das jeweilige Check-Plugin seine Daten bezieht.
Ein für unser Beispiel passender Name für das neue Check-Plugin ist zum Beispiel myhostgroups
.
Nun haben Sie alle Informationen zusammen, um das Skript mit dem Agentenplugin zu erstellen.
Legen Sie als root
-Benutzer eine neue Datei myhostgroups
im Verzeichnis /usr/lib/check_mk_agent/plugins/
an:
#!/bin/bash
columns="name members"
site="mysite"
echo '<<<myhostgroups:sep(59)>>>'
su - ${site} lq "GET hostgroups\nColumns: ${columns}"
Was bedeutet das nun im Einzelnen?
Die erste Zeile enthält den „Shebang“ (das ist eine Abkürzung für sharp und bang, wobei Letzteres eine Abkürzung für das Ausrufezeichen ist), an dem Linux erkennt, dass es das Skript mit der angegebenen Shell ausführen soll.
Um das Skript erweiterbar zu halten, werden als nächstes zwei Variablen eingeführt:
die Variable
columns
, die aktuell die Gruppennamen und die zugehörigen Mitglieder enthält,die Variable
site
, die den Namen der Checkmk-Instanz enthält.
Mit dem echo
-Befehl geben Sie den Sektions-Header aus.
Da die Spalten der Tabelle mit dem Semikolon getrennt werden, legen Sie gleichzeitig mit dem Zusatz sep(59)
fest, dass das Semikolon als Trennzeichen (separator) für die Daten in der Agentenausgabe genutzt wird.
59 steht für das ASCII-Zeichen Nummer 59, das Semikolon.
Ohne diesen Zusatz würde als Standard das Leerzeichen (ASCII-Zeichen 32) als Trennzeichen verwendet werden.
Um den lq
-Befehl, der Ihnen als Instanzbenutzer zur Verfügung steht, auch in einem Skript nutzen zu können, das vom root
-Benutzer ausgeführt wird, stellen Sie ein su
voran.
Hinweis: Es kann vorkommen, dass der Zugriff auf lq
per su
Probleme bereitet.
Sie können alternativ als root
-Benutzer auch direkt auf Livestatus in der Shell mit printf
oder echo -e
über einen Unix-Socket zugreifen.
Im Artikel zu Livestatus erfahren Sie, wie das geht.
Eines ist noch sehr wichtig, sobald Sie die Datei erstellt haben. Machen Sie die Datei ausführbar:
root@linux# chmod +x /usr/lib/check_mk_agent/plugins/myhostgroups
Sie können das Agentenplugin direkt von Hand ausprobieren, indem Sie den kompletten Pfad als Befehl eingeben:
root@linux# /usr/lib/check_mk_agent/plugins/myhostgroups
<<<myhostgroups:sep(59)>>>
Hamburg;myhost11,myhost22,myhost33
Munich;myhost1,myhost2,myhost3
check_mk;localhost
Host-Gruppen, die keine Hosts enthalten, werden hierbei nicht aufgeführt.
2.3. Agent ausprobieren
Test und Fehlersuche sind am wichtigsten, um zu einem funktionierenden Agentenplugin zu gelangen. Am besten gehen Sie in drei Schritten vor:
Agentenplugin solo ausprobieren. Das haben Sie gerade im vorherigen Abschnitt gemacht.
Agent als Ganzes lokal testen.
Agent vom Checkmk-Server aus abrufen.
Das lokale Testen des Agenten ist sehr einfach.
Rufen Sie als root
den Befehl check_mk_agent
auf:
root@linux# check_mk_agent
Irgendwo in der sehr langen Ausgabe muss die neue Sektion erscheinen. Agentenplugins werden vom Agenten zum Schluss ausgegeben.
Durch Anhängen von less
können Sie in der Ausgabe blättern (drücken Sie die Leertaste zum Blättern, /
zum Suchen und q
zum Beenden):
root@linux# check_mk_agent | less
Oder Sie durchsuchen die Ausgabe nach den interessanten Zeilen.
So hat grep
mit -A
eine Option, nach jedem Treffer noch einige Zeilen mehr auszugeben.
Damit können Sie bequem die Sektion suchen und ausgeben:
root@linux# check_mk_agent | grep -A3 '^<<<myhostgroups'
<<<myhostgroups:sep(59)>>>
Hamburg;myhost11,myhost22,myhost33
Munich;myhost1,myhost2,myhost3
check_mk;localhost
Der dritte und letzte Test ist dann direkt von der Checkmk-Instanz aus.
Nehmen Sie den Host ins Monitoring auf (zum Beispiel als localhost
), melden Sie sich als Instanzbenutzer an und rufen Sie dann die Agentendaten mit cmk -d
ab:
OMD[mysite]:~$ cmk -d localhost | grep -A3 '^<<<myhostgroups'
Hier sollte die gleiche Ausgabe kommen wie beim vorherigen Befehl.
Wenn das funktioniert, ist Ihr Agent vorbereitet.
Und was haben Sie dafür gemacht?
Sie haben ein kurzes Skript unter dem Pfad /usr/lib/check_mk_agent/plugins/myhostgroups
erzeugt und ausführbar gemacht.
Alles was nun folgt, geschieht nur noch auf dem Checkmk-Server: Dort schreiben Sie das Check-Plugin.
3. Ein einfaches Check-Plugin schreiben
Das Vorbereiten des Agenten ist nur die halbe Miete. Jetzt müssen Sie Checkmk noch beibringen, wie es mit den Informationen aus der neuen Agentensektion umgehen soll, welche Services es erzeugen soll, wann diese auf WARN oder CRIT gehen sollen usw. All dies machen Sie durch die Programmierung eines Check-Plugins in Python.
3.1. Die Datei vorbereiten
Für Ihre eigenen Check-Plugins finden Sie ein Verzeichnis vorbereitet in der local
-Hierarchie des Instanzverzeichnisses.
Dieses lautet ~/local/lib/check_mk/base/plugins/agent_based/
.
Hier im Pfad meint base
den Teil von Checkmk, der für das eigentlich Monitoring und die Benachrichtigungen zuständig ist.
Der Ordner agent_based
beinhaltet alle Plugins, die sich auf den Checkmk-Agenten beziehen (also zum Beispiel nicht Benachrichtigungs-Plugins).
Am besten wechseln Sie zum Arbeiten dort hinein:
OMD[mysite]:~$ cd local/lib/check_mk/base/plugins/agent_based
Das Verzeichnis gehört dem Instanzbenutzer und ist daher für Sie schreibbar. Sie können Ihr Check-Plugin mit jedem auf dem Linux-System installierten Texteditor bearbeiten.
Legen Sie also die Datei myhostgroups.py
für das Check-Plugin hier an.
Konvention ist, dass der Dateiname den Namen der Agentensektion wiedergibt.
Pflicht ist, dass die Datei mit .py
endet, denn ab Version 2.0.0 von Checkmk handelt es sich bei den Check-Plugins immer um echte Python-Module.
Ein lauffähiges Grundgerüst (Download bei GitHub), das Sie im Folgenden Schritt für Schritt weiter ausbauen werden, sieht so aus:
#!/usr/bin/env python3
from .agent_based_api.v1 import check_levels, Metric, register, Result, Service, State
def parse_myhostgroups(string_table):
parsed = {}
return parsed
def discover_myhostgroups(section):
yield Service()
def check_myhostgroups(section):
yield Result(state=State.OK, summary="Everything is fine")
register.agent_section(
name = "myhostgroups",
parse_function = parse_myhostgroups,
)
register.check_plugin(
name = "myhostgroups",
service_name = "Host group check_mk",
discovery_function = discover_myhostgroups,
check_function = check_myhostgroups,
)
Als erstes müssen Sie die für die Check-Plugins nötigen Funktionen und Klassen aus Python-Modulen importieren.
Die einfachste Methode dafür ist import *
, allerdings sollten Sie das vermeiden, da so verschleiert wird, welche Namensräume tatsächlich verfügbar gemacht wurden.
Für unser Beispiel wird also nur das importiert, was im weiteren Verlauf des Artikels genutzt wird:
from .agent_based_api.v1 import check_levels, Metric, register, Result, Service, State
3.2. Die Parse-Funktion schreiben
Die Parse-Funktion hat die Aufgabe, die „rohen“ Agentendaten zu „parsen“, das heißt zu analysieren und zu zerteilen, und in eine logisch aufgeräumte Form zu bringen, die für alle weiteren Schritte einfach zu verarbeiten ist.
Wie im Abschnitt zum Testen des Agenten gezeigt, hat die vom Agentenplugin gelieferte Sektion die folgende Struktur:
<<<myhostgroups:sep(59)>>>
Hamburg;myhost11,myhost22,myhost33
Munich;myhost1,myhost2,myhost3
check_mk;localhost
Checkmk zerlegt bereits die Zeilen der vom Agentenplugin gelieferten Sektion anhand des Trennzeichens im Sektions-Header (im Beispiel ;
) in eine Liste von Zeilen, die ihrerseits wiederum Listen von Worten sind.
In Checkmk steht daher statt der Rohdaten des Agentenplugins die folgende Datenstruktur zur Verfügung:
[
['Hamburg', 'myhost11,myhost22,myhost33'],
['Munich', 'myhost1,myhost2,myhost3'],
['check_mk', 'localhost']
]
In der inneren Liste enthält das jeweils erste Element den Namen der Host-Gruppe und das zweite die Namen der der Gruppe angehörenden Hosts.
Diese Informationen können Sie zwar alle ansprechen, aber jeweils nur über ihre Position im Datensatz. Sie müssten also immer angeben, die wievielte eckige Klammer und den wievielten Inhalt innerhalb der jeweiligen Klammer Sie brauchen. Bei größeren Datenmengen gestaltet sich dies komplex und es wird immer schwieriger, den Überblick zu behalten.
An diesem Punkt bietet die Parse-Funktion durch die von ihr erzeugte Struktur deutliche Vorteile. Durch sie wird der Code leichter lesbar, die Zugriffe werden performanter und Sie behalten viel einfacher den Überblick. Sie transformiert die von Checkmk gelieferte Datenstruktur so, dass man jeden der einzelnen Werte wahlfrei durch einen Namen (oder Schlüssel) ansprechen kann und nicht darauf angewiesen ist, den Bereich (array) iterativ zu durchlaufen, um das zu finden, was man sucht:
{
'Hamburg': {'members': 'myhost11,myhost22,myhost33'},
'Munich': {'members': 'myhost1,myhost2,myhost3'},
'check_mk': {'members': 'localhost'}
}
Konvention ist, dass die Parse-Funktion nach der Agentensektion benannt wird und mit parse_
beginnt.
Sie bekommt als einziges Argument string_table
.
Beachten Sie, dass Sie hier nicht frei in der Wahl des Arguments sind.
Es muss wirklich so heißen.
def parse_myhostgroups(string_table):
# print(string_table)
parsed = {}
for line in string_table:
parsed[line[0]] = {"members": line[1]}
# print(parsed)
return parsed
Mit def
geben Sie in Python an, dass nachfolgend eine Funktion definiert wird.
parsed = {}
erzeugt das Dictionary mit der verbesserten Datenstruktur.
In unserem Beispiel wird jede Zeile Element für Element durchgegangen.
Aus jeder Zeile wird die Host-Gruppe gefolgt von den Mitgliedern der Host-Gruppe genommen und zu einem Eintrag für das Dictionary zusammengesetzt.
Mit return parsed
wird dann das Dictionary zurückgegeben.
Hinweis: Im oben gezeigten Beispiel finden Sie zwei auskommentierte Zeilen. Wenn Sie diese später beim Testen des Check-Plugins einkommentieren, werden Ihnen die Daten vor und nach der Ausführung der Parse-Funktion auf der Kommandozeile angezeigt. So können Sie überprüfen, ob die Funktion auch wirklich das tut, was sie soll.
3.3. Die Agentensektion registrieren
Damit das Ganze auch etwas bewirken kann, müssen Sie die Parse-Funktion, und überhaupt die neue Agentensektion, bei Checkmk bekannt machen. Dazu rufen Sie eine Registrierfunktion auf:
register.agent_section(
name = "myhostgroups",
parse_function = parse_myhostgroups,
)
Hier ist es wichtig, dass der Name der Sektion wirklich exakt mit dem Sektions-Header in der Agentenausgabe übereinstimmt.
Von diesem Moment an bekommt jedes Check-Plugin, das die Sektion myhostgroups
benutzt, den Rückgabewert der Parse-Funktion übergeben.
In der Regel wird das das gleichnamige Check-Plugin sein.
Aber auch andere Check-Plugins können dieses Sektion abonnieren, wie wir bei der Erweiterung des Check-Plugins noch zeigen werden.
Übrigens: Wenn Sie es ganz genau wissen wollen, können Sie an dieser Stelle einen Blick in die Check-API-Dokumentation werfen. Dort finden Sie die detaillierte Beschreibung dieser Registrierfunktion — und auch der Funktionen und Objekte, die später im Artikel noch verwendet werden.
3.4. Das Check-Plugin registrieren
Damit Checkmk weiß, dass es ein neues Check-Plugin gibt, muss dieses registriert werden.
Dies geschieht durch den Aufruf der Funktion register.check_plugin
.
Dabei müssen Sie immer mindestens vier Dinge angeben:
name
: Der Name des Check-Plugins. Am einfachsten ist es, wenn Sie hier den gleichen Namen wie bei Ihrer neuen Agentensektion nehmen. Damit weiß der später in der Check-Funktion definierte Check automatisch, welche Sektion er auswerten soll.service_name
: Der Name des Services wie er dann im Monitoring erscheinen soll.discovery_function
: Die Funktion zum Erkennen von Services dieses Typs (dazu gleich mehr).check_funktion
: Die Funktion zum Durchführen des eigentlichen Checks (auch dazu gleich mehr).
Für das Check-Plugin sieht das dann also so aus:
register.check_plugin(
name = "myhostgroups",
service_name = "Host group check_mk",
discovery_function = discover_myhostgroups,
check_function = check_myhostgroups,
)
Versuchen Sie am besten noch nicht, das gleich auszuprobieren, denn Sie müssen die Funktionen discover_myhostgroups
und check_myhostgroups
vorher noch schreiben.
Und diese müssen im Quellcode vor obiger Registrierung erscheinen.
3.5. Die Discovery-Funktion schreiben
Eine Besonderheit von Checkmk ist die automatische Erkennung von zu überwachenden Services. Damit dies klappt, muss jedes Check-Plugin eine Funktion definieren, welche anhand der Agentenausgabe erkennt, ob ein Service dieses Typs bzw. welche Services des Typs für den betreffenden Host angelegt werden sollen.
Die Discovery-Funktion wird immer dann aufgerufen, wenn für einen Host die Service-Erkennung durchgeführt wird.
Sie entscheidet dann ob, bzw. welche Services angelegt werden sollen.
Im Standardfall bekommt sie genau ein Argument mit dem Namen section
.
Dieses enthält die Daten der Agentensektion in einem durch die Parse-Funktion aufbereiteten Format.
Daher implementieren Sie folgende simple Logik:
Wenn die Agentensektion myhostgroups
vorhanden ist, dann legen Sie auch einen passenden Service an.
Dann erscheint dieser automatisch auf allen Hosts, auf denen das Agentenplugin verteilt ist.
Bei Check-Plugins, die pro Host nur einen Service erzeugen, benötigt man keine weiteren Angaben:
def discover_myhostgroups(section):
yield Service()
Die Discovery-Funktion muss für jeden anzulegenden Service mittels yield
ein Objekt vom Typ Service
zurückgeben (nicht mit return
).
yield
hat in Python die gleiche Aufgabe wie return
– beide geben einen Wert an die aufrufende Funktion zurück.
Der entscheidende Unterschied besteht darin, dass sich bei einem yield
gemerkt wird, wie weit die Funktion in einer Datenverarbeitung gekommen ist.
Beim nächsten Aufruf wird nach der letzten yield
-Anweisung weiter gemacht - und nicht wieder am Anfang begonnen.
Dadurch wird nicht nur der erste Treffer ausgelesen (wie es beim return
der Fall wäre), sondern sukzessive alle Treffer (dieser Vorteil wird später in unserem Beispiel bei der Service-Erkennung noch relevant).
3.6. Die Check-Funktion schreiben
Somit können Sie nun zur eigentlichen Check-Funktion kommen, welche anhand der aktuellen Agentenausgabe entscheidet, welchen Zustand der Service annehmen soll und weitere Informationen ausgeben kann.
Ziel der Check-Funktion ist es, eine Prüfung aufzusetzen, mit der kontrolliert werden kann, ob für keinen Host vergessen wurde, eine Host-Gruppe zuzuweisen.
Dazu wird überprüft, ob die Host-Gruppe check_mk
Hosts enthält.
Wenn das der Fall ist, soll der Service den Zustand CRIT erhalten.
Wenn nicht, ist alles OK und der Zustand des Services auch.
Hier ist die Implementierung:
def check_myhostgroups(section):
attr = section.get("check_mk")
hosts = attr["members"] if attr else ""
if hosts:
yield Result(state=State.CRIT, summary=f"Default group is not empty; Current member list: {hosts}")
else:
yield Result(state=State.OK, summary="Everything is fine")
Und nun die Erklärung dazu:
Die Funktion check_myhostgroups()
holt als erstes den zum Schlüssel check_mk
gehörenden Wert in die Variable attr
.
Dann wird die Variable hosts
mit dem members
-Wert verknüpft, wenn dieser vorhanden ist.
Gibt es keine members
, so bleibt hosts
leer.
Jetzt folgt eine if
-Abfrage für die eigentliche Auswertung:
Enthält die Variable
hosts
Inhalte, ist also die Host-Gruppecheck_mk
nicht leer, so geht der Status des Services auf CRIT und es wird ein Hinweistext ausgegeben. Dieser enthält zusätzlich eine Auflistung der Host-Namen aller Hosts, die sich in der Host-Gruppecheck_mk
befinden. Für die Ausgabe des Textes mit Ausdrücken wird der Python F-String verwendet, der so heißt, weil vor der String-Zeichenfolge der Buchstabef
steht.Ist die Variable
hosts
leer, sind also keine Hosts in der Host-Gruppecheck_mk
, so geht stattdessen der Status des Services auf OK. Dann wird zudem ein passender Hinweistext ausgegeben.
Mit der Erstellung der Check-Funktion ist das Check-Plugin fertig.
Das Check-Plugin und auch das Agentenplugin haben wir auf GitHub bereitgestellt.
3.7. Das Check-Plugin testen und aktivieren
Test und Aktivierung werden auf der Kommandozeile mit dem Befehl cmk
erledigt.
Probieren Sie zunächst die Service-Erkennung mit der Option -I
aus.
Durch Zugabe der Option v
(für verbose) werden ausführliche Ausgaben angefordert.
Das --detect-plugins
beschränkt die Befehlsausführung auf dieses Check-Plugin und durch localhost
auf eben diesen Host:
OMD[mysite]:~$ cmk -vI --detect-plugins=myhostgroups localhost
Discovering services and host labels on: localhost
localhost:
+ FETCHING DATA
[TCPFetcher] Execute data source
[PiggybackFetcher] Execute data source
No piggyback files for 'localhost'. Skip processing.
No piggyback files for '127.0.0.1'. Skip processing.
+ ANALYSE DISCOVERED HOST LABELS
SUCCESS - Found no new host labels
+ ANALYSE DISCOVERED SERVICES
+ EXECUTING DISCOVERY PLUGINS (1)
1 myhostgroups
SUCCESS - Found 1 services
Wie geplant, erkennt die Service-Erkennung einen neuen Service im Check-Plugin myhostgroups
.
Jetzt können Sie den im Check-Plugin enthaltenen Check ausprobieren:
OMD[mysite]:~$ cmk --detect-plugins=myhostgroups -v localhost
+ FETCHING DATA
[TCPFetcher] Execute data source
[PiggybackFetcher] Execute data source
No piggyback files for 'localhost'. Skip processing.
No piggyback files for '127.0.0.1'. Skip processing.
Host group check_mk Default group is not empty; Current member list: localhost
[agent] Success, [piggyback] Success (but no data found for this host), execution time 1.3 sec | execution_time=1.330 user_time=0.010 system_time=0.000 children_user_time=0.000 children_system_time=0.000 cmk_time_agent=1.330
Durch Ausführung des Checks wird der Zustand des zuvor gefundenen Services bestimmt.
Wenn soweit alles wie erwartet abgelaufen ist, können Sie die Änderungen aktivieren. Falls nicht, finden Sie im Kapitel zur Fehlerbehebung Hinweise.
Aktivieren Sie zum Abschluss die Änderungen durch einen Neustart des Monitoring-Kerns:
OMD[mysite]:~$ cmk -R
Generating configuration for core (type nagios)...
Precompiling host checks...OK
Validating Nagios configuration...OK
Restarting monitoring core...OK
Im Monitoring von Checkmk finden Sie nun beim Host localhost
den neuen Service Host group check_mk:
check_mk
nicht leer ist, ist der Service CRIT
Wir gratulieren Ihnen zur erfolgreichen Erstellung des ersten Check-Plugins!
4. Das Check-Plugin erweitern
4.1. Vorbereitungen
Das gerade frisch fertiggestellte erste Check-Plugin soll nun schrittweise erweitert werden. Bisher hat das Agentenplugin nur die Informationen über die Namen und die Mitglieder der Host-Gruppen geliefert. Um etwa die Zustände der Hosts und der auf ihnen laufenden Services auswerten zu können, braucht es mehr Daten.
Agentenplugin erweitern
Sie werden zunächst das Agentenplugin einmal erweitern, um all die Informationen einzusammeln, die für die Erweiterung des Check-Plugins in den nächsten Abschnitten benötigt werden.
Um herauszubekommen, welche Informationen Checkmk denn für Host-Gruppen so bietet, können Sie alle verfügbaren Spalten der Host-Gruppentabelle mit folgendem Befehl als Instanzbenutzer abfragen:
OMD[mysite]:~$ lq "GET columns\nFilter: table = hostgroups\nColumns: name"
action_url
alias
members
members_with_state
name
notes
notes_url
num_hosts
...
Die Ausgabe geht noch weiter.
Fast 30 Spalten bietet die Tabelle — und unter den meisten Spaltennamen kann man sich sogar etwas vorstellen.
Hier interessieren die folgenden Spalten:
Anzahl der Hosts pro Gruppe (Spalte num_hosts
), Anzahl der Hosts im Zustand UP (num_hosts_up
), Anzahl der Services aller Hosts in der Gruppe (num_services
) und Anzahl der Services im Zustand OK (num_services_ok
).
Jetzt müssen diese neuen Spalten nur noch vom Agenten geliefert werden. Das erreichen Sie durch Erweiterung des im vorherigen Kapitel erstellten Agentenplugins.
Editieren Sie als root-Benutzer das Skript des Agentenplugins.
Da das Skript die konfigurierbaren Werte bereits in Variablen gesteckt hat, reicht es aus, nur die mit columns
beginnende Zeile zu ändern und dort die zusätzlich abgerufenen vier Spalten einzutragen:
#!/bin/bash
columns="name members num_hosts num_hosts_up num_services num_services_ok"
site="mysite"
echo '<<<myhostgroups:sep(59)>>>'
su - ${site} lq "GET hostgroups\nColumns: ${columns}"
Führen Sie zur Kontrolle das Skript aus:
root@linux# /usr/lib/check_mk_agent/plugins/myhostgroups
<<<myhostgroups:sep(59)>>>
Munich;myhost3,myhost2,myhost1;3;3;180;144
Hamburg;myhost22,myhost33,myhost11;3;2;132;105
check_mk;SQL-Server,localhost;2;2;95;83
Am Ende jeder Zeile stehen nun, durch Semikolon abgetrennt, die vier neuen Werte.
Mit dieser Änderung liefert das Agentenplugin nun andere Daten als vorher. An dieser Stelle ist es wichtig, sich zu vergewissern, dass das Check-Plugin auch mit den geänderten Daten immer noch das tut, was es soll.
Parse-Funktion erweitern
Im Check-Plugin ist die Parse-Funktion für die Umwandlung der vom Agentenplugin gelieferten Daten verantwortlich. Beim Schreiben der Parse-Funktion haben Sie nur zwei Spalten der Host-Gruppentabelle berücksichtigt. Nun werden sechs statt zwei Spalten geliefert. Die Parse-Funktion muss also fit gemacht werden, um auch die zusätzlichen vier Spalten zu verarbeiten.
Ändern Sie als Instanzbenutzer die Parse-Funktion in der Datei myhostgroups.py
, die das Check-Plugin enthält:
def parse_myhostgroups(string_table):
parsed = {}
column_names = [
"name",
"members",
"num_hosts",
"num_hosts_up",
"num_services",
"num_services_ok",
]
for line in string_table:
parsed[line[0]] = {}
for n in range(1, len(column_names)):
parsed[line[0]][column_names[n]] = line[n]
return parsed
Geändert wurde hier alles zwischen parsed = {}
und return parsed
.
Zuerst werden die zu verarbeitenden Spalten unter ihren Namen als Liste column_names
definiert.
In der for
-Schleife wird dann ein Dictionary aufgebaut, indem in jeder Zeile aus Spaltenname und ausgelesenem Wert die Schlüssel-Wert-Paare erzeugt werden.
Für die existierende Check-Funktion ist diese Erweiterung unkritisch, denn die Datenstruktur für die ersten beiden Spalten bleibt unverändert. Es werden nur zusätzliche Spalten bereitgestellt, die in der Check-Funktion (noch) gar nicht ausgewertet werden.
Nun, da die neuen Daten verarbeitet werden können, werden Sie sie auch nutzen.
4.2. Service-Erkennung
Im Beispiel haben Sie einen sehr einfachen Check gebaut, der auf einem Host einen Service erzeugt. Ein sehr üblicher Fall ist aber auch, dass es von einem Check mehrere Services auf einem Host geben kann.
Das häufigste Beispiel dafür sind die Dateisysteme eines Hosts.
Das Check-Plugin mit dem Namen df
legt pro Dateisystem auf dem Host einen Service an.
Um diese Services zu unterscheiden, wird der Mount-Punkt des Dateisystems (zum Beispiel /var
) bzw. der Laufwerksbuchstabe (zum Beispiel C:
) in den Namen des Services eingebaut.
Das ergibt dann als Service-Name zum Beispiel Filesystem /var
oder Filesystem C:
.
Das Wort /var
bzw. C:
wird hier als Item bezeichnet.
Wir sprechen also auch von einem Check mit Items.
Wenn Sie einen Check mit Items bauen möchten, müssen Sie folgende Dinge umsetzen:
Die Discovery-Funktion muss für jedes der Items, die auf dem Host sinnvollerweise überwacht werden sollen, einen Service generieren.
Im Service-Namen müssen Sie das Item mithilfe des Platzhalters
%s
einbauen (also zum Beispiel"Filesystem %s"
).Die Check-Funktion wird pro Item einmal separat aufgerufen und bekommt dieses als Argument. Sie muss dann aus den Agentendaten die für dieses Item relevanten Daten herausfischen.
Um dies praktisch auszuprobieren, werden Sie für jede existierende Host-Gruppe einen eigenen Service erzeugen.
Da das im vorherigen Kapitel erstellte Check-Plugin myhostgroups
zur Überprüfung der Standardgruppe check_mk
weiterhin funktionieren soll, bleibt dieses Check-Plugin so, wie es ist.
Für die Erweiterung erstellen Sie in der Datei myhostgroups.py
ein neues Check-Plugin — im ersten Schritt so wie zuvor durch die Registrierung des Plugins.
Wichtig: Die neue Registrierung wird zusätzlich zur bereits vorhandenen vorgenommen, die im vorherigen Kapitel gezeigte bleibt unverändert enthalten. Hier nur der neue Code:
...
register.check_plugin(
name = "myhostgroups_advanced",
sections = ["myhostgroups"],
service_name = "Host group %s",
discovery_function = discover_myhostgroups_advanced,
check_function = check_myhostgroups_advanced,
)
Damit man das neue vom alten Check-Plugin unterscheiden kann, erhält es mit myhostgroups_advanced
einen eindeutigen Namen.
Der Parameter sections
bestimmt die Sektionen der Agentenausgabe, die das Check-Plugin abonniert.
Mit myhostgroups
wird hier festgelegt, dass das neue Check-Plugin die gleichen Daten nutzt wie das alte: die durch die Parse-Funktion aufbereitete Sektion des Agentenplugins.
Der Service-Name enthält jetzt den Platzhalter %s
.
An dieser Stelle wird später dann von Checkmk der Name des Items eingesetzt.
In den letzten beiden Zeilen werden schließlich die Namen für die neue Discovery-Funktion und die neue Check-Funktion festgelegt, die beide noch geschrieben werden wollen.
Zuerst zur Discovery-Funktion, die jetzt die Aufgabe hat, die zu überwachenden Items zu ermitteln – auch diese wird zusätzlich zur vorhandenen eingetragen:
def discover_myhostgroups_advanced(section):
for group in section:
if group != "check_mk":
yield Service(item=group)
Wie zuvor bekommt die Discovery-Funktion das Argument section
.
Mit einer Schleife werden die einzelnen Host-Gruppen durchlaufen.
Hier interessieren alle Host-Gruppen — mit Ausnahme von check_mk
, denn um diese spezielle Host-Gruppe kümmert sich bereits das existierende Check-Plugin myhostgroups
.
Immer, wenn ein Item gefunden wurde, wird es mit yield
zurück gegeben, wobei ein Objekt vom Typ Service
erzeugt wird, das den Host-Gruppennamen als Item übergeben bekommt.
Wenn später der Host überwacht wird, dann wird die Check-Funktion für jeden Service — und damit für jedes Item — separat aufgerufen.
Womit Sie bereits bei der Definition der Check-Funktion für das neue Check-Plugin myhostgroups_advanced
angekommen sind.
Die Check-Funktion bekommt zusätzlich zur Sektion das Argument item
übergeben.
Die erste Zeile der Funktion sieht dann so aus:
def check_myhostgroups_advanced(item, section):
Der Algorithmus für die Check-Funktion ist einfach: Wenn die Host-Gruppe existiert, wird der Service auf OK gesetzt und Anzahl und Namen der Hosts in der Gruppe aufgelistet. Die komplette Funktion dazu:
def check_myhostgroups_advanced(item, section):
attr = section.get(item)
if attr:
yield Result(state=State.OK, summary=f"{attr['num_hosts']} hosts in this group: {attr['members']}")
Das Check-Ergebnis wird geliefert, indem ein Objekt der Klasse Result
per yield
zurückgeben wird.
Dieses benötigt die Parameter state
und summary
.
Dabei legt state
den Zustand des Services fest (im Beispiel OK
) und summary
den Text, der in dem Summary des Services angezeigt wird.
Er ist rein informativ und wird von Checkmk nicht weiter ausgewertet.
Mehr dazu erfahren Sie im nächsten Abschnitt.
So weit, so gut. Was passiert aber, wenn das gesuchte Item nicht gefunden wird? Das kann passieren, wenn in der Vergangenheit ein Service für eine Host-Gruppe bereits erzeugt wurde, diese Host-Gruppe aber nun verschwunden ist — entweder weil die Host-Gruppe in Checkmk noch existiert, aber keinen Host mehr enthält, oder weil sie gleich ganz gelöscht wurde. In beiden Fällen ist diese Host-Gruppe in der Agentenausgabe nicht (mehr) präsent.
Die gute Nachricht:
Checkmk kümmert sich darum!
Wird ein gesuchtes Item nicht gefunden, so erzeugt Checkmk automatisch für den Service das Resultat UNKNOWN - Item not found in monitoring data
.
Das ist so gewollt und gut so.
Wenn ein gesuchtes Item nicht gefunden wird, so können Sie Python einfach aus der Funktion herauslaufen und Checkmk seine Arbeit erledigen lassen.
Checkmk weiß nur, dass das Item, das vorher da war, nun weg ist. Den Grund dafür kennt Checkmk nicht — Sie aber schon. Darum ist es legitim, Ihr Wissen nicht für sich zu behalten und diesen Fall in der Check-Funktion abzufangen und dabei eine hilfreiche Meldung ausgeben zu lassen.
def check_myhostgroups_advanced(item, section):
attr = section.get(item)
if not attr:
yield Result(state=State.CRIT, summary="Group is empty or has been deleted")
return
yield Result(state=State.OK, summary=f"{attr['num_hosts']} hosts in this group: {attr['members']}")
Was hat sich geändert?
Der Fehlerfall wird jetzt zuerst abgehandelt.
Daher überprüfen Sie im if
-Zweig, ob das Item nicht existiert, setzen den Status auf CRIT und verlassen mit return
die Funktion.
In allen anderen Fällen geben Sie, wie zuvor, OK zurück.
Damit haben Sie in der Check-Funktion den Fall der verschwundenen Host-Gruppen übernommen. Statt UNKNOWN wird der zugehörige Service nun CRIT sein und die Information über die Ursache des kritischen Zustands beinhalten.
Damit ist das neue Check-Plugin als Erweiterung des alten fertiggestellt.
Das erweiterte Agentenplugin
und die erweiterte Datei für die Check-Plugins
finden Sie wieder auf GitHub.
Letztere enthält das einfache Check-Plugin myhostgroups
aus dem vorherigen Kapitel, die erweiterte Parse-Funktion und die Komponenten des neuen Check-Plugins myhostgroups_advanced
mit der Registrierung, der Discovery-Funktion und der Check-Funktion.
Beachten Sie, dass die Funktionen immer vor dem Registrieren definiert werden müssen, damit es keine Fehler wegen nicht definierter Funktionsnamen gibt.
Da das neue Check-Plugin myhostgroups_advanced
neue Services zur Verfügung stellt, müssen Sie eine Service-Erkennung für dieses Check-Plugin durchführen und die Änderungen aktivieren, um die Services im Monitoring zu sehen:
Gehen Sie dabei so vor, wie es im Kapitel für das einfache Check-Plugin beschrieben ist.
4.3. Summary und Details
Im Monitoring von Checkmk hat jeder Service neben dem Status — OK, WARN usw. — auch eine Zeile Text. Diese steht in der Spalte Summary — wie im vorherigen Screenshot zu sehen ist — und hat also die Aufgabe einer knappen Zusammenfassung des Zustands. Die Idee ist, dass dieser Text eine Länge von 60 Zeichen nicht überschreitet. Das sorgt dann immer für eine übersichtliche Tabellendarstellung ohne störende Zeilenumbrüche.
Daneben gibt es noch das Feld Details, in dem alle Details zum Zustand des Services angezeigt werden, wobei alle Informationen des Summary auch in den Details enthalten sind. Nach Anklicken des Services wird die Service-Seite geöffnet, in der neben vielen anderen auch die beiden Felder Summary und Details zu sehen sind.
Beim Aufruf von yield Result(...)
können Sie bestimmen, welche Informationen so wichtig sind, dass sie im Summary angezeigt werden sollen, und bei welchen es genügt, dass diese in den Details erscheinen.
In unserem Beispiel haben Sie bisher immer einen Aufruf der folgenden Art verwendet:
yield Result(state=State.OK, summary=f"{attr['num_hosts']} hosts in this group: {attr['members']}")
Dieser führt dazu, dass der als summary
festgelegte Text immer im Summary erscheint — und zusätzlich auch in den Details.
Dies sollten Sie also nur für wichtige Informationen verwenden.
Enthält eine Host-Gruppe viele Hosts, kann die Liste sehr lang werden — länger als die empfohlenen 60 Zeichen.
Ist eine Information eher untergeordnet, so können Sie mit details
festlegen, dass der Text nur in den Details erscheint:
yield Result(
state = State.OK,
summary = f"{attr['num_hosts']} hosts in this group",
details = f"{attr['num_hosts']} hosts in this group: {attr['members']}",
)
Im obigen Beispiel wird die Liste der Hosts daher nur noch in den Details angezeigt. Im Summary steht dann nur die Anzahl der Hosts in der Gruppe:
Es gibt neben summary
und details
noch einen dritten Parameter.
Mit notice
bestimmen Sie, dass ein Text für einen Service im Zustand OK nur in den Details angezeigt wird — aber zusätzlich im Summary für alle anderen Zustände.
Somit wird dann aus dem Summary sofort klar, warum der Service nicht OK ist.
Der Parameter notice
ist nicht besonders sinnvoll, wenn Texte fest an Zustände gebunden sind, wie bisher in unserem Beispiel.
Zusammengefasst bedeutet das:
Der Gesamttext für das Summary sollte bei Services, die OK sind, nicht länger als 60 Zeichen sein.
Verwenden Sie immer entweder
summary
odernotice
— nicht beides und nicht keines davon.Fügen Sie bei Bedarf
details
hinzu, wenn der Text für die Details ein alternativer sein soll.
4.4. Mehrere Teilresultate pro Service
Um die Anzahl der Services auf einem Host nicht ins Unermessliche steigen zu lassen, sind in einem Service oft mehrere Teilresultate zusammengefasst. So prüft zum Beispiel der Service Memory unter Linux nicht nur RAM- und Swap-Nutzung, sondern auch geteilten Speicher (shared memory), Page-Tabellen und alles Mögliche andere.
Die Check-API bietet dafür eine sehr komfortable Schnittstelle.
So darf eine Check-Funktion einfach beliebig oft ein Ergebnis mit yield
erzeugen.
Der Gesamtstatus des Services richtet sich dann nach dem schlechtesten Teilergebnis in der Reihenfolge OK → WARN → UNKNOWN → CRIT.
Nutzen Sie diese Möglichkeit, um in unserem Beispiel für jeden Service der Host-Gruppen zu dem bestehenden Resultat zwei weitere zu definieren. Diese werten den Prozentsatz der Hosts im Zustand UP und der Services im Zustand OK aus. Dabei nutzen Sie die zuvor in der Agentenausgabe und der Parse-Funktion festgelegten zusätzlichen Spalten der Host-Gruppentabelle.
Erweitern Sie die Check-Funktion nun sukzessive von oben nach unten:
def check_myhostgroups_advanced(item, section):
attr = section.get(item)
if not attr:
yield Result(state=State.CRIT, summary="Group is empty or has been deleted")
return
members = attr["members"]
num_hosts = int(attr["num_hosts"])
num_hosts_up = int(attr["num_hosts_up"])
num_services = int(attr["num_services"])
num_services_ok = int(attr["num_services_ok"])
Der if
-Zweig bleibt unverändert, das heißt auch die neuen Teilresultate gelten nur für Host-Gruppen, die auch existieren.
Anschließend definieren Sie fünf Variablen für die in der Sektion enthaltenen Spalten der Host-Gruppentabelle.
Dies erhöht zum einen im folgenden die Lesbarkeit und nebenbei können Sie für die vier Spalten, mit denen noch gerechnet werden soll, die ausgelesenen Strings mit int()
in Zahlen umwandeln.
Auch das bisher einzig existierende Resultat bleibt (fast) unverändert:
yield Result(
state = State.OK,
summary = f"{num_hosts} hosts in this group",
details = f"{num_hosts} hosts in this group: {members}",
)
Nur der Zugriff im Python-„F-String“ auf den Ausdruck, der den Wert liefert, ist nun einfacher als zuvor, da das attr
bereits in den Variablendefinitionen steckt.
Nun zum eigentlichen Kern der Erweiterung, der Definition eines Resultats, das die folgende Aussage umsetzt:
„Der Service der Host-Gruppe ist WARN, wenn 90 % der Hosts UP sind, und CRIT bei 80 % der Hosts.“
Dabei gilt die Konvention, dass der Check bereits beim Erreichen der Schwelle — und nicht erst beim Überschreiten — auf WARN bzw. CRIT geht.
Für den Vergleich eines ermittelten Werts mit Schwellwerten stellt die Check-API die Hilfsfunktion check_levels
bereit.
hosts_up_perc = 100.0 * num_hosts_up / num_hosts
yield from check_levels(
hosts_up_perc,
levels_lower = (90.0, 80.0),
label = "UP hosts",
notice_only = True,
)
In der ersten Zeile wird aus Gesamtzahl und Zahl der Hosts im Zustand UP der Prozentsatz berechnet und in der Variablen hosts_up_perc
gespeichert.
Durch den einfachen Schrägstrich (/
) wird eine Gleitkommadivison ausgeführt, die sicherstellt, das das Ergebnis ein Float-Wert ist.
Das ist sinnvoll, weil einige im weiteren Verlauf verwendete Funktionen Float als Eingabe erwarten.
In der zweiten Zeile wird dann das Ergebnis der Funktion check_levels
als Objekt vom Typ Result
zurückgegeben.
Die Funktion wird gefüttert mit dem gerade berechneten Prozentsatz als Wert (hosts_up_perc
), den beiden unteren Schwellwerten (levels_lower
), einer Bezeichnung, die der Ausgabe vorangestellt wird (label
) und schließlich mit notice_only=True
.
Der letzte Parameter nutzt den im vorherigen Abschnitt für das Objekt Result()
bereits vorgestellten Parameter notice
.
Mit notice_only=True
legen Sie fest, dass der Text für den Service nur dann im Summary angezeigt wird, wenn der Zustand nicht OK ist.
Allerdings werden Teilergebnisse, die zu einem WARN oder CRIT führen, sowieso immer im Summary sichtbar werden — unabhängig davon, welchen Wert notice_only
hat.
Schließlich definieren Sie analog zum zweiten das dritte Resultat, das den Prozentsatz der Services im Zustand OK auswertet:
services_ok_perc = 100.0 * num_services_ok / num_services
yield from check_levels(
services_ok_perc,
levels_lower = (90.0, 80.0),
label = "OK services",
notice_only = True,
)
Damit ist die Check-Funktion komplett.
Der Service für eine Host-Gruppe wertet jetzt drei Resultate aus und zeigt aus diesen den schlechtesten Zustand im Monitoring an, wie im folgenden Beispiel:
4.5. Metriken
Nicht immer, aber oft befassen sich Checks mit Zahlen.
Und sehr oft handelt es sich bei diesen Zahlen um gemessene oder berechnete Werte.
In unserem Beispiel sind die Zahl der Hosts in der Host-Gruppe (num_hosts
) und die Zahl der Hosts im Zustand UP (num_hosts_up
) die Messwerte.
Der Prozentsatz der Hosts im Zustand UP (hosts_up_perc
) ist ein daraus berechneter Wert.
Wenn dann solch ein Wert im zeitlichen Verlauf dargestellt werden kann, wird er auch als Metrik bezeichnet.
Mit seinem Graphing-System hat Checkmk eine Komponente, um solche Zahlen zu speichern, auszuwerten und darzustellen. Das geht dabei völlig unabhängig von der Berechnung der Zustände OK, WARN und CRIT.
Sie werden in diesem Beispiel die beiden berechneten Werte hosts_up_perc
und services_ok_perc
als Metriken definieren.
Metriken werden in der grafischen Benutzeroberfläche von Checkmk sofort sichtbar, ohne dass Sie etwas dafür tun müssen.
Pro Metrik wird automatisch ein Graph erzeugt.
Metriken werden von der Check-Funktion ermittelt und als zusätzliches Ergebnis zurückgegeben.
Am einfachsten ist es, der Funktion check_levels()
im Aufruf die Metrikinformationen mitzugeben.
Zur Erinnerung folgt die Zeile mit dem Funktionsaufruf von check_levels()
aus dem vorherigen Abschnitt:
yield from check_levels(
hosts_up_perc,
levels_lower = (90.0, 80.0),
label = "UP hosts",
notice_only = True,
)
Die beiden neuen Argumente für die Metrik sind metric_name
und boundaries
:
yield from check_levels(
hosts_up_perc,
levels_lower = (90.0, 80.0),
metric_name = "hosts_up_perc",
label = "UP hosts",
boundaries = (0.0, 100.0),
notice_only = True,
)
Um es schön einfach und aussagekräftig zu halten, nehmen Sie als Namen der Metrik den Namen der Variabel, in der der Prozentsatz als Wert gespeichert ist.
Mit boundaries
können Sie dem Graphing-System die Information über den möglichen Wertebereich mitgeben.
Damit ist der kleinste und größte mögliche Wert gemeint.
Bei einem Prozentsatz sind die Grenzen mit 0.0
und 100.0
nicht allzu schwer zu bestimmen.
Es sind sowohl Gleitkommazahlen (Float) als auch Ganzzahlen (Integer, die intern wiederum in Gleitkommazahlen umgewandelt werden) erlaubt, aber keine Strings.
Falls nur eine Grenze des Wertebereichs definiert ist, tragen Sie für die andere einfach None
ein, also zum Beispiel boundaries=(0.0, None)
.
Durch diese Erweiterung gibt die Funktion check_levels
nun per yield
zusätzlich zum Result
auch noch ein Objekt vom Typ Metric
zurück.
Analog können Sie jetzt auch die Metrik services_ok_perc
definieren.
Die letzten Zeilen der Check-Funktion sehen dann so aus:
hosts_up_perc = 100.0 * num_hosts_up / num_hosts
yield from check_levels(
hosts_up_perc,
levels_lower = (90.0, 80.0),
metric_name = "hosts_up_perc",
label = "UP hosts",
boundaries = (0.0, 100.0),
notice_only = True,
)
services_ok_perc = 100.0 * num_services_ok / num_services
yield from check_levels(
services_ok_perc,
levels_lower = (90.0, 80.0),
metric_name = "services_ok_perc",
label = "OK services",
boundaries = (0.0, 100.0),
notice_only = True,
)
Mit der so erweiterten Check-Funktion sind die beiden Graphen im Monitoring sichtbar. In der Service-Liste zeigt nun das Symbol , dass es Graphen zum Service gibt. Wenn Sie mit der Maus auf das Symbol zeigen, werden die Graphen als Vorschau eingeblendet.
Eine Übersicht aller Graphen inklusive Legende und mehr finden Sie in den Service-Details.
Was macht man aber, wenn der Wert für die gewünschte Metrik gar nicht mit der Funktion check_levels()
definiert wurde?
Sie können selbstverständlich eine Metrik auch unabhängig von einem Funktionsaufruf festlegen.
Dazu dient das Objekt Metric()
, welches Sie auch direkt über seinen Konstruktor erzeugen können.
Die alternative Definition einer Metrik für den Wert hosts_up_perc
sieht so aus:
yield Metric(
name = "hosts_up_perc",
value = hosts_up_perc,
levels = (80.0, 90.0),
boundaries = (0.0, 100.0),
)
Die Argumente von Metric()
sind sehr ähnlich zu denen im oben gezeigten Funktionsaufruf:
Verpflichtend sind die ersten beiden Argumente für den Metriknamen und den Wert.
Zusätzlich gibt es noch zwei optionale Argumente: levels
für die Schwellwerte WARN und CRIT und boundaries
für den Wertebereich.
Wichtig: Die Angabe von levels
dient hier lediglich als Information für die Darstellung des Graphen.
Im Graphen werden die Schwellwerte üblicherweise als gelbe und rote Linien eingezeichnet.
Für die Überprüfung ist die Funktion check_levels
mit den dort festgelegten Schwellwerten verantwortlich.
Nutzen Sie nun die Möglichkeit, nicht nur die beiden berechneten Werte, sondern mit Metric()
alle Messwerte als Metriken zu definieren — in unserem Beispiel also die vier Messwerte aus der Host-Gruppentabelle.
Beschränken Sie sich dabei auf die beiden obligatorischen Angaben von Metrikname und Wert.
Die vier neuen Zeilen komplettieren die Erweiterung der Check-Funktion für Metriken:
hosts_up_perc = 100.0 * num_hosts_up / num_hosts
yield from check_levels(
hosts_up_perc,
levels_lower = (90.0, 80.0),
metric_name = "hosts_up_perc",
label = "UP hosts",
boundaries = (0.0, 100.0),
notice_only = True,
)
services_ok_perc = 100.0 * num_services_ok / num_services
yield from check_levels(
services_ok_perc,
levels_lower = (90.0, 80.0),
metric_name = "services_ok_perc",
label = "OK services",
boundaries = (0.0, 100.0),
notice_only = True,
)
yield Metric(name="num_hosts", value=num_hosts)
yield Metric(name="num_hosts_up", value=num_hosts_up)
yield Metric(name="num_services", value=num_services)
yield Metric(name="num_services_ok", value=num_services_ok)
Das erhöht schon einmal die Zahl der Graphen pro Service, bietet Ihnen aber zum Beispiel auch die Möglichkeit, mehrere Metriken in einem Graphen zu kombinieren. Wir zeigen diese und andere Möglichkeiten im Abschnitt Darstellung von Metriken anpassen weiter unten.
In der Beispieldatei auf GitHub finden Sie wieder die gesamte Check-Funktion.
4.6. Check-Parameter für Regelsätze
Im erweiterten Check-Plugin myhostgroups_advanced
haben Sie den Zustand WARN erzeugt, wenn nur 90 % der Hosts UP sind, und CRIT bei 80 %.
Dabei sind die Zahlen 90
und 80
direkt in der Check-Funktion fest einprogrammiert oder, wie Programmierer sagen würden, hart codiert.
In Checkmk ist man allerdings als Benutzer gewohnt, dass man solche Schwellwerte und andere Check-Parameter per Regel konfigurieren kann.
Denn hat zum Beispiel eine Host-Gruppe nur vier Mitglieder, dann passen die beiden Schwellwerte von 90 und 80 % nicht wirklich gut,
da bereits beim Ausfall des ersten Hosts der Prozentsatz auf 75 % sinkt und der Zustand — ohne Umweg über WARN — direkt auf CRIT geht.
Daher soll das Check-Plugin nun so verbessert werden, dass es über die Setup-Oberfläche konfigurierbar ist. Dazu benötigen Sie einen Regelsatz. Falls es in Checkmk bereits einen passenden Regelsatz gibt, dann können Sie diesen verwenden. Da Check-Funktion und Regelsatz zusammenpassen müssen, wird dies in aller Regel nicht der Fall sein. Weiter unten finden Sie mehr Informationen zu vorhandenen Regelsätzen.
Hinweis: Beachten Sie, dass die in diesem Abschnitt vorgestellten Beispiele nicht durch die Check-API abgedeckt sind.
Neuen Regelsatz definieren
Um einen neuen Regelsatz zu erstellen, legen Sie eine neue Datei im Verzeichnis ~/local/share/check_mk/web/plugins/wato
an.
Der Name der Datei sollte sich an dem des Check-Plugins orientieren und muss wie alle Plugin-Dateien die Endung .py
haben.
Für unser Beispiel passt der Dateiname myhostgroups_advanced_parameters.py
.
Sehen Sie sich den Aufbau so einer Datei Schritt für Schritt an. Zunächst kommen einige Importbefehle.
Falls die Texte in Ihrer Datei, die auf der Checkmk-GUI angezeigt werden, in andere Sprachen übersetzbar sein sollen, importieren Sie _
(Unterstrich):
from cmk.gui.i18n import _
Dies ist eine Funktion und fungiert als Markierung für alle übersetzbaren Texte.
Im Weiteren schreiben Sie dann zum Beispiel anstelle von "Threshold for Warning"
ein _("Threshold for Warning")
für den Funktionsaufruf.
Das Übersetzungssystem von Checkmk, welches auf gettext basiert,
findet solche Texte und übernimmt sie in die Liste der zu übersetzenden Texte.
Falls Sie den Check nur für sich selbst bauen, können Sie darauf und damit auch auf die import
-Zeile verzichten.
Als nächstes importieren Sie sogenannte ValueSpecs. Ein ValueSpec ist ein sehr praktisches und universelles Werkzeug, das Checkmk an vielen Stellen verwendet. Es dient dem Generieren von angepassten Eingabeformularen, der Darstellung und Validierung der eingegebenen Werte und der Umwandlung in Python-Datenstrukturen.
from cmk.gui.valuespec import (
Dictionary,
Percentage,
TextInput,
Tuple,
)
Das Dictionary
benötigen Sie auf jeden Fall, denn seit Checkmk-Version 2.0.0 werden Check-Parameter in Python-Dictionaries gespeichert.
Percentage
ist für die Eingabe von Prozentwerten verantwortlich, TextInput
für einen Unicode-Text und Tuple
für eine geordnete, eindimensionale Menge von Objekten.
Sie werden auch häufiger Float
(Gleitkommazahlen) und Integer
(Ganzzahlen) verwenden.
Als nächstes werden noch Klassen und Funktionen importiert, die beim Registrieren benötigt werden:
from cmk.gui.plugins.wato.utils import (
CheckParameterRulespecWithItem,
rulespec_registry,
RulespecGroupCheckParametersApplications,
)
Da Ihr Check-Plugin myhostgroups_advanced
mehrere Services erzeugt, importieren Sie CheckParameterRulespecWithItem
.
Falls Ihr Check keinen Service erzeugt, mit anderen Worten kein Item hat, importieren Sie stattdessen CheckParameterRulespecWithoutItem
.
Zur RulespecGroup
gibt es am Ende dieses Abschnitts noch Informationen.
Nun kommen die eigentlichen Definitionen. Zunächst deklarieren Sie ein Eingabefeld, mit dem der Benutzer das Item des Checks angeben kann. Dieses Feld wird in der Bedingung der Regel angezeigt und erlaubt es den Benutzern, die Regel auf bestimmte Services einzuschränken. Es ist auch für das manuelle Anlegen von Checks notwendig, welche ohne Discovery funktionieren sollen.
Das Feld erstellen Sie mit TextInput
.
Es bekommt per title
einen Titel zugewiesen, welcher dann in der Regel als Überschrift für das Eingabefeld angezeigt wird.
Wenn Sie ein Herz für Ihre Benutzer haben, geben Sie ihnen auch noch einen hilfreichen Text für die Inline-Hilfe mit:
def _item_valuespec_myhostgroups_advanced():
return TextInput(
title = "Host group name",
help = "You can restrict this rule to certain services of the specified hosts.",
)
Den Namen der Funktion, welche dieses ValueSpec zurückgibt, können Sie frei wählen, er wird nur bei der Registrierung weiter unten benötigt. Damit die Funktion nicht über die Modulgrenze hinaus sichtbar wird, sollte der Name mit einem Unterstrich beginnen.
Als nächstes kommt das ValueSpec für die Eingabe der eigentlichen Check-Parameter. Auch hierfür legen Sie eine Funktion an, welche das ValueSpec erzeugt. Der Benutzer soll die beiden Schwellwerte für WARN und CRIT festlegen können und zwar getrennt für die Anzahl der Hosts im Zustand UP und der Services im Zustand OK:
def _parameter_valuespec_myhostgroups_advanced():
return Dictionary(
elements = [
("hosts_up_lower",
Tuple(
title = _("Lower percentage threshold for host in UP status"),
elements = [
Percentage(title=_("Warning")),
Percentage(title=_("Critical")),
],
)
),
("services_ok_lower",
Tuple(
title = _("Lower percentage threshold for services in OK status"),
elements = [
Percentage(title=_("Warning")),
Percentage(title=_("Critical")),
],
)
),
],
)
Das return Dictionary()
ist vorgeschrieben.
Innerhalb dessen legen Sie mit elements=[]
die Liste der Parameter an, im Beispiel hosts_up_lower
und services_ok_lower
.
Beide Parameter werden jeweils durch ein Tupel von zwei Werten für die CRIT und WARN Schwellwerte definiert.
Alle Schwellwerte sollen Prozentzahlen sein, also verwenden Sie hier Percentage
.
Hinweis: Wenn Sie den Benutzern des Regelsatzes statt leerer Eingabefelder bereits Werte vorgeben wollen, können Sie das tun, indem Sie die Percentage()
Funktion um das Argument default_value
erweitern.
Für das Tupel hosts_up_lower
aus dem eben gezeigten Beispiel sieht das dann so aus:
("hosts_up_lower",
Tuple(
title = _("Lower percentage threshold for host in UP status"),
elements = [
Percentage(title=_("Warning"), default_value=90.0),
Percentage(title=_("Critical"), default_value=80.0),
],
)
),
Beachten Sie, dass diese in der GUI angezeigten Werte nicht die Standardwerte sind, die weiter unten bei der Registrierung des Check-Plugins per check_default_parameters
gesetzt werden.
Wollen Sie die gleichen Standardwerte in der GUI anzeigen lassen, die auch für die Check-Funktion gelten, dann müssen Sie die Werte an beiden Stellen selbst konsistent halten.
Zu guter Letzt registrieren Sie jetzt mithilfe der importierten und selbst definierten Dinge den neuen Regelsatz.
Dazu gibt es die Funktion rulespec_registry.register()
:
rulespec_registry.register(
CheckParameterRulespecWithItem(
check_group_name = "myhostgroups_advanced",
group = RulespecGroupCheckParametersApplications,
match_type = "dict",
item_spec = _item_valuespec_myhostgroups_advanced,
parameter_valuespec = _parameter_valuespec_myhostgroups_advanced,
title = lambda: _("Host group status"),
)
)
Dazu die folgenden Erklärungen:
Falls Ihr Check kein Item verwendet, lautet die innere Funktion
CheckParameterRulespecWithoutItem
. Die Zeileitem_spec
entfällt dann.Der
check_group_name
als Name des Regelsatzes stellt die Verbindung zu den Check-Plugins her. Ein Check-Plugin, das diesen Regelsatz verwenden will, muss beim Registrieren diesen Namen alscheck_ruleset_name
verwenden. Der Name darf auf keinen Fall identisch sein mit einem bereits existierenden Regelsatz, weil dieser damit überschrieben würde. Um dieser Gefahr aus dem Weg zu gehen, verwenden Sie am besten im Namen einen Präfix.Die
group
legt fest, wo im Setup der Regelsatz auftauchen soll. Mit dem im Beispiel gewählten Wert finden Sie ihn unter Setup > Services > Service monitoring rules im ersten Kasten Applications, Processes & Services. Die meisten dieser Gruppen sind in der Datei~/lib/check_mk/gui/plugins/wato/utils/__init__.py
definiert. Dort finden Sie auch Beispiele, wie Sie eine eigene neue Gruppe anlegen können.Der
match_type
ist immer"dict"
.Als
item_spec
undparameter_valuespec
geben Sie die Namen der beiden zuvor erstellten Funktionen ein.title
legt den Titel des Regelsatzes fest, so wie er auch in der Checkmk-GUI erscheint. Der Titel wird aber nicht direkt als Text, sondern als ausführbare Funktion angegeben, welche den Text zurückliefert (deswegen daslambda:
).
Regelsatz testen
Wenn Sie die Datei für den Regelsatz angelegt haben, sollten Sie ausprobieren, ob alles soweit funktioniert — noch ohne Verbindung zum Check-Plugin. Dazu müssen Sie zuerst den Apache der Instanz neu starten, damit die neue Datei gelesen wird. Das macht der Befehl:
OMD[mysite]:~$ omd restart apache
Danach sollte der Regelsatz im Setup auf der oben genannten Seite zu finden sein. Mit der Suchfunktion im Setup-Menü finden Sie den Regelsatz auch — aber erst nach einem Neustart von Redis:
OMD[mysite]:~$ omd restart redis
Der soeben definierte Regelsatz sieht in der GUI so aus:
In der Bedingung finden Sie das definierte Feld Host group name mit der Inline-Hilfe:
Die Texte zum Umgang mit den regulären Ausrücken hat übrigens Checkmk Ihrem Regelsatz spendiert.
Legen Sie eine Regel an und probieren Sie verschiedene Werte aus, wie im obigen Screenshot gezeigt. Wenn das ohne Fehler geht, können Sie die Check-Parameter jetzt in der Check-Funktion verwenden.
Regelsatz im Check-Plugin benutzen
Damit die Regel zum Greifen kommt, müssen Sie dem Check-Plugin erlauben, Check-Parameter entgegenzunehmen und ihm sagen, welche Regel benutzt werden soll. Dazu fügen Sie zwei neue Zeilen in die Registrierungsfunktion ein:
register.check_plugin(
name = "myhostgroups_advanced",
sections = ["myhostgroups"],
service_name = "Host group %s",
discovery_function = discover_myhostgroups_advanced,
check_function = check_myhostgroups_advanced,
check_default_parameters = {},
check_ruleset_name = "myhostgroups_advanced",
)
Mit dem Eintrag check_default_parameters
können Sie die Standardwerte setzen, die gelten, solange noch keine Regel angelegt ist.
Bei der Registrierung muss diese Zeile unbedingt vorhanden sein.
Im einfachsten Fall übergeben Sie ein leeres Dictionary {}
.
Als zweites übergeben Sie der Registrierungsfunktion noch den check_ruleset_name
, also den Namen, den Sie oben mittels check_group_name
an den Regelsatz vergeben haben.
So weiß Checkmk aus welchem Regelsatz die Parameter bestimmt werden sollen.
Nun wird Checkmk versuchen, der Check-Funktion Parameter zu übergeben.
Damit das klappen kann, müssen Sie die Check-Funktion so erweitern, dass sie das Argument params
erwartet, welches sich zwischen item
und section
schiebt:
def check_myhostgroups_advanced(item, params, section):
Falls Sie einen Check ohne Item bauen, entfällt das item
und params
steht am Anfang.
Es ist sehr empfehlenswert, sich jetzt als ersten Test den Inhalt der Variable params
mit einem print
ausgeben zu lassen:
def check_myhostgroups_advanced(item, params, section):
print(params)
Bei der Ausführung des Check-Plugins sehen die beiden ausgedruckten Zeilen (für jeden Service eine) dann etwa so aus:
OMD[mysite]:~$ cmk --detect-plugins=myhostgroups_advanced -v localhost
Parameters({'hosts_up_lower': (70.0, 60.0), 'services_ok_lower': (75.0, 65.0)})
Parameters({'hosts_up_lower': (70.0, 60.0), 'services_ok_lower': (75.0, 65.0)})
Parameters({'hosts_up_lower': (70.0, 60.0), 'services_ok_lower': (75.0, 65.0)})
Wichtig: Wenn alles fertig ist und funktioniert, entfernen Sie unbedingt die print
-Befehle wieder.
Diese können die interne Kommunikation von Checkmk durcheinanderbringen.
Nun passen Sie Ihre Check-Funktion weiter an, so dass die übergebenen Parameter ihre Wirkung entfalten können. Holen Sie sich die beiden Tupel mit dem in der Regel gewählten Namen aus den Parametern:
def check_myhostgroups_advanced(item, params, section):
hosts_up_lower = params["hosts_up_lower"]
services_ok_lower = params["services_ok_lower"]
Weiter unten in der Check-Funktion werden dann die bisher hart kodierten Schwellwerte (90, 80)
durch die Variablen hosts_up_lower
und services_ok_lower
ersetzt:
hosts_up_perc = 100.0 * num_hosts_up / num_hosts
yield from check_levels(
hosts_up_perc,
levels_lower = (hosts_up_lower),
metric_name = "hosts_up_perc",
label="UP hosts",
boundaries = (0, 100),
notice_only = True,
)
services_ok_perc = 100.0 * num_services_ok / num_services
yield from check_levels(
services_ok_perc,
levels_lower = (services_ok_lower),
metric_name = "services_ok_perc",
label = "OK services",
boundaries = (0, 100),
notice_only = True,
)
Falls eine Regel konfiguriert ist, können Sie nun die Host-Gruppen des Beispiels mit den über die GUI gesetzten Schwellwerten überwachen.
Wenn allerdings keine Regel definiert ist, wird diese Check-Funktion abstürzen:
Da die Default-Parameter des Check-Plugins nicht befüllt sind, wird das Plugin bei Abwesenheit einer Regel einen KeyError
erzeugen.
Dieses Problem kann aber behoben werden, wenn bei der Registrierung die Standardwerte gesetzt sind:
register.check_plugin(
name = "myhostgroups_advanced",
sections = ["myhostgroups"],
service_name = "Host group %s",
discovery_function = discover_myhostgroups_advanced,
check_function = check_myhostgroups_advanced,
check_default_parameters = {"hosts_up_lower": (90, 80), "services_ok_lower": (90, 80)},
check_ruleset_name = "myhostgroups_advanced",
)
Sie sollten Standardwerte immer auf diese Weise übergeben (und den Fall fehlender Parameter nicht im Check-Plugin abfangen), da diese Standardwerte auch in der Setup-Oberfläche angezeigt werden können. Dazu gibt es zum Beispiel bei der Service-Erkennung eines Hosts, auf der Seite Services of host, im Menü Display den Eintrag Show check parameters.
Hinweis: Auf GitHub finden Sie sowohl die Datei mit dem Regelsatz als auch das um den Regelsatz erweiterte Check-Plugin.
Vorhandenen Regelsatz verwenden
Unwahrscheinlich, doch nicht ausgeschlossen ist es, dass ein mit Checkmk ausgelieferter Regelsatz zu Ihrer Check-Funktion passt. Das kann eigentlich nur dann der Fall sein, wenn der Check etwas prüft, für das Checkmk in gleicher Form bereits Check-Plugins hat, zum Beispiel das Überwachen einer Temperatur oder anderer mit Sensoren gemessener Werte. Wenn aber so ein Regelsatz zu Ihrem Check-Plugin passt, dann können Sie sich die Erstellung eines eigenen Regelsatzes sparen.
Die ausgelieferten Regelsätze für Parameter von Checks finden Sie im Verzeichnis ~/lib/check_mk/gui/plugins/wato/check_parameters/
.
Nehmen Sie als Beispiel die Datei memory_simple.py
.
Diese deklariert einen Regelsatz mit folgendem Abschnitt:
rulespec_registry.register(
CheckParameterRulespecWithItem(
check_group_name = "memory_simple",
group = RulespecGroupCheckParametersOperatingSystem,
item_spec = _item_spec_memory_simple,
match_type = "dict",
parameter_valuespec = _parameter_valuespec_memory_simple,
title = lambda: _("Main memory usage of simple devices"),
)
)
Entscheidend ist dabei, wie beim selbst geschriebenen Regelsatz, das Schlüsselwort check_group_name
, welches hier auf "memory_simple"
gesetzt ist.
Damit wird die Verbindung zum Check-Plugin hergestellt.
Das machen Sie beim Registrieren des Check-Plugins mit dem Schlüsselwort check_ruleset_name
, zum Beispiel so:
register.check_plugin(
name = "foobar",
service_name = "Foobar %s",
discovery_function = discover_foobar,
check_function = check_foobar,
check_ruleset_name = "memory_simple",
check_default_parameters = {},
)
Notwendig ist auch hier die Definition von Standardwerten über das Schlüsselwort check_default_parameters
.
5. Darstellung von Metriken anpassen
Im obigen Beispiel haben Sie das Check-Plugin myhostgroups_advanced
Metriken für alle gemessenen und berechneten Werte erzeugen lassen.
Dazu haben wir zwei Wege vorgestellt.
Zuerst wurden die Metriken der berechneten Werte als Bestandteil der Funktion check_levels()
mit dem Argument metric_name
erstellt, zum Beispiel so:
yield from check_levels(
services_ok_perc,
levels_lower = (services_ok_lower),
metric_name = "services_ok_perc",
label = "OK services",
boundaries = (0.0, 100.0),
notice_only = True,
)
Dann haben wir die gemessenen Metriken direkt mit dem Objekt Metric()
erzeugt — für die Anzahl der Services im Zustand OK zum Beispiel so:
yield Metric(name="num_services_ok", value=num_services_ok)
Metriken werden in der grafischen Benutzeroberfläche von Checkmk zwar sofort sichtbar, ohne dass Sie etwas dafür tun müssen. Allerdings gibt es dabei ein paar Einschränkungen:
Es werden nicht automatisch passende Metriken in einem Graphen kombiniert, sondern jede erscheint einzeln.
Die Metrik hat keinen richtigen Titel, sondern es wird der interne Variablenname der Metrik gezeigt.
Es wird keine Einheit verwendet, die eine sinnvolle Darstellung erlaubt (zum Beispiel GB anstelle von einzelnen Bytes).
Es wird zufällig eine Farbe ausgewählt.
Es erscheint nicht automatisch ein „Perf-O-Meter“, also die grafische Vorschau der Metrik als Balken in der Service-Liste (zum Beispiel in der Ansicht, die alle Services eines Hosts darstellt).
Um die Darstellung Ihrer Metriken in diesen Belangen zu vervollständigen, benötigen Sie Metrikdefinitionen.
Hinweis: Beachten Sie, dass die in diesem Kapitel vorgestellten Beispiele nicht durch die Check-API abgedeckt sind.
5.1. Vorhandene Metrikdefinitionen verwenden
Bevor Sie eine neue Metrikdefinition erstellen, sollten Sie zunächst prüfen, ob Checkmk nicht bereits eine geeignete Definition mitbringt.
Die vordefinierten Metrikdefinitionen finden Sie im Verzeichnis ~/lib/check_mk/gui/plugins/metrics/
.
In der Datei cpu.py
finden Sie beispielsweise die Metrik mit dem Namen util
für die CPU-Auslastung (CPU utilization):
metric_info["util"] = {
"title": _l("CPU utilization"),
"unit": "%",
"color": "26/a",
}
Falls Ihr Check-Plugin die CPU-Auslastung in Prozent misst, können Sie diese Metrikdefinition problemlos verwenden.
Sie müssen lediglich in Ihrer Check-Funktion "util"
als den Namen für die Metrik einsetzen.
Alles andere leitet sich dann automatisch davon ab.
5.2. Neue Metrikdefinition erstellen
Falls keine passende Metrikdefinition dabei ist, legen Sie einfach selbst eine an.
Für unser Beispiel definieren Sie nun eine eigene Metrik für die Anzahl der Services im Zustand OK.
Dazu legen Sie eine Datei in ~/local/share/check_mk/web/plugins/metrics
an:
from cmk.gui.i18n import _
from cmk.gui.plugins.metrics import metric_info
metric_info["num_services_ok"] = {
"title": _("Services in OK status"),
"unit": "count",
"color": "15/a",
}
Hier die Erklärung dazu:
Das Importieren und Verwenden des Unterstrichs (
_
) für die Internationalisierung ist optional, wie bereits bei der Erstellung von Regelsätzen besprochen.Der Schlüssel (hier
"num_services_ok"
) ist der Metrikname und muss dem entsprechen, was die Check-Funktion ausgibt. Wählen Sie einen eindeutigen Namen, so dass keine vorhandene Metrik „überschrieben“ wird.Der
title
ist die Überschrift im Metrikgraphen und ersetzt den bisher verwendeten internen Variablennamen.Welche Definitionen es für Einheiten (
unit
) gibt, erfahren Sie in der Datei~/lib/check_mk/gui/plugins/metrics/unit.py
.Die Farbdefinition
color
verwendet eine Palette. Zu jeder Palettenfarbe gibt es/a
und/b
. Dies sind zwei Schattierungen der gleichen Farbe. In den vorhandenen Metrikdefinitionen werden Sie auch viele direkte Farbkodierungen wie"#ff8800"
finden. Diese werden nach und nach abgeschafft und durch Palettenfarben ersetzt, da diese ein einheitlicheres Aussehen bieten und auch leichter an die Farbthemen der GUI angepasst werden können.
Diese Definition in der Metrikdatei sorgt jetzt dafür, dass Titel, Einheit und Farbe der Metrik angepasst dargestellt werden.
Analog zur Erstellung einer Regelsatzdatei muss die Metrikdatei erst gelesen werden, bevor die Änderung in der GUI sichtbar ist. Das macht der Neustart des Apache der Instanz:
OMD[mysite]:~$ omd restart apache
Der Metrikgraph sieht dann in der Checkmk-GUI ungefähr so aus:
5.3. Graph mit mehreren Metriken
Möchten Sie mehrere Metriken in einem Graphen kombinieren (was oft sehr sinnvoll ist), benötigen Sie eine Graphdefinition, die Sie der im vorherigen Abschnitt erstellten Metrikdatei hinzufügen können.
Dies geschieht über das globale Dictionary graph_info
.
Für unser Beispiel sollen die beiden Metriken num_services_ok
und num_services
in einem Graphen dargestellt werden.
Die Metrikdefinitionen dazu sehen wie folgt aus:
from cmk.gui.i18n import _
from cmk.gui.plugins.metrics import (
metric_info,
graph_info,
)
metric_info["num_services_ok"] = {
"title": _("Services in OK status"),
"unit": "count",
"color": "15/a",
}
metric_info["num_services"] = {
"title": _("Number of services"),
"unit": "count",
"color": "24/a",
}
Fügen Sie nun einen Graphen an, der diese beiden Metriken als Linien einzeichnet:
graph_info["num_services_combined"] = {
"metrics": [
("num_services_ok", "line"),
("num_services", "line"),
],
}
Der erste Eintrag unter metrics
legt fest, welche Metrik für den Titel des Graphen genommen wird.
Das Resultat ist der kombinierte Graph in der Checkmk-GUI:
5.4. Metriken im Perf-O-Meter
Möchten Sie zu einer Metrik noch ein Perf-O-Meter in der Zeile der Service-Liste anzeigen? Das könnte zum Beispiel so aussehen:
Um so ein Perf-O-Meter zu erstellen, benötigen Sie eine weitere Datei, diesmal im Verzeichnis ~/local/share/check_mk/web/plugins/perfometer
:
from cmk.gui.plugins.metrics import perfometer_info
perfometer_info.append(
{
"type": "logarithmic",
"metric": "num_services_ok",
"half_value": 25,
"exponent": 2.0,
}
)
Perf-O-Meter sind etwas trickreicher als Graphen, da es keine Legende gibt. Daher ist die Darstellung des Wertebereichs schwierig. Da das Perf-O-Meter nicht wissen kann, welche Werte denn überhaupt möglich sind, und der Platz sehr begrenzt ist, verwenden viele eingebaute Check-Plugins eine logarithmische Darstellung. Dies ist auch im obigen Beispiel der Fall:
type
wählt die Darstellungsform der Werte, hier die logarithmische Darstellung.metric
bezeichnet den Namen der Metrik, hier die Anzahl der Services im Zustand OK.half_value
ist der Messwert, welcher genau in der Mitte des Perf-O-Meters angezeigt wird. Bei einem Wert von25
ist der Balken also halb gefüllt.exponent
legt den Faktor fest, welcher notwendig ist, damit weitere 10 % des Bereichs gefüllt werden. Also würde im Beispiel ein Messwert von50
den Balken bis 60 % füllen, und einer von100
bis 70 %.
Der Vorteil dieser Methode: Wenn Sie eine Liste von Services gleicher Art mit gleichartigen Perf-O-Metern ausstatten, können Sie die Perf-O-Meter untereinander optisch schnell vergleichen, da alle die gleiche Skala nutzen. Und trotz der kleinen Darstellungsform können Sie sowohl bei sehr kleinen als auch bei sehr großen Werten die Unterschiede gut erkennen. Dafür sind die Werte allerdings nicht maßstabsgetreu.
Das obige Perf-O-Meter zeigt zwar eine Metrik, aber nicht diejenige, die für den Zustand des Services verantwortlich ist. Nicht die Anzahl der Services im Zustand OK, sondern ihr Prozentsatz bestimmt den Zustand des gezeigten Services der Host-Gruppe.
Um die Metrik des Prozentsatzes (services_ok_perc
) darzustellen, können Sie ein lineares Perf-O-Meter verwenden.
Das ist immer dann sinnvoll, wenn es einen bekannten Maximalwert gibt.
Das sähe dann zum Beispiel in der Datei so aus:
perfometer_info.append(
{
"type": "linear",
"segments": ["services_ok_perc"],
"total": 100.0,
}
)
Und so in der GUI:
Das ist schon einmal eine Verbesserung. Allerdings wird der Zustand des Services nicht nur durch den Prozentsatz der Services im Zustand OK, sondern auch durch den Prozentsatz der Hosts im Zustand UP bestimmt. Um mehrere Metriken in einem Perf-O-Meter zu kombinieren, gibt es verschiedene Möglichkeiten. Eine davon sieht so aus:
perfometer_info.append(
{
"type": "dual",
"perfometers": [
{
"type": "linear",
"segments": ["hosts_up_perc"],
"total": 100.0,
},
{
"type": "linear",
"segments": ["services_ok_perc"],
"total": 100.0,
},
],
}
)
Dies sorgt dafür, dass sich die beiden Metriken den Balken des Perf-O-Meters teilen und nebeneinander angezeigt werden:
Auch zu diesem Thema finden Sie viele Beispiele in den von Checkmk ausgelieferten Check-Plugins — in der Datei ~/lib/check_mk/gui/plugins/metrics/perfometers.py
.
6. Zahlen formatieren
Im Summary und den Details eines Services werden oft Zahlen ausgegeben.
Um Ihnen eine schöne und korrekte Formatierung möglichst einfach zu machen,
und auch um die Ausgaben von allen Check-Plugins zu vereinheitlichen, gibt es Hilfsfunktionen für die Darstellung von verschiedenen Arten von Größen.
All diese sind Unterfunktionen vom Modul render
und werden folglich mit render.
aufgerufen.
Zum Beispiel ergibt render.bytes(2000)
den Text 1.95 KiB
.
All diesen Funktionen ist gemein, dass Sie ihren Wert in einer sogenannten kanonischen oder natürlichen Einheit bekommen. So muss man nie nachdenken und es gibt keine Schwierigkeiten oder Fehler bei der Umrechnung. Beispielsweise werden Zeiten immer in Sekunden angegeben und Größen von Festplatten, Dateien etc. immer in Bytes und nicht in Kilobytes, Kibibytes, Blöcken oder sonstigem Durcheinander.
Verwenden Sie diese Funktionen auch dann, wenn Ihnen die Darstellung nicht so gut gefällt. Immerhin ist diese dann für den Benutzer einheitlich. Und zukünftige Versionen von Checkmk können die Darstellung möglicherweise ändern oder sogar konfigurierbar für den Benutzer machen, wie es zum Beispiel bereits bei der Anzeige der Temperatur der Fall ist. Davon wird dann Ihr Check-Plugin auch profitieren.
Bevor Sie die render
-Funktion in Ihrem Check-Plugin nutzen können, müssen Sie sie zusätzlich importieren:
from .agent_based_api.v1 import check_levels, Metric, register, render, Result, Service, State
Nach der ausführlichen Beschreibung aller Darstellungsfunktionen (Render-Funktionen) finden Sie eine Zusammenfassung in Form einer übersichtlichen Tabelle.
6.1. Zeiten, Zeiträume, Frequenzen
Absolute Zeitangaben (Zeitstempel) werden mit render.date()
oder render.datetime()
formatiert.
Die Angaben erfolgen immer als Unix-Zeit, also in Sekunden ab dem 1. Januar 1970, 00:00:00 UTC — dem Beginn der Unix-Epoche.
Dies ist auch das Format, mit dem die Python-Funktion time.time()
arbeitet.
Vorteil an dieser Darstellung ist, dass sich damit sehr einfach rechnen lässt, also zum Beispiel die Berechnung des Zeitraums, wenn Start- und Endzeit bekannt sind.
Die Formel ist dann einfach duration = end - start
.
Und diese Berechnungen funktionieren unabhängig von der Zeitzone, Sommerzeitumstellungen oder Schaltjahren.
render.date()
gibt nur das Datum aus, render.datetime()
fügt noch die Uhrzeit hinzu.
Die Ausgabe erfolgt dabei gemäß der aktuellen Zeitzone desjenigen Checkmk-Servers, welcher den Check ausführt.
Beispiele:
Aufruf | Ausgabe |
---|---|
|
|
|
|
|
|
|
|
Wundern Sie sich jetzt nicht, dass render.datetime(0)
als Uhrzeit nicht 00:00
, sondern 01:00
ausgibt.
Das liegt daran, dass wir dieses Handbuch in der Zeitzone von Deutschland schreiben — und die ist der Standardzeit UTC eine Stunde voraus (zumindest während der Normalzeit, denn der 1. Januar liegt ja bekanntlich nicht in der Sommerzeit).
Für Zeiträume (oder Zeitspannen) gibt es noch die Funktion render.timespan()
.
Diese bekommt eine Dauer in Sekunden und gibt das menschenlesbar aus.
Bei größeren Zeitspannen werden Sekunden oder Minuten weggelassen.
Wenn Sie eine Zeitspanne in einem TimeDelta
-Objekt vorliegen haben, lesen Sie aus diesem mit der Funktion total_seconds()
die Zahl der Sekunden als Fließkommazahl aus.
Aufruf | Ausgabe |
---|---|
|
|
|
|
|
|
|
|
Eine Frequenz ist quasi der Kehrwert der Zeit. Die kanonische Einheit ist Hz, was das gleiche bedeutet wie 1 / sec. Einsatzgebiet ist zum Beispiel die Taktrate einer CPU:
Aufruf | Ausgabe |
---|---|
|
|
6.2. Bytes
Überall wo es um Arbeitsspeicher, Dateien, Festplatten, Dateisysteme und dergleichen geht, ist die kanonische Einheit das Byte. Da Computer so etwas meist in Zweierpotenzen organisieren, also zum Beispiel in Einheiten zu 512, 1024 oder 65 536 Bytes, hatte sich dabei von Beginn an eingebürgert, dass ein Kilobyte nicht 1000, und damit das Tausendfache der Einheit ist, sondern 1024 (2 hoch 10) Bytes. Das ist zwar unlogisch, aber sehr praktisch, weil so meist runde Zahlen herauskommen. Der legendäre Commodore C64 hatte eben 64 Kilobyte Speicher und nicht 65,536.
Leider kamen irgendwann Festplattenhersteller auf die Idee, die Größen ihrer Platten in 1000er-Einheiten anzugeben. Da bei jeder Größenordnung der Unterschied zwischen 1000 und 1024 immerhin 2,4 % ausmacht, und diese sich aufmultiplizieren, wird so aus einer Platte der Größe 1 GB (1024 mal 1024 mal 1024) auf einmal 1,07 GB. Das verkauft sich besser.
Diese lästige Verwirrung besteht bis heute und sorgt immer wieder für Fehler. Als Linderung wurden von der internationalen elektrotechnischen Kommission (IEC) neue Präfixe auf Grundlage des Binärsystems festgelegt. Demnach ist heute offiziell ein Kilobyte 1000 Bytes und ein Kibibyte 1024 Bytes (2 hoch 10). Außerdem soll man Mebibyte, Gibibyte und Tebibyte sagen. Die Abkürzungen lauten dann KiB, MiB, GiB und TiB.
Checkmk passt sich an diesen Standard an und hilft Ihnen mit mehreren angepassten Render-Funktionen dabei, dass Sie immer korrekte Ausgaben machen.
So gibt es speziell für Festplatten und Dateisysteme die Funktion render.disksize()
, welche die Ausgabe in 1000er-Potenzen macht.
Aufruf | Ausgabe |
---|---|
|
|
|
|
|
|
Bei der Größe von Dateien ist es oft üblich, die genaue Größe in Bytes ohne Rundung anzugeben.
Dies hat den Vorteil, dass man so sehr schnell sehen kann, wenn sich eine Datei auch nur minimal geändert hat oder dass zwei Dateien (wahrscheinlich) gleich sind.
Hierfür ist die Funktion render.filesize()
verantwortlich:
Aufruf | Ausgabe |
---|---|
|
|
|
|
|
|
Wenn Sie eine Größe ausgeben möchten, die keine Festplatten- oder Dateigröße ist, dann verwenden Sie einfach das generische render.bytes()
.
Hier bekommen Sie die Ausgabe in klassischen 1024er-Potenzen in der offiziellen Schreibweise:
Aufruf | Ausgabe |
---|---|
|
|
|
|
|
|
6.3. Bandbreiten, Datenraten
Die Netzwerker haben ihre eigenen Begriffe und Arten, Dinge auszudrücken. Und wie immer gibt sich Checkmk Mühe, in jeder Domäne die dort übliche Art zu kommunizieren, zu übernehmen. Deswegen gibt es für Datenraten und Geschwindigkeiten gleich drei verschiedene Render-Funktionen. Alle haben gemeinsam, dass die Raten in Bytes pro Sekunde übergeben werden, selbst dann, wenn die Ausgabe in Bits erfolgt!
render.nicspeed()
stellt die Maximalgeschwindigkeit einer Netzwerkkarte oder eines Switchports dar.
Da es keine Messwerte sind, muss auch nicht gerundet werden.
Obwohl kein Port einzelne Bits versenden kann, sind die Angaben aus historischen Gründen in Bits.
Wichtig: Trotzdem müssen Sie auch hier Bytes pro Sekunde übergeben!
Aufruf | Ausgabe |
---|---|
|
|
|
|
render.networkbandwidth()
ist gedacht für eine tatsächlich gemessene Übertragungsgeschwindigkeit im Netzwerk.
Eingabewert ist wieder Bytes pro Sekunde:
Aufruf | Ausgabe |
---|---|
|
|
|
|
|
|
Wo es nicht ums Netzwerk geht und dennoch Datenraten ausgegeben werden, sind wieder Bytes üblich.
Prominentester Fall sind IO-Raten von Festplatten.
Dafür gibt es die Funktion render.iobandwidth()
, die in Checkmk mit 1000er-Potenzen arbeitet:
Aufruf | Ausgabe |
---|---|
|
|
|
|
|
|
6.4. Prozentsätze
Die Funktion render.percent()
stellt einen Prozentsatz dar — auf zwei Nachkommastellen gerundet.
Sie ist insofern eine Ausnahme zu den anderen Funktionen, als hier nicht der eigentlich natürliche Wert — also das Verhältnis — übergeben wird, sondern wirklich die Prozentzahl.
Wenn also etwas zum Beispiel zur Hälfte voll ist, müssen Sie nicht 0.5
sondern 50
übergeben.
Weil es manchmal interessant sein kann, zu wissen, ob ein Wert beinahe Null oder exakt Null ist, werden Werte durch Anfügen eines „<
“ Zeichens markiert,
die größer als Null, aber kleiner als 0,01 Prozent sind.
Aufruf | Ausgabe |
---|---|
|
|
|
|
|
|
6.5. Zusammenfassung
Hier folgt zum Abschluss die Übersicht über alle Render-Funktionen:
Funktion | Eingabe | Beschreibung | Beispielausgabe |
---|---|---|---|
|
Unix-Zeit |
Datum |
|
|
Unix-Zeit |
Datum und Uhrzeit |
|
|
Sekunden |
Dauer / Alter |
|
|
Hz |
Frequenz (z. B. Taktrate) |
|
|
Bytes |
Größe einer Festplatte, Basis 1000 |
|
|
Bytes |
Größe einer Datei, volle Genauigkeit |
|
|
Bytes |
Größe, Basis 1024 |
|
|
Bytes pro Sekunde |
Geschwindigkeit von Netzwerkkarten |
|
|
Bytes pro Sekunde |
Übertragungsgeschwindigkeit |
|
|
Bytes pro Sekunde |
IO-Bandbreiten |
|
|
Prozentzahl |
Prozentsatz, sinnvoll gerundet |
|
7. Fehler beheben
Die korrekte Behandlung von Fehlern nimmt (leider) einen großen Teil der Programmierarbeit ein. Dabei ist die gute Nachricht, dass Ihnen die Check-API hinsichtlich der Fehlerbehandlung bereits viel Arbeit abnimmt. Bei einigen Arten von Fehlern ist es daher richtig, diese gar nicht selbst zu behandeln.
Wenn Python in eine Situation kommt, die in irgendeiner Form unerwartet ist, reagiert es mit einer sogenannten Ausnahme (Exception). Hier sind ein paar Beispiele:
Sie konvertieren mit
int()
einen String in eine Zahl, aber der String enthält keine Zahl, zum Beispielint("foo")
.Sie greifen mit
bar[4]
auf das fünfte Element vonbar
zu, aber das hat nur vier Elemente.Sie rufen eine Funktion auf, die es nicht gibt.
Um entscheiden zu können, wie Sie Fehler angehen, ist es zunächst wichtig, die exakte Stelle im Code zu kennen, an der ein Fehler auftritt. Hierfür können Sie sowohl die GUI als auch die Kommandozeile verwenden — je nachdem, wo Sie gerade arbeiten.
7.1. Exceptions und Absturzberichte in der GUI
Tritt eine Exception im Monitoring oder während der Service-Erkennung im Setup auf, enthält das Summary Hinweise auf den eben erstellten Absturzbericht (crash report). Das sieht dann zum Beispiel so aus:
Durch einen Klick auf das Icon wird eine Seite mit Details angezeigt, auf der Sie:
die Datei angezeigt bekommen, in der der Absturz stattgefunden hat,
alle Informationen über den Absturz erhalten, wie die Auflistung der aufgetretenen Fehler im Programm (Traceback), aktuelle Werte lokaler Variablen, Agentenausgabe und vieles mehr sowie
den Report zu uns (Checkmk GmbH) als Feedback einsenden können.
Der Traceback hilft Ihnen als Programmierer zu entscheiden, ob ein Fehler im Programm vorliegt (beispielsweise der Aufruf einer nicht vorhandenen Funktion) oder Agentendaten vorliegen, die nicht wie erwartet verarbeitet werden konnten. Im ersten Fall werden Sie den Fehler beheben wollen, im zweiten Fall ist es häufig sinnvoll, nichts zu tun.
Das Einsenden des Reports ist natürlich nur für Check-Plugins sinnvoll, die offiziell Teil von Checkmk sind. Falls Sie eigene Plugins Dritten zugänglich machen, können Sie Ihre Anwender bitten, Ihnen die Daten zukommen zu lassen.
7.2. Exceptions auf der Kommandozeile ansehen
Wenn Sie Ihr Check-Plugin auf der Kommandozeile ausführen, erhalten Sie keinen Hinweis auf die ID des erzeugten Absturzberichts. Sie sehen nur die zusammengefasste Fehlermeldung:
OMD[mysite]:~$ cmk --detect-plugins=myhostgroups_advanced localhost
Error in agent based plugin myhostgroups: invalid syntax (myhostgroups.py, line 11)
Hängen Sie die Option --debug
als zusätzlichen Aufrufparameter an, dann bekommen Sie den Traceback des Python-Interpreters:
OMD[mysite]:~$ cmk --debug --detect-plugins=myhostgroups_advanced localhost
Traceback (most recent call last):
File "/omd/sites/mysite/bin/cmk", line 97, in <module>
errors = config.load_all_agent_based_plugins(
^^^^^^^^^^^^
File "/omd/sites/mysite/lib/python3/cmk/base/config.py", line 1673, in load_all_agent_based_plugins
errors = agent_based_register.load_all_plugins()
^^^^^^^^^^^^^
File "/omd/sites/mysite/lib/python3/cmk/base/api/agent_based/register/init.py", line 48, in load_all_plugins
raise exception
File "/omd/sites/mysite/lib/python3/cmk/utils/plugin_loader.py", line 49, in load_plugins_with_exceptions
importlib.import_module(full_name)
File "/omd/sites/mysite/lib/python3.11/importlib/init.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1206, in _gcd_import
File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 936, in exec_module
File "<frozen importlib._bootstrap_external>", line 1074, in get_code
File "<frozen importlib._bootstrap_external>", line 1004, in source_to_code
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/omd/sites/mysite/local/lib/python3/cmk/base/plugins/agent_based/myhostgroups.py", line 11
parsed =
^
SyntaxError: invalid syntax
Tritt der Fehler beim Aufruf mit --debug
beim nächsten Mal nicht mehr auf, zum Beispiel, weil eine neue Agentenausgabe bereitsteht, können Sie die letzten Absturzberichte auch im Dateisystem einsehen:
OMD[mysite]:~$ ls -lhtr ~/var/check_mk/crashes/check/ | tail -n 5
drwx------ 1 mysite mysite 44 Sep 3 05:15 f47550fc-4a18-11ee-a46c-001617122312/
drwx------ 1 mysite mysite 44 Sep 3 05:17 38657652-4a19-11ee-a46c-001617122312/
drwx------ 1 mysite mysite 44 Sep 3 09:31 9e716690-4a3c-11ee-9eaf-001617122312/
drwx------ 1 mysite mysite 44 Sep 3 10:40 479b20ea-4a46-11ee-9eaf-001617122312/
drwx------ 1 mysite mysite 44 Sep 4 14:11 fdec3ef6-4b2c-11ee-9eaf-001617122312/
In jedem dieser Ordner liegen zwei Dateien:
crash.info
enthält ein Python-Dictionary mit Traceback und vielen weiteren Informationen. Oft genügt der Blick in die Datei mit dem Pager.agent_output
enthält die vollständige Agentenausgabe, die zum Zeitpunkt des Absturzes aktuell war.
7.3. Eigene Debug-Ausgabe
In den oben gezeigten Beispielen verwenden wir die Funktion print()
, um für Sie als Programmierer den Inhalt von Variablen oder die Struktur von Objekten auszugeben.
Diese Funktionen für Debug-Ausgaben müssen aus dem fertigen Check-Plugin wieder entfernt werden.
Alternativ zur Entfernung können Sie Ihre Debug-Ausgabe auch nur ausgeben lassen, wenn das Check-Plugin in der Konsole im Debug-Modus aufgerufen wird.
Dafür importieren Sie das Debug-Objekt aus der Checkmk-Werkzeugkiste und gegebenenfalls die Formatierungshilfe pprint()
.
Sie können nun Debug-Ausgaben abhängig vom Wert des Debug-Objektes vornehmen:
from cmk.utils import debug
from pprint import pprint
def check_mystuff(section):
if debug.enabled():
pprint(section)
Beachten Sie, dass verbleibende Debug-Ausgaben sparsam verwendet und auf Hinweise beschränkt sein sollten, die den späteren Benutzern bei der Fehlersuche helfen. Offensichtliche und abzusehende Fehler des Benutzers (zum Beispiel, dass die Inhalte der Agentensektion darauf hindeuten, dass das Agentenplugin falsch konfiguriert ist) sollten Sie mit dem Zustand UNKNOWN und aussagekräftigen Hinweisen im Summary beantworten.
7.4. Ungültige Agentenausgabe
Die Frage ist, wie Sie reagieren sollen, wenn die Ausgaben vom Agenten nicht die Form haben, die Sie eigentlich erwarten — egal ob vom Checkmk-Agenten oder per SNMP erhalten. Angenommen, Sie erwarten pro Zeile immer drei Worte. Was sollen Sie tun, falls nur zwei kommen?
Nun, wenn das ein erlaubtes und bekanntes Verhalten des Agenten ist, dann müssen Sie das natürlich abfangen und mit einer Fallunterscheidung arbeiten. Falls das aber eigentlich nicht sein darf, dann tun Sie am besten so, als ob die Zeile immer aus drei Worten besteht, also zum Beispiel mit folgender Parse-Funktion:
def parse_foobar(string_table):
for foo, bar, baz in string_table:
# ...
Sollte jetzt mal eine Zeile dabei sein, die nicht aus genau drei Worten besteht, wird eine Exception erzeugt und Sie bekommen den gerade erwähnten sehr hilfreichen Absturzbericht.
Falls Sie auf Schlüssel in einem Dictionary zugreifen, die gelegentlich erwartbar fehlen, kann es natürlich sinnvoll sein, darauf entsprechend zu reagieren.
Das kann erfolgen, indem Sie den Service auf CRIT oder UNKNOWN setzen und im Summary einen Hinweis auf die nicht auswertbare Agentenausgabe unterbringen.
In jedem Fall ist es besser, Sie verwenden hierfür die get()
-Funktion des Dictionaries als die KeyError
Exception abzufangen.
Denn get()
liefert bei Nichtvorhandensein des Schlüssels ein Objekt vom Typ None
oder einen optional als zweiten Parameter zu übergebenden Ersatz:
def check_foobar(section):
foo = section.get("bar")
if not foo:
yield Result(state=State.CRIT, summary="Missing key in section: bar")
return
# ...
7.5. Fehlende Items
Was ist, wenn der Agent korrekte Daten ausgibt, aber das Item fehlt, das überprüft werden soll? Also zum Beispiel auf diese Art:
def check_foobar(item, section):
# Try to access the item as key in the section:
foo = section.get(item)
if foo:
yield Result(state=State.OK, summary="Item found in monitoring data")
# If foo is None, nothing is yielded here
Ist das gesuchte Item nicht dabei, so wird die Schleife durchlaufen und Python fällt am Ende der Funktion einfach hinten raus, ohne dass ein Resultat per yield
zurückgegeben wurde.
Und das ist genau das Richtige!
Denn daran erkennt Checkmk, dass das zu überwachende Item fehlt und erzeugt mit UNKNOWN den richtigen Status und einen passenden Standardtext dazu.
7.6. Test mit Spool-Dateien
Wenn Sie bestimmte Agentenausgaben simulieren wollen, sind Spool-Dateien sehr hilfreich. Diese können Sie nutzen, um sonst schwer nachzustellende Grenzfälle zu testen. Oder Sie verwenden direkt die Agentenausgabe, welche zu einem Absturzbericht geführt hat, zur Prüfung von Änderungen an einem Check-Plugin.
Deaktivieren Sie zunächst Ihr reguläres Agentenplugin, beispielsweise indem Sie ihm die Ausführungsberechtigung entziehen.
Erstellen Sie dann eine Datei im Verzeichnis /var/lib/check_mk_agent/spool/
, welche die von Ihrem Check-Plugin erwartete Agentensektion (oder erwarteten Agentensektionen) inklusive Sektions-Header enthält und auf Newline endet.
Beim nächsten Aufruf des Agenten wird dann der Inhalt der Spool-Datei statt der Ausgabe des Agentenplugins übertragen.
7.7. Alte Check-Plugins werden bei vielen Services langsam
Bei einigen Check-Plugins, die Items nutzen, ist es auf größeren Servern durchaus möglich, dass einige hundert Services erzeugt werden. Wenn keine eigene Parse-Funktion verwendet wird, hat dies zur Folge, dass für jedes der hunderten Items die gesamte Liste von hunderten Zeilen durchlaufen werden muss. Die zum Suchen benötigte Zeit steigt also im Quadrat mit der Zahl der Listenelemente, bei hunderten Services bedeutet das zigtausende Vergleiche. Ist dagegen die geschachtelte Liste in ein Dictionary überführt, steigt der Aufwand für die Suche eines Elements nur linear mit der Größe des Dictionaries.
Im Python-Wiki finden Sie eine Übersicht der Kosten für die Suche in verschiedenen Datentypen, inklusive Erläuterung und O-Notation. Mit der Parse-Funktion reduzieren die Komplexität der Suche von O(n) auf O(1).
Da ältere Versionen dieses Artikels keinen Gebrauch von der Parse-Funktion gemacht haben, sollten Sie derartige Check-Plugins identifizieren und diese auf Verwendung einer Parse-Funktion umschreiben.
8. Dateien und Verzeichnisse
Pfad | Bedeutung |
---|---|
|
Ablageort für selbst geschriebene Check-Plugins. |
|
Ablageort für Ihre Regelsätze für Check-Parameter. |
|
Ablageort für eigene Metrikdefinitionen. |
|
Ablageort für eigene Definitionen von Perf-O-Metern. |
|
Hier finden Sie die Regelsatzdefinitionen von allen mitgelieferten Check-Plugins von Checkmk. |
|
In dieser Datei sind die Gruppen der Setup-Oberfläche definiert, in welchen Sie neue Regelsätze ablegen können. |
|
Hier finden Sie die Metrikdefinitionen der mitgelieferten Plugins. |
|
In dieser Datei stehen die vordefinierten Einheiten für Metriken. |
|
Dieses Verzeichnis bezieht sich auf einen überwachten Linux-Host. Hier erwartet der Checkmk-Agent für Linux Erweiterungen des Agenten (Agentenplugins). |