Checkmk
to checkmk.com

1. Einleitung

Check-Plugins sind in Python geschriebene Software-Module, die auf der Checkmk-Instanz ausgeführt werden und die Services eines Hosts erstellen und auswerten.

Checkmk umfasst über 2000 fertige Check-Plugins für alle nur denkbare Hardware und Software. Diese werden vom Checkmk-Team gepflegt und jede Woche kommen neue dazu. Daneben gibt es auf der Checkmk Exchange weitere Plugins, die von unseren Anwendern beigesteuert werden.

Und trotzdem kann es immer wieder vorkommen, dass ein Gerät, eine Anwendung oder einfach nur eine bestimmte Metrik, die für Sie wichtig ist, noch von keinem dieser Plugins erfasst wird — vielleicht auch einfach deshalb, weil es sich dabei um etwas handelt, das in Ihrer Firma entwickelt wurde und es daher niemand anders haben kann. Im Artikel zur Einführung in die Programmierung von Erweiterungen für Checkmk erfahren Sie, welche Möglichkeiten Sie haben.

Dieser Artikel zeigt Ihnen, wie Sie echte Check-Plugins für den Checkmk-Agenten entwickeln können — mit allem was dazugehört. Für SNMP-basierte Check-Plugins gibt es einen eigenen Artikel.

1.1. Die Check-API-Dokumentation

Für die Programmierung der Check-Plugins gibt es seit der Checkmk-Version 2.0.0 eine neu entwickelte Check-API. Wir zeigen Ihnen, wie Sie diese Check-API für die Plugin-Programmierung nutzen können.

Über die Checkmk-Benutzeroberfläche haben Sie jederzeit Zugriff auf die Dokumentation der Check-API: Help > Developer resources > Check plugin API reference. Wählen Sie im neuen Browserfenster in der linken Navigationsleiste BASE > Agent based API ("Check API") aus:

Seite zum Einstieg in die Check-API-Dokumentation.

Hinweis für Benutzer der bis zur Version 1.6.0 gültigen Check-API: Falls Sie noch Check-Plugins haben, die mit der alten API entwickelt wurden, sollten Sie diese bald auf die neue Check-API migrieren. Zwar wird die alte Check-API für eine Übergangszeit weiterhin unterstützt — aber auch diese Zeit wird einmal enden. Den einmaligen Aufwand zur Migration machen die Vorteile der neuen Check-API wett, denn diese ist konsistenter, logischer, besser dokumentiert und zukunftssicher. Im Blogpost zur Migration von Check-Plugins informieren wir Sie ausführlich über die notwendigen Schritte.

1.2. Voraussetzungen

Wenn Sie Lust haben, sich mit dem Programmieren von Check-Plugins zu befassen, benötigen Sie folgendes:

  • Kenntnisse in der Programmiersprache Python.

  • Erfahrung mit Checkmk, vor allem was das Thema Agenten und Checks betrifft.

  • Übung mit Linux auf der Kommandozeile.

1.3. Begriffe

Zwar ist hier immer die Rede von einem Check-Plugin und davon, dieses zu schreiben. Aber genau genommen benötigen Sie immer zwei Plugins, das Agentenplugin auf dem überwachten Host und das Check-Plugin auf dem Checkmk-Server. Beide müssen geschrieben und aufeinander abgestimmt werden. Nur so funktionieren sie hinterher reibungsfrei.

2. Ein Agentenplugin schreiben

Wer sich für die Programmierung von Check-Plugins in Checkmk interessiert, hat mit großer Wahrscheinlichkeit auch schon einmal einen Checkmk-Server aufgesetzt. Haben auch Sie dies bereits gemacht, haben Sie dabei als Einstieg vermutlich auch Ihren Checkmk-Server selbst als Host überwacht.

Im Folgenden gehen wir von einem beispielhaften Szenario aus, in dem Checkmk-Server und überwachter Host identisch sind. So ist es uns möglich über Livestatus-Abfragen vom Host Informationen über Host-Gruppen zu erhalten, die der Checkmk-Server zur Verfügung stellt.

Im beschriebenen Beispiel gehen wir von einem Unternehmen mit mehreren Standorten aus:

  • Jeder dieser Standorte wird in Checkmk durch eine Host-Gruppe abgebildet.

  • Jeder Standort hat sein eigenes Service-Team.

Damit bei Problemen jeweils das richtige Service-Team verständigt werden kann, gilt, dass jeder Host einem Standort — also auch einer Host-Gruppe — zugewiesen sein muss. Das Ziel in diesem Beispiel ist nun, eine Prüfung aufzusetzen, mit der kontrolliert werden kann, ob für keinen Host vergessen wurde, eine Host-Gruppe zuzuweisen.

Das Ganze läuft in zwei Schritten:

  1. Informationen für das Monitoring aus dem Host auslesen. Darum geht es in diesem Kapitel.

  2. Schreiben eines Check-Plugins in der Checkmk-Instanz, welches diese Daten auswertet. Das zeigen wir im nächsten Kapitel.

Und los geht’s …​

2.1. Informationen auslesen und filtern

Am Anfang jeder Plugin-Programmierung steht: die Recherche! Das bedeutet, dass Sie herausfinden müssen, wie Sie überhaupt an die Informationen kommen, die Sie für die Überwachung brauchen.

Für das gewählte Beispiel nutzen wir die Tatsache, dass der Checkmk-Server zugleich der Host ist. Damit genügt zunächst ein Abruf der Statusdaten via Livestatus, also der in Tabellen organisierten Daten, die Checkmk über die überwachten Hosts und Services im flüchtigen Speicher vorhält.

Melden Sie sich als Instanzbenutzer an und fragen Sie die Informationen zu den Host-Gruppen mit folgendem Befehl ab:

OMD[mysite]:~$ lq "GET hostgroups"
action_url;alias;members;members_with_state;name;notes;notes_url;num_hosts;num_hosts_down;num_hosts_handled_problems;num_hosts_pending;num_hosts_unhandled_problems;num_hosts_unreach;num_hosts_up;num_services;num_services_crit;num_services_handled_problems;num_services_hard_crit;num_services_hard_ok;num_services_hard_unknown;num_services_hard_warn;num_services_ok;num_services_pending;num_services_unhandled_problems;num_services_unknown;num_services_warn;worst_host_state;worst_service_hard_state;worst_service_state
;Hamburg;myhost11,myhost22,myhost33;myhost11|0|1,myhost22|0|1,myhost33|0|1;Hamburg;;;3;0;0;0;0;0;3;123;10;0;10;99;0;14;99;0;24;0;14;0;2;2
;Munich;myhost1,myhost2,myhost3;myhost1|0|1,myhost2|0|1,myhost3|0|1;Munich;;;3;0;0;0;0;0;3;123;10;0;10;99;0;14;99;0;24;0;14;0;2;2
;check_mk;localhost;localhost|0|1;check_mk;;;1;0;0;0;0;0;1;66;0;0;0;4;0;1;4;61;1;0;1;0;1;1

Die erste Zeile der Ausgabe enthält die Spaltennamen der abgefragten Tabelle hostgroups. Als Trennzeichen fungiert das Semikolon. In den folgenden Zeilen folgen dann die Inhalte sämtlicher Spalten, ebenfalls durch Semikolons getrennt.

Die Ausgabe ist bereits für dieses kleine Beispiel relativ unübersichtlich und enthält Informationen, die für unser Beispiel nicht relevant sind. Generell sollten Sie zwar die Interpretation der Daten Checkmk überlassen. Allerdings kann eine Vorfilterung auf dem Host den Umfang der zu übertragenden Daten reduzieren, wenn diese gar nicht gebraucht werden. Also schränken Sie in diesem Fall die Abfrage auf die relevanten Spalten (Columns) ein — auf die Namen der Host-Gruppen (name) und die in den Gruppen befindlichen Hosts (members):

OMD[mysite]:~$ lq "GET hostgroups\nColumns: name members"
Hamburg;myhost11,myhost22,myhost33
Munich;myhost1,myhost2,myhost3
check_mk;localhost

Die Livestatus-Schnittstelle erwartet alle Befehle und Header in jeweils einer eigenen Zeile. Die daher notwendigen Zeilenumbrüche machen Sie durch \n kenntlich.

In diesem Beispiel existieren aktuell drei Host-Gruppen: zwei Gruppen für die Standorte sowie die Gruppe check_mk. Diese enthält einen Host namens localhost.

Die Host-Gruppe check_mk stellt eine Besonderheit innerhalb der Host-Gruppen dar. Sie haben diese nämlich nicht selbst angelegt. Und Sie können auch keinen Host aktiv in diese Gruppe aufnehmen. Woher also stammt diese Host-Gruppe? Da in Checkmk per Definition jeder Host einer Gruppe angehören muss, weist Checkmk jeden Host, den Sie keiner Gruppe zuweisen, der „speziellen“ Gruppe check_mk zu.

Sobald Sie einen Host einer Ihrer eigenen Host-Gruppen zuweisen, wird er aus der Gruppe check_mk entfernt. Auch gibt es keinen Weg, einen Host erneut der Host-Gruppe check_mk zuzuweisen.

Genau diese Eigenschaften der Gruppe check_mk werden nun für unser Beispiel genutzt: Da jeder Host einem Standort zugeordnet sein soll, müsste die Host-Gruppe check_mk leer sein. Ist sie nicht leer, so besteht Handlungsbedarf, sprich darin befindliche Hosts müssen den Host-Gruppen und damit den Standorten zugeordnet werden.

2.2. Den Befehl in den Agenten einbauen

Bis jetzt haben Sie sich mit dem lq-Befehl als Instanzbenutzer die Informationen anzeigen lassen. Das ist hilfreich, um sich einen Einblick in die Daten zu verschaffen.

Damit Sie diese Daten vom Checkmk-Server aus abrufen können, muss der neue Befehl jedoch Teil vom Checkmk-Agenten auf dem überwachten Host werden. Theoretisch könnten Sie nun direkt den Checkmk-Agenten in der Datei /usr/bin/check_mk_agent editieren und diesen Teil einbauen. Das hätte aber den Nachteil, dass Ihr neuer Befehl bei einem Software-Update des Agenten wieder verschwindet, weil die Datei ersetzt wird.

Besser ist es daher, ein Agentenplugin zu erstellen. Alles was Sie dafür brauchen, ist eine ausführbare Datei, die den Befehl enthält und im Verzeichnis /usr/lib/check_mk_agent/plugins/ liegt.

Und noch eins ist wichtig: Die Daten können nicht einfach so ausgegeben werden. Sie brauchen noch einen Sektions-Header (section header). Das ist eine speziell formatierte Zeile, die den Namen des neuen Agentenplugins enthält. An diesem Sektions-Header kann Checkmk später erkennen, wo die Daten des Agentenplugins beginnen und die des vorherigen aufhören. Am einfachsten ist es, wenn Sektions-Header und Check-Plugin den gleichen Namen tragen — auch wenn dies nicht verpflichtend ist.

Also brauchen Sie jetzt erst einmal einen sinnvollen Namen für Ihr neues Check-Plugin. Dieser Name darf nur Kleinbuchstaben (nur a-z, keine Umlaute, keine Akzente), Unterstriche und Ziffern enthalten und muss eindeutig sein. Vermeiden Sie Namenskollisionen mit vorhandenen Check-Plugins. Wenn Sie neugierig sind, welche Namen es schon gibt, können Sie diese in einer Checkmk-Instanz auf der Kommandozeile mit cmk -L auflisten lassen:

OMD[mysite]:~$ cmk -L
3par_capacity               agent      HPE 3PAR: Capacity
3par_cpgs                   agent      HPE 3PAR: CPGs
3par_cpgs_usage             agent      HPE 3PAR: CPGs Usage
3par_hosts                  agent      HPE 3PAR: Hosts
3par_ports                  agent      HPE 3PAR: Ports
3par_remotecopy             agent      HPE 3PAR: Remote Copy
3par_system                 agent      HPE 3PAR: System
3par_volumes                agent      HPE 3PAR: Volumes
3ware_disks                 agent      3ware ATA RAID Controller: State of Disks
3ware_info                  agent      3ware ATA RAID Controller: General Information
3ware_units                 agent      3ware ATA RAID Controller: State of Units
acme_agent_sessions         snmp       ACME Devices: Agent Sessions
acme_certificates           snmp       ACME Devices: Certificates

Die Ausgabe zeigt nur die ersten Zeilen der sehr langen Liste. Durch die Verwendung von Präfixen ist die Zugehörigkeit vieler Check-Plugins hier bereits gut zu erkennen. Auch für Ihre eigenen Check-Plugins empfiehlt sich daher die Verwendung von Präfixen. Die zweite Spalte zeigt übrigens an, wie das jeweilige Check-Plugin seine Daten bezieht.

Ein für unser Beispiel passender Name für das neue Check-Plugin ist zum Beispiel myhostgroups.

Nun haben Sie alle Informationen zusammen, um das Skript mit dem Agentenplugin zu erstellen. Legen Sie als root-Benutzer eine neue Datei myhostgroups im Verzeichnis /usr/lib/check_mk_agent/plugins/ an:

/usr/lib/check_mk_agent/plugins/myhostgroups
#!/bin/bash

columns="name members"
site="mysite"

echo '<<<myhostgroups:sep(59)>>>'
su - ${site} lq "GET hostgroups\nColumns: ${columns}"

Was bedeutet das nun im Einzelnen?

Die erste Zeile enthält den „Shebang“ (das ist eine Abkürzung für sharp und bang, wobei Letzteres eine Abkürzung für das Ausrufezeichen ist), an dem Linux erkennt, dass es das Skript mit der angegebenen Shell ausführen soll.

Um das Skript erweiterbar zu halten, werden als nächstes zwei Variablen eingeführt:

  • die Variable columns, die aktuell die Gruppennamen und die zugehörigen Mitglieder enthält,

  • die Variable site, die den Namen der Checkmk-Instanz enthält.

Mit dem echo-Befehl geben Sie den Sektions-Header aus. Da die Spalten der Tabelle mit dem Semikolon getrennt werden, legen Sie gleichzeitig mit dem Zusatz sep(59) fest, dass das Semikolon als Trennzeichen (separator) für die Daten in der Agentenausgabe genutzt wird. 59 steht für das ASCII-Zeichen Nummer 59, das Semikolon. Ohne diesen Zusatz würde als Standard das Leerzeichen (ASCII-Zeichen 32) als Trennzeichen verwendet werden.

Um den lq-Befehl, der Ihnen als Instanzbenutzer zur Verfügung steht, auch in einem Skript nutzen zu können, das vom root-Benutzer ausgeführt wird, stellen Sie ein su voran.

Hinweis: Es kann vorkommen, dass der Zugriff auf lq per su Probleme bereitet. Sie können alternativ als root-Benutzer auch direkt auf Livestatus in der Shell mit printf oder echo -e über einen Unix-Socket zugreifen. Im Artikel zu Livestatus erfahren Sie, wie das geht.

Eines ist noch sehr wichtig, sobald Sie die Datei erstellt haben. Machen Sie die Datei ausführbar:

root@linux# chmod +x /usr/lib/check_mk_agent/plugins/myhostgroups

Sie können das Agentenplugin direkt von Hand ausprobieren, indem Sie den kompletten Pfad als Befehl eingeben:

root@linux# /usr/lib/check_mk_agent/plugins/myhostgroups
<<<myhostgroups:sep(59)>>>
Hamburg;myhost11,myhost22,myhost33
Munich;myhost1,myhost2,myhost3
check_mk;localhost

Host-Gruppen, die keine Hosts enthalten, werden hierbei nicht aufgeführt.

2.3. Agent ausprobieren

Test und Fehlersuche sind am wichtigsten, um zu einem funktionierenden Agentenplugin zu gelangen. Am besten gehen Sie in drei Schritten vor:

  1. Agentenplugin solo ausprobieren. Das haben Sie gerade im vorherigen Abschnitt gemacht.

  2. Agent als Ganzes lokal testen.

  3. Agent vom Checkmk-Server aus abrufen.

Das lokale Testen des Agenten ist sehr einfach. Rufen Sie als root den Befehl check_mk_agent auf:

root@linux# check_mk_agent

Irgendwo in der sehr langen Ausgabe muss die neue Sektion erscheinen. Agentenplugins werden vom Agenten zum Schluss ausgegeben.

Durch Anhängen von less können Sie in der Ausgabe blättern (drücken Sie die Leertaste zum Blättern, / zum Suchen und q zum Beenden):

root@linux# check_mk_agent | less

Oder Sie durchsuchen die Ausgabe nach den interessanten Zeilen. So hat grep mit -A eine Option, nach jedem Treffer noch einige Zeilen mehr auszugeben. Damit können Sie bequem die Sektion suchen und ausgeben:

root@linux# check_mk_agent | grep -A3 '^<<<myhostgroups'
<<<myhostgroups:sep(59)>>>
Hamburg;myhost11,myhost22,myhost33
Munich;myhost1,myhost2,myhost3
check_mk;localhost

Der dritte und letzte Test ist dann direkt von der Checkmk-Instanz aus. Nehmen Sie den Host ins Monitoring auf (zum Beispiel als localhost), melden Sie sich als Instanzbenutzer an und rufen Sie dann die Agentendaten mit cmk -d ab:

OMD[mysite]:~$ cmk -d localhost | grep -A3 '^<<<myhostgroups'

Hier sollte die gleiche Ausgabe kommen wie beim vorherigen Befehl.

Wenn das funktioniert, ist Ihr Agent vorbereitet. Und was haben Sie dafür gemacht? Sie haben ein kurzes Skript unter dem Pfad /usr/lib/check_mk_agent/plugins/myhostgroups erzeugt und ausführbar gemacht.

Alles was nun folgt, geschieht nur noch auf dem Checkmk-Server: Dort schreiben Sie das Check-Plugin.

3. Ein einfaches Check-Plugin schreiben

Das Vorbereiten des Agenten ist nur die halbe Miete. Jetzt müssen Sie Checkmk noch beibringen, wie es mit den Informationen aus der neuen Agentensektion umgehen soll, welche Services es erzeugen soll, wann diese auf WARN oder CRIT gehen sollen usw. All dies machen Sie durch die Programmierung eines Check-Plugins in Python.

3.1. Die Datei vorbereiten

Für Ihre eigenen Check-Plugins finden Sie ein Verzeichnis vorbereitet in der local-Hierarchie des Instanzverzeichnisses. Dieses lautet ~/local/lib/check_mk/base/plugins/agent_based/. Hier im Pfad meint base den Teil von Checkmk, der für das eigentlich Monitoring und die Benachrichtigungen zuständig ist. Der Ordner agent_based beinhaltet alle Plugins, die sich auf den Checkmk-Agenten beziehen (also zum Beispiel nicht Benachrichtigungs-Plugins). Am besten wechseln Sie zum Arbeiten dort hinein:

OMD[mysite]:~$ cd local/lib/check_mk/base/plugins/agent_based

Das Verzeichnis gehört dem Instanzbenutzer und ist daher für Sie schreibbar. Sie können Ihr Check-Plugin mit jedem auf dem Linux-System installierten Texteditor bearbeiten.

Legen Sie also die Datei myhostgroups.py für das Check-Plugin hier an. Konvention ist, dass der Dateiname den Namen der Agentensektion wiedergibt. Pflicht ist, dass die Datei mit .py endet, denn ab Version 2.0.0 von Checkmk handelt es sich bei den Check-Plugins immer um echte Python-Module.

Ein lauffähiges Grundgerüst (Download bei GitHub), das Sie im Folgenden Schritt für Schritt weiter ausbauen werden, sieht so aus:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
#!/usr/bin/env python3

from .agent_based_api.v1 import check_levels, Metric, register, Result, Service, State

def parse_myhostgroups(string_table):
    parsed = {}
    return parsed

def discover_myhostgroups(section):
    yield Service()

def check_myhostgroups(section):
    yield Result(state=State.OK, summary="Everything is fine")

register.agent_section(
    name = "myhostgroups",
    parse_function = parse_myhostgroups,
)

register.check_plugin(
    name = "myhostgroups",
    service_name = "Host group check_mk",
    discovery_function = discover_myhostgroups,
    check_function = check_myhostgroups,
)

Als erstes müssen Sie die für die Check-Plugins nötigen Funktionen und Klassen aus Python-Modulen importieren. Die einfachste Methode dafür ist import *, allerdings sollten Sie das vermeiden, da so verschleiert wird, welche Namensräume tatsächlich verfügbar gemacht wurden.

Für unser Beispiel wird also nur das importiert, was im weiteren Verlauf des Artikels genutzt wird:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
from .agent_based_api.v1 import check_levels, Metric, register, Result, Service, State

3.2. Die Parse-Funktion schreiben

Die Parse-Funktion hat die Aufgabe, die „rohen“ Agentendaten zu „parsen“, das heißt zu analysieren und zu zerteilen, und in eine logisch aufgeräumte Form zu bringen, die für alle weiteren Schritte einfach zu verarbeiten ist.

Wie im Abschnitt zum Testen des Agenten gezeigt, hat die vom Agentenplugin gelieferte Sektion die folgende Struktur:

<<<myhostgroups:sep(59)>>>
Hamburg;myhost11,myhost22,myhost33
Munich;myhost1,myhost2,myhost3
check_mk;localhost

Checkmk zerlegt bereits die Zeilen der vom Agentenplugin gelieferten Sektion anhand des Trennzeichens im Sektions-Header (im Beispiel ;) in eine Liste von Zeilen, die ihrerseits wiederum Listen von Worten sind. In Checkmk steht daher statt der Rohdaten des Agentenplugins die folgende Datenstruktur zur Verfügung:

[
    ['Hamburg', 'myhost11,myhost22,myhost33'],
    ['Munich', 'myhost1,myhost2,myhost3'],
    ['check_mk', 'localhost']
]

In der inneren Liste enthält das jeweils erste Element den Namen der Host-Gruppe und das zweite die Namen der der Gruppe angehörenden Hosts.

Diese Informationen können Sie zwar alle ansprechen, aber jeweils nur über ihre Position im Datensatz. Sie müssten also immer angeben, die wievielte eckige Klammer und den wievielten Inhalt innerhalb der jeweiligen Klammer Sie brauchen. Bei größeren Datenmengen gestaltet sich dies komplex und es wird immer schwieriger, den Überblick zu behalten.

An diesem Punkt bietet die Parse-Funktion durch die von ihr erzeugte Struktur deutliche Vorteile. Durch sie wird der Code leichter lesbar, die Zugriffe werden performanter und Sie behalten viel einfacher den Überblick. Sie transformiert die von Checkmk gelieferte Datenstruktur so, dass man jeden der einzelnen Werte wahlfrei durch einen Namen (oder Schlüssel) ansprechen kann und nicht darauf angewiesen ist, den Bereich (array) iterativ zu durchlaufen, um das zu finden, was man sucht:

{
    'Hamburg': {'members': 'myhost11,myhost22,myhost33'},
    'Munich': {'members': 'myhost1,myhost2,myhost3'},
    'check_mk': {'members': 'localhost'}
}

Konvention ist, dass die Parse-Funktion nach der Agentensektion benannt wird und mit parse_ beginnt. Sie bekommt als einziges Argument string_table. Beachten Sie, dass Sie hier nicht frei in der Wahl des Arguments sind. Es muss wirklich so heißen.

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
def parse_myhostgroups(string_table):
    # print(string_table)
    parsed = {}
    for line in string_table:
        parsed[line[0]] = {"members": line[1]}
    # print(parsed)
    return parsed

Mit def geben Sie in Python an, dass nachfolgend eine Funktion definiert wird. parsed = {} erzeugt das Dictionary mit der verbesserten Datenstruktur. In unserem Beispiel wird jede Zeile Element für Element durchgegangen. Aus jeder Zeile wird die Host-Gruppe gefolgt von den Mitgliedern der Host-Gruppe genommen und zu einem Eintrag für das Dictionary zusammengesetzt.

Mit return parsed wird dann das Dictionary zurückgegeben.

Hinweis: Im oben gezeigten Beispiel finden Sie zwei auskommentierte Zeilen. Wenn Sie diese später beim Testen des Check-Plugins einkommentieren, werden Ihnen die Daten vor und nach der Ausführung der Parse-Funktion auf der Kommandozeile angezeigt. So können Sie überprüfen, ob die Funktion auch wirklich das tut, was sie soll.

3.3. Die Agentensektion registrieren

Damit das Ganze auch etwas bewirken kann, müssen Sie die Parse-Funktion, und überhaupt die neue Agentensektion, bei Checkmk bekannt machen. Dazu rufen Sie eine Registrierfunktion auf:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
register.agent_section(
    name = "myhostgroups",
    parse_function = parse_myhostgroups,
)

Hier ist es wichtig, dass der Name der Sektion wirklich exakt mit dem Sektions-Header in der Agentenausgabe übereinstimmt. Von diesem Moment an bekommt jedes Check-Plugin, das die Sektion myhostgroups benutzt, den Rückgabewert der Parse-Funktion übergeben. In der Regel wird das das gleichnamige Check-Plugin sein. Aber auch andere Check-Plugins können dieses Sektion abonnieren, wie wir bei der Erweiterung des Check-Plugins noch zeigen werden.

Übrigens: Wenn Sie es ganz genau wissen wollen, können Sie an dieser Stelle einen Blick in die Check-API-Dokumentation werfen. Dort finden Sie die detaillierte Beschreibung dieser Registrierfunktion — und auch der Funktionen und Objekte, die später im Artikel noch verwendet werden.

Check-API-Dokumentation für die Registrierfunktion 'agent_section'.

3.4. Das Check-Plugin registrieren

Damit Checkmk weiß, dass es ein neues Check-Plugin gibt, muss dieses registriert werden. Dies geschieht durch den Aufruf der Funktion register.check_plugin. Dabei müssen Sie immer mindestens vier Dinge angeben:

  1. 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.

  2. service_name: Der Name des Services wie er dann im Monitoring erscheinen soll.

  3. discovery_function: Die Funktion zum Erkennen von Services dieses Typs (dazu gleich mehr).

  4. check_funktion: Die Funktion zum Durchführen des eigentlichen Checks (auch dazu gleich mehr).

Für das Check-Plugin sieht das dann also so aus:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
register.check_plugin(
    name = "myhostgroups",
    service_name = "Host group check_mk",
    discovery_function = discover_myhostgroups,
    check_function = check_myhostgroups,
)

Versuchen Sie am besten noch nicht, das gleich auszuprobieren, denn Sie müssen die Funktionen discover_myhostgroups und check_myhostgroups vorher noch schreiben. Und diese müssen im Quellcode vor obiger Registrierung erscheinen.

3.5. Die Discovery-Funktion schreiben

Eine Besonderheit von Checkmk ist die automatische Erkennung von zu überwachenden Services. Damit dies klappt, muss jedes Check-Plugin eine Funktion definieren, welche anhand der Agentenausgabe erkennt, ob ein Service dieses Typs bzw. welche Services des Typs für den betreffenden Host angelegt werden sollen.

Die Discovery-Funktion wird immer dann aufgerufen, wenn für einen Host die Service-Erkennung durchgeführt wird. Sie entscheidet dann ob, bzw. welche Services angelegt werden sollen. Im Standardfall bekommt sie genau ein Argument mit dem Namen section. Dieses enthält die Daten der Agentensektion in einem durch die Parse-Funktion aufbereiteten Format.

Daher implementieren Sie folgende simple Logik: Wenn die Agentensektion myhostgroups vorhanden ist, dann legen Sie auch einen passenden Service an. Dann erscheint dieser automatisch auf allen Hosts, auf denen das Agentenplugin verteilt ist.

Bei Check-Plugins, die pro Host nur einen Service erzeugen, benötigt man keine weiteren Angaben:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
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:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
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-Gruppe check_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-Gruppe check_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 Buchstabe f steht.

  • Ist die Variable hosts leer, sind also keine Hosts in der Host-Gruppe check_mk, so geht stattdessen der Status des Services auf OK. Dann wird zudem ein passender Hinweistext ausgegeben.

Mit der Erstellung der Check-Funktion ist das Check-Plugin fertig.

Das Check-Plugin und auch das Agentenplugin haben wir auf GitHub bereitgestellt.

3.7. Das Check-Plugin testen und aktivieren

Test und Aktivierung werden auf der Kommandozeile mit dem Befehl cmk erledigt.

Probieren Sie zunächst die Service-Erkennung mit der Option -I aus. Durch Zugabe der Option v (für verbose) werden ausführliche Ausgaben angefordert. Das --detect-plugins beschränkt die Befehlsausführung auf dieses Check-Plugin und durch localhost auf eben diesen Host:

OMD[mysite]:~$ cmk -vI --detect-plugins=myhostgroups localhost
Discovering services and host labels on: localhost
localhost:
+ FETCHING DATA
[TCPFetcher] Execute data source
[PiggybackFetcher] Execute data source
No piggyback files for 'localhost'. Skip processing.
No piggyback files for '127.0.0.1'. Skip processing.
+ ANALYSE DISCOVERED HOST LABELS
SUCCESS - Found no new host labels
+ ANALYSE DISCOVERED SERVICES
+ EXECUTING DISCOVERY PLUGINS (1)
  1 myhostgroups
SUCCESS - Found 1 services

Wie geplant, erkennt die Service-Erkennung einen neuen Service im Check-Plugin myhostgroups.

Jetzt können Sie den im Check-Plugin enthaltenen Check ausprobieren:

OMD[mysite]:~$ cmk --detect-plugins=myhostgroups -v localhost
+ FETCHING DATA
[TCPFetcher] Execute data source
[PiggybackFetcher] Execute data source
No piggyback files for 'localhost'. Skip processing.
No piggyback files for '127.0.0.1'. Skip processing.
Host group check_mk   Default group is not empty; Current member list: localhost
[agent] Success, [piggyback] Success (but no data found for this host), execution time 1.3 sec | execution_time=1.330 user_time=0.010 system_time=0.000 children_user_time=0.000 children_system_time=0.000 cmk_time_agent=1.330

Durch Ausführung des Checks wird der Zustand des zuvor gefundenen Services bestimmt.

Wenn soweit alles wie erwartet abgelaufen ist, können Sie die Änderungen aktivieren. Falls nicht, finden Sie im Kapitel zur Fehlerbehebung Hinweise.

Aktivieren Sie zum Abschluss die Änderungen durch einen Neustart des Monitoring-Kerns:

OMD[mysite]:~$ cmk -R
Generating configuration for core (type nagios)...
Precompiling host checks...OK
Validating Nagios configuration...OK
Restarting monitoring core...OK

Im Monitoring von Checkmk finden Sie nun beim Host localhost den neuen Service Host group check_mk:

Der vom Check-Plugin erzeugte neue Service im Monitoring.
Da die Host-Gruppe 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:

/usr/lib/check_mk_agent/plugins/myhostgroups
#!/bin/bash

columns="name members num_hosts num_hosts_up num_services num_services_ok"
site="mysite"

echo '<<<myhostgroups:sep(59)>>>'
su - ${site} lq "GET hostgroups\nColumns: ${columns}"

Führen Sie zur Kontrolle das Skript aus:

root@linux# /usr/lib/check_mk_agent/plugins/myhostgroups
<<<myhostgroups:sep(59)>>>
Munich;myhost3,myhost2,myhost1;3;3;180;144
Hamburg;myhost22,myhost33,myhost11;3;2;132;105
check_mk;SQL-Server,localhost;2;2;95;83

Am Ende jeder Zeile stehen nun, durch Semikolon abgetrennt, die vier neuen Werte.

Mit dieser Änderung liefert das Agentenplugin nun andere Daten als vorher. An dieser Stelle ist es wichtig, sich zu vergewissern, dass das Check-Plugin auch mit den geänderten Daten immer noch das tut, was es soll.

Parse-Funktion erweitern

Im Check-Plugin ist die Parse-Funktion für die Umwandlung der vom Agentenplugin gelieferten Daten verantwortlich. Beim Schreiben der Parse-Funktion haben Sie nur zwei Spalten der Host-Gruppentabelle berücksichtigt. Nun werden sechs statt zwei Spalten geliefert. Die Parse-Funktion muss also fit gemacht werden, um auch die zusätzlichen vier Spalten zu verarbeiten.

Ändern Sie als Instanzbenutzer die Parse-Funktion in der Datei myhostgroups.py, die das Check-Plugin enthält:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
def parse_myhostgroups(string_table):
    parsed = {}
    column_names = [
        "name",
        "members",
        "num_hosts",
        "num_hosts_up",
        "num_services",
        "num_services_ok",
    ]
    for line in string_table:
        parsed[line[0]] = {}
        for n in range(1, len(column_names)):
            parsed[line[0]][column_names[n]] = line[n]
    return parsed

Geändert wurde hier alles zwischen parsed = {} und return parsed. Zuerst werden die zu verarbeitenden Spalten unter ihren Namen als Liste column_names definiert. In der for-Schleife wird dann ein Dictionary aufgebaut, indem in jeder Zeile aus Spaltenname und ausgelesenem Wert die Schlüssel-Wert-Paare erzeugt werden.

Für die existierende Check-Funktion ist diese Erweiterung unkritisch, denn die Datenstruktur für die ersten beiden Spalten bleibt unverändert. Es werden nur zusätzliche Spalten bereitgestellt, die in der Check-Funktion (noch) gar nicht ausgewertet werden.

Nun, da die neuen Daten verarbeitet werden können, werden Sie sie auch nutzen.

4.2. Service-Erkennung

Im Beispiel haben Sie einen sehr einfachen Check gebaut, der auf einem Host einen Service erzeugt. Ein sehr üblicher Fall ist aber auch, dass es von einem Check mehrere Services auf einem Host geben kann.

Das häufigste Beispiel dafür sind die Dateisysteme eines Hosts. Das Check-Plugin mit dem Namen df legt pro Dateisystem auf dem Host einen Service an. Um diese Services zu unterscheiden, wird der Mount-Punkt des Dateisystems (zum Beispiel /var) bzw. der Laufwerksbuchstabe (zum Beispiel C:) in den Namen des Services eingebaut. Das ergibt dann als Service-Name zum Beispiel Filesystem /var oder Filesystem C:. Das Wort /var bzw. C: wird hier als Item bezeichnet. Wir sprechen also auch von einem Check mit Items.

Wenn Sie einen Check mit Items bauen möchten, müssen Sie folgende Dinge umsetzen:

  • Die Discovery-Funktion muss für jedes der Items, die auf dem Host sinnvollerweise überwacht werden sollen, einen Service generieren.

  • Im Service-Namen müssen Sie das Item mithilfe des Platzhalters %s einbauen (also zum Beispiel "Filesystem %s").

  • Die Check-Funktion wird pro Item einmal separat aufgerufen und bekommt dieses als Argument. Sie muss dann aus den Agentendaten die für dieses Item relevanten Daten herausfischen.

Um dies praktisch auszuprobieren, werden Sie für jede existierende Host-Gruppe einen eigenen Service erzeugen.

Da das im vorherigen Kapitel erstellte Check-Plugin myhostgroups zur Überprüfung der Standardgruppe check_mk weiterhin funktionieren soll, bleibt dieses Check-Plugin so, wie es ist. Für die Erweiterung erstellen Sie in der Datei myhostgroups.py ein neues Check-Plugin — im ersten Schritt so wie zuvor durch die Registrierung des Plugins.

Wichtig: Die neue Registrierung wird zusätzlich zur bereits vorhandenen vorgenommen, die im vorherigen Kapitel gezeigte bleibt unverändert enthalten. Hier nur der neue Code:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
...

register.check_plugin(
    name = "myhostgroups_advanced",
    sections = ["myhostgroups"],
    service_name = "Host group %s",
    discovery_function = discover_myhostgroups_advanced,
    check_function = check_myhostgroups_advanced,
)

Damit man das neue vom alten Check-Plugin unterscheiden kann, erhält es mit myhostgroups_advanced einen eindeutigen Namen. Der Parameter sections bestimmt die Sektionen der Agentenausgabe, die das Check-Plugin abonniert. Mit myhostgroups wird hier festgelegt, dass das neue Check-Plugin die gleichen Daten nutzt wie das alte: die durch die Parse-Funktion aufbereitete Sektion des Agentenplugins. Der Service-Name enthält jetzt den Platzhalter %s. An dieser Stelle wird später dann von Checkmk der Name des Items eingesetzt. In den letzten beiden Zeilen werden schließlich die Namen für die neue Discovery-Funktion und die neue Check-Funktion festgelegt, die beide noch geschrieben werden wollen.

Zuerst zur Discovery-Funktion, die jetzt die Aufgabe hat, die zu überwachenden Items zu ermitteln – auch diese wird zusätzlich zur vorhandenen eingetragen:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
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:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
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:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
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.

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
def check_myhostgroups_advanced(item, section):
    attr = section.get(item)
    if not attr:
        yield Result(state=State.CRIT, summary="Group is empty or has been deleted")
        return

    yield Result(state=State.OK, summary=f"{attr['num_hosts']} hosts in this group: {attr['members']}")

Was hat sich geändert? Der Fehlerfall wird jetzt zuerst abgehandelt. Daher überprüfen Sie im if-Zweig, ob das Item nicht existiert, setzen den Status auf CRIT und verlassen mit return die Funktion. In allen anderen Fällen geben Sie, wie zuvor, OK zurück.

Damit haben Sie in der Check-Funktion den Fall der verschwundenen Host-Gruppen übernommen. Statt UNKNOWN wird der zugehörige Service nun CRIT sein und die Information über die Ursache des kritischen Zustands beinhalten.

Damit ist das neue Check-Plugin als Erweiterung des alten fertiggestellt. Das erweiterte Agentenplugin und die erweiterte Datei für die Check-Plugins finden Sie wieder auf GitHub. Letztere enthält das einfache Check-Plugin myhostgroups aus dem vorherigen Kapitel, die erweiterte Parse-Funktion und die Komponenten des neuen Check-Plugins myhostgroups_advanced mit der Registrierung, der Discovery-Funktion und der Check-Funktion. Beachten Sie, dass die Funktionen immer vor dem Registrieren definiert werden müssen, damit es keine Fehler wegen nicht definierter Funktionsnamen gibt.

Da das neue Check-Plugin myhostgroups_advanced neue Services zur Verfügung stellt, müssen Sie eine Service-Erkennung für dieses Check-Plugin durchführen und die Änderungen aktivieren, um die Services im Monitoring zu sehen:

Die vom erweiterten Check-Plugin erzeugten zwei neuen Services im Monitoring.
Zwei neue Services im Monitoring

Gehen Sie dabei so vor, wie es im Kapitel für das einfache Check-Plugin beschrieben ist.

4.3. Summary und Details

Im Monitoring von Checkmk hat jeder Service neben dem Status — OK, WARN usw. — auch eine Zeile Text. Diese steht in der Spalte Summary — wie im vorherigen Screenshot zu sehen ist — und hat also die Aufgabe einer knappen Zusammenfassung des Zustands. Die Idee ist, dass dieser Text eine Länge von 60 Zeichen nicht überschreitet. Das sorgt dann immer für eine übersichtliche Tabellendarstellung ohne störende Zeilenumbrüche.

Daneben gibt es noch das Feld Details, in dem alle Details zum Zustand des Services angezeigt werden, wobei alle Informationen des Summary auch in den Details enthalten sind. Nach Anklicken des Services wird die Service-Seite geöffnet, in der neben vielen anderen auch die beiden Felder Summary und Details zu sehen sind.

Beim Aufruf von yield Result(...) können Sie bestimmen, welche Informationen so wichtig sind, dass sie im Summary angezeigt werden sollen, und bei welchen es genügt, dass diese in den Details erscheinen.

In unserem Beispiel haben Sie bisher immer einen Aufruf der folgenden Art verwendet:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    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:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    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:

'Summary' und 'Details' in den Service-Details.
Unterschiedliche Inhalte für Summary und Details im Monitoring

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 oder notice — 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 OKWARNUNKNOWNCRIT.

Nutzen Sie diese Möglichkeit, um in unserem Beispiel für jeden Service der Host-Gruppen zu dem bestehenden Resultat zwei weitere zu definieren. Diese werten den Prozentsatz der Hosts im Zustand UP und der Services im Zustand OK aus. Dabei nutzen Sie die zuvor in der Agentenausgabe und der Parse-Funktion festgelegten zusätzlichen Spalten der Host-Gruppentabelle.

Erweitern Sie die Check-Funktion nun sukzessive von oben nach unten:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
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:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    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.

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    hosts_up_perc = 100.0 * num_hosts_up / num_hosts
    yield from check_levels(
        hosts_up_perc,
        levels_lower = (90.0, 80.0),
        label = "UP hosts",
        notice_only = True,
    )

In der ersten Zeile wird aus Gesamtzahl und Zahl der Hosts im Zustand UP der Prozentsatz berechnet und in der Variablen hosts_up_perc gespeichert. Durch den einfachen Schrägstrich (/) wird eine Gleitkommadivison ausgeführt, die sicherstellt, das das Ergebnis ein Float-Wert ist. Das ist sinnvoll, weil einige im weiteren Verlauf verwendete Funktionen Float als Eingabe erwarten.

In der zweiten Zeile wird dann das Ergebnis der Funktion check_levels als Objekt vom Typ Result zurückgegeben. Die Funktion wird gefüttert mit dem gerade berechneten Prozentsatz als Wert (hosts_up_perc), den beiden unteren Schwellwerten (levels_lower), einer Bezeichnung, die der Ausgabe vorangestellt wird (label) und schließlich mit notice_only=True.

Der letzte Parameter nutzt den im vorherigen Abschnitt für das Objekt Result() bereits vorgestellten Parameter notice. Mit notice_only=True legen Sie fest, dass der Text für den Service nur dann im Summary angezeigt wird, wenn der Zustand nicht OK ist. Allerdings werden Teilergebnisse, die zu einem WARN oder CRIT führen, sowieso immer im Summary sichtbar werden — unabhängig davon, welchen Wert notice_only hat.

Schließlich definieren Sie analog zum zweiten das dritte Resultat, das den Prozentsatz der Services im Zustand OK auswertet:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    services_ok_perc = 100.0 * num_services_ok / num_services
    yield from check_levels(
        services_ok_perc,
        levels_lower = (90.0, 80.0),
        label = "OK services",
        notice_only = True,
    )

Damit ist die Check-Funktion komplett.

Der Service für eine Host-Gruppe wertet jetzt drei Resultate aus und zeigt aus diesen den schlechtesten Zustand im Monitoring an, wie im folgenden Beispiel:

Das Summary zeigt den Text zum kritischen Zustand.
Das Summary zeigt den Text zum kritischen Zustand

4.5. Metriken

Nicht immer, aber oft befassen sich Checks mit Zahlen. Und sehr oft handelt es sich bei diesen Zahlen um gemessene oder berechnete Werte. In unserem Beispiel sind die Zahl der Hosts in der Host-Gruppe (num_hosts) und die Zahl der Hosts im Zustand UP (num_hosts_up) die Messwerte. Der Prozentsatz der Hosts im Zustand UP (hosts_up_perc) ist ein daraus berechneter Wert. Wenn dann solch ein Wert im zeitlichen Verlauf dargestellt werden kann, wird er auch als Metrik bezeichnet.

Mit seinem Graphing-System hat Checkmk eine Komponente, um solche Zahlen zu speichern, auszuwerten und darzustellen. Das geht dabei völlig unabhängig von der Berechnung der Zustände OK, WARN und CRIT.

Sie werden in diesem Beispiel die beiden berechneten Werte hosts_up_perc und services_ok_perc als Metriken definieren. Metriken werden in der grafischen Benutzeroberfläche von Checkmk sofort sichtbar, ohne dass Sie etwas dafür tun müssen. Pro Metrik wird automatisch ein Graph erzeugt.

Metriken werden von der Check-Funktion ermittelt und als zusätzliches Ergebnis zurückgegeben. Am einfachsten ist es, der Funktion check_levels() im Aufruf die Metrikinformationen mitzugeben.

Zur Erinnerung folgt die Zeile mit dem Funktionsaufruf von check_levels() aus dem vorherigen Abschnitt:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    yield from check_levels(
        hosts_up_perc,
        levels_lower = (90.0, 80.0),
        label = "UP hosts",
        notice_only = True,
    )

Die beiden neuen Argumente für die Metrik sind metric_name und boundaries:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    yield from check_levels(
        hosts_up_perc,
        levels_lower = (90.0, 80.0),
        metric_name = "hosts_up_perc",
        label = "UP hosts",
        boundaries = (0.0, 100.0),
        notice_only = True,
    )

Um es schön einfach und aussagekräftig zu halten, nehmen Sie als Namen der Metrik den Namen der Variabel, in der der Prozentsatz als Wert gespeichert ist.

Mit boundaries können Sie dem Graphing-System die Information über den möglichen Wertebereich mitgeben. Damit ist der kleinste und größte mögliche Wert gemeint. Bei einem Prozentsatz sind die Grenzen mit 0.0 und 100.0 nicht allzu schwer zu bestimmen. Es sind sowohl Gleitkommazahlen (Float) als auch Ganzzahlen (Integer, die intern wiederum in Gleitkommazahlen umgewandelt werden) erlaubt, aber keine Strings. Falls nur eine Grenze des Wertebereichs definiert ist, tragen Sie für die andere einfach None ein, also zum Beispiel boundaries=(0.0, None).

Durch diese Erweiterung gibt die Funktion check_levels nun per yield zusätzlich zum Result auch noch ein Objekt vom Typ Metric zurück.

Analog können Sie jetzt auch die Metrik services_ok_perc definieren. Die letzten Zeilen der Check-Funktion sehen dann so aus:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    hosts_up_perc = 100.0 * num_hosts_up / num_hosts
    yield from check_levels(
        hosts_up_perc,
        levels_lower = (90.0, 80.0),
        metric_name = "hosts_up_perc",
        label = "UP hosts",
        boundaries = (0.0, 100.0),
        notice_only = True,
    )
    services_ok_perc = 100.0 * num_services_ok / num_services
    yield from check_levels(
        services_ok_perc,
        levels_lower = (90.0, 80.0),
        metric_name = "services_ok_perc",
        label = "OK services",
        boundaries = (0.0, 100.0),
        notice_only = True,
    )

Mit der so erweiterten Check-Funktion sind die beiden Graphen im Monitoring sichtbar. In der Service-Liste zeigt nun das Symbol Symbol zur Anzeige der Graphen eines Services., dass es Graphen zum Service gibt. Wenn Sie mit der Maus auf das Symbol zeigen, werden die Graphen als Vorschau eingeblendet.

Die Service-Liste mit 2 Graphen als Vorschau.
Die Namen der Metriken werden als Titel der Graphen verwendet

Eine Übersicht aller Graphen inklusive Legende und mehr finden Sie in den Service-Details.

Was macht man aber, wenn der Wert für die gewünschte Metrik gar nicht mit der Funktion check_levels() definiert wurde? Sie können selbstverständlich eine Metrik auch unabhängig von einem Funktionsaufruf festlegen. Dazu dient das Objekt Metric(), welches Sie auch direkt über seinen Konstruktor erzeugen können. Die alternative Definition einer Metrik für den Wert hosts_up_perc sieht so aus:

    yield Metric(
        name = "hosts_up_perc",
        value = hosts_up_perc,
        levels = (80.0, 90.0),
        boundaries = (0.0, 100.0),
    )

Die Argumente von Metric() sind sehr ähnlich zu denen im oben gezeigten Funktionsaufruf: Verpflichtend sind die ersten beiden Argumente für den Metriknamen und den Wert. Zusätzlich gibt es noch zwei optionale Argumente: levels für die Schwellwerte WARN und CRIT und boundaries für den Wertebereich.

Wichtig: Die Angabe von levels dient hier lediglich als Information für die Darstellung des Graphen. Im Graphen werden die Schwellwerte üblicherweise als gelbe und rote Linien eingezeichnet. Für die Überprüfung ist die Funktion check_levels mit den dort festgelegten Schwellwerten verantwortlich.

Nutzen Sie nun die Möglichkeit, nicht nur die beiden berechneten Werte, sondern mit Metric() alle Messwerte als Metriken zu definieren — in unserem Beispiel also die vier Messwerte aus der Host-Gruppentabelle. Beschränken Sie sich dabei auf die beiden obligatorischen Angaben von Metrikname und Wert. Die vier neuen Zeilen komplettieren die Erweiterung der Check-Funktion für Metriken:

    hosts_up_perc = 100.0 * num_hosts_up / num_hosts
    yield from check_levels(
        hosts_up_perc,
        levels_lower = (90.0, 80.0),
        metric_name = "hosts_up_perc",
        label = "UP hosts",
        boundaries = (0.0, 100.0),
        notice_only = True,
    )
    services_ok_perc = 100.0 * num_services_ok / num_services
    yield from check_levels(
        services_ok_perc,
        levels_lower = (90.0, 80.0),
        metric_name = "services_ok_perc",
        label = "OK services",
        boundaries = (0.0, 100.0),
        notice_only = True,
    )

    yield Metric(name="num_hosts", value=num_hosts)
    yield Metric(name="num_hosts_up", value=num_hosts_up)
    yield Metric(name="num_services", value=num_services)
    yield Metric(name="num_services_ok", value=num_services_ok)

Das erhöht schon einmal die Zahl der Graphen pro Service, bietet Ihnen aber zum Beispiel auch die Möglichkeit, mehrere Metriken in einem Graphen zu kombinieren. Wir zeigen diese und andere Möglichkeiten im Abschnitt Darstellung von Metriken anpassen weiter unten.

In der Beispieldatei auf GitHub finden Sie wieder die gesamte Check-Funktion.

4.6. Check-Parameter für Regelsätze

Im erweiterten Check-Plugin myhostgroups_advanced haben Sie den Zustand WARN erzeugt, wenn nur 90 % der Hosts UP sind, und CRIT bei 80 %. Dabei sind die Zahlen 90 und 80 direkt in der Check-Funktion fest einprogrammiert oder, wie Programmierer sagen würden, hart codiert. In Checkmk ist man allerdings als Benutzer gewohnt, dass man solche Schwellwerte und andere Check-Parameter per Regel konfigurieren kann. Denn hat zum Beispiel eine Host-Gruppe nur vier Mitglieder, dann passen die beiden Schwellwerte von 90 und 80 % nicht wirklich gut, da bereits beim Ausfall des ersten Hosts der Prozentsatz auf 75 % sinkt und der Zustand — ohne Umweg über WARN — direkt auf CRIT geht.

Daher soll das Check-Plugin nun so verbessert werden, dass es über die Setup-Oberfläche konfigurierbar ist. Dazu benötigen Sie einen Regelsatz. Falls es in Checkmk bereits einen passenden Regelsatz gibt, dann können Sie diesen verwenden. Da Check-Funktion und Regelsatz zusammenpassen müssen, wird dies in aller Regel nicht der Fall sein. Weiter unten finden Sie mehr Informationen zu vorhandenen Regelsätzen.

Hinweis: Beachten Sie, dass die in diesem Abschnitt vorgestellten Beispiele nicht durch die Check-API abgedeckt sind.

Neuen Regelsatz definieren

Um einen neuen Regelsatz zu erstellen, legen Sie eine neue Datei im Verzeichnis ~/local/share/check_mk/web/plugins/wato an. Der Name der Datei sollte sich an dem des Check-Plugins orientieren und muss wie alle Plugin-Dateien die Endung .py haben. Für unser Beispiel passt der Dateiname myhostgroups_advanced_parameters.py.

Sehen Sie sich den Aufbau so einer Datei Schritt für Schritt an. Zunächst kommen einige Importbefehle.

Falls die Texte in Ihrer Datei, die auf der Checkmk-GUI angezeigt werden, in andere Sprachen übersetzbar sein sollen, importieren Sie _ (Unterstrich):

~/local/share/check_mk/web/plugins/wato/myhostgroups_advanced_parameters.py
from cmk.gui.i18n import _

Dies ist eine Funktion und fungiert als Markierung für alle übersetzbaren Texte. Im Weiteren schreiben Sie dann zum Beispiel anstelle von "Threshold for Warning" ein _("Threshold for Warning") für den Funktionsaufruf. Das Übersetzungssystem von Checkmk, welches auf gettext basiert, findet solche Texte und übernimmt sie in die Liste der zu übersetzenden Texte auf. Falls Sie den Check nur für sich selbst bauen, können Sie darauf und damit auch auf die import-Zeile verzichten.

Als nächstes importieren Sie sogenannte ValueSpecs. Ein ValueSpec ist ein sehr praktisches und universelles Werkzeug, das Checkmk an vielen Stellen verwendet. Es dient dem Generieren von angepassten Eingabeformularen, der Darstellung und Validierung der eingegebenen Werte und der Umwandlung in Python-Datenstrukturen.

~/local/share/check_mk/web/plugins/wato/myhostgroups_advanced_parameters.py
from cmk.gui.valuespec import (
    Dictionary,
    Percentage,
    TextInput,
    Tuple,
)

Das Dictionary benötigen Sie auf jeden Fall, denn seit Checkmk-Version 2.0.0 werden Check-Parameter in Python-Dictionaries gespeichert. Percentage ist für die Eingabe von Prozentwerten verantwortlich, TextInput für einen Unicode-Text und Tuple für eine geordnete, eindimensionale Menge von Objekten. Sie werden auch häufiger Float (Gleitkommazahlen) und Integer (Ganzzahlen) verwenden.

Als nächstes werden noch Klassen und Funktionen importiert, die beim Registrieren benötigt werden:

~/local/share/check_mk/web/plugins/wato/myhostgroups_advanced_parameters.py
from cmk.gui.plugins.wato.utils import (
    CheckParameterRulespecWithItem,
    rulespec_registry,
    RulespecGroupCheckParametersApplications,
)

Da Ihr Check-Plugin myhostgroups_advanced mehrere Services erzeugt, importieren Sie CheckParameterRulespecWithItem. Falls Ihr Check keinen Service erzeugt, mit anderen Worten kein Item hat, importieren Sie stattdessen CheckParameterRulespecWithoutItem. Zur RulespecGroup gibt es am Ende dieses Abschnitts noch Informationen.

Nun kommen die eigentlichen Definitionen. Zunächst deklarieren Sie ein Eingabefeld, mit dem der Benutzer das Item des Checks angeben kann. Dieses Feld wird in der Bedingung der Regel angezeigt und erlaubt es den Benutzern, die Regel auf bestimmte Services einzuschränken. Es ist auch für das manuelle Anlegen von Checks notwendig, welche ohne Discovery funktionieren sollen.

Das Feld erstellen Sie mit TextInput. Es bekommt per title einen Titel zugewiesen, welcher dann in der Regel als Überschrift für das Eingabefeld angezeigt wird. Wenn Sie ein Herz für Ihre Benutzer haben, geben Sie ihnen auch noch einen hilfreichen Text für die Inline-Hilfe mit:

~/local/share/check_mk/web/plugins/wato/myhostgroups_advanced_parameters.py
def _item_valuespec_myhostgroups_advanced():
    return TextInput(
        title = "Host group name",
        help = "You can restrict this rule to certain services of the specified hosts.",
    )

Den Namen der Funktion, welche dieses ValueSpec zurückgibt, können Sie frei wählen, er wird nur bei der Registrierung weiter unten benötigt. Damit die Funktion nicht über die Modulgrenze hinaus sichtbar wird, sollte der Name mit einem Unterstrich beginnen.

Als nächstes kommt das ValueSpec für die Eingabe der eigentlichen Check-Parameter. Auch hierfür legen Sie eine Funktion an, welche das ValueSpec erzeugt. Der Benutzer soll die beiden Schwellwerte für WARN und CRIT festlegen können und zwar getrennt für die Anzahl der Hosts im Zustand UP und der Services im Zustand OK:

~/local/share/check_mk/web/plugins/wato/myhostgroups_advanced_parameters.py
def _parameter_valuespec_myhostgroups_advanced():
    return Dictionary(
        elements = [
            ("hosts_up_lower",
                Tuple(
                    title = _("Lower percentage threshold for host in UP status"),
                    elements = [
                        Percentage(title=_("Warning")),
                        Percentage(title=_("Critical")),
                    ],
                )
            ),
            ("services_ok_lower",
                Tuple(
                    title = _("Lower percentage threshold for services in OK status"),
                    elements = [
                        Percentage(title=_("Warning")),
                        Percentage(title=_("Critical")),
                    ],
                )
            ),
        ],
    )

Das return Dictionary() ist vorgeschrieben. Innerhalb dessen legen Sie mit elements=[] die Liste der Parameter an, im Beispiel hosts_up_lower und services_ok_lower. Beide Parameter werden jeweils durch ein Tupel von zwei Werten für die CRIT und WARN Schwellwerte definiert. Alle Schwellwerte sollen Prozentzahlen sein, also verwenden Sie hier Percentage.

Hinweis: Wenn Sie den Benutzern des Regelsatzes statt leerer Eingabefelder bereits Werte vorgeben wollen, können Sie das tun, indem Sie die Percentage() Funktion um das Argument default_value erweitern. Für das Tupel hosts_up_lower aus dem eben gezeigten Beispiel sieht das dann so aus:

            ("hosts_up_lower",
                Tuple(
                    title = _("Lower percentage threshold for host in UP status"),
                    elements = [
                        Percentage(title=_("Warning"), default_value=90.0),
                        Percentage(title=_("Critical"), default_value=80.0),
                    ],
                )
            ),

Beachten Sie, dass diese in der GUI angezeigten Werte nicht die Standardwerte sind, die weiter unten bei der Registrierung des Check-Plugins per check_default_parameters gesetzt werden. Wollen Sie die gleichen Standardwerte in der GUI anzeigen lassen, die auch für die Check-Funktion gelten, dann müssen Sie die Werte an beiden Stellen selbst konsistent halten.

Zu guter Letzt registrieren Sie jetzt mithilfe der importierten und selbst definierten Dinge den neuen Regelsatz. Dazu gibt es die Funktion rulespec_registry.register():

~/local/share/check_mk/web/plugins/wato/myhostgroups_advanced_parameters.py
rulespec_registry.register(
    CheckParameterRulespecWithItem(
        check_group_name = "myhostgroups_advanced",
        group = RulespecGroupCheckParametersApplications,
        match_type = "dict",
        item_spec = _item_valuespec_myhostgroups_advanced,
        parameter_valuespec = _parameter_valuespec_myhostgroups_advanced,
        title = lambda: _("Host group status"),
    )
)

Dazu die folgenden Erklärungen:

  • Falls Ihr Check kein Item verwendet, lautet die innere Funktion CheckParameterRulespecWithoutItem. Die Zeile item_spec entfällt dann.

  • Der check_group_name als Name des Regelsatzes stellt die Verbindung zu den Check-Plugins her. Ein Check-Plugin, das diesen Regelsatz verwenden will, muss beim Registrieren diesen Namen als check_ruleset_name verwenden. Der Name darf auf keinen Fall identisch sein mit einem bereits existierenden Regelsatz, weil dieser damit überschrieben würde. Um dieser Gefahr aus dem Weg zu gehen, verwenden Sie am besten im Namen einen Präfix.

  • Die group legt fest, wo im Setup der Regelsatz auftauchen soll. Mit dem im Beispiel gewählten Wert finden Sie ihn unter Setup > Services > Service monitoring rules im ersten Kasten Applications, Processes & Services. Die meisten dieser Gruppen sind in der Datei ~/lib/check_mk/gui/plugins/wato/utils/__init__.py definiert. Dort finden Sie auch Beispiele, wie Sie eine eigene neue Gruppe anlegen können.

  • Der match_type ist immer "dict".

  • Als item_spec und parameter_valuespec geben Sie die Namen der beiden zuvor erstellten Funktionen ein.

  • title legt den Titel des Regelsatzes fest, so wie er auch in der Checkmk-GUI erscheint. Der Titel wird aber nicht direkt als Text, sondern als ausführbare Funktion angegeben, welche den Text zurückliefert (deswegen das lambda:).

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:

Der neu erstellte Regelsatz im Setup.

In der Bedingung finden Sie das definierte Feld Host group name mit der Inline-Hilfe:

Das Feld zur Service-Auswahl in der Regelbedingung.

Die Texte zum Umgang mit den regulären Ausrücken hat übrigens Checkmk Ihrem Regelsatz spendiert.

Legen Sie eine Regel an und probieren Sie verschiedene Werte aus, wie im obigen Screenshot gezeigt. Wenn das ohne Fehler geht, können Sie die Check-Parameter jetzt in der Check-Funktion verwenden.

Regelsatz im Check-Plugin benutzen

Damit die Regel zum Greifen kommt, müssen Sie dem Check-Plugin erlauben, Check-Parameter entgegenzunehmen und ihm sagen, welche Regel benutzt werden soll. Dazu fügen Sie zwei neue Zeilen in die Registrierungsfunktion ein:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
register.check_plugin(
    name = "myhostgroups_advanced",
    sections = ["myhostgroups"],
    service_name = "Host group %s",
    discovery_function = discover_myhostgroups_advanced,
    check_function = check_myhostgroups_advanced,
    check_default_parameters = {},
    check_ruleset_name = "myhostgroups_advanced",
)

Mit dem Eintrag check_default_parameters können Sie die Standardwerte setzen, die gelten, solange noch keine Regel angelegt ist. Bei der Registrierung muss diese Zeile unbedingt vorhanden sein. Im einfachsten Fall übergeben Sie ein leeres Dictionary {}.

Als zweites übergeben Sie der Registrierungsfunktion noch den check_ruleset_name, also den Namen, den Sie oben mittels check_group_name an den Regelsatz vergeben haben. So weiß Checkmk aus welchem Regelsatz die Parameter bestimmt werden sollen.

Nun wird Checkmk versuchen, der Check-Funktion Parameter zu übergeben. Damit das klappen kann, müssen Sie die Check-Funktion so erweitern, dass sie das Argument params erwartet, welches sich zwischen item und section schiebt:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
def check_myhostgroups_advanced(item, params, section):

Falls Sie einen Check ohne Item bauen, entfällt das item und params steht am Anfang.

Es ist sehr empfehlenswert, sich jetzt als ersten Test den Inhalt der Variable params mit einem print ausgeben zu lassen:

def check_myhostgroups_advanced(item, params, section):
    print(params)

Bei der Ausführung des Check-Plugins sehen die beiden ausgedruckten Zeilen (für jeden Service eine) dann etwa so aus:

OMD[mysite]:~$ cmk --detect-plugins=myhostgroups_advanced -v localhost
Parameters({'hosts_up_lower': (70.0, 60.0), 'services_ok_lower': (75.0, 65.0)})
Parameters({'hosts_up_lower': (70.0, 60.0), 'services_ok_lower': (75.0, 65.0)})
Parameters({'hosts_up_lower': (70.0, 60.0), 'services_ok_lower': (75.0, 65.0)})

Wichtig: Wenn alles fertig ist und funktioniert, entfernen Sie unbedingt die print-Befehle wieder. Diese können die interne Kommunikation von Checkmk durcheinanderbringen.

Nun passen Sie Ihre Check-Funktion weiter an, so dass die übergebenen Parameter ihre Wirkung entfalten können. Holen Sie sich die beiden Tupel mit dem in der Regel gewählten Namen aus den Parametern:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
def check_myhostgroups_advanced(item, params, section):
    hosts_up_lower = params["hosts_up_lower"]
    services_ok_lower = params["services_ok_lower"]

Weiter unten in der Check-Funktion werden dann die bisher hart kodierten Schwellwerte (90, 80) durch die Variablen hosts_up_lower und services_ok_lower ersetzt:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    hosts_up_perc = 100.0 * num_hosts_up / num_hosts
    yield from check_levels(
        hosts_up_perc,
        levels_lower = (hosts_up_lower),
        metric_name = "hosts_up_perc",
        label="UP hosts",
        boundaries = (0, 100),
        notice_only = True,
    )
    services_ok_perc = 100.0 * num_services_ok / num_services
    yield from check_levels(
        services_ok_perc,
        levels_lower = (services_ok_lower),
        metric_name = "services_ok_perc",
        label = "OK services",
        boundaries = (0, 100),
        notice_only = True,
    )

Falls eine Regel konfiguriert ist, können Sie nun die Host-Gruppen des Beispiels mit den über die GUI gesetzten Schwellwerten überwachen. Wenn allerdings keine Regel definiert ist, wird diese Check-Funktion abstürzen: Da die Default-Parameter des Check-Plugins nicht befüllt sind, wird das Plugin bei Abwesenheit einer Regel einen KeyError erzeugen.

Dieses Problem kann aber behoben werden, wenn bei der Registrierung die Standardwerte gesetzt sind:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
register.check_plugin(
    name = "myhostgroups_advanced",
    sections = ["myhostgroups"],
    service_name = "Host group %s",
    discovery_function = discover_myhostgroups_advanced,
    check_function = check_myhostgroups_advanced,
    check_default_parameters = {"hosts_up_lower": (90, 80), "services_ok_lower": (90, 80)},
    check_ruleset_name = "myhostgroups_advanced",
)

Sie sollten Standardwerte immer auf diese Weise übergeben (und den Fall fehlender Parameter nicht im Check-Plugin abfangen), da diese Standardwerte auch in der Setup-Oberfläche angezeigt werden können. Dazu gibt es zum Beispiel bei der Service-Erkennung eines Hosts, auf der Seite Services of host, im Menü Display den Eintrag Show check parameters.

Hinweis: Auf GitHub finden Sie sowohl die Datei mit dem Regelsatz als auch das um den Regelsatz erweiterte Check-Plugin.

Vorhandenen Regelsatz verwenden

Unwahrscheinlich, doch nicht ausgeschlossen ist es, dass ein mit Checkmk ausgelieferter Regelsatz zu Ihrer Check-Funktion passt. Das kann eigentlich nur dann der Fall sein, wenn der Check etwas prüft, für das Checkmk in gleicher Form bereits Check-Plugins hat, zum Beispiel das Überwachen einer Temperatur oder anderer mit Sensoren gemessener Werte. Wenn aber so ein Regelsatz zu Ihrem Check-Plugin passt, dann können Sie sich die Erstellung eines eigenen Regelsatzes sparen.

Die ausgelieferten Regelsätze für Parameter von Checks finden Sie im Verzeichnis ~/lib/check_mk/gui/plugins/wato/check_parameters/.

Nehmen Sie als Beispiel die Datei memory_simple.py. Diese deklariert einen Regelsatz mit folgendem Abschnitt:

~/lib/check_mk/gui/plugins/wato/check_parameters/memory_simple.py
rulespec_registry.register(
    CheckParameterRulespecWithItem(
        check_group_name = "memory_simple",
        group = RulespecGroupCheckParametersOperatingSystem,
        item_spec = _item_spec_memory_simple,
        match_type = "dict",
        parameter_valuespec = _parameter_valuespec_memory_simple,
        title = lambda: _("Main memory usage of simple devices"),
    )
)

Entscheidend ist dabei, wie beim selbst geschriebenen Regelsatz, das Schlüsselwort check_group_name, welches hier auf "memory_simple" gesetzt ist. Damit wird die Verbindung zum Check-Plugin hergestellt. Das machen Sie beim Registrieren des Check-Plugins mit dem Schlüsselwort check_ruleset_name, zum Beispiel so:

register.check_plugin(
    name = "foobar",
    service_name = "Foobar %s",
    discovery_function = discover_foobar,
    check_function = check_foobar,
    check_ruleset_name = "memory_simple",
    check_default_parameters = {},
)

Notwendig ist auch hier die Definition von Standardwerten über das Schlüsselwort check_default_parameters.

5. Darstellung von Metriken anpassen

Im obigen Beispiel haben Sie das Check-Plugin myhostgroups_advanced Metriken für alle gemessenen und berechneten Werte erzeugen lassen. Dazu haben wir zwei Wege vorgestellt. Zuerst wurden die Metriken der berechneten Werte als Bestandteil der Funktion check_levels() mit dem Argument metric_name erstellt, zum Beispiel so:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    yield from check_levels(
        services_ok_perc,
        levels_lower = (services_ok_lower),
        metric_name = "services_ok_perc",
        label = "OK services",
        boundaries = (0.0, 100.0),
        notice_only = True,
    )

Dann haben wir die gemessenen Metriken direkt mit dem Objekt Metric() erzeugt — für die Anzahl der Services im Zustand OK zum Beispiel so:

~/local/lib/check_mk/base/plugins/agent_based/myhostgroups.py
    yield Metric(name="num_services_ok", value=num_services_ok)

Metriken werden in der grafischen Benutzeroberfläche von Checkmk zwar sofort sichtbar, ohne dass Sie etwas dafür tun müssen. Allerdings gibt es dabei ein paar Einschränkungen:

  • Es werden nicht automatisch passende Metriken in einem Graphen kombiniert, sondern jede erscheint einzeln.

  • Die Metrik hat keinen richtigen Titel, sondern es wird der interne Variablenname der Metrik gezeigt.

  • Es wird keine Einheit verwendet, die eine sinnvolle Darstellung erlaubt (zum Beispiel GB anstelle von einzelnen Bytes).

  • Es wird zufällig eine Farbe ausgewählt.

  • Es erscheint nicht automatisch ein „Perf-O-Meter“, also die grafische Vorschau der Metrik als Balken in der Service-Liste (zum Beispiel in der Ansicht, die alle Services eines Hosts darstellt).

Um die Darstellung Ihrer Metriken in diesen Belangen zu vervollständigen, benötigen Sie Metrikdefinitionen.

Hinweis: Beachten Sie, dass die in diesem Kapitel vorgestellten Beispiele nicht durch die Check-API abgedeckt sind.

5.1. Vorhandene Metrikdefinitionen verwenden

Bevor Sie eine neue Metrikdefinition erstellen, sollten Sie zunächst prüfen, ob Checkmk nicht bereits eine geeignete Definition mitbringt. Die vordefinierten Metrikdefinitionen finden Sie im Verzeichnis ~/lib/check_mk/gui/plugins/metrics/.

In der Datei cpu.py finden Sie beispielsweise die Metrik mit dem Namen util für die CPU-Auslastung (CPU utilization):

~/lib/check_mk/gui/plugins/metrics/cpu.py
metric_info["util"] = {
    "title": _l("CPU utilization"),
    "unit": "%",
    "color": "26/a",
}

Falls Ihr Check-Plugin die CPU-Auslastung in Prozent misst, können Sie diese Metrikdefinition problemlos verwenden. Sie müssen lediglich in Ihrer Check-Funktion "util" als den Namen für die Metrik einsetzen. Alles andere leitet sich dann automatisch davon ab.

5.2. Neue Metrikdefinition erstellen

Falls keine passende Metrikdefinition dabei ist, legen Sie einfach selbst eine an.

Für unser Beispiel definieren Sie nun eine eigene Metrik für die Anzahl der Services im Zustand OK. Dazu legen Sie eine Datei in ~/local/share/check_mk/web/plugins/metrics an:

~/local/share/check_mk/web/plugins/metrics/myhostgroups_advanced_metrics.py
from cmk.gui.i18n import _

from cmk.gui.plugins.metrics import metric_info

metric_info["num_services_ok"] = {
    "title": _("Services in OK status"),
    "unit": "count",
    "color": "15/a",
}

Hier die Erklärung dazu:

  • Das Importieren und Verwenden des Unterstrichs (_) für die Internationalisierung ist optional, wie bereits bei der Erstellung von Regelsätzen besprochen.

  • Der Schlüssel (hier "num_services_ok") ist der Metrikname und muss dem entsprechen, was die Check-Funktion ausgibt. Wählen Sie einen eindeutigen Namen, so dass keine vorhandene Metrik „überschrieben“ wird.

  • Der title ist die Überschrift im Metrikgraphen und ersetzt den bisher verwendeten internen Variablennamen.

  • Welche Definitionen es für Einheiten (unit) gibt, erfahren Sie in der Datei ~/lib/check_mk/gui/plugins/metrics/unit.py.

  • Die Farbdefinition color verwendet eine Palette. Zu jeder Palettenfarbe gibt es /a und /b. Dies sind zwei Schattierungen der gleichen Farbe. In den vorhandenen Metrikdefinitionen werden Sie auch viele direkte Farbkodierungen wie "#ff8800" finden. Diese werden nach und nach abgeschafft und durch Palettenfarben ersetzt, da diese ein einheitlicheres Aussehen bieten und auch leichter an die Farbthemen der GUI angepasst werden können.

Diese Definition in der Metrikdatei sorgt jetzt dafür, dass Titel, Einheit und Farbe der Metrik angepasst dargestellt werden.

Analog zur Erstellung einer Regelsatzdatei muss die Metrikdatei erst gelesen werden, bevor die Änderung in der GUI sichtbar ist. Das macht der Neustart des Apache der Instanz:

OMD[mysite]:~$ omd restart apache

Der Metrikgraph sieht dann in der Checkmk-GUI ungefähr so aus:

Die neue Metrikdefinition in den Service-Details.

5.3. Graph mit mehreren Metriken

Möchten Sie mehrere Metriken in einem Graphen kombinieren (was oft sehr sinnvoll ist), benötigen Sie eine Graphdefinition, die Sie der im vorherigen Abschnitt erstellten Metrikdatei hinzufügen können. Dies geschieht über das globale Dictionary graph_info.

Für unser Beispiel sollen die beiden Metriken num_services_ok und num_services in einem Graphen dargestellt werden. Die Metrikdefinitionen dazu sehen wie folgt aus:

~/local/share/check_mk/web/plugins/metrics/myhostgroups_advanced_metrics.py
from cmk.gui.i18n import _

from cmk.gui.plugins.metrics import (
    metric_info,
    graph_info,
)

metric_info["num_services_ok"] = {
    "title": _("Services in OK status"),
    "unit": "count",
    "color": "15/a",
}

metric_info["num_services"] = {
    "title": _("Number of services"),
    "unit": "count",
    "color": "24/a",
}

Fügen Sie nun einen Graphen an, der diese beiden Metriken als Linien einzeichnet:

graph_info["num_services_combined"] = {
    "metrics": [
        ("num_services_ok", "line"),
        ("num_services", "line"),
    ],
}

Der erste Eintrag unter metrics legt fest, welche Metrik für den Titel des Graphen genommen wird. Das Resultat ist der kombinierte Graph in der Checkmk-GUI:

Der Graph zeigt beide Metriken in den Service-Details.

5.4. Metriken im Perf-O-Meter

Möchten Sie zu einer Metrik noch ein Perf-O-Meter in der Zeile der Service-Liste anzeigen? Das könnte zum Beispiel so aussehen:

Das Perf-O-Meter zeigt die Anzahl der Services im Status 'OK'.
Das Perf-O-Meter zeigt die absolute Zahl der Services

Um so ein Perf-O-Meter zu erstellen, benötigen Sie eine weitere Datei, diesmal im Verzeichnis ~/local/share/check_mk/web/plugins/perfometer:

~/local/share/check_mk/web/plugins/perfometer/myhostgroups_advanced_perfometer.py
from cmk.gui.plugins.metrics import perfometer_info

perfometer_info.append(
    {
        "type": "logarithmic",
        "metric": "num_services_ok",
        "half_value": 25,
        "exponent": 2.0,
    }
)

Perf-O-Meter sind etwas trickreicher als Graphen, da es keine Legende gibt. Daher ist die Darstellung des Wertebereichs schwierig. Da das Perf-O-Meter nicht wissen kann, welche Werte denn überhaupt möglich sind, und der Platz sehr begrenzt ist, verwenden viele eingebaute Check-Plugins eine logarithmische Darstellung. Dies ist auch im obigen Beispiel der Fall:

  • type wählt die Darstellungsform der Werte, hier die logarithmische Darstellung.

  • metric bezeichnet den Namen der Metrik, hier die Anzahl der Services im Zustand OK.

  • half_value ist der Messwert, welcher genau in der Mitte des Perf-O-Meters angezeigt wird. Bei einem Wert von 25 ist der Balken also halb gefüllt.

  • exponent legt den Faktor fest, welcher notwendig ist, damit weitere 10 % des Bereichs gefüllt werden. Also würde im Beispiel ein Messwert von 50 den Balken bis 60 % füllen, und einer von 100 bis 70 %.

Der Vorteil dieser Methode: Wenn Sie eine Liste von Services gleicher Art mit gleichartigen Perf-O-Metern ausstatten, können Sie die Perf-O-Meter untereinander optisch schnell vergleichen, da alle die gleiche Skala nutzen. Und trotz der kleinen Darstellungsform können Sie sowohl bei sehr kleinen als auch bei sehr großen Werten die Unterschiede gut erkennen. Dafür sind die Werte allerdings nicht maßstabsgetreu.

Das obige Perf-O-Meter zeigt zwar eine Metrik, aber nicht diejenige, die für den Zustand des Services verantwortlich ist. Nicht die Anzahl der Services im Zustand OK, sondern ihr Prozentsatz bestimmt den Zustand des gezeigten Services der Host-Gruppe.

Um die Metrik des Prozentsatzes (services_ok_perc) darzustellen, können Sie ein lineares Perf-O-Meter verwenden. Das ist immer dann sinnvoll, wenn es einen bekannten Maximalwert gibt. Das sähe dann zum Beispiel in der Datei so aus:

~/local/share/check_mk/web/plugins/perfometer/myhostgroups_advanced_perfometer.py
perfometer_info.append(
    {
        "type": "linear",
        "segments": ["services_ok_perc"],
        "total": 100.0,
    }
)

Und so in der GUI:

Das Perf-O-Meter zeigt den Prozentsatz der Services im Status 'OK'.
Hier wird der Prozentsatz der Services dargestellt

Das ist schon einmal eine Verbesserung. Allerdings wird der Zustand des Services nicht nur durch den Prozentsatz der Services im Zustand OK, sondern auch durch den Prozentsatz der Hosts im Zustand UP bestimmt. Um mehrere Metriken in einem Perf-O-Meter zu kombinieren, gibt es verschiedene Möglichkeiten. Eine davon sieht so aus:

~/local/share/check_mk/web/plugins/perfometer/myhostgroups_advanced_perfometer.py
perfometer_info.append(
    {
        "type": "dual",
        "perfometers": [
            {
                "type": "linear",
                "segments": ["hosts_up_perc"],
                "total": 100.0,
            },
            {
                "type": "linear",
                "segments": ["services_ok_perc"],
                "total": 100.0,
            },
        ],
    }
)

Dies sorgt dafür, dass sich die beiden Metriken den Balken des Perf-O-Meters teilen und nebeneinander angezeigt werden:

Das Perf-O-Meter zeigt zwei Prozentsätze nebeneinander an.
Hier werden zwei Prozentsätze nebeneinander angezeigt

Auch zu diesem Thema finden Sie viele Beispiele in den von Checkmk ausgelieferten Check-Plugins — in der Datei ~/lib/check_mk/gui/plugins/metrics/perfometers.py.

6. Zahlen formatieren

Im Summary und den Details eines Services werden oft Zahlen ausgegeben. Um Ihnen eine schöne und korrekte Formatierung möglichst einfach zu machen, und auch um die Ausgaben von allen Check-Plugins zu vereinheitlichen, gibt es Hilfsfunktionen für die Darstellung von verschiedenen Arten von Größen. All diese sind Unterfunktionen vom Modul render und werden folglich mit render. aufgerufen. Zum Beispiel ergibt render.bytes(2000) den Text 1.95 KiB.

All diesen Funktionen ist gemein, dass Sie ihren Wert in einer sogenannten kanonischen oder natürlichen Einheit bekommen. So muss man nie nachdenken und es gibt keine Schwierigkeiten oder Fehler bei der Umrechnung. Beispielsweise werden Zeiten immer in Sekunden angegeben und Größen von Festplatten, Dateien etc. immer in Bytes und nicht in Kilobytes, Kibibytes, Blöcken oder sonstigem Durcheinander.

Verwenden Sie diese Funktionen auch dann, wenn Ihnen die Darstellung nicht so gut gefällt. Immerhin ist diese dann für den Benutzer einheitlich. Und zukünftige Versionen von Checkmk können die Darstellung möglicherweise ändern oder sogar konfigurierbar für den Benutzer machen, wie es zum Beispiel bereits bei der Anzeige der Temperatur der Fall ist. Davon wird dann Ihr Check-Plugin auch profitieren.

Bevor Sie die render-Funktion in Ihrem Check-Plugin nutzen können, müssen Sie sie zusätzlich importieren:

from .agent_based_api.v1 import check_levels, Metric, register, render, Result, Service, State

Nach der ausführlichen Beschreibung aller Darstellungsfunktionen (Render-Funktionen) finden Sie eine Zusammenfassung in Form einer übersichtlichen Tabelle.

6.1. Zeiten, Zeiträume, Frequenzen

Absolute Zeitangaben (Zeitstempel) werden mit render.date() oder render.datetime() formatiert. Die Angaben erfolgen immer als Unix-Zeit, also in Sekunden ab dem 1. Januar 1970, 00:00:00 UTC — dem Beginn der Unix-Epoche. Dies ist auch das Format, mit dem die Python-Funktion time.time() arbeitet.

Vorteil an dieser Darstellung ist, dass sich damit sehr einfach rechnen lässt, also zum Beispiel die Berechnung des Zeitraums, wenn Start- und Endzeit bekannt sind. Die Formel ist dann einfach duration = end - start. Und diese Berechnungen funktionieren unabhängig von der Zeitzone, Sommerzeitumstellungen oder Schaltjahren.

render.date() gibt nur das Datum aus, render.datetime() fügt noch die Uhrzeit hinzu. Die Ausgabe erfolgt dabei gemäß der aktuellen Zeitzone desjenigen Checkmk-Servers, welcher den Check ausführt. Beispiele:

Aufruf Ausgabe

render.date(0)

Jan 01 1970

render.datetime(0)

Jan 01 1970 01:00:00

render.date(1700000000)

Nov 14 2023

render.datetime(1700000000)

Nov 14 2023 23:13:20

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

render.timespan(1)

1 second

render.timespan(123)

2 minutes 3 seconds

render.timespan(12345)

3 hours 25 minutes

render.timespan(1234567)

14 days 6 hours

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

render.frequency(111222333444)

111 GHz

6.2. Bytes

Überall wo es um Arbeitsspeicher, Dateien, Festplatten, Dateisysteme und dergleichen geht, ist die kanonische Einheit das Byte. Da Computer so etwas meist in Zweierpotenzen organisieren, also zum Beispiel in Einheiten zu 512, 1024 oder 65 536 Bytes, hatte sich dabei von Beginn an eingebürgert, dass ein Kilobyte nicht 1000, und damit das Tausendfache der Einheit ist, sondern 1024 (2 hoch 10) Bytes. Das ist zwar unlogisch, aber sehr praktisch, weil so meist runde Zahlen herauskommen. Der legendäre Commodore C64 hatte eben 64 Kilobyte Speicher und nicht 65,536.

Leider kamen irgendwann Festplattenhersteller auf die Idee, die Größen ihrer Platten in 1000er-Einheiten anzugeben. Da bei jeder Größenordnung der Unterschied zwischen 1000 und 1024 immerhin 2,4 % ausmacht, und diese sich aufmultiplizieren, wird so aus einer Platte der Größe 1 GB (1024 mal 1024 mal 1024) auf einmal 1,07 GB. Das verkauft sich besser.

Diese lästige Verwirrung besteht bis heute und sorgt immer wieder für Fehler. Als Linderung wurden von der internationalen elektrotechnischen Kommission (IEC) neue Präfixe auf Grundlage des Binärsystems festgelegt. Demnach ist heute offiziell ein Kilobyte 1000 Bytes und ein Kibibyte 1024 Bytes (2 hoch 10). Außerdem soll man Mebibyte, Gibibyte und Tebibyte sagen. Die Abkürzungen lauten dann KiB, MiB, GiB und TiB.

Checkmk passt sich an diesen Standard an und hilft Ihnen mit mehreren angepassten Render-Funktionen dabei, dass Sie immer korrekte Ausgaben machen. So gibt es speziell für Festplatten und Dateisysteme die Funktion render.disksize(), welche die Ausgabe in 1000er-Potenzen macht.

Aufruf Ausgabe

render.disksize(1000)

1.00 kB

render.disksize(1024)

1.02 kB

render.disksize(2000000)

2.00 MB

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

render.filesize(1000)

1,000 B

render.filesize(1024)

1,024 B

render.filesize(2000000)

2,000,000 B

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

render.bytes(1000)

1000 B

render.bytes(1024)

1.00 KiB

render.bytes(2000000)

1.91 MiB

6.3. Bandbreiten, Datenraten

Die Netzwerker haben ihre eigenen Begriffe und Arten, Dinge auszudrücken. Und wie immer gibt sich Checkmk Mühe, in jeder Domäne die dort übliche Art zu kommunizieren, zu übernehmen. Deswegen gibt es für Datenraten und Geschwindigkeiten gleich drei verschiedene Render-Funktionen. Alle haben gemeinsam, dass die Raten in Bytes pro Sekunde übergeben werden, selbst dann, wenn die Ausgabe in Bits erfolgt!

render.nicspeed() stellt die Maximalgeschwindigkeit einer Netzwerkkarte oder eines Switchports dar. Da es keine Messwerte sind, muss auch nicht gerundet werden. Obwohl kein Port einzelne Bits versenden kann, sind die Angaben aus historischen Gründen in Bits.

Wichtig: Trotzdem müssen Sie auch hier Bytes pro Sekunde übergeben!

Aufruf Ausgabe

render.nicspeed(12500000)

100 MBit/s

render.nicspeed(100000000)

800 MBit/s

render.networkbandwidth() ist gedacht für eine tatsächlich gemessene Übertragungsgeschwindigkeit im Netzwerk. Eingabewert ist wieder Bytes pro Sekunde:

Aufruf Ausgabe

render.networkbandwidth(123)

984 Bit/s

render.networkbandwidth(123456)

988 kBit/s

render.networkbandwidth(123456789)

988 MBit/s

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

render.iobandwidth(123)

123 B/s

render.iobandwidth(123456)

123 kB/s

render.iobandwidth(123456789)

123 MB/s

6.4. Prozentsätze

Die Funktion render.percent() stellt einen Prozentsatz dar — auf zwei Nachkommastellen gerundet. Sie ist insofern eine Ausnahme zu den anderen Funktionen, als hier nicht der eigentlich natürliche Wert — also das Verhältnis — übergeben wird, sondern wirklich die Prozentzahl. Wenn also etwas zum Beispiel zur Hälfte voll ist, müssen Sie nicht 0.5 sondern 50 übergeben.

Weil es manchmal interessant sein kann, zu wissen, ob ein Wert beinahe Null oder exakt Null ist, werden Werte durch Anfügen eines „<“ Zeichens markiert, die größer als Null, aber kleiner als 0,01 Prozent sind.

Aufruf Ausgabe

render.percent(0.004)

<0.01%

render.percent(18.5)

18.50%

render.percent(123)

123.00%

6.5. Zusammenfassung

Hier folgt zum Abschluss die Übersicht über alle Render-Funktionen:

Funktion Eingabe Beschreibung Beispielausgabe

date()

Unix-Zeit

Datum

Nov 14 2023

datetime()

Unix-Zeit

Datum und Uhrzeit

Nov 14 2023 23:13:20

timespan()

Sekunden

Dauer / Alter

3 hours 25 minutes

frequency()

Hz

Frequenz (z. B. Taktrate)

111 GHz

disksize()

Bytes

Größe einer Festplatte, Basis 1000

1,234 GB

filesize()

Bytes

Größe einer Datei, volle Genauigkeit

1,334,560 B

bytes()

Bytes

Größe, Basis 1024

23,4 KiB

nicspeed()

Bytes pro Sekunde

Geschwindigkeit von Netzwerkkarten

100 MBit/s

networkbandwidth()

Bytes pro Sekunde

Übertragungsgeschwindigkeit

23.50 GBit/s

iobandwidth()

Bytes pro Sekunde

IO-Bandbreiten

124 MB/s

percent()

Prozentzahl

Prozentsatz, sinnvoll gerundet

99.997%

7. Fehler beheben

Die korrekte Behandlung von Fehlern nimmt (leider) einen großen Teil der Programmierarbeit ein. Dabei ist die gute Nachricht, dass Ihnen die Check-API hinsichtlich der Fehlerbehandlung bereits viel Arbeit abnimmt. Bei einigen Arten von Fehlern ist es daher richtig, diese gar nicht selbst zu behandeln.

Wenn Python in eine Situation kommt, die in irgendeiner Form unerwartet ist, reagiert es mit einer sogenannten Ausnahme (Exception). Hier sind ein paar Beispiele:

  • Sie konvertieren mit int() einen String in eine Zahl, aber der String enthält keine Zahl, zum Beispiel int("foo").

  • Sie greifen mit bar[4] auf das fünfte Element von bar zu, aber das hat nur vier Elemente.

  • Sie rufen eine Funktion auf, die es nicht gibt.

Um entscheiden zu können, wie Sie Fehler angehen, ist es zunächst wichtig, die exakte Stelle im Code zu kennen, an der ein Fehler auftritt. Hierfür können Sie sowohl die GUI als auch die Kommandozeile verwenden — je nachdem, wo Sie gerade arbeiten.

7.1. Exceptions und Absturzberichte in der GUI

Tritt eine Exception im Monitoring oder während der Service-Erkennung im Setup auf, enthält das Summary Hinweise auf den eben erstellten Absturzbericht (crash report). Das sieht dann zum Beispiel so aus:

Ein Service, dessen Check-Plugin abgestürzt ist.

Durch einen Klick auf das Icon Symbol für ein abgestürztes Check-Plugin. wird eine Seite mit Details angezeigt, auf der Sie:

  • die Datei angezeigt bekommen, in der der Absturz stattgefunden hat,

  • alle Informationen über den Absturz erhalten, wie die Auflistung der aufgetretenen Fehler im Programm (Traceback), aktuelle Werte lokaler Variablen, Agentenausgabe und vieles mehr sowie

  • den Report zu uns (Checkmk GmbH) als Feedback einsenden können.

Der Traceback hilft Ihnen als Programmierer zu entscheiden, ob ein Fehler im Programm vorliegt (beispielsweise der Aufruf einer nicht vorhandenen Funktion) oder Agentendaten vorliegen, die nicht wie erwartet verarbeitet werden konnten. Im ersten Fall werden Sie den Fehler beheben wollen, im zweiten Fall ist es häufig sinnvoll, nichts zu tun.

Das Einsenden des Reports ist natürlich nur für Check-Plugins sinnvoll, die offiziell Teil von Checkmk sind. Falls Sie eigene Plugins Dritten zugänglich machen, können Sie Ihre Anwender bitten, Ihnen die Daten zukommen zu lassen.

7.2. Exceptions auf der Kommandozeile ansehen

Wenn Sie Ihr Check-Plugin auf der Kommandozeile ausführen, erhalten Sie keinen Hinweis auf die ID des erzeugten Absturzberichts. Sie sehen nur die zusammengefasste Fehlermeldung:

OMD[mysite]:~$ cmk --detect-plugins=myhostgroups_advanced localhost
Error in agent based plugin myhostgroups: invalid syntax (myhostgroups.py, line 11)

Hängen Sie die Option --debug als zusätzlichen Aufrufparameter an, dann bekommen Sie den Traceback des Python-Interpreters:

OMD[mysite]:~$ cmk --debug --detect-plugins=myhostgroups_advanced localhost
Traceback (most recent call last):
  File "/omd/sites/mysite/bin/cmk", line 97, in <module>
    errors = config.load_all_agent_based_plugins(
             ^^^^^^^^^^^^
  File "/omd/sites/mysite/lib/python3/cmk/base/config.py", line 1673, in load_all_agent_based_plugins
    errors = agent_based_register.load_all_plugins()
             ^^^^^^^^^^^^^
  File "/omd/sites/mysite/lib/python3/cmk/base/api/agent_based/register/init.py", line 48, in load_all_plugins
    raise exception
  File "/omd/sites/mysite/lib/python3/cmk/utils/plugin_loader.py", line 49, in load_plugins_with_exceptions
    importlib.import_module(full_name)
  File "/omd/sites/mysite/lib/python3.11/importlib/init.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1206, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 936, in exec_module
  File "<frozen importlib._bootstrap_external>", line 1074, in get_code
  File "<frozen importlib._bootstrap_external>", line 1004, in source_to_code
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/omd/sites/mysite/local/lib/python3/cmk/base/plugins/agent_based/myhostgroups.py", line 11
    parsed =
            ^
SyntaxError: invalid syntax

Tritt der Fehler beim Aufruf mit --debug beim nächsten Mal nicht mehr auf, zum Beispiel, weil eine neue Agentenausgabe bereitsteht, können Sie die letzten Absturzberichte auch im Dateisystem einsehen:

OMD[mysite]:~$ ls -lhtr ~/var/check_mk/crashes/check/ | tail -n 5
drwx------ 1 mysite mysite 44 Sep  3 05:15 f47550fc-4a18-11ee-a46c-001617122312/
drwx------ 1 mysite mysite 44 Sep  3 05:17 38657652-4a19-11ee-a46c-001617122312/
drwx------ 1 mysite mysite 44 Sep  3 09:31 9e716690-4a3c-11ee-9eaf-001617122312/
drwx------ 1 mysite mysite 44 Sep  3 10:40 479b20ea-4a46-11ee-9eaf-001617122312/
drwx------ 1 mysite mysite 44 Sep  4 14:11 fdec3ef6-4b2c-11ee-9eaf-001617122312/

In jedem dieser Ordner liegen zwei Dateien:

  1. crash.info enthält ein Python-Dictionary mit Traceback und vielen weiteren Informationen. Oft genügt der Blick in die Datei mit dem Pager.

  2. agent_output enthält die vollständige Agentenausgabe, die zum Zeitpunkt des Absturzes aktuell war.

7.3. Eigene Debug-Ausgabe

In den oben gezeigten Beispielen verwenden wir die Funktion print(), um für Sie als Programmierer den Inhalt von Variablen oder die Struktur von Objekten auszugeben. Diese Funktionen für Debug-Ausgaben müssen aus dem fertigen Check-Plugin wieder entfernt werden.

Alternativ zur Entfernung können Sie Ihre Debug-Ausgabe auch nur ausgeben lassen, wenn das Check-Plugin in der Konsole im Debug-Modus aufgerufen wird. Dafür importieren Sie das Debug-Objekt aus der Checkmk-Werkzeugkiste und gegebenenfalls die Formatierungshilfe pprint(). Sie können nun Debug-Ausgaben abhängig vom Wert des Debug-Objektes vornehmen:

from cmk.utils import debug

from pprint import pprint

def check_mystuff(section):
    if debug.enabled():
        pprint(section)

Beachten Sie, dass verbleibende Debug-Ausgaben sparsam verwendet und auf Hinweise beschränkt sein sollten, die den späteren Benutzern bei der Fehlersuche helfen. Offensichtliche und abzusehende Fehler des Benutzers (zum Beispiel, dass die Inhalte der Agentensektion darauf hindeuten, dass das Agentenplugin falsch konfiguriert ist) sollten Sie mit dem Zustand UNKNOWN und aussagekräftigen Hinweisen im Summary beantworten.

7.4. Ungültige Agentenausgabe

Die Frage ist, wie Sie reagieren sollen, wenn die Ausgaben vom Agenten nicht die Form haben, die Sie eigentlich erwarten — egal ob vom Checkmk-Agenten oder per SNMP erhalten. Angenommen, Sie erwarten pro Zeile immer drei Worte. Was sollen Sie tun, falls nur zwei kommen?

Nun, wenn das ein erlaubtes und bekanntes Verhalten des Agenten ist, dann müssen Sie das natürlich abfangen und mit einer Fallunterscheidung arbeiten. Falls das aber eigentlich nicht sein darf, dann tun Sie am besten so, als ob die Zeile immer aus drei Worten besteht, also zum Beispiel mit folgender Parse-Funktion:

def parse_foobar(string_table):
    for foo, bar, baz in string_table:
        # ...

Sollte jetzt mal eine Zeile dabei sein, die nicht aus genau drei Worten besteht, wird eine Exception erzeugt und Sie bekommen den gerade erwähnten sehr hilfreichen Absturzbericht.

Falls Sie auf Schlüssel in einem Dictionary zugreifen, die gelegentlich erwartbar fehlen, kann es natürlich sinnvoll sein, darauf entsprechend zu reagieren. Das kann erfolgen, indem Sie den Service auf CRIT oder UNKNOWN setzen und im Summary einen Hinweis auf die nicht auswertbare Agentenausgabe unterbringen. In jedem Fall ist es besser, Sie verwenden hierfür die get()-Funktion des Dictionaries als die KeyError Exception abzufangen. Denn get() liefert bei Nichtvorhandensein des Schlüssels ein Objekt vom Typ None oder einen optional als zweiten Parameter zu übergebenden Ersatz:

def check_foobar(section):
    foo = section.get("bar")
    if not foo:
        yield Result(state=State.CRIT, summary="Missing key in section: bar")
        return
    # ...

7.5. Fehlende Items

Was ist, wenn der Agent korrekte Daten ausgibt, aber das Item fehlt, das überprüft werden soll? Also zum Beispiel auf diese Art:

def check_foobar(item, section):
    # Try to access the item as key in the section:
    foo = section.get(item)
    if foo:
        yield Result(state=State.OK, summary="Item found in monitoring data")
    # If foo is None, nothing is yielded here

Ist das gesuchte Item nicht dabei, so wird die Schleife durchlaufen und Python fällt am Ende der Funktion einfach hinten raus, ohne dass ein Resultat per yield zurückgegeben wurde. Und das ist genau das Richtige! Denn daran erkennt Checkmk, dass das zu überwachende Item fehlt und erzeugt mit UNKNOWN den richtigen Status und einen passenden Standardtext dazu.

7.6. Test mit Spool-Dateien

Wenn Sie bestimmte Agentenausgaben simulieren wollen, sind Spool-Dateien sehr hilfreich. Diese können Sie nutzen, um sonst schwer nachzustellende Grenzfälle zu testen. Oder Sie verwenden direkt die Agentenausgabe, welche zu einem Absturzbericht geführt hat, zur Prüfung von Änderungen an einem Check-Plugin.

Deaktivieren Sie zunächst Ihr reguläres Agentenplugin, beispielsweise indem Sie ihm die Ausführungsberechtigung entziehen. Erstellen Sie dann eine Datei im Verzeichnis /var/lib/check_mk_agent/spool/, welche die von Ihrem Check-Plugin erwartete Agentensektion (oder erwarteten Agentensektionen) inklusive Sektions-Header enthält und auf Newline endet. Beim nächsten Aufruf des Agenten wird dann der Inhalt der Spool-Datei statt der Ausgabe des Agentenplugins übertragen.

7.7. Alte Check-Plugins werden bei vielen Services langsam

Bei einigen Check-Plugins, die Items nutzen, ist es auf größeren Servern durchaus möglich, dass einige hundert Services erzeugt werden. Wenn keine eigene Parse-Funktion verwendet wird, hat dies zur Folge, dass für jedes der hunderten Items die gesamte Liste von hunderten Zeilen durchlaufen werden muss. Die zum Suchen benötigte Zeit steigt also im Quadrat mit der Zahl der Listenelemente, bei hunderten Services bedeutet das zigtausende Vergleiche. Ist dagegen die geschachtelte Liste in ein Dictionary überführt, steigt der Aufwand für die Suche eines Elements nur linear mit der Größe des Dictionaries.

Im Python-Wiki finden Sie eine Übersicht der Kosten für die Suche in verschiedenen Datentypen, inklusive Erläuterung und O-Notation. Mit der Parse-Funktion reduzieren die Komplexität der Suche von O(n) auf O(1).

Da ältere Versionen dieses Artikels keinen Gebrauch von der Parse-Funktion gemacht haben, sollten Sie derartige Check-Plugins identifizieren und diese auf Verwendung einer Parse-Funktion umschreiben.

8. Dateien und Verzeichnisse

Pfad Bedeutung

~/local/lib/check_mk/base/plugins/agent_based/

Ablageort für selbst geschriebene Check-Plugins.

~/local/share/check_mk/web/plugins/wato/

Ablageort für Ihre Regelsätze für Check-Parameter.

~/local/share/check_mk/web/plugins/metrics/

Ablageort für eigene Metrikdefinitionen.

~/local/share/check_mk/web/plugins/perfometer/

Ablageort für eigene Definitionen von Perf-O-Metern.

~/lib/check_mk/gui/plugins/wato/check_parameters/

Hier finden Sie die Regelsatzdefinitionen von allen mitgelieferten Check-Plugins von Checkmk.

~/lib/check_mk/gui/plugins/wato/utils/__init__.py

In dieser Datei sind die Gruppen der Setup-Oberfläche definiert, in welchen Sie neue Regelsätze ablegen können.

~/lib/check_mk/gui/plugins/metrics/

Hier finden Sie die Metrikdefinitionen der mitgelieferten Plugins.

~/lib/check_mk/gui/plugins/metrics/unit.py

In dieser Datei stehen die vordefinierten Einheiten für Metriken.

/usr/lib/check_mk_agent/plugins/

Dieses Verzeichnis bezieht sich auf einen überwachten Linux-Host. Hier erwartet der Checkmk-Agent für Linux Erweiterungen des Agenten (Agentenplugins).

Auf dieser Seite