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.
Bei der Entwicklung eines agentenbasierten Check-Plugins benötigen Sie in aller Regel zwei Plugins: das Agentenplugin auf dem überwachten Host, welches die Daten liefert, und das Check-Plugin auf dem Checkmk-Server, das diese Daten auswertet. Beide müssen geschrieben und aufeinander abgestimmt werden. Nur so funktionieren sie hinterher reibungsfrei. |
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. In der Checkmk-Version 2.3.0 ist die Check-API V2 die aktuelle Version. Wir zeigen Ihnen in diesem Artikel, wie Sie die Check-API Version 2 für die Plugin-Programmierung nutzen können. Hinweise zum Umstieg auf die Check-API Version 2 finden Sie am Ende dieses Artikels im Kapitel Migration.
Über die Checkmk-Benutzeroberfläche haben Sie jederzeit Zugriff auf die Dokumentation der Check-API: Help > Developer resources > Plugin API references. Wählen Sie im neuen Browserfenster in der linken Navigationsleiste Agent based ("Check API") > Version 2 aus:
Hier finden Sie nicht nur die Dokumentation der Check-API in allen unterstützten Versionen, sondern auch die Dokumentation aller anderen, für die Erstellung von Check-Plugins relevanten APIs. In diesem Artikel nutzen wir nicht nur die Check-API, sondern bei der Erstellung eines Regelsatzes auch die Rulesets-API V1 und bei der Erstellung von Metrikdefinitionen die Graphing-API V1.
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.
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.
Hier kreieren wir ein Szenario, in dem es nützlich ist, wenn 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 Agentenplugin, 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.
Es kann vorkommen, dass der Zugriff auf |
Eines ist noch 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 Pfadnamen /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 das Basisverzeichnis vorbereitet in der local
-Hierarchie des Instanzverzeichnisses.
Dieses lautet ~/local/lib/python3/cmk_addons/plugins/
.
Das Verzeichnis gehört dem Instanzbenutzer und ist daher für Sie schreibbar.
In diesem Verzeichnis werden die Plugins in Plugin-Familien organisiert, deren Verzeichnisnamen Sie frei wählen können.
Zum Beispiel werden alle Plugins, die Cisco-Geräte betreffen, im Ordner cisco
abgelegt, oder im Ordner myhostgroups
alle Plugins, die Ihre Host-Gruppen betreffen.
Diese Konvention ermöglicht es, dass alle Plugins, die zur gleichen Familie gehören, Code gemeinsam nutzen können — und wird genau so auch von den Plugins genutzt, die mit Checkmk ausgeliefert werden.
In diesem Unterverzeichnis <plug-in_family>
werden dann nach Bedarf für die verschiedenen APIs weitere Unterverzeichnisse mit vorgegebenem Namen angelegt, z.B. agent_based
für die Check-API agentenbasierter Check-Plugins.
Weitere Unterverzeichnisse für die Rulesets-API und die Graphing-API werden wir später vorstellen.
Erstellen Sie die beiden Unterverzeichnisse für das neue agentenbasierte Check-Plugin und wechseln Sie am besten danach zum Arbeiten dort hinein:
OMD[mysite]:~$ mkdir -p local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based
OMD[mysite]:~$ cd local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based
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 cmk.agent_based.v2 import AgentSection, CheckPlugin, Service, Result, State, Metric, check_levels
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")
agent_section_myhostgroups = AgentSection(
name = "myhostgroups",
parse_function = parse_myhostgroups,
)
check_plugin_myhostgroups = CheckPlugin(
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.
Für unser Beispiel wird nur importiert, was im weiteren Verlauf des Artikels genutzt wird oder nützlich sein kann.
Dabei wird mit cmk.agent_based.v2
festgelegt, dass die Module aus der Check-API V2 importiert werden:
from cmk.agent_based.v2 import AgentSection, CheckPlugin, Service, Result, State, Metric, check_levels
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 eine 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
übergeben.
Beachten Sie, dass Sie hier nicht frei in der Wahl des Arguments sind.
Es muss exakt 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.
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 erstellen
Damit das Ganze auch etwas bewirken kann, müssen Sie die neue Agentensektion mit der neuen Parse-Funktion erzeugen.
Nur so wird sie von Checkmk erkannt und berücksichtigt.
Dazu erstellen Sie die Agentensektion als Instanz der Klasse AgentSection
:
agent_section_myhostgroups = AgentSection(
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 Klasse — und auch der Klassen, Funktionen und Objekte, die später im Artikel noch verwendet werden.
3.4. Das Check-Plugin erstellen
Damit Checkmk erkennen kann, dass es ein neues Check-Plugin gibt, muss dieses erzeugt werden.
Dies geschieht durch die Erstellung einer Instanz der Klasse CheckPlugin
.
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).
Der Name der Instanz muss dabei mit check_plugin_
beginnen.
Das sieht dann so aus:
check_plugin_myhostgroups = CheckPlugin(
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 Erstellung von Agentensektion und Check-Plugin 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
No piggyback files for 'localhost'. Skip processing.
Get piggybacked data
+ 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
No piggyback files for 'localhost'. Skip processing.
Get piggybacked data
Host group check_mk Default group is not empty; Current member list: localhost
No piggyback files for 'localhost'. Skip processing.
[agent] Success, [piggyback] Success (but no data found for this host), execution time 1.8 sec | execution_time=1.800 user_time=0.030 system_time=0.000 children_user_time=0.000 children_system_time=0.000 cmk_time_agent=1.780
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;localhost;1;1;62;45
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 vorherigen Kapitel 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 bestehenden Datei myhostgroups.py
das neue Check-Plugin myhostgroups_advanced
— im ersten Schritt so wie zuvor durch die Erstellung einer Instanz der Klasse CheckPlugin
.
Hier finden Sie den alten und markiert den neuen Code:
check_plugin_myhostgroups = CheckPlugin(
name = "myhostgroups",
service_name = "Host group check_mk",
discovery_function = discover_myhostgroups,
check_function = check_myhostgroups,
)
check_plugin_myhostgroups_advanced = CheckPlugin(
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 Erstellung des Check-Plugins, der Discovery-Funktion und der Check-Funktion.
Beachten Sie, dass die Funktionen immer vor der Erstellung von Check-Plugins oder Agentensektionen 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.
Führen Sie dabei die ersten beiden Kommandos nicht für myhostgroups
sondern für das neue Check-Plugin myhostgroups_advanced
aus.
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 im 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 = ("fixed", (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, was in der API-Dokumentation nachgelesen werden kann.
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 = ("fixed", (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 folgen die Zeilen mit dem Funktionsaufruf von check_levels()
aus dem vorherigen Abschnitt:
yield from check_levels(
hosts_up_perc,
levels_lower = ("fixed", (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 = ("fixed", (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 Variablen, 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 = ("fixed", (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 = ("fixed", (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.
Die Angabe von |
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.
5. Regelsätze für Check-Parameter
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
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.
Mit der Erstellung eines Regelsatzes für ein Check-Plugin verlassen Sie die Check-Plugin-Entwicklung und wechseln Dateiverzeichnis, Datei und API. Seit Checkmk 2.3.0 unterstützt Sie die Rulesets-API bei der Erstellung solcher Regelsätze für Check-Plugins. Die API-Dokumentation für Regelsätze finden Sie in Ihrer Checkmk-Instanz auf der gleichen Seite wie die der Check-API unter Rulesets > Version 1.
5.1. Neuen Regelsatz definieren
Um einen neuen Regelsatz zu erstellen, legen Sie im Verzeichnis Ihrer Plugin-Familie ~/local/lib/python3/cmk_addons/plugins/myhostgroups/
zuerst ein neues Unterverzeichnis an.
Der Name rulesets
dieses Unterverzeichnisses ist dabei vorgegeben:
OMD[mysite]:~$ mkdir -p local/lib/python3/cmk_addons/plugins/myhostgroups/rulesets
OMD[mysite]:~$ cd local/lib/python3/cmk_addons/plugins/myhostgroups/rulesets
In diesem Verzeichnis erstellen Sie dann eine Datei für die Definition des Regelsatzes.
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 ruleset_myhostgroups.py
.
Sehen Sie sich den Aufbau dieser Datei Schritt für Schritt an. Zunächst kommen einige Importbefehle:
from cmk.rulesets.v1 import Label, Title
from cmk.rulesets.v1.form_specs import BooleanChoice, DefaultValue, DictElement, Dictionary, Float, LevelDirection, SimpleLevels
from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic
Zuerst werden die Klassen für die Texte importiert.
Die zweite Zeile trifft eine Auswahl aus den Form Specs, das heißt, den Basiselementen für die GUI, die im Regelsatz genutzt werden,
z. B. für die binäre Auswahl (BooleanChoice
), die Mehrfachauswahl (Dictionary
, DictElement
), die Festlegung von Schwellwerten (SimpleLevel
, LevelDirection
, DefaultValue
) und die Eingabe von Gleitkommazahlen (Float
).
Dabei fordern Sie hier lediglich die logischen Formularelemente an und überlassen Checkmk die Gestaltung der GUI.
Die letzte Zeile importiert die Rule Specs, die das Anwendungsgebiet der Regel in Checkmk bestimmen, also hier die Festlegung von Check-Parametern, die Zuordnung zu Hosts und Items und die Ablage unter einem Thema (topic).
Da Ihr Check-Plugin myhostgroups_advanced
mehrere Services erzeugt, importieren Sie hier HostAndItemCondition
.
Falls Ihr Check keinen Service erzeugt, mit anderen Worten kein Item hat, importieren Sie stattdessen HostCondition
.
Nun kommen die eigentlichen Definitionen des Formulars für die Eingabe der Check-Parameter. 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_form():
return Dictionary(
elements = {
"hosts_up_lower": DictElement(
parameter_form = SimpleLevels(
title = Title("Lower percentage threshold for host in UP status"),
form_spec_template = Float(),
level_direction = LevelDirection.LOWER,
prefill_fixed_levels = DefaultValue(value=(90.0, 80.0)),
),
required = True,
),
"services_ok_lower": DictElement(
parameter_form = SimpleLevels(
title = Title("Lower percentage threshold for services in OK status"),
form_spec_template = Float(),
level_direction = LevelDirection.LOWER,
prefill_fixed_levels = DefaultValue(value=(90.0, 80.0)),
),
required = True,
),
}
)
Hierfür legen Sie eine Funktion an, welche das Dictionary erzeugt. Den Namen der Funktion können Sie frei wählen, er wird nur bei der Erstellung der Regel weiter unten benötigt. Damit die Funktion nicht über die Modulgrenze hinaus sichtbar wird, sollte der Name mit einem Unterstrich beginnen.
Das return Dictionary()
ist vorgeschrieben.
Innerhalb dessen erstellen Sie mit elements={}
die Elemente des Dictionaries, im Beispiel hosts_up_lower
und services_ok_lower
.
Das Parameterformular SimpleLevels
dient der Eingabe von festen Schwellwerten.
Im Formular bestimmen Sie zuerst den Titel (title
) und Gleitkommazahlen für die einzugebenden Werte (form_spec_template
).
Dass sich der Zustand bei Unterschreitung der Werte ändert, legen Sie mit LevelDirection.LOWER
fest.
Schließlich können Sie mit prefill_fixed_levels
den Benutzern des Regelsatzes statt leerer Eingabefelder bereits Werte vorgeben.
Beachten Sie, dass diese in der GUI angezeigten Werte nicht die Standardwerte sind, die weiter unten bei der Erstellung 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 erstellen Sie jetzt mithilfe der importierten und selbst definierten Dinge den neuen Regelsatz.
Dies geschieht durch die Erstellung einer Instanz der Klasse CheckParameters
.
Der Name dieser Instanz muss dabei mit rule_spec_
beginnen:
rule_spec_myhostgroups = CheckParameters(
name = "myhostgroups_advanced",
title = Title("Host group status"),
topic = Topic.GENERAL,
parameter_form = _parameter_form,
condition = HostAndItemCondition(item_title=Title("Host group name")),
)
Dazu die folgenden Erklärungen:
Der Name des Regelsatzes
name
stellt die Verbindung zu den Check-Plugins her. Ein Check-Plugin, das diesen Regelsatz verwenden will, muss beim Erstellen diesen Namen alscheck_ruleset_name
verwenden. Um bei mehreren neuen Regelsätzen den Überblick zu behalten, empfiehlt es sich im Namen einen Präfix zu verwenden.title
legt den Titel des Regelsatzes fest, so wie er auch in der Checkmk-GUI erscheint.Das
topic
legt fest, wo im Setup der Regelsatz auftauchen soll. Mit dem im Beispiel gewählten Wert finden Sie den Regelsatz unter Setup > Services > Service monitoring rules im Kasten Various, wo er in aller Regel gut aufgehoben ist.Als
parameter_form
geben Sie den Namen der zuvor erstellten Funktion ein.Falls Ihr Check kein Item verwendet, lautet die Bedingung
HostCondition
und nichtHostAndItemCondition
, wie im obigen Beispiel. Mit dem Titel"Host group name"
des Items legen Sie das Label in der GUI fest, mit dem Sie die Regel auf bestimmte Host-Gruppen einschränken können.
5.2. 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 Kasten Conditions finden Sie das per HostAndItemCondition
definierte Eingabefeld Host group name wieder zur Einschränkung der Regel für Host-Gruppen.
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.
5.3. Regelsatz mit Check-Plugin verbinden
Der frisch erstellte Regelsatz wird nun mit dem im vorherigen Kapitel vorläufig fertiggestellten Check-Plugin verbunden.
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 in der Datei des Check-Plugins zwei neue Zeilen bei der Erstellung des Check-Plugins myhostgroups_advanced
ein:
check_plugin_myhostgroups_advanced = CheckPlugin(
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 Umstellung des Check-Plugins für Check-Parameter muss diese Zeile unbedingt vorhanden sein.
Im einfachsten Fall übergeben Sie ein leeres Dictionary {}
.
Als zweites tragen Sie noch check_ruleset_name
ein, also den Namen des Regelsatzes.
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 ausgedruckten Zeilen (für jeden Service eine) mit den per Regel festgelegten Werten dann etwa so aus:
OMD[mysite]:~$ cmk --detect-plugins=myhostgroups_advanced -v localhost
Parameters({'hosts_up_lower': ('fixed', (75.0, 60.0)), 'services_ok_lower': ('fixed', (75.0, 60.0))})
Parameters({'hosts_up_lower': ('fixed', (75.0, 60.0)), 'services_ok_lower': ('fixed', (75.0, 60.0))})
Parameters({'hosts_up_lower': ('fixed', (75.0, 60.0)), 'services_ok_lower': ('fixed', (75.0, 60.0))})
Wenn alles fertig ist und funktioniert, entfernen Sie die |
Nun passen Sie Ihre Check-Funktion weiter an, so dass die übergebenen Parameter ihre Wirkung entfalten können. Holen Sie sich die beiden in der Regel festgelegten Dictionary-Elemente mit dem dort 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 "fixed", (90.0, 80.0)
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.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 = (services_ok_lower),
metric_name = "services_ok_perc",
label = "OK services",
boundaries = (0.0, 100.0),
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 Erstellung des Check-Plugins die Standardwerte gesetzt sind:
check_plugin_myhostgroups_advanced = CheckPlugin(
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": ("fixed", (90, 80)), "services_ok_lower": ("fixed", (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 Schalter Show check parameters.
Auf GitHub finden Sie sowohl die Datei mit dem Regelsatz als auch das um den Regelsatz erweiterte Check-Plugin. |
6. 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 = ("fixed", (90.0, 80.0)),
metric_name = "services_ok_perc",
label = "OK services",
boundaries = (0.0, 100.0),
notice_only = True,
)
Dann haben Sie 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.
So, wie für Regelsätze gibt es seit Checkmk 2.3.0 mit der Graphing-API auch eine eigene API für Metriken, Graphen und Perf-O-Meter. Die Dokumentation der Graphing-API finden Sie in Ihrer Checkmk-Instanz auf der gleichen Seite wie die der Check-API unter Graphing > Version 1.
6.1. Neue Metrikdefinition erstellen
Das Vorgehen bei der Erstellung von Regelsätzen und Metrikdefinitionen ist sehr ähnlich.
Legen Sie im Verzeichnis Ihrer Plugin-Familie ~/local/lib/python3/cmk_addons/plugins/myhostgroups/
zuerst ein neues Unterverzeichnis mit dem vorgegeben Namen graphing
an.
OMD[mysite]:~$ mkdir -p local/lib/python3/cmk_addons/plugins/myhostgroups/graphing
OMD[mysite]:~$ cd local/lib/python3/cmk_addons/plugins/myhostgroups/graphing
In diesem Verzeichnis erstellen Sie dann eine Graphing-Datei, z. B. graphing_myhostgroups.py
.
Zunächst kommen wieder einige Importbefehle:
from cmk.graphing.v1 import Title
from cmk.graphing.v1.graphs import Graph, MinimalRange
from cmk.graphing.v1.metrics import Color, DecimalNotation, Metric, Unit
from cmk.graphing.v1.perfometers import Closed, FocusRange, Open, Perfometer
Für unser Beispiel definieren Sie nun eine eigene Metrik für den Prozentsatz der Services im Zustand OK.
Dies geschieht durch die Erstellung einer Instanz der Klasse Metric
.
Der Name dieser Instanz muss dabei mit metric_
beginnen
metric_myhostgroups_services_ok_perc = Metric(
name = "services_ok_perc",
title = Title("Percentage of services in OK state"),
unit = Unit(DecimalNotation("%")),
color = Color.ORANGE,
)
Hier die Erklärung dazu:
Der Metrikname (hier
services_ok_perc
) muss dem entsprechen, was die Check-Funktion ausgibt.Der
title
ist die Überschrift im Metrikgraphen und ersetzt den bisher verwendeten internen Variablennamen.Die verfügbaren Einheiten (
unit
) können Sie in der API-Dokumentation nachlesen, sie enden alle aufNotation
, z. B.DecimalNotation
,EngineeringScientificNotation
oderTimeNotation
.Die in Checkmk verwendeten Farbnamen für die Farbdefinition
color
finden Sie auf GitHub.
Diese Definition in der Graphing-Datei sorgt jetzt dafür, dass Titel, Einheit und Farbe der Metrik angepasst dargestellt werden.
Analog zur Erstellung einer Regelsatzdatei muss die Graphing-Datei 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:
6.2. 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 Graphing-Datei hinzufügen können.
Für unser Beispiel sollen die beiden Metriken num_services
und num_services_ok
in einem Graphen dargestellt werden.
Die Metrikdefinitionen dazu werden analog wie im vorherigen Abschnitt für services_ok_perc
erstellt und sehen wie folgt aus:
metric_myhostgroups_services = Metric(
name = "num_services",
title = Title("Number of services in group"),
unit = Unit(DecimalNotation("")),
color = Color.PINK,
)
metric_myhostgroups_services_ok = Metric(
name = "num_services_ok",
title = Title("Number of services in OK state"),
unit = Unit(DecimalNotation("")),
color = Color.BLUE,
)
Fügen Sie nun einen Graphen an, der diese beiden Metriken als Linien einzeichnet.
Dies geschieht über eine Instanz der Klasse Graph
, wobei der Instanzname auch hier wieder mit einem vorgegebenen Präfix (graph_
) beginnen muss:
graph_myhostgroups_combined = Graph(
name = "services_ok_comparison",
title = Title("Services in OK state out of total"),
simple_lines=[ "num_services", "num_services_ok" ],
minimal_range=MinimalRange(0, 50),
)
Der Parameter minimal_range
beschreibt, was die vertikale Achse des Graphen mindestens abdeckt, unabhängig von den Werten der Metrik.
Die vertikale Achse wird erweitert, wenn die Werte den minimalen Bereich überschreiten, aber sie wird nie kleiner sein.
Das Resultat ist der kombinierte Graph in der Checkmk-GUI:
6.3. 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 Instanz, diesmal der Klasse Perfometer
, deren Name mit dem Präfix perfometer_
beginnt:
perfometer_myhostgroups_advanced = Perfometer(
name = "myhostgroups_advanced",
focus_range = FocusRange(Closed(0), Closed(100)),
segments = [ "services_ok_perc" ],
)
Perf-O-Meter sind etwas trickreicher als Graphen, da es keine Legende gibt. Daher ist die Darstellung des Wertebereichs schwierig, insbesondere, wenn es um die Darstellung absoluter Werte geht. Einfacher ist es, einen Prozentsatz darzustellen. Dies ist auch im obigen Beispiel der Fall:
In
segments
werden die Metriknamen eingetragen, hier der Prozentsatz der Services im Zustand OK.In
focus_range
wird die untere und obere Grenze eingetragen. Geschlossene (Closed
) Grenzen sind vorgesehen für Metriken, die nur Werte zwischen den angegebenen Grenzen annehmen können (hier also0
und100
).
Weitere, noch ausgefeiltere Möglichkeiten zur Umsetzung von Metriken in Perf-O-Metern finden Sie in der Dokumentation der Graphing-API,
z. B. mit den Klassen Bidirectional
und Stacked
, die es erlauben mehrere Perf-O-Meter in einem darzustellen.
Die Graphing-Datei dieses Kapitels finden Sie wieder auf GitHub. Sie enthält unter anderem die Metrikdefinitionen der vier gemessenen und der zwei berechneten Werte.
7. 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 cmk.agent_based.v2 import render
Nach der ausführlichen Beschreibung aller Darstellungsfunktionen (Render-Funktionen) finden Sie eine Zusammenfassung in Form einer übersichtlichen Tabelle.
7.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 |
---|---|
|
|
7.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 Kilobytes 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 |
---|---|
|
|
|
|
|
|
7.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 |
---|---|
|
|
|
|
|
|
7.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 |
---|---|
|
|
|
|
|
|
7.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 |
|
8. 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.
8.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 Symbol 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.
8.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: cmk_addons.plugins.myhostgroups.agent_based.myhostgroups: name 'agent_section_myhostgroups' is not defined
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/lib/python3/cmk/discover_plugins/_python_plugins.py", line 195, in add_from_module
module = importer(mod_name, raise_errors=True)
^^^^^^^^^^^^^
File "/omd/sites/mysite/lib/python3/cmk/discover_plugins/_python_plugins.py", line 156, in _import_optionally
return importlib.import_module(module_name)
^^^^^^^^^^^^
File "/omd/sites/mysite/lib/python3.12/importlib/init.py", line 90, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 995, in exec_module
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "/omd/sites/mysite/local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based/myhostgroups.py", line 110, in <module>
agent_section_myhostgroups == AgentSection(
^^^^^^^^^^
NameError: name 'agent_section_myhostgroups' is not defined
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
drwxrwxr-x 2 mysite mysite 4.0K Aug 6 17:56 7b59a49e-540c-11ef-9595-7574c603ce8d/
drwxrwxr-x 2 mysite mysite 4.0K Aug 6 17:56 7c8870c0-540c-11ef-9595-7574c603ce8d/
drwxrwxr-x 2 mysite mysite 4.0K Aug 6 17:56 7cf9626c-540c-11ef-9595-7574c603ce8d/
drwxrwxr-x 2 mysite mysite 4.0K Aug 6 17:56 7d192d68-540c-11ef-9595-7574c603ce8d/
drwxrwxr-x 2 mysite mysite 4.0K Aug 6 17:56 7e2d5ec2-540c-11ef-9595-7574c603ce8d/
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.
8.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.
8.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
# ...
8.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.
8.6. Test mit Spool-Dateien
Wenn Sie bestimmte Agentenausgaben simulieren wollen, sind Dateien im Spool-Verzeichnis 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.
8.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.
9. Migration
Die in der Checkmk-Version 2.0.0 eingeführte Check-API V1 wird in der Version 2.3.0 weiterhin unterstützt, aber nicht mehr in der nächsten Version 2.4.0. Wir empfehlen daher, die Zeit bis zur Freigabe der Version 2.4.0 zu nutzen und alle Check-Plugins auf die neuen APIs zu migrieren.
Die folgenden Informationen helfen Ihnen bei der Migration von Check-Plugins der Check-API V1 auf die V2:
Die neue Verzeichnisstruktur und Namenskonvention zur Ablage der Dateien für alle Plugin-APIs finden Sie in der API-Dokumentation auf der Hauptseite Checkmk’s Plug-in APIs.
Die Zusammenfassung der Änderungen in der Check-API V2 steht ebenfalls in der API-Dokumentation in Checkmk’s Plug-in APIs > Agent based ("Check API") > Version 2 > New in this version. Dort finden Sie auch den Link zu einem GitHub-Commit, der das bestehende Check-Plugin
apt
zur Check-API V2 migriert.Auf GitHub finden Sie im
treasures
-Verzeichnis von Checkmk Skripte, die Ihnen bei der Migration auf die neuen APIs helfen.
Ein abschließender Hinweis für Benutzer, die noch Check-Plugins haben, die mit der alten, bis zur Checkmk-Version 1.6.0 gültigen Check-API entwickelt wurden. Diese Check-Plugins werden in der Checkmk-Version 2.3.0 nicht mehr offiziell unterstützt. Details finden Sie im Werk #16689. Wie Sie solche, veralteten Check-Plugins auf die Check-API V1 migrieren, erfahren Sie im Blogpost zur Migration.
10. Dateien und Verzeichnisse
Pfad | Bedeutung |
---|---|
|
Basisverzeichnis zur Ablage von Plugin-Dateien. |
|
Ablageort für nach der Check-API V2 selbst geschriebene Check-Plugins. |
|
Ablageort für nach der Rulesets-API erstellte Regelsatzdateien. |
|
Ablageort für nach der Graphing-API erstellte Graphing-Dateien. |
|
Dieses Verzeichnis liegt auf einem überwachten Linux-Host. Hier erwartet der Checkmk-Agent für Linux Erweiterungen des Agenten (Agentenplugins). |