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.

Tip

Bei der Entwicklung eines agentenbasierten Check-Plugins benötigen Sie in aller Regel zwei Plugins: das Agentenplugin auf dem überwachten Host, welches die Daten liefert, und das Check-Plugin auf dem Checkmk-Server, das diese Daten auswertet. Beide müssen geschrieben und aufeinander abgestimmt werden. Nur so funktionieren sie hinterher reibungsfrei.

1.1. Die Check-API-Dokumentation

Für die Programmierung der Check-Plugins gibt es seit der Checkmk-Version 2.0.0 eine neu entwickelte Check-API. In der Checkmk-Version 2.3.0 ist die Check-API V2 die aktuelle Version. Wir zeigen Ihnen in diesem Artikel, wie Sie die Check-API Version 2 für die Plugin-Programmierung nutzen können. Hinweise zum Umstieg auf die Check-API Version 2 finden Sie am Ende dieses Artikels im Kapitel Migration.

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

Seite zum Einstieg in die Dokumentation der Check-API Version 2.

Hier finden Sie nicht nur die Dokumentation der Check-API in allen unterstützten Versionen, sondern auch die Dokumentation aller anderen, für die Erstellung von Check-Plugins relevanten APIs. In diesem Artikel nutzen wir nicht nur die Check-API, sondern bei der Erstellung eines Regelsatzes auch die Rulesets-API V1 und bei der Erstellung von Metrikdefinitionen die Graphing-API V1.

1.2. Voraussetzungen

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

  • Kenntnisse in der Programmiersprache Python.

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

  • Übung mit Linux auf der Kommandozeile.

2. Ein Agentenplugin schreiben

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

Hier kreieren wir ein Szenario, in dem es nützlich ist, wenn Checkmk-Server und überwachter Host identisch sind. So ist es uns möglich über Livestatus-Abfragen vom Host Informationen über Host-Gruppen zu erhalten, die der Checkmk-Server zur Verfügung stellt.

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

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

  • Jeder Standort hat sein eigenes Service-Team.

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

Das Ganze läuft in zwei Schritten:

  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 Agentenplugin, Sektions-Header und Check-Plugin den gleichen Namen tragen — auch wenn dies nicht verpflichtend ist.

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

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

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

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

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

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

Tip

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 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 Pfadnamen /usr/lib/check_mk_agent/plugins/myhostgroups erzeugt und ausführbar gemacht.

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

3. Ein einfaches Check-Plugin schreiben

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

3.1. Die Datei vorbereiten

Für Ihre eigenen Check-Plugins finden Sie das Basisverzeichnis vorbereitet in der local-Hierarchie des Instanzverzeichnisses. Dieses lautet ~/local/lib/python3/cmk_addons/plugins/. Das Verzeichnis gehört dem Instanzbenutzer und ist daher für Sie schreibbar.

In diesem Verzeichnis werden die Plugins in Plugin-Familien organisiert, deren Verzeichnisnamen Sie frei wählen können. Zum Beispiel werden alle Plugins, die Cisco-Geräte betreffen, im Ordner cisco abgelegt, oder im Ordner myhostgroups alle Plugins, die Ihre Host-Gruppen betreffen. Diese Konvention ermöglicht es, dass alle Plugins, die zur gleichen Familie gehören, Code gemeinsam nutzen können — und wird genau so auch von den Plugins genutzt, die mit Checkmk ausgeliefert werden.

In diesem Unterverzeichnis <plug-in_family> werden dann nach Bedarf für die verschiedenen APIs weitere Unterverzeichnisse mit vorgegebenem Namen angelegt, z.B. agent_based für die Check-API agentenbasierter Check-Plugins. Weitere Unterverzeichnisse für die Rulesets-API und die Graphing-API werden wir später vorstellen.

Erstellen Sie die beiden Unterverzeichnisse für das neue agentenbasierte Check-Plugin und wechseln Sie am besten danach zum Arbeiten dort hinein:

OMD[mysite]:~$ mkdir -p local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based
OMD[mysite]:~$ cd local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based

Sie können Ihr Check-Plugin mit jedem auf dem Linux-System installierten Texteditor bearbeiten.

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

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

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

from cmk.agent_based.v2 import AgentSection, CheckPlugin, Service, Result, State, Metric, check_levels

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

def discover_myhostgroups(section):
    yield Service()

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

agent_section_myhostgroups = AgentSection(
    name = "myhostgroups",
    parse_function = parse_myhostgroups,
)

check_plugin_myhostgroups = CheckPlugin(
    name = "myhostgroups",
    service_name = "Host group check_mk",
    discovery_function = discover_myhostgroups,
    check_function = check_myhostgroups,
)

Als erstes müssen Sie die für die Check-Plugins nötigen Funktionen und Klassen aus Python-Modulen importieren. Für unser Beispiel wird nur importiert, was im weiteren Verlauf des Artikels genutzt wird oder nützlich sein kann. Dabei wird mit cmk.agent_based.v2 festgelegt, dass die Module aus der Check-API V2 importiert werden:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based/myhostgroups.py
from cmk.agent_based.v2 import AgentSection, CheckPlugin, Service, Result, State, Metric, check_levels

3.2. Die Parse-Funktion schreiben

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

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

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

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

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

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

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

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

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

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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.

Tip

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

3.3. Die Agentensektion erstellen

Damit das Ganze auch etwas bewirken kann, müssen Sie die neue Agentensektion mit der neuen Parse-Funktion erzeugen. Nur so wird sie von Checkmk erkannt und berücksichtigt. Dazu erstellen Sie die Agentensektion als Instanz der Klasse AgentSection:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based/myhostgroups.py
agent_section_myhostgroups = AgentSection(
    name = "myhostgroups",
    parse_function = parse_myhostgroups,
)

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

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

Check-API-Dokumentation für die Klasse 'AgentSection'.

3.4. Das Check-Plugin erstellen

Damit Checkmk erkennen kann, dass es ein neues Check-Plugin gibt, muss dieses erzeugt werden. Dies geschieht durch die Erstellung einer Instanz der Klasse CheckPlugin.

Dabei müssen Sie immer mindestens vier Dinge angeben:

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

Der Name der Instanz muss dabei mit check_plugin_ beginnen. Das sieht dann so aus:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based/myhostgroups.py
check_plugin_myhostgroups = CheckPlugin(
    name = "myhostgroups",
    service_name = "Host group check_mk",
    discovery_function = discover_myhostgroups,
    check_function = check_myhostgroups,
)

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

3.5. Die Discovery-Funktion schreiben

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

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

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

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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/python3/cmk_addons/plugins/myhostgroups/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
No piggyback files for 'localhost'. Skip processing.
Get piggybacked data
+ ANALYSE DISCOVERED HOST LABELS
SUCCESS - Found no new host labels
+ ANALYSE DISCOVERED SERVICES
+ EXECUTING DISCOVERY PLUGINS (1)
  1 myhostgroups
SUCCESS - Found 1 services

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

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

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

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

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

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

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

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

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;localhost;1;1;62;45

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

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

Parse-Funktion erweitern

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

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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 vorherigen Kapitel haben Sie einen sehr einfachen Check gebaut, der auf einem Host einen Service erzeugt. Ein sehr üblicher Fall ist aber auch, dass es von einem Check mehrere Services auf einem Host geben kann.

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

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

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

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

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

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

Da das im vorherigen Kapitel erstellte Check-Plugin myhostgroups zur Überprüfung der Standardgruppe check_mk weiterhin funktionieren soll, bleibt dieses Check-Plugin so, wie es ist. Für die Erweiterung erstellen Sie in der bestehenden Datei myhostgroups.py das neue Check-Plugin myhostgroups_advanced — im ersten Schritt so wie zuvor durch die Erstellung einer Instanz der Klasse CheckPlugin.

Hier finden Sie den alten und markiert den neuen Code:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based/myhostgroups.py
check_plugin_myhostgroups = CheckPlugin(
    name = "myhostgroups",
    service_name = "Host group check_mk",
    discovery_function = discover_myhostgroups,
    check_function = check_myhostgroups,
)

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

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

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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/python3/cmk_addons/plugins/myhostgroups/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/python3/cmk_addons/plugins/myhostgroups/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/python3/cmk_addons/plugins/myhostgroups/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 Erstellung des Check-Plugins, der Discovery-Funktion und der Check-Funktion. Beachten Sie, dass die Funktionen immer vor der Erstellung von Check-Plugins oder Agentensektionen definiert werden müssen, damit es keine Fehler wegen nicht definierter Funktionsnamen gibt.

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

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. Führen Sie dabei die ersten beiden Kommandos nicht für myhostgroups sondern für das neue Check-Plugin myhostgroups_advanced aus.

4.3. Summary und Details

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

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

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

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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/python3/cmk_addons/plugins/myhostgroups/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 im Beispiel für jeden Service der Host-Gruppen zu dem bestehenden Resultat zwei weitere zu definieren. Diese werten den Prozentsatz der Hosts im Zustand UP und der Services im Zustand OK aus. Dabei nutzen Sie die zuvor in der Agentenausgabe und der Parse-Funktion festgelegten zusätzlichen Spalten der Host-Gruppentabelle.

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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/python3/cmk_addons/plugins/myhostgroups/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/python3/cmk_addons/plugins/myhostgroups/agent_based/myhostgroups.py
    hosts_up_perc = 100.0 * num_hosts_up / num_hosts
    yield from check_levels(
        hosts_up_perc,
        levels_lower = ("fixed", (90.0, 80.0)),
        label = "UP hosts",
        notice_only = True,
    )

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

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

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

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

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

Damit ist die Check-Funktion komplett.

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

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 folgen die Zeilen mit dem Funktionsaufruf von check_levels() aus dem vorherigen Abschnitt:

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

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

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

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

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

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

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

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

Mit der so erweiterten Check-Funktion sind die beiden Graphen im Monitoring sichtbar. In der Service-Liste zeigt nun das Symbol 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.

Tip

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:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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,
    )

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

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

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

5. Regelsätze für Check-Parameter

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

Daher soll das Check-Plugin nun so verbessert werden, dass es über die Setup-Oberfläche konfigurierbar ist. Dazu benötigen Sie einen Regelsatz.

Mit der Erstellung eines Regelsatzes für ein Check-Plugin verlassen Sie die Check-Plugin-Entwicklung und wechseln Dateiverzeichnis, Datei und API. Seit Checkmk 2.3.0 unterstützt Sie die Rulesets-API bei der Erstellung solcher Regelsätze für Check-Plugins. Die API-Dokumentation für Regelsätze finden Sie in Ihrer Checkmk-Instanz auf der gleichen Seite wie die der Check-API unter Rulesets > Version 1.

5.1. Neuen Regelsatz definieren

Um einen neuen Regelsatz zu erstellen, legen Sie im Verzeichnis Ihrer Plugin-Familie ~/local/lib/python3/cmk_addons/plugins/myhostgroups/ zuerst ein neues Unterverzeichnis an. Der Name rulesets dieses Unterverzeichnisses ist dabei vorgegeben:

OMD[mysite]:~$ mkdir -p local/lib/python3/cmk_addons/plugins/myhostgroups/rulesets
OMD[mysite]:~$ cd local/lib/python3/cmk_addons/plugins/myhostgroups/rulesets

In diesem Verzeichnis erstellen Sie dann eine Datei für die Definition des Regelsatzes. Der Name der Datei sollte sich an dem des Check-Plugins orientieren und muss wie alle Plugin-Dateien die Endung .py haben. Für unser Beispiel passt der Dateiname ruleset_myhostgroups.py.

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/rulesets/ruleset_myhostgroups.py
from cmk.rulesets.v1 import Label, Title
from cmk.rulesets.v1.form_specs import BooleanChoice, DefaultValue, DictElement, Dictionary, Float, LevelDirection, SimpleLevels
from cmk.rulesets.v1.rule_specs import CheckParameters, HostAndItemCondition, Topic

Zuerst werden die Klassen für die Texte importiert.

Die zweite Zeile trifft eine Auswahl aus den Form Specs, das heißt, den Basiselementen für die GUI, die im Regelsatz genutzt werden, z. B. für die binäre Auswahl (BooleanChoice), die Mehrfachauswahl (Dictionary, DictElement), die Festlegung von Schwellwerten (SimpleLevel, LevelDirection, DefaultValue) und die Eingabe von Gleitkommazahlen (Float). Dabei fordern Sie hier lediglich die logischen Formularelemente an und überlassen Checkmk die Gestaltung der GUI.

Die letzte Zeile importiert die Rule Specs, die das Anwendungsgebiet der Regel in Checkmk bestimmen, also hier die Festlegung von Check-Parametern, die Zuordnung zu Hosts und Items und die Ablage unter einem Thema (topic). Da Ihr Check-Plugin myhostgroups_advanced mehrere Services erzeugt, importieren Sie hier HostAndItemCondition. Falls Ihr Check keinen Service erzeugt, mit anderen Worten kein Item hat, importieren Sie stattdessen HostCondition.

Nun kommen die eigentlichen Definitionen des Formulars für die Eingabe der Check-Parameter. Der Benutzer soll die beiden Schwellwerte für WARN und CRIT festlegen können und zwar getrennt für die Anzahl der Hosts im Zustand UP und der Services im Zustand OK:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/rulesets/ruleset_myhostgroups.py
def _parameter_form():
    return Dictionary(
        elements = {
            "hosts_up_lower": DictElement(
                parameter_form = SimpleLevels(
                    title = Title("Lower percentage threshold for host in UP status"),
                    form_spec_template = Float(),
                    level_direction = LevelDirection.LOWER,
                    prefill_fixed_levels = DefaultValue(value=(90.0, 80.0)),
                ),
                required = True,
            ),
            "services_ok_lower": DictElement(
                parameter_form = SimpleLevels(
                    title = Title("Lower percentage threshold for services in OK status"),
                    form_spec_template = Float(),
                    level_direction = LevelDirection.LOWER,
                    prefill_fixed_levels = DefaultValue(value=(90.0, 80.0)),
                ),
                required = True,
            ),
        }
    )

Hierfür legen Sie eine Funktion an, welche das Dictionary erzeugt. Den Namen der Funktion können Sie frei wählen, er wird nur bei der Erstellung der Regel weiter unten benötigt. Damit die Funktion nicht über die Modulgrenze hinaus sichtbar wird, sollte der Name mit einem Unterstrich beginnen.

Das return Dictionary() ist vorgeschrieben. Innerhalb dessen erstellen Sie mit elements={} die Elemente des Dictionaries, im Beispiel hosts_up_lower und services_ok_lower. Das Parameterformular SimpleLevels dient der Eingabe von festen Schwellwerten. Im Formular bestimmen Sie zuerst den Titel (title) und Gleitkommazahlen für die einzugebenden Werte (form_spec_template). Dass sich der Zustand bei Unterschreitung der Werte ändert, legen Sie mit LevelDirection.LOWER fest.

Schließlich können Sie mit prefill_fixed_levels den Benutzern des Regelsatzes statt leerer Eingabefelder bereits Werte vorgeben. Beachten Sie, dass diese in der GUI angezeigten Werte nicht die Standardwerte sind, die weiter unten bei der Erstellung des Check-Plugins per check_default_parameters gesetzt werden. Wollen Sie die gleichen Standardwerte in der GUI anzeigen lassen, die auch für die Check-Funktion gelten, dann müssen Sie die Werte an beiden Stellen selbst konsistent halten.

Zu guter Letzt erstellen Sie jetzt mithilfe der importierten und selbst definierten Dinge den neuen Regelsatz. Dies geschieht durch die Erstellung einer Instanz der Klasse CheckParameters. Der Name dieser Instanz muss dabei mit rule_spec_ beginnen:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/rulesets/ruleset_myhostgroups.py
rule_spec_myhostgroups = CheckParameters(
    name = "myhostgroups_advanced",
    title = Title("Host group status"),
    topic = Topic.GENERAL,
    parameter_form = _parameter_form,
    condition = HostAndItemCondition(item_title=Title("Host group name")),
)

Dazu die folgenden Erklärungen:

  • Der Name des Regelsatzes name stellt die Verbindung zu den Check-Plugins her. Ein Check-Plugin, das diesen Regelsatz verwenden will, muss beim Erstellen diesen Namen als check_ruleset_name verwenden. Um bei mehreren neuen Regelsätzen den Überblick zu behalten, empfiehlt es sich im Namen einen Präfix zu verwenden.

  • title legt den Titel des Regelsatzes fest, so wie er auch in der Checkmk-GUI erscheint.

  • Das topic legt fest, wo im Setup der Regelsatz auftauchen soll. Mit dem im Beispiel gewählten Wert finden Sie den Regelsatz unter Setup > Services > Service monitoring rules im Kasten Various, wo er in aller Regel gut aufgehoben ist.

  • Als parameter_form geben Sie den Namen der zuvor erstellten Funktion ein.

  • Falls Ihr Check kein Item verwendet, lautet die Bedingung HostCondition und nicht HostAndItemCondition, wie im obigen Beispiel. Mit dem Titel "Host group name" des Items legen Sie das Label in der GUI fest, mit dem Sie die Regel auf bestimmte Host-Gruppen einschränken können.

5.2. Regelsatz testen

Wenn Sie die Datei für den Regelsatz angelegt haben, sollten Sie ausprobieren, ob alles soweit funktioniert — noch ohne Verbindung zum Check-Plugin. Dazu müssen Sie zuerst den Apache der Instanz neu starten, damit die neue Datei gelesen wird. Das macht der Befehl:

OMD[mysite]:~$ omd restart apache

Danach sollte der Regelsatz im Setup auf der oben genannten Seite zu finden sein. Mit der Suchfunktion im Setup-Menü finden Sie den Regelsatz auch — aber erst nach einem Neustart von Redis:

OMD[mysite]:~$ omd restart redis

Der soeben definierte Regelsatz sieht in der GUI so aus:

Der neu erstellte Regelsatz im Setup.

In Kasten Conditions finden Sie das per HostAndItemCondition definierte Eingabefeld Host group name wieder zur Einschränkung der Regel für Host-Gruppen.

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

5.3. Regelsatz mit Check-Plugin verbinden

Der frisch erstellte Regelsatz wird nun mit dem im vorherigen Kapitel vorläufig fertiggestellten Check-Plugin verbunden. Damit die Regel zum Greifen kommt, müssen Sie dem Check-Plugin erlauben, Check-Parameter entgegenzunehmen und ihm sagen, welche Regel benutzt werden soll. Dazu fügen Sie in der Datei des Check-Plugins zwei neue Zeilen bei der Erstellung des Check-Plugins myhostgroups_advanced ein:

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

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

Als zweites tragen Sie noch check_ruleset_name ein, also den Namen des Regelsatzes. So weiß Checkmk aus welchem Regelsatz die Parameter bestimmt werden sollen.

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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 ausgedruckten Zeilen (für jeden Service eine) mit den per Regel festgelegten Werten dann etwa so aus:

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

Wenn alles fertig ist und funktioniert, entfernen Sie die print-Aufrufe wieder. Diese können die interne Kommunikation von Checkmk durcheinanderbringen. Alternativ können Sie die Ausgabe von Debug-Informationen auf deren explizite Anforderung beschränken.

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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 "fixed", (90.0, 80.0) durch die Variablen hosts_up_lower und services_ok_lower ersetzt:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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.0, 100.0),
        notice_only = True,
    )
    services_ok_perc = 100.0 * num_services_ok / num_services
    yield from check_levels(
        services_ok_perc,
        levels_lower = (services_ok_lower),
        metric_name = "services_ok_perc",
        label = "OK services",
        boundaries = (0.0, 100.0),
        notice_only = True,
    )

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

Dieses Problem kann aber behoben werden, wenn bei der Erstellung des Check-Plugins die Standardwerte gesetzt sind:

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

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

Tip

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

6. Darstellung von Metriken anpassen

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based/myhostgroups.py
    yield from check_levels(
        services_ok_perc,
        levels_lower = ("fixed", (90.0, 80.0)),
        metric_name = "services_ok_perc",
        label = "OK services",
        boundaries = (0.0, 100.0),
        notice_only = True,
    )

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

~/local/lib/python3/cmk_addons/plugins/myhostgroups/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.

So, wie für Regelsätze gibt es seit Checkmk 2.3.0 mit der Graphing-API auch eine eigene API für Metriken, Graphen und Perf-O-Meter. Die Dokumentation der Graphing-API finden Sie in Ihrer Checkmk-Instanz auf der gleichen Seite wie die der Check-API unter Graphing > Version 1.

6.1. Neue Metrikdefinition erstellen

Das Vorgehen bei der Erstellung von Regelsätzen und Metrikdefinitionen ist sehr ähnlich. Legen Sie im Verzeichnis Ihrer Plugin-Familie ~/local/lib/python3/cmk_addons/plugins/myhostgroups/ zuerst ein neues Unterverzeichnis mit dem vorgegeben Namen graphing an.

OMD[mysite]:~$ mkdir -p local/lib/python3/cmk_addons/plugins/myhostgroups/graphing
OMD[mysite]:~$ cd local/lib/python3/cmk_addons/plugins/myhostgroups/graphing

In diesem Verzeichnis erstellen Sie dann eine Graphing-Datei, z. B. graphing_myhostgroups.py.

Zunächst kommen wieder einige Importbefehle:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/graphing/graphing_myhostgroups.py
from cmk.graphing.v1 import Title
from cmk.graphing.v1.graphs import Graph, MinimalRange
from cmk.graphing.v1.metrics import Color, DecimalNotation, Metric, Unit
from cmk.graphing.v1.perfometers import Closed, FocusRange, Open, Perfometer

Für unser Beispiel definieren Sie nun eine eigene Metrik für den Prozentsatz der Services im Zustand OK. Dies geschieht durch die Erstellung einer Instanz der Klasse Metric. Der Name dieser Instanz muss dabei mit metric_ beginnen

~/local/lib/python3/cmk_addons/plugins/myhostgroups/graphing/graphing_myhostgroups.py
metric_myhostgroups_services_ok_perc = Metric(
    name = "services_ok_perc",
    title = Title("Percentage of services in OK state"),
    unit = Unit(DecimalNotation("%")),
    color = Color.ORANGE,
)

Hier die Erklärung dazu:

  • Der Metrikname (hier services_ok_perc) muss dem entsprechen, was die Check-Funktion ausgibt.

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

  • Die verfügbaren Einheiten (unit) können Sie in der API-Dokumentation nachlesen, sie enden alle auf Notation, z. B. DecimalNotation, EngineeringScientificNotation oder TimeNotation.

  • Die in Checkmk verwendeten Farbnamen für die Farbdefinition color finden Sie auf GitHub.

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

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

OMD[mysite]:~$ omd restart apache

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

Die neue Metrikdefinition in den Service-Details.

6.2. Graph mit mehreren Metriken

Möchten Sie mehrere Metriken in einem Graphen kombinieren (was oft sehr sinnvoll ist), benötigen Sie eine Graphdefinition, die Sie der im vorherigen Abschnitt erstellten Graphing-Datei hinzufügen können.

Für unser Beispiel sollen die beiden Metriken num_services und num_services_ok in einem Graphen dargestellt werden. Die Metrikdefinitionen dazu werden analog wie im vorherigen Abschnitt für services_ok_perc erstellt und sehen wie folgt aus:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/graphing/graphing_myhostgroups.py
metric_myhostgroups_services = Metric(
    name = "num_services",
    title = Title("Number of services in group"),
    unit = Unit(DecimalNotation("")),
    color = Color.PINK,
)

metric_myhostgroups_services_ok = Metric(
    name = "num_services_ok",
    title = Title("Number of services in OK state"),
    unit = Unit(DecimalNotation("")),
    color = Color.BLUE,
)

Fügen Sie nun einen Graphen an, der diese beiden Metriken als Linien einzeichnet. Dies geschieht über eine Instanz der Klasse Graph, wobei der Instanzname auch hier wieder mit einem vorgegebenen Präfix (graph_) beginnen muss:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/graphing/graphing_myhostgroups.py
graph_myhostgroups_combined = Graph(
    name = "services_ok_comparison",
    title = Title("Services in OK state out of total"),
    simple_lines=[ "num_services", "num_services_ok" ],
    minimal_range=MinimalRange(0, 50),
)

Der Parameter minimal_range beschreibt, was die vertikale Achse des Graphen mindestens abdeckt, unabhängig von den Werten der Metrik. Die vertikale Achse wird erweitert, wenn die Werte den minimalen Bereich überschreiten, aber sie wird nie kleiner sein.

Das Resultat ist der kombinierte Graph in der Checkmk-GUI:

Der Graph zeigt beide Metriken in den Service-Details.

6.3. Metriken im Perf-O-Meter

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

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

Um so ein Perf-O-Meter zu erstellen, benötigen Sie eine weitere Instanz, diesmal der Klasse Perfometer, deren Name mit dem Präfix perfometer_ beginnt:

~/local/lib/python3/cmk_addons/plugins/myhostgroups/graphing/graphing_myhostgroups.py
perfometer_myhostgroups_advanced = Perfometer(
    name = "myhostgroups_advanced",
    focus_range = FocusRange(Closed(0), Closed(100)),
    segments = [ "services_ok_perc" ],
)

Perf-O-Meter sind etwas trickreicher als Graphen, da es keine Legende gibt. Daher ist die Darstellung des Wertebereichs schwierig, insbesondere, wenn es um die Darstellung absoluter Werte geht. Einfacher ist es, einen Prozentsatz darzustellen. Dies ist auch im obigen Beispiel der Fall:

  • In segments werden die Metriknamen eingetragen, hier der Prozentsatz der Services im Zustand OK.

  • In focus_range wird die untere und obere Grenze eingetragen. Geschlossene (Closed) Grenzen sind vorgesehen für Metriken, die nur Werte zwischen den angegebenen Grenzen annehmen können (hier also 0 und 100).

Weitere, noch ausgefeiltere Möglichkeiten zur Umsetzung von Metriken in Perf-O-Metern finden Sie in der Dokumentation der Graphing-API, z. B. mit den Klassen Bidirectional und Stacked, die es erlauben mehrere Perf-O-Meter in einem darzustellen.

Die Graphing-Datei dieses Kapitels finden Sie wieder auf GitHub. Sie enthält unter anderem die Metrikdefinitionen der vier gemessenen und der zwei berechneten Werte.

7. Zahlen formatieren

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

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

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

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

from cmk.agent_based.v2 import render

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

7.1. Zeiten, Zeiträume, Frequenzen

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

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

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

Aufruf Ausgabe

render.date(0)

1970-01-01

render.datetime(0)

1970-01-01 01:00:00

render.date(1700000000)

2023-11-14

render.datetime(1700000000)

2023-11-14 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

7.2. Bytes

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

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

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

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

Aufruf Ausgabe

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

7.3. Bandbreiten, Datenraten

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

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

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

Aufruf Ausgabe

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

7.4. Prozentsätze

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

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

Aufruf Ausgabe

render.percent(0.004)

<0.01%

render.percent(18.5)

18.50%

render.percent(123)

123.00%

7.5. Zusammenfassung

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

Funktion Eingabe Beschreibung Beispielausgabe

date()

Unix-Zeit

Datum

2023-11-14

datetime()

Unix-Zeit

Datum und Uhrzeit

2023-11-14 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%

8. Fehler beheben

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

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

  • Sie konvertieren mit int() einen String in eine Zahl, aber der String enthält keine Zahl, zum 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.

8.1. Exceptions und Absturzberichte in der GUI

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

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

Durch einen Klick auf das Symbol 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.

8.2. Exceptions auf der Kommandozeile ansehen

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

OMD[mysite]:~$ cmk --detect-plugins=myhostgroups_advanced localhost
Error in agent based plugin: cmk_addons.plugins.myhostgroups.agent_based.myhostgroups: name 'agent_section_myhostgroups' is not defined

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

OMD[mysite]:~$ cmk --debug --detect-plugins=myhostgroups_advanced localhost
Traceback (most recent call last):
  File "/omd/sites/mysite/lib/python3/cmk/discover_plugins/_python_plugins.py", line 195, in add_from_module
    module = importer(mod_name, raise_errors=True)
             ^^^^^^^^^^^^^
  File "/omd/sites/mysite/lib/python3/cmk/discover_plugins/_python_plugins.py", line 156, in _import_optionally
    return importlib.import_module(module_name)
           ^^^^^^^^^^^^
  File "/omd/sites/mysite/lib/python3.12/importlib/init.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/omd/sites/mysite/local/lib/python3/cmk_addons/plugins/myhostgroups/agent_based/myhostgroups.py", line 110, in <module>
    agent_section_myhostgroups == AgentSection(
    ^^^^^^^^^^
NameError: name 'agent_section_myhostgroups' is not defined

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

OMD[mysite]:~$ ls -lhtr ~/var/check_mk/crashes/check/ | tail -n 5
drwxrwxr-x 2 mysite mysite 4.0K Aug  6 17:56 7b59a49e-540c-11ef-9595-7574c603ce8d/
drwxrwxr-x 2 mysite mysite 4.0K Aug  6 17:56 7c8870c0-540c-11ef-9595-7574c603ce8d/
drwxrwxr-x 2 mysite mysite 4.0K Aug  6 17:56 7cf9626c-540c-11ef-9595-7574c603ce8d/
drwxrwxr-x 2 mysite mysite 4.0K Aug  6 17:56 7d192d68-540c-11ef-9595-7574c603ce8d/
drwxrwxr-x 2 mysite mysite 4.0K Aug  6 17:56 7e2d5ec2-540c-11ef-9595-7574c603ce8d/

In jedem dieser Ordner liegen zwei Dateien:

  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.

8.3. Eigene Debug-Ausgabe

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

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

from cmk.utils import debug
from pprint import pprint

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

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

8.4. Ungültige Agentenausgabe

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

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

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

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

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

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

8.5. Fehlende Items

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

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

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

8.6. Test mit Spool-Dateien

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

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

8.7. Alte Check-Plugins werden bei vielen Services langsam

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

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

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

9. Migration

Die in der Checkmk-Version 2.0.0 eingeführte Check-API V1 wird in der Version 2.3.0 weiterhin unterstützt, aber nicht mehr in der nächsten Version 2.4.0. Wir empfehlen daher, die Zeit bis zur Freigabe der Version 2.4.0 zu nutzen und alle Check-Plugins auf die neuen APIs zu migrieren.

Die folgenden Informationen helfen Ihnen bei der Migration von Check-Plugins der Check-API V1 auf die V2:

  • Die neue Verzeichnisstruktur und Namenskonvention zur Ablage der Dateien für alle Plugin-APIs finden Sie in der API-Dokumentation auf der Hauptseite Checkmk’s Plug-in APIs.

  • Die Zusammenfassung der Änderungen in der Check-API V2 steht ebenfalls in der API-Dokumentation in Checkmk’s Plug-in APIs > Agent based ("Check API") > Version 2 > New in this version. Dort finden Sie auch den Link zu einem GitHub-Commit, der das bestehende Check-Plugin apt zur Check-API V2 migriert.

  • Auf GitHub finden Sie im treasures-Verzeichnis von Checkmk Skripte, die Ihnen bei der Migration auf die neuen APIs helfen.

Ein abschließender Hinweis für Benutzer, die noch Check-Plugins haben, die mit der alten, bis zur Checkmk-Version 1.6.0 gültigen Check-API entwickelt wurden. Diese Check-Plugins werden in der Checkmk-Version 2.3.0 nicht mehr offiziell unterstützt. Details finden Sie im Werk #16689. Wie Sie solche, veralteten Check-Plugins auf die Check-API V1 migrieren, erfahren Sie im Blogpost zur Migration.

10. Dateien und Verzeichnisse

Pfad Bedeutung

~/local/lib/python3/cmk_addons/plugins/

Basisverzeichnis zur Ablage von Plugin-Dateien.

~/local/lib/python3/cmk_addons/plugins/<plug-in_family>/agent_based/

Ablageort für nach der Check-API V2 selbst geschriebene Check-Plugins.

~/local/lib/python3/cmk_addons/plugins/<plug-in_family>/rulesets/

Ablageort für nach der Rulesets-API erstellte Regelsatzdateien.

~/local/lib/python3/cmk_addons/plugins/<plug-in_family>/graphing/

Ablageort für nach der Graphing-API erstellte Graphing-Dateien.

/usr/lib/check_mk_agent/plugins/

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

Auf dieser Seite