Wir nutzen Cookies, um Ihnen eine optimale Nutzung dieser Webseite zu ermöglichen. Mehr Informationen finden Sie im Datenschutzhinweis. Wir nehmen an, dass Sie damit einverstanden sind, falls Sie diese Webseite weiter besuchen.

Ihre Cookie-Einstellungen
Ihre Einstellungen wurden aktualisiert.
Damit die Änderungen wirksam werden, löschen Sie bitte Ihre Browser-Cookies und den Cache und laden dann die Seite neu.

Eigene Checkplugins schreiben

Checkmk Manual

Auf dieser Seite

Suche im Handbuch

Dieser Artikel ist noch nicht fertig und nur ein Entwurf!

1. Einleitung

Checkmk umfasst fast 2.000 fertige Checkplugins 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 gibt es immer wieder Situationen, in denen ein Gerät, eine Anwendung oder einfach nur eine bestimmte Metrik, die für Sie wichtig ist, noch von keinem dieser Plugins erfasst ist – vielleicht auch einfach deshalb, weil es sich dabei um etwas handelt, dass in Ihrer Firma entwickelt wurde und es daher niemand anders haben kann.

1.1. Muss es immer ein echtes Plugin sein?

Welche Möglichkeiten haben Sie also, hier dennoch eine sinnvolle Überwachung zu implementieren? Nun – natürlich können Sie sich an unseren Support wenden und ein geeignetes Plugin entwickeln lassen. Aber Sie können sich auch selbst helfen. Dabei haben Sie erstmal vier Möglichkeiten:

Methode So geht's Vorteile Nachteile
Localcheck Checkmk-Agent um einfaches Skript erweitern Geht sehr einfach, ist in allen Programmiersprachen möglich, welche das Betriebssystem des überwachten Hosts anbietet, unterstützt sogar Serviceerkennung Konfiguration der Schwellwerte nur beim Agenten selbst, für komplexere Dinge unkomfortabel, keine Unterstützung für SNMP.
Nagios-kompatibles Checkplugin Plugin per MRPE vom Windows- oder Linux-Agenten aufrufen lassen. Zugriff auf alle vorhandenen Nagios-Plugins, auch hier freie Wahl der Programmiersprache Konfiguration der Schwellwerte nur beim Agenten selbst, Keine SNMP-Unterstützung durch Checkmk, keine Serviceerkennung möglich
Logmeldungen auswerten Meldungen überwachen per Event Console Keine Entwicklung notwendig sondern nur aufstellen von Regeln in der Event Console Geht nur, wenn passende Logmeldungen vorhanden sind, kein gesichterter aktueller Status, kein Erfassen von Metriken, keine konfigurierbaren Schwellwerte
Echtes Checkmk-Plugin Wird in diesem Artikel erklärt Fügt sich zu 100% in Checkmk ein, automatische Serviceerkennung, zentrale Konfiguration der Schwellwerte über die grafische Oberfläche, sehr performant, unterstützt SNMP, automatische Host- und Servicelabels möglich, unterstützt HW/SW-Inventur, Unterstützung durch Standardbibliothken von Checkmk. Erfordert mehr Einarbeitungszeit sowie Kenntnisse in der Programmsprache Python

Dieser Artikel zeigt Ihnen, wie Sie echte Checkmk-Checkplugins entwickeln können – mit allem was dazugehört. Dabei zeigen wir Ihnen, wie Sie die in Version 2.0 von Checkmk neu entwickelte API für die Pluginprogrammierung nutzen. Wenn Sie Plugins entwickeln möchten, welche auch auf älteren Checkmk-Versionen funktionieren sollen, können Sie die auf die früheren Handbuchartikel zurückgreifen. Diese werden allerdings schon seit längerem nicht mehr gepflegt und sind nur auf Englisch verfügbar.

1.2. Was hat sich seit der alten API geändert?

Haben Sie schon Erfahrung mit dem Entwickeln von Checkplugins für die Checkmk-Version 1.6.0 oder früher? Dann finden Sie hier eine knappe Übersicht über alle Änderungen, welche die ab 2.0 verfügbare neue Check-API mit sich bringt:

  • Plugins sind jetzt Python-3-Module und die Dateien müssen mit .py enden.
  • Die eigenen Plugins liegen jetzt im Verzeichnis local/lib/check_mk/base/plugins/agent_based.
  • Am Anfang der Datei brauchen Sie nun mindestens eine spezielle import-Anweisung.
  • Die Sektionen und die eigentlichen Checks werden getrennt registiert. Dazu gibt es die neuen Funktionen register.agent_section und register.check_plugin.
  • Etliche Funktions- und Argumentnamen wurden umbenannt. Unter anderem wird jetzt immer konsequent von Discovery gesprochen (früher: Inventory).
  • Die Discovery-Funktion (vormals Inventory-Funktion) und auch die Check-Funktion müssen nun immer als Generatoren arbeiten (also yield verwenden).
  • Die Namen der Argumente der deklarierten Funktionen sind jetzt fest vorgegeben.
  • Anstelle der SNMP-Scanfunktion schreiben Sie eine Deklaration, welche OIDs mit welchen Werten erwartet werden.
  • Die Funktionen zum Darstellen von Zahlen wurden neu strukturiert (z. B. wird render_bytes_human_readable zu render.bytes).
  • Es gibt nun eine eigene Methode, mit der Checks andere ausschließen können (superseeds). Das wird nicht mehr in der SNMP-Scanfunktion gemacht.
  • Die Hilfsfunktionen für die Arbeit mit Countern, Raten und Durchschnitten haben sich geändert.
  • Anstelle von magischen Rückgabewerten wie z. B. 2 für CRIT gibt es jetzt Konstanten (z. B. State.CRIT).
  • Viele mögliche Programmierfehler in Ihrem Plugin erkennt Checkmk jetzt sehr früh und kann Sie gleich darauf hinweisen.

1.3. Wird die alte API noch unterstützt

Ja, die bis zu Version 1.6.0 von Checkmk gültige API für die Entwicklung von Checkplugins wird noch etliche Jahre unterstützt werden, da mit dieser sehr sehr viele Plugins entwickelt wurden. Während dieser Zeit wird Checkmk bei APIs parallel anbieten.

Trotzdem empfehlen wir für die Enwicklung von neuen Plugins die neue API, da diese konsistenter und logischer ist, besser dokumentiert und langfristig am zukunftssichersten.

1.4. Verschiedene Arten von Agenten

Checkplugins werten die Daten der Checkmk-Agenten aus. Und deswegen sollten wir, bevor wir uns in Geschehen stürzen, uns zunächst einen Überblick darüber verschaffen, welche Arten von Agenten Checkmk eigentlich kennt:

Checkmk-Agent Hier werten die Plugins Daten aus, welcher der Checkmk-Agent für Linux, Windows oder andere Betriebssysteme sendet. Damit werden Betriebssystemparameter und Anwendungen überwacht und teilweise auch Serverhardware. Jedes neue Checkplugin erfordert eine Erweiterung des Agenten in Form eines Agent-Plugins, damit dieser die nötigen Daten bereitstellt.
Spezialagent / API-Integration Einen Spezialagenten benötigen Sie, wenn Sie weder mit dem normalen Checkmk-Agenten noch per SNMP an die Daten kommen, welche für das Monitoring relevant sind. Der häufigste Fall ist das Abfragen von HTTP-basierten APIs. Beispiele sind die Überwachung von AWS, Azure oder VMware. Hier schreiben Sie ein Skript, welches direkt auf dem Checkmk-Server läuft, sich mit der API verbindet, und Daten im gleichen Format ausgibt, wie dies ein Agentenplugin tun würde.
SNMP Bei der Überwachung via SNMP benötigen Sie keine Erweiterung eines Agenten sondern werten Daten aus, welche Checkmk von dem zu überwachenden Gerät per SNMP abruft, welche dieses standardmäßig bereitstellt. Checkmk unterstützt Sie dabei und übernimmt sämtliche Details und Sonderheiten des SNMP-Protokolls. Eigentlich gibt es auch hier einen Agenten: nämlich den auf dem überwachten System vorinstallierten SNMP-Agenten.
Aktiver Check Dieser Checktyp bildet eine Sonderrolle. Hier schreiben Sie zunächst ein klassisches Nagios-kompatibles Plugin, welches für die Ausführung auf dem Checkmk-Server bestimmt ist und von dort aus mit einem Netzwerkprotokoll direkt einen Dienst auf dem Zielgerät abfragt. Das prominenteste Beispiel ist das Plugin check_http, mit welchem Sie Webserver und Webseiten überwachen können. Dieses Plugin können Sie dann so in Checkmk integrieren, dass man es wie gewohnt per Regeln einrichten kann.

1.5. Voraussetzungen

Wenn Sie Lust haben, sich mit dem Programmieren von Checkplugins zu befassen, benötigen Sie die folgenden Voraussetzungen:

  • Kenntnisse in der Programmiersprache Python
  • Erfahrung mit Checkmk, vor allem was das Thema Agenten und Checks betrifft
  • etwas Übung mit Linux auf der Kommandozeile

Als Vorbereitung sind außerdem folgende Artikel gut:

2. Ein erstes einfaches Checkplugin

Nach dieser langen Einleitung wird es Zeit, dass wir unser erstes einfaches Checkplugin programmieren. Als Beispiel nehmen wir eine einfache Überwachung für Linux. Denn da Checkmk selbst auf Linux läuft, ist es sehr wahrscheinlich, dass Sie auch auf ein Linuxsystem Zugriff haben.

Das Checkplugin soll einen neuen Service anlegen, welcher erkennt, ob auf einem Linuxserver jemand einen USB-Stick eingesteckt hat. In diesem Fall soll er kritisch werden. Vielleicht werden Sie sowas sogar nützlich finden, aber es ist wirklich nur ein vereinfachtes Beispiel und möglicherweise auch nicht ganz wasserdicht programmiert. Denn darum geht es hier erstmal nicht.

Das Ganze läuft in zwei Schritten:

  1. Wir finden heraus, mit welchem Linuxbefehl man sehen kann, ob ein USB-Stick eingesteckt ist, und erweitern den Linux-Agenten um ein kleines Skript, welches diesen Befehl aufruft.
  2. Wir schreiben in der Checkmk-Instanz ein Checkplugin, welches diese Daten auswertet.

Und los geht's...

2.1. Den richtigen Befehl finden

Am Anfang jeder Checkprogrammierung steht: die Recherche! Das bedeutet, dass wir herausfinden, wie wir überhaupt an die Informationen kommen, die wie für die Überwachung brauchen. Bei Linux sind das oft Kommandozeilenbefehle, bei Windows hilft die PowerShell, VBScript oder WMI und bei SNMP müssen wir die richtigen OIDs finden (dazu gibt es einen eigenen Abschnitt).

Für das Herausfinden des richtigen Befehls gibt es leider kein allgemeines Vorgehen und so will ich mich auch nicht allzulange mit dem Thema aufhalten, erkläre aber kurz, wie das mit dem USB-Stick funktioniert.

Zunächst loggen wir uns also auf dem zu überwachenden Host ein. Unter Linux läuft der Agent per Default als root-Benutzer. Deswegen machen wir auch alle unsere Tests einfach als root. Für unsere Aufgabe mit dem USB-Stick gibt es praktischerweise symbolische Links im Verzeichnis /dev/disk/by-id. Diese zeigen auf alle Linux-Block-Devices. Und ein solches ist auch ein eingesteckter USB-Stick. Außerdem kann man an der ID am Präfix usb- erkennen, wenn ein Block-Device ein USB-Gerät ist. Folgender Befehl listet alle Einträge in diesem Verzeichnis auf:

root@linux# ls -l /dev/disk/by-id/
total 0
lrwxrwxrwx 1 root root  9 May 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191 -> ../../sda
lrwxrwxrwx 1 root root 10 May 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part1 -> ../../sda1
lrwxrwxrwx 1 root root 10 May 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part2 -> ../../sda2
lrwxrwxrwx 1 root root 10 May 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part3 -> ../../sda3
lrwxrwxrwx 1 root root 10 May 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part4 -> ../../sda4
lrwxrwxrwx 1 root root 10 May 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part5 -> ../../sda5
lrwxrwxrwx 1 root root  9 May 14 11:21 wwn-0x5002538655584d30 -> ../../sda
lrwxrwxrwx 1 root root 10 May 14 11:21 wwn-0x5002538655584d30-part1 -> ../../sda1
lrwxrwxrwx 1 root root 10 May 14 11:21 wwn-0x5002538655584d30-part2 -> ../../sda2
lrwxrwxrwx 1 root root 10 May 14 11:21 wwn-0x5002538655584d30-part3 -> ../../sda3
lrwxrwxrwx 1 root root 10 May 14 11:21 wwn-0x5002538655584d30-part4 -> ../../sda4
lrwxrwxrwx 1 root root 10 May 14 11:21 wwn-0x5002538655584d30-part5 -> ../../sda5

So. Und das Ganze jetzt mit eingestecktem USB-Stick:

root@linux# ls -l /dev/disk/by-id/
total 0
lrwxrwxrwx 1 root root  9 Mai 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191 -> ../../sda
lrwxrwxrwx 1 root root 10 Mai 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part1 -> ../../sda1
lrwxrwxrwx 1 root root 10 Mai 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part2 -> ../../sda2
lrwxrwxrwx 1 root root 10 Mai 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part3 -> ../../sda3
lrwxrwxrwx 1 root root 10 Mai 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part4 -> ../../sda4
lrwxrwxrwx 1 root root 10 Mai 14 11:21 ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part5 -> ../../sda5
lrwxrwxrwx 1 root root  9 Mai 14 12:15 usb-SCSI_DISK-0:0 -> ../../sdc
lrwxrwxrwx 1 root root 10 Mai 14 12:15 usb-SCSI_DISK-0:0-part1 -> ../../sdc1
lrwxrwxrwx 1 root root 10 Mai 14 12:15 usb-SCSI_DISK-0:0-part2 -> ../../sdc2
lrwxrwxrwx 1 root root  9 Mai 14 11:21 wwn-0x5002538655584d30 -> ../../sda
lrwxrwxrwx 1 root root 10 Mai 14 11:21 wwn-0x5002538655584d30-part1 -> ../../sda1
lrwxrwxrwx 1 root root 10 Mai 14 11:21 wwn-0x5002538655584d30-part2 -> ../../sda2
lrwxrwxrwx 1 root root 10 Mai 14 11:21 wwn-0x5002538655584d30-part3 -> ../../sda3
lrwxrwxrwx 1 root root 10 Mai 14 11:21 wwn-0x5002538655584d30-part4 -> ../../sda4
lrwxrwxrwx 1 root root 10 Mai 14 11:21 wwn-0x5002538655584d30-part5 -> ../../sda5

2.2. Die Daten entschlacken

Eigentlich wären wir damit fertig und könnten diese ganze Ausgabe per Checkmk-Agent zum Checkmk-Server transportieren und dort analysieren lassen. Denn im Checkmk gilt immer folgende Empfehlung: lassen Sie die komplexe Arbeit immer den Server erledigen. Halten Sie das Agentenplugin so einfach wie möglich.

Aber: Hier ist trotzdem noch zuviel heiße Luft drin. Es ist immer gut, unnötige Daten nicht zu übertragen. Das spart Netzwerkverkehr, Speicher, Rechenzeit und macht alles auch übersichtlicher. Das geht besser!

Als erstes können wir das -l weglassen. Damit ist die Ausgabe von ls schon deutlich schlanker:

root@linux# ls /dev/disk/by-id/
ata-APPLE_SSD_SM0512F_S1K5NYBF810191        ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part5  wwn-0x5002538655584d30-part3
ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part1  wwn-0x5002538655584d30-part4                ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part2
wwn-0x5002538655584d30                      wwn-0x5002538655584d30-part5                ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part3
wwn-0x5002538655584d30-part1                ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part4  wwn-0x5002538655584d30-part2

Jetzt wiederum stört der mehrspaltige Aufbau. Dieser ist aber nur deswegen, weil der ls-Befehl erkennt, dass er in einem interaktiven Terminal läuft. Später als Teil vom Agenten wird er die Daten einspaltig ausgeben. Das können wir aber auch ganz einfach hier mit der Option -1 erzwingen:

root@linux# ls -1 /dev/disk/by-id/
ata-APPLE_SSD_SM0512F_S1K5NYBF810191
ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part1
ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part2
ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part3
ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part4
ata-APPLE_SSD_SM0512F_S1K5NYBF810191-part5
wwn-0x5002538655584d30
wwn-0x5002538655584d30-part1
wwn-0x5002538655584d30-part2
wwn-0x5002538655584d30-part3
wwn-0x5002538655584d30-part4
wwn-0x5002538655584d30-part5

Wenn Sie genau hinsehen, werden Sie nicht nur die Blockgeräte selbst sehen, sondern auch dort vorhandene Partitionen. Dies sind die Einträge, die auf -part1, -part2 usw. enden. Diese brauchen wir für unseren Check nicht und bekommen sie ganz einfach mit einem grep. Dort nehmen wir die Option -v für eine negative Logik. Hier sieht man jetzt auch viel deutlicher, dass es in meinem Beispiel eigentlich genau vier Geräte sind, falls der USB-Stick eingesteckt ist:

root@linux# ls /dev/disk/by-id/ | grep -v -- -part
ata-APPLE_SSD_SM0512F_S1K5NYBF810191
usb-SCSI_DISK-0:0
wwn-0x5002538655584d30

Perfekt! Jetzt haben wir eine übersichtliche Liste aller Blockgeräte, die mit einem einfachen Befehl ermittelt wird. Mehr brauchen wir nicht.

Das -1 hab ich jetzt hier wieder weggelassen, weil ls jetzt in eine Pipe schreibt und von sich aus einspaltig ausgibt. Und grep braucht das --, da es sonst das Wort -part als die vier Optionen -p, -a, -r und -t interpretieren würde.

Übrigens: Warum greppen wir nicht gleich noch nach usb? So dass nur noch USB-Geräte übertragen werden? Nun, natürlich könnten wir das tun. Aber zum Einen wird dann unser Beispiel zunehmend langweilig und außerdem ist es irgendwie beruhigender, im Normalfall irgendeinen Inhalt in der Sektion zu bekommen und nicht einfach nur nichts. So kann man auf dem Checkmk-Server sofort erkennen, dass das Agentenplugin korrekt funktioniert.

2.3. Den Befehl in den Agenten einbauen

Damit wir vom Checkmk-Server aus diese Daten abrufen können, müssen wir den neuen Befehl Teil vom Checkmk-Agenten auf dem überwachten System machen. Wir könnten dazu natürlich einfach dort die Datei /bin/bin/check_mk_agent editieren und das einbauen. Das hätte dann aber den Nachteil, dass bei einem Softwareupdate des Agenten unser Befehl wieder verschwindet, weil die Datei ersetzt wird.

Besser ist daher, wenn wir ein Agentenplugin machen. Das ist sogar noch einfacher. Alles was wir brauchen, ist eine ausführbare Datei mit unserem Befehl im Verzeichnis /usr/lib/check_mk_agent/plugins.

Und noch eins ist wichtig: Wir können unsere Daten nicht einfach so ausgeben. Was wir noch brauchen, ist eine Sektionskopf (section header). Das ist eine speziell formatierte Zeile, in der der Name unseres neuen Checks steht. An diesen Sektionsköpfen kann Checkmk später erkennen, wo die Daten des Plugins beginnen und die des vorherigen aufhören.

Also brauchen wir jetzt erstmal einen sinnvollen Namen für unseren neuen Check. Dieser Name muss aus Kleinbuchstaben, Unterstrichen und Ziffern bestehen und eindeutig sein. Es darf also nicht schon eine Sektion mit diesem Namen geben. 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 | head -n 20
3par_capacity                     tcp    HPE 3PAR: Capacity
3par_cpgs                         tcp    HPE 3PAR: CPGs
3par_cpgs.usage                   tcp    HPE 3PAR: CPGs Usage
3par_hosts                        tcp    HPE 3PAR: Hosts
3par_ports                        tcp    HPE 3PAR: Ports
3par_remotecopy                   tcp    HPE 3PAR: Remote Copy
3par_system                       tcp    HPE 3PAR: System
3par_volumes                      tcp    HPE 3PAR: Volumes
3ware_disks                       tcp    3ware ATA RAID Controller: State of Disks
3ware_info                        tcp    3ware ATA RAID Controller: General Information
3ware_units                       tcp    3ware ATA RAID Controller: State of Units
acme_agent_sessions               snmp   ACME Devices: Agent Sessions
acme_certificates                 snmp   ACME Devices: Certificates
acme_fan                          snmp   ACME Devices: Fans
acme_powersupply                  snmp   ACME Devices: Power Supplies

Wählen wir für unser Beispiel den Namen linux_usbstick. In diesem Fall muss der Sektionskopf so aussehen:

<<<linux_usbstick>>>

Den können wir einfach mit echo ausgeben. Wenn wir dann noch den „Shabang“ nicht vergessen (das ist kein giftiger Stachel aus dem Wüstenplaneten sondern 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 Shell ausführen soll, dann sieht unser Plugin so aus:

/usr/lib/check_mk_agent/plugins/linux_usbstick
#!/bin/sh
echo '<<<linux_usbstick>>>'
ls /dev/disk/by-id/ | grep -v -- -part

Als Dateiname hab ich jetzt einfach auch linux_usbstick verwendet, auch wenn der eigentlich egal ist. Aber eines ist noch sehr wichtig: Machen Sie die Datei ausführbar!

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

Natürlich können Sie das Plugin ganz einfach von Hand ausprobieren, indem Sie den kompletten Pfad als Befehl eingeben:

root@linux# /usr/lib/check_mk_agent/plugins/linux_usbstick
<<<linux_usbstick>>>
ata-APPLE_SSD_SM0512F_S1K5NYBF810191
wwn-0x5002538655584d30

2.4. Agent ausprobieren

Wie immer ist am wichtigsten Test und Fehlersuche. Am besten gehen Sie in drei Schritten vor:

  1. Plugin solo ausprobieren. Das haben wir gerade gemacht.
  2. Agent aus ganzes lokal testen.
  3. Agent vom Checkmk-Server aus abrufen.

Das lokale Testen des Agenten ist sehr einfach. Rufen Sie einfach als root den Befehl check_mk_agent auf. Irgendwo in der Ausgabe muss die neue Sektion erscheinen:

root@linux# check_mk_agent

Hier ist ein Ausschnitt der Ausgabe, welcher die neue Sektion enthält:

<<<lnx_thermal:sep(124)>>>
thermal_zone0|-|BAT0|35600
thermal_zone1|-|x86_pkg_temp|81000|0|passive|0|passive
<<<local>>>
<<<linux_usbstick>>>
ata-APPLE_SSD_SM0512F_S1K5NYBF810191
wwn-0x5002538655584d30
<<<lnx_packages:sep(124):persist(1589463274)>>>
accountsservice|0.6.45-1ubuntu1|amd64|deb|-||install ok installed
acl|2.2.52-3build1|amd64|deb|-||install ok installed
acpi|1.7-1.1|amd64|deb|-||install ok installed

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

Der dritte Test ist dann direkt von der Checkmk-Instanz aus. Nehmen Sie den Host ins Monitoring auf (z. B. als myserver01) und rufen Sie die Agentendaten dann mit cmk -d ab. Hier sollte die gleiche Ausgabe kommen:

OMD[mysite]:~$ cmk -d myserver01 | less

Übrigens: grep hat mit -A eine Option, nach jedem Treffer noch einige Zeilen mehr auszugeben. Damit können Sie bequem die Sektion suchen und ausgeben:

root@linux# cmk -d heute | grep -A5 '^<<<linux_usbstick'
<<<linux_usbstick>>>
ata-APPLE_SSD_SM0512F_S1K5NYBF810191
wwn-0x5002538655584d30
<<<lnx_packages:sep(124):persist(1589463559)>>>
accountsservice|0.6.45-1ubuntu1|amd64|deb|-||install ok installed

Wenn das funktioniert, ist Ihr Agent vorbereitet! Und was haben wir dafür gemacht? Wir haben lediglich ein dreizeiliges Skript mit dem Pfad /usr/lib/check_mk_agent/plugins/usbstick erzeugt und ausführbar gemacht!

Alles was nun folgt, geschieht nur noch auf dem Checkmk-Server: Dort schreiben wir das eigentliche Checkplugin.

2.5. Die Sektion deklarieren

Das Vorbereiten des Agenten ist zwar der komplizierteste Teil, aber nur die halbe Miete. Jetzt müssen wir Checkmk noch beibringen, wie es mit den Informationen und der neuen Agentensektion umgehen soll, welches Services es erzeugen soll, wann diese auf OK oder CRIT gehen sollen usw. All dies machen wir durch die Programmierung eines Checkplugins in Python.

Für Ihre eigenen Checkplugins finden Sie ein Verzeichnis vorbereitet in der local-Hierarchie des Instanzverzeichnisses. Dieses lautet local/lib/check_mk/base/plugins/agent_based/. Hier im Pfad bedeutet base den Teil von Checkmk, der für das eigentlich Monitoring und die Alarmierung zuständig ist. Das agent_based ist für alle Plugins, die sich auf den Checkmk-Agenten beziehen (also z. B. nicht Alarmierungsplugins). Am einfachsten, Sie wechseln zum Arbeiten dort hinein:

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

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

Legen wir also unser 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 von Checkmk handelt es sich bei den Plugins immer um echte Pythonmodule.

Als erstes müssen wir die für die Plugins nötigen Funktionen aus anderen Pythonmodulen importieren. Die einfachste Methode dafür ist die mit einem *. Wie Sie vielleicht ahnen können, steckt hier auch eine Versionsnummer der API für die Pluginprogrammierung. Diese ist bis auf weiteres Version 1, was hier durch v1 abgekürzt ist:

local/lib/check_mk/base/plugins/agent_based/linux_usbstick.py
from .agent_based_api.v1 import *

Diese Versionierung ermöglicht es uns in Zukunft eventuell neue Versionen der API parallel zu den bisherigen bereitzustellen, so dass bestehende Checkplugins weiterhin problemlos funktionieren.

2.6. Den Check deklarieren

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

  1. name: Der Name des Checkplugins. Wenn Sie keinen Ärger bekommen möchten, nehmen Sie hier den gleichen Namen wie bei Ihrer neuen Agentensektion. Damit weiß der Check automatisch, welche Sektion er auswerten soll.
  2. service_name: Der Name des Services wie er dann im Monitoring erscheinen soll.
  3. discovery_function: Die Funktion zum Erkennen von Services dieses Typs (dazu gleich mehr).
  4. check_funktion: Die Funktion zum Durchführen des eigentlichen Checks (auch dazu gleich mehr).

Für unseren Check sieht das dann also so aus:

register.check_plugin(
    name = "linux_usbstick",
    service_name = "USB stick",
    discovery_function = discover_linux_usbstick,
    check_function = check_linux_usbstick,
)

Versuchen Sie am besten noch nicht, das gleich auszuprobieren, denn natürlich müssen wir die Funktionen discovery_linux_usbstick und check_linux_usbstick vorher noch schreiben. Und diese müssen im Quellcode vor obiger Deklaration erscheinen.

2.7. Die Discovery-Funktion schreiben

Eine Besonderheit von Checkmk ist die automatische Erkennung von zu überwachenden Services. Damit dies klappt, muss jedes Checkplugin eine Funktion definieren, welche anhand der Agentenausgaben 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 Serviceerkennung durchgeführt wird. Sie entscheidet dann ob, bzw. welche Services angelegt werden sollen. In Standardfall bekommt sie genau ein Argument mit dem Namen section. Dieses enthält die Daten der Agentensektion in einem geparsten Format (dazu später mehr).

Wir implementieren folgende simple Logik: Wenn die Agentensektion linux_usbstick vorhanden ist, dann legen wir auch einen passenden Service an. Dann erscheint dieser automatisch auf allen Hosts, wo unser Agentenplugin ausgerollt ist. Das Vorhandensein der Sektion erkennen wir ganz einfach daran, dass unsere Discovery überhaupt aufgerufen wird!

Die Discovery-Funktion muss Für jeden anzulegenden Service mittels yield ein Objekt vom Typ Service zurückgeben (nicht mit return). Bei Checks, die pro Host nur einmal auftreten können, benötigt man keine weitere Angaben:

def discovery_linux_usbstick(section):
    yield Service()

2.8. Die Check-Funktion schreiben

Somit können wir nun zur eigentlichen Check-Funktion kommen, welche anhand aktueller Agentenausgaben endlich entscheidet, welchen Zustand ein Service annehmen soll. Da unser Check keine Parameter hat und es auch immer nur einen pro Host gibt, wird unsere Funktion ebenfalls mit dem einzigen Argument section aufgerufen.

Da wir diesmal den Inhalt auch wirklich brauchen, müssen wir uns mit dem Format dieses Arguments befassen. Solange Sie keine explizite Parse-Funktion definiert haben, zerlegt Checkmk jede Zeile der Sektion anhand von Leerzeichen in eine Liste von Worten. Das Ganze wird dann wiederum eine Liste dieser Wortlisten. Als Endergebnis haben wir also immer eine Liste von Listen.

Im einfachen Fall, dass unserer Agentenplugin nur zwei Devices findet, sieht das dann z. B. so aus (hier gibt es pro Zeile nur ein Wort):

[['ata-APPLE_SSD_SM0512F_S1K5NYBF810191'], ['wwn-0x5002538655584d30']]

Die Checkfunktion geht nun Zeile für Zeile durch und sucht nach einer Zeile, deren erstes (und einziges) Wort mit usb-SCSI_DISK beginnt. Wenn das der Falll ist, wird der Zustand CRIT. Hier ist die Implementierung:

def check_linux_usbstick(section):
    for line in section:
        if line[0].startswith("usb-SCSI_DISK"):
            yield Result(state=State.CRIT, summary="Found USB stick")
            return
    yield Result(state=State.OK, summary="No USB stick found")

Und hier die Erklärung:

  • Mit for line in section gehen wir in einer Schleife alle Zeilen der Agentenausgabe durch.
  • Dann prüfen wir, ob das erste Wort der Zeile – das jeweilige Gerät – mit usb-SCSI_DISK beginnt.
  • Falls ja, erzeugen wir ein Check-Resultat mit dem Status CRIT und dem Text Found USB stick. Und wir beenden dann die Funktion mit einem return.
  • Falls die Schleife durchlaufen wird, ohne etwas zu finden, erzeugen wir den Status OK und den Text No USB stick found.

2.9. Die Discovery testen

2.10. Den Check testen

2.11. Das ganze Plugin auf einen Blick

Und hier ist das ganze Plugin nochmal komplett:

local/lib/check_mk/base/plugins/agent_based/linux_usbstick.py
from .agent_based_api.v1 import *

def discover_linux_usbstick(section):
    yield Service()

def check_linux_usbstick(section):
    for line in section:
        if line[0].startswith("usb-SCSI_DISK"):
            yield Result(state=State.CRIT, summary="Found USB stick")
            return
    yield Result(state=State.OK, summary="No USB stick found")

register.check_plugin(
    name = "linux_usbstick",
    service_name = "USB stick",
    discovery_function = discover_linux_usbstick,
    check_function = check_linux_usbstick,
)

Und das hier war das Plugin für den Linuxagenten:

/usr/lib/check_mk_agent/plugins/linux_usbstick
#!/bin/sh
echo '<<<linux_usbstick>>>'
ls /dev/disk/by-id/ | grep -v -- -part

3. Checks mit mehr als einem Service pro Host (Items)

3.1. Grundprinzip

In unserem Beispiel haben wir einen sehr einfachen Check gebaut, der auf einem Host einen Service erzeugt – oder eben nicht. Ein sehr üblicher Fall ist aber natürlich 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 Plugin mit dem Namen df legt pro Dateisystem auf dem Host einen Service an. Um diese Services zu unterscheiden, wird der Mountpunkt des Dateisystems (z. B. /var) bzw. der Laufwerksbuchstabe (z. B. C:) in den Namen des Services eingebaut. Das ergibt dann als Servicename z. B. 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 die Liste der Items generieren, die auf dem Host sinnvollerweise überwacht werden sollen.
  • Im Servicenamen müssen Sie das Item mithilfe des Platzhalters %s einbauen (also z. B. "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.

3.2. Ein einfaches Beispiel

Um das ganze praktisch ausprobieren zu können, bauen wir uns einfach eine weitere Agentensektion, die nur Spieldaten ausgibt. Dazu genügt ein kleines Shellskript. Die Sektion soll hier im Beispiel foobar heißen:

/usr/lib/check_mk_agent/plugins/foobar
#!/bin/sh
echo "<<<foobar>>>"
echo "West 100 100"
echo "East 197 200"
echo "North 0 50"

Von Foobar gibt es hier drei Sektionen: West, East und North (was immer auch das bedeuten mag). In jeder Sektion gibt es eine Anzahl von Plätzen von denen einige belegt sind (z. B. sind in West 34 von 180 Plätzen belegt).

Nun legen wir dazu ein passendes Checkplugin an. Die Registrierung ist wie gehabt, allerdings mit dem wichtigen Unterschied, dass der Servicename jetzt genau einmal ein %s enthält. An dieser Stelle wird später dann von Checkmk der Name des Items eingesetzt:

register.check_plugin(
    name = "foobar",
    service_name = "Foobar Sector %s",
    discovery_function = discover_foobar,
    check_function = check_foobar,
)

Die Discovery-Funktion hat jetzt die Aufgabe, die zu überwachenden Items zu ermitteln. Wie gehabt bekommt sie das Argument section. Und auch hier handelt es sich um eine Liste von Zeilen, welche ihrerseits wiederum Listen von Worten sind. Diese sieht in unserem Beispiel aus aus:

[['West', '100', '100'], ['East', '197', '200'], ['North', '0', '50']]

So eine Liste kann man mit Python prima in einer Schleife durchlaufen und den drei Worten pro Zeile gleich sinnvolle Namen geben:

for sector, used, slots in section:
    ...

In jeder Zeile ist das erste Wort – hier der Sektor – unser Item. Immer wenn wir ein Item gefunden haben, geben wir das mit yield zurück, wobei wir ein Objekt vom Typ Service erzeugen, welches den Sektornamen als Item bekommt. Die beiden andere Spalten in der Ausgabe sind uns erstmal egal, denn bei der Discovery ist es schließlich unerheblich, wieviele Slots belegt sind. Insgesamt sieht das dann so aus:

def discover_foobar(section):
    for sector, used, slots in section:
        yield Service(item=sector)

Es wäre natürlich ein Leichtes, hier anhand von beliebigen Kriterien manche Zeilen auszulassen. Vielleicht gibt es ja Sektoren, welche die Größe 0 haben und die man grundsätzlich nie überwachen möchte? Lassen Sie solche Zeilen einfach aus und yielden Sie dafür kein Item.

Wenn dann später der Host überwacht wird, dann wird die Check-Funktion für jeden Service – und damit für jedes Item – separat aufgerufen. Sie bekommt deswegen zusätzlich zur Sektion das Argment item mit dem jeweils gesuchten Item. Jetzt gehen wir wieder alle Zeilen der Reihe nach durch. Dabei suchen diejenige Zeile heraus, die zum gewünschten Item gehört:

def check_foobar(item, section):
    for sector, used, slots in section:
        if sector == item:
            ...

Jetzt fehlt nur noch die eigentliche Logik, welche festlegt, wann das Ding denn überhaupt OK, WARN oder CRIT sein soll. Wir machen es hier so:

  • Wenn alle Slots belegt sind, soll das Ding CRIT werden.
  • Wenn weniger als 10 Slots frei sind, dann wird es WARN.
  • Ansonsten OK

Die belegten und insgesamten Slots kommen ja immer als Wort zwei und drei in jeder Zeile. Aber: es handelt sich hier um Strings, nicht um Zahlen. Diese brauchen wir aber, um vergleichen und rechnen zu können. Daher wandeln wir die Strings mit int() in Zahlen um.

Das Checkergebnis liefern wir dann, indem wir ein Objekt vom Typ Result per yield liefern. Dieses benötigt die Parameter state und summary:

def check_foobar(item, section):
    for sector, used, slots in section:
        if sector == item:
            used = int(used)   # convert string to int
            slots = int(slots)   # convert string to int
            if used == slots:
                s = State.CRIT
            elif slots - used < 10:
                s = State.WARN
            else:
                s = State.OK
            yield Result(
                state = s,
                summary = f"used {used} out of {slots} slots")
            return

Dazu noch folgende Hinweise:

  1. Der Befehl return sorgt dafür, dass die Check-Funktion nach dem Bearbeiten des gefundenen Items sofort abgebrochen wird. Es gibt schließlich auch nichts mehr weiter zu tun.
  2. Wird die Schleife durchlaufen, ohne das gesuchte Item zu finden, so erzeugt Checkmk automatisch das Resultat UNKNOWN - Item not found in monitoring data. Das ist so gewollt und gut so. Behandeln Sie diesen Fall nicht selbst. Wenn sie ein gesuchtes Item nicht finden, so lassen sie Python einfach aus der Funktion rauslaufen und Checkmk seine Arbeit erledigen.
  3. Mit dem Argument summary definieren Sie den Text, den der Service aus Statusausgabe produziert. Er ist rein informell und wird von Checkmk nicht weiter ausgewertet.

Probieren wir jetzt zunächst die Discovery aus. Der Übersicht halber beschränke ich das ganze mit der Option --checks=foobar auf unser Plugin:

OMD[mysite]:~$ cmk --checks=foobar -vI myhost123
  3 foobar
SUCCESS - Found 3 services, 1 host labels

Und jetzt können wir auch gleich das Checken ausprobieren (ebenfalls auf foobar begrenzt):

OMD[mysite]:~$ cmk --checks=foobar -v myhost123
Foobar Sector East   WARN - used 197 out of 200 slots
Foobar Sector North  OK - used 0 out of 50 slots
Foobar Sector West   CRIT - used 100 out of 100 slots

3.3. Beispiel komplett

Und hier nochmal das ganze Beispiel komplett. Damit es keine Fehler wegen nicht definierter Funktionsnamen gibt, müssen die Funktionen immer vor dem Registrieren definiert werden.

local/lib/check_mk/base/plugins/agent_based/foobar.py
from .agent_based_api.v1 import *
import pprint

def discover_foobar(section):
    for sector, used, slots in section:
        yield Service(item=sector)

def check_foobar(item, section):
    for sector, used, slots in section:
        if sector == item:
            used = int(used)    # convert string to int
            slots = int(slots)  # convert string to int
            if used == slots:
                s = State.CRIT
            elif slots - used < 10:
                s = State.WARN
            else:
                s = State.OK
            yield Result(
                state = s,
                summary = f"used {used} out of {slots} slots")
            return

register.check_plugin(
    name = "foobar",
    service_name = "Foobar Sector %s",
    discovery_function = discover_foobar,
    check_function = check_foobar,
)

4. Messwerte

4.1. Werte in der Checkfunktion ermitteln

Nicht immer, aber oft befassen sich Checks mit Zahlen. Mit seinem Graphingsystem hat Checkmk eine Komponente, um solche Zahlen zu speichern, auszuwerten und darzustellen. Das geht dabei völlig unabhängig von der Berechnung der Zuständige OK, WARN und CRIT.

Solche Messwerte – oder auch Metriken genannt – werden von der Checkfunktion ermittelt und einfach als zusätzliches Ergebnis zurückgegeben. Dazu dient das Objekt Metrik, welches mindestens die beiden Argument name und value benötigt. Hier ist ein Beispiel:

    yield Metrik(name="fooslots", value=used)

4.2. Informationen zu den Schwellwerten

Weiterhin gibt es noch zwei optionale Argumente. Mit dem Argument levels können Sie eine Information zu Schwellwerten für WARN und CRIT mitgeben, und zwar in Form eines Paares von zwei Zahlen. Diese wird dann üblicherweise im Graphen als gelbe und rote Linie eingezeichnet. Die erste Zahl steht für die Warnschwelle, die zweite für die kritische. Dabei gilt die Konvention, dass der Check beim Erreichen der Warnschwelle bereits auf WARN geht (bei CRIT analog).

Das sieht dann z. B. so aus (hier mit hartkodierten Schwellwerten):

    yield Metrik(name="fooslots", value=used, levels=(190,200))

Hinweise:

  • Falls nur eine der beiden Schwellen definiert ist, tragen Sie für die andere einfach None ein, also z. B. levels=(None, 200).
  • Es sind auch Fließkommazahlen erlaubt, aber keine Strings.
  • Achtung: für die Überprüfung der Schwellwerte ist die Checkfunktion selbst verantwortlich. Die Angabe von levels dient lediglich als Randinformation für das Graphingsystem!

4.3. Der Wertebereich

Analog zu den Schwellwerten können Sie dem Graphingsystem auch die Information über den möglichen Wertebereich mitgeben. Damit ist der kleinste und größte mögliche Wert gemeint. Das geschieht im Argument boundaries, wobei auch hier optional für eine der beiden Grenzen None eingesetzt werden kann. Beispiel:

    yield Metrik(name="fooslots", value=used, boundaries=(0, 200))

Und jetzt unsere Checkfunktion aus dem obigen Beispiel nochmal, aber diesmal mit der Rückgabe von Metrikinformation inklusive Schwellwerte und Wertebereich (diesmal natürlich nicht mit fixen sondern mit berechneten Werten):

def check_foobar(item, section):
    for sector, used, slots in section:
        if sector == item:
            used = int(used)    # convert string to int
            slots = int(slots)  # convert string to int

            yield Metric(
                name="fooslots",
                value=used,
                levels=(slots-10, slots),
                boundaries=(0, slots))

            if used == slots:
                s = State.CRIT
            elif slots - used < 10:
                s = State.WARN
            else:
                s = State.OK
            yield Result(
                state = s,
                summary = f"used {used} out of {slots} slots")
            return

5. Checks mit mehreren Teilresultaten

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 z. B. der Service Memory used unter Linux nicht nur den RAM- und Swapverbrauch, sondern auch Shared memory, Pagetabellen und alles mögliche Andere.

Die API von Checkmk bietet dafür eine sehr komfortable Schnittstelle. So darf eine Checkfunktion einfach beliebig oft ein Ergebnis mit yield erzeugen. Der Gesamtstatus des Services richtet sich dann nach dem „schlechtsten“ Teilergebnis nach dem Schema OKWARNUNKNOWNCRIT.

Hier ist ein gekürztes fingiertes Beispiel:

def check_foobar(section):
    yield Result(state=State.OK, summary="Knulf rate optimal")
    # ...
    yield Result(state=State.WARN, summary="Gnarz required")
    # ...
    yield Result(state=State.OK, summary="Last Szork was good")

Die Summary des Services in der GUI sieht dann so aus: „Knulf rate optimal, Gnarz required WARN, Last Szork was good“. Und der Gesamtstatus ist WARN.

Auf die gleiche Art können Sie auch mehrere Metriken zurückgeben. Rufen Sie einfach für jede Metrik einmal yield Metrik(...) auf.

6. Summary und Details

In der Monitoringansicht im Checkmk hat jeder Service neben dem Status OK, WARN, usw. auch eine Zeile Text. Diese hieß bis zur Version 1.6.0 Output of check plugin. Ab 2.0 heißt diese Summary – hat also die Aufgabe einer knappen Zusammenfassung des Zustandes. 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, welches früher {{Long output of check plugin (multiline)}} hieß. Hier werden alle Details des Zustandes angezeigt, wobei die Idee ist, dass alle Informationen des Summary hier auch enthalten sind.

Beim Aufruf von yield Result(...) können Sie bestimmen, welche Informationen so wichtig sind, dass sie in der Summary angezeigt werden sollen und bei welchen es genügt, dass diese in den Details erscheinen. Dabei gilt die Regel, dass Teilergebnisse, die zu einem WARN/CRIT führen, immer in der Summary sichtbar werden.

In unseren Beispiele bisher haben wir immer folgenden Aufruf verwendet:

    yield Result(state=State.OK, summary="some important text")

Dieser führt dazu, dass some important text immer in der Summary erscheint – und zusätlich auch in den Details. Dies sollten sie also nur für wichtige Informationen verwenden. Ist ein Teilergebnis eher untergeordnet, so ersetzen Sie summary durch notice und der Text erscheint – falls es OK ist nur in den Details.

    yield Result(state=State.OK, notice="some additional text")

Falls der Zustand WARN oder CRIT ist, taucht der Text dann automatisch zusätzlich in der Summary auf:

    yield Result(state=State.CRIT, notice="some additional text")

Somit wird aus der Summary dann sofort klar, warum der Service nicht OK ist.

Zu guter Letzt haben Sie noch – sowohl bei summary als auch bei notice die Möglichkeit, für die Details einen alternativen Text anzugeben, der evtl. mehr Informationen zu dem Teilergebnis enthält:

    yield Result(state=State.OK,
                 summary="55% used space",
                 details="55.2% of 160 GB used (82 GB)")

Zusammengefasst bedeutet das:

  • Der Gesamttext für die 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.

7. Fehlerbehandlung

7.1. Exceptions und Crashreports

Die korrekte Behandlung von Fehlern nimmt (leider) einen großen Teil der Programmierabeit ein. Die gute Nachricht ist: die API von Checkmk erledigt dabei bereits die meiste Arbeit. Meistens ist für Sie daher wichtig, dass Sie Fehler einfach garnicht behandeln.

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

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

Hier gilt die generelle wichtige Regel: Fangen Sie Exceptions nicht selbst ab! Denn Checkmk übernimmt das für Sie in einer sinnvollen immer gleichen Art und Weise. Und zwar meist mit einem Crashreport. Das sieht dann z. B. so aus:

Durch einen Klick auf das Icon gelangt der User dann auf eine Seite, auf der er

  • Die Datei angezeigt bekommt, in der der Crash stattgefunden hat.
  • Alle Informationen über den Crash angezeigt bekommt (wie Fehlermeldung, Aufrufstack, Agentenausgabe, aktuelle Werte von lokalen Variablen und vieles mehr).
  • Den Report zu uns als Feedback einsenden kann.

Das Einsenden des Reports macht natürlich nur Sinn für Checkplugins, welche offiziell Teil von Checkmk sind. Aber Sie können Ihre Anwender bitten, Ihnen die Daten einfach zukommen zu lassen. Diese werden Ihnen beim Finden des Fehlers helfen. Oft ist es ja so, dass das Checkplugin bei Ihnen selbst funktioniert, aber es bei anderen Anwendern vielleicht sehr sporadisch zu Fehlern kommt. Diese können Sie dann so meist sehr leicht finden.

Falls Sie aber die Exception selbst abfangen würden, wären diese ganzen Informationen plötzlich nicht verfügbar. Sie würden vielleicht den Service auf UNKNOWN setzen und eine Fehlermeldung ausgeben. Aber die ganzen Umstände, wie es dazu kam (z. B. die Daten vom Agenten), wäre verschleiert.

7.2. Exceptions auf der Kommandozeile ansehen

Falls Sie ihr Plugin auf der Kommandozeile ausführen, werden keine Crashreports erzeugt. Sie sehen nur die zusammengefasste Fehlermeldung:

OMD[mysite]:~$ cmk -II --checks=foobar myhost123
  WARNING: Exception in discovery function of check plugin 'foobar': invalid literal for int() with base 10: 'foo'

Aber: hängen Sie einfach die Option --debug dran. Dann bekommt Sie den Python-Stacktrace:

OMD[mysite]:~$ cmk --debug -II --checks=foobar myhost123
Traceback (most recent call last):
  File "/omd/sites/myhost123/bin/cmk", line 82, in <module>
    exit_status = modes.call(mode_name, mode_args, opts, args)
  File "/omd/sites/myhost123/lib/python3/cmk/base/modes/__init__.py", line 68, in call
    return handler(*handler_args)
  File "/omd/sites/myhost123/lib/python3/cmk/base/modes/check_mk.py", line 1577, in mode_discover
    discovery.do_discovery(set(hostnames), options.get("checks"), options["discover"] == 1)
  File "/omd/sites/myhost123/lib/python3/cmk/base/discovery.py", line 345, in do_discovery
    _do_discovery_for(
  File "/omd/sites/myhost123/lib/python3/cmk/base/discovery.py", line 397, in _do_discovery_for
    discovered_services = _discover_services(
  File "/omd/sites/myhost123/lib/python3/cmk/base/discovery.py", line 1265, in _discover_services
    service_table.update({
  File "/omd/sites/myhost123/lib/python3/cmk/base/discovery.py", line 1265, in <dictcomp>
    service_table.update({
  File "/omd/sites/myhost123/lib/python3/cmk/base/discovery.py", line 1337, in _execute_discovery
    yield from _enriched_discovered_services(hostname, check_plugin.name, plugins_services)
  File "/omd/sites/myhost123/lib/python3/cmk/base/discovery.py", line 1351, in _enriched_discovered_services
    for service in plugins_services:
  File "/omd/sites/myhost123/lib/python3/cmk/base/api/agent_based/register/check_plugins.py", line 69, in filtered_generator
    for element in generator(*args, **kwargs):
  File "/omd/sites/myhost123/local/lib/python3/cmk/base/plugins/agent_based/foobar.py", line 5, in discover_foobar
    int("foo")
ValueError: invalid literal for int() with base 10: 'foo'

7.3. Ungültige Ausgaben vom Agenten

Die Frage ist, wie Sie reagieren sollen, wenn die Ausgaben vom Agenten nicht die Form haben, die Sie eigentlich erwarten würden - egal ob es der „echte“ Agent ist oder die Daten per SNMP kommen. Nehmen wir an, dass Sie pro Zeile immer drei Worte erwarten. Was sollen Sie tun, falls nur zwei kommen?

Nun – wenn das ein erlaubtes und bekanntes Verhalten des Agenten ist, dann müssen 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 z. B. mit:

def check_foobar(section):
    for foo, bar, baz in section:
        # ...

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

7.4. Fehlende Items

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

def check_foobar(item, section):
    for sector, used, slots in section:
        if item == sector:
            # ... Check state ...
            yield Result(...)
            return

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 „geyieldet“ 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. SNMP-basierte Checks

8.1. Grundsätzliches

Das Entwickeln von Checks, die mit SNMP arbeiten, läuft sehr ähnlich zu den agentenbasierten, nur dass Sie hier noch angeben müssen, welche SNMP-Bereiche (OIDs) der Check benötigt. Falls Sie noch keine Erfahrung mit SNMP haben, so empfehlen wir Ihnen an dieser Stelle als Vorbereitung unbedingt den Artikel über das Monitoren via SNMP.

Der Ablauf der Discovery und des Checkens via SNMP ist etwas anders als beim normalen Agenten. Denn anders also dort – wo der Agent von sich aus alle interessanten Informationen senden – müssen wir bei SNMP selbst genau sagen, welche Datenbereiche wir benötigen. Ein Komplettabzug aller Daten wäre zwar theoretisch möglich (via SNMP-Walk), dauert aber bei schnellen Geräten eher im Bereich von Minuten und bei komplexen Switches gern auch über eine Stunde. Daher scheidet das beim Checken und sogar auch bei der Discovery natürlich aus. Checkmk geht deswegen etwas zielsicherer vor.

Detection

Die Serviceerkennung teilt sich in zwei Phasen auf. Zunächst geschieht die SNMP-Detection. Diese hat die Aufgabe, zu ermitteln, welche Plugins denn überhaupt auf dem jeweiligen Gerät interessant sind. Dazu werden einige wenige SNMP-OIDs abgerufen – und zwar einzelne, ohne Walk. Die wichtigste davon ist die sysDescr (OID: 1.3.6.1.2.1.1.1.0). Unter dieser OID hält jedes SNMP-Gerät eine Beschreibung von sich selbst bereit, z. B. Cisco NX-OS(tm) n5000, Software (n5000-uk9),.... Ausgehend von diesem Text kann man für sehr viele Plugins schon definitiv entscheiden, ob diese hier Sinn ergeben.

Ergebnis der SNMP-Detection ist dann eine Kandidaten-Liste von Checkplugins.

Discovery

Im zweiten Schritt werden für jeden dieser Kandidaten die für das jeweilige Plugin eigentlich Monitoringdaten mit SNMP-Walks geholt. Diese werden dann der Discovery-Funktion des Checks in dem Argument section bereitgestellt, welche dann daraus wie gewohnt die zu überwachenden Items ermittelt.

Checken

Beim Checken ist ja schon bekannt, welche Plugins für das Gerät ausgeführt werden sollen und die SNMP-Detection entfällt. Hier werden gleich per SNMP-Walks die für die Plugins benötigten Monitoringdaten geholt und daraus das Argument section für die Checkfunktion befüllt.

Zusammenfassung

Was müssen Sie also bei einem SNMP-Check anders machen als bei einem agentenbasierten?

  1. Sie benötigen kein Plugin für den Agenten.
  2. Sie müssen die für die SNMP-Detection nötigen Einzel-OIDs und Suchtexte festlegen.
  3. Sie müssen festlegen, welche SNMP-Bereiche für das Monitoring geholt werden müssen.

Ein Wort zu den MIBs

Bevor wir weitermachen will ich hier noch ein Wort zu den berüchtigten SNMP-MIBs verlieren, denn über diese gibt es viele Vorurteile. Gleich zu Beginn eine gute Nachricht: Checkmk benötigt sie nicht. Wirklich! Sie sind aber eine wichtige Hilfe, um einen SNMP-Check entwickleln zu können.

Was ist nun eine MIB? Wörtlich bedeutet die Abkürzung Management Information Base – etwas nichtssagend. Konkret ist eine MIB eine ganz gut lesbare Textdatei, welche einen bestimmten Teilbaum der SNMP-Welt beschreibt. Und zwar steht hier, welcher Ast im Baum – also welche OID – welche Bedeutung hat. Das umfasst einen Namen für die OID, einen Hinweis, welche Werte diese annehmen kann (z. B. bei enumerierten Datentypen, wo dann Dinge wie 1=up, 2=down, festgelegt sind) und manchmal auch noch einen nützlichen Kommentar.

Checkmk liefert eine ganze Reihe von MIB-Dateien mit aus, aber nur soweit diese frei verfügbar sind. Diese sind sehr allgemeiner Art, und wenn Sie selbst einen Check für ein Gerät entwickeln wollen, welches von Checkmk noch nicht unterstützt wird, so ist die Wahrscheinlichkeit, dass dessen MIB bereits dabei ist, gleich Null.

Versuchen Sie also, die nötigen MIB-Dateien irgendwo auf den Webseiten vom Hersteller oder sogar auf dem Management-Interface des Geräts zu finden und installieren Sie diese in der Checkmk-Instanz nach local/share/check_mk/mibs. Dann können Sie in SNMP-Walks OID-Nummern in Namen umrechnen lassen und so schneller finden, wo die für das Monitoring interessanten Daten sind. Auch enthalten die MIBs, wenn sie sorgfältig gemacht sind, wie gesagt interessante Information in den Kommentaren.

8.2. Die richtigen OIDs finden

Die entscheidende Voraussetzung, um ein Plugin zu entwickeln, ist natürlich, dass Sie wissen, welche OIDs die notwendigen Informationen enthalten. Der erste Schritt dabei ist (falls das Gerät das nicht verweigert), einen kompletten SNMP-Walk zu ziehen. Dabei werden alle per SNMP verfügbaren Daten abgerufen.

Checkmk kann es sehr einfach für Sie erledigen. Nehmen Sie dazu zunächst das Gerät (oder eines der Geräte), für das Sie ein Plugin entwickeln wollen, ins Monitoring auf. Sagen wir es heißt mydevice01. Stellen Sie sicher, dass dieses in den Grundfunktionen überwacht werden kann. Zumindest müssen die Service SNMP Info und Uptime gefunden werden und wahrscheinlich auch noch mindestens ein Interface.

Wechseln Sie dann auf die Kommandozeile der Checkmk-Instanz. Hier können Sie mit folgendem Befehl einen kompletten Walk ziehen. Dabei empfehle ich, gleich die Option -v (verbose) zu verwenden:

OMD[mysite]:~$ cmk -v --snmpwalk mydevice01
mydevice01:
Walk on ".1.3.6.1.2.1"...3898 variables.
Walk on ".1.3.6.1.4.1"...6025 variables.
Wrote fetched data to /omd/sites/heute/var/check_mk/snmpwalks/mydevice01.

Wie bereits erwähnt, kann so ein Komplettwalk Minuten oder sogar Stunden dauern (auch wenn letzteres eher selten ist). Werden Sie also nicht nervös, wenn es hier etwas dauert. Der Walk wurde nun gespeichert in der Datei var/check_mk/snmpwalks/mydevice01. Es handelt sich dabei um eine gut lesbare Textdatei, die etwa so beginnt:

var/check_mk/snmpwalks/mydevice01
.1.3.6.1.2.1.1.1.0 JetStream 24-Port Gigabit L2 Managed Switch with 4 Combo SFP Slots
.1.3.6.1.2.1.1.2.0 .1.3.6.1.4.1.11863.1.1.3
.1.3.6.1.2.1.1.3.0 546522419
.1.3.6.1.2.1.1.4.0 hh@example.com
.1.3.6.1.2.1.1.5.0 sw-ks-01
.1.3.6.1.2.1.1.6.0 Core Switch Serverraum klein
.1.3.6.1.2.1.1.7.0 3
.1.3.6.1.2.1.2.1.0 27

In jeder Zeile steht eine OID und danach deren Wert. Die allerste ist auch gleich die wichtigste, nämlich die sysDescr.

Nun sind die OIDs nicht sehr aussagekräftig. Wenn die richtigen MIBs installiert sind, können Sie diese in einem zweiten Schritt mit dem Befehl cmk --snmptranslate in Namen umrechnen lassen. Am besten leiten Sie das Ergebnis, was ansonsten im Terminal käme, in eine Datei um:

OMD[heute]:~$ cmk --snmptranslate mydevice01  > translated
Processing 9923 lines.
finished.

Die Datei translated ist jetzt wie der ursprüngliche Walk, hat aber in jeder Zeile nach dem --> einen übersetzten Wert für die OID:

translated
.1.3.6.1.2.1.1.1.0 JetStream 24-Port Gigabit L2 Managed Switch with 4 Combo SFP Slots --> SNMPv2-MIB::sysDescr.0
.1.3.6.1.2.1.1.2.0 .1.3.6.1.4.1.11863.1.1.3 --> SNMPv2-MIB::sysObjectID.0
.1.3.6.1.2.1.1.3.0 546522419 --> DISMAN-EVENT-MIB::sysUpTimeInstance
.1.3.6.1.2.1.1.4.0 hh@example.com --> SNMPv2-MIB::sysContact.0
.1.3.6.1.2.1.1.5.0 sw-ks-01 --> SNMPv2-MIB::sysName.0
.1.3.6.1.2.1.1.6.0 Core Switch Serverraum klein --> SNMPv2-MIB::sysLocation.0
.1.3.6.1.2.1.1.7.0 3 --> SNMPv2-MIB::sysServices.0
.1.3.6.1.2.1.2.1.0 27 --> IF-MIB::ifNumber.0
.1.3.6.1.2.1.2.2.1.1.1 1 --> IF-MIB::ifIndex.1
.1.3.6.1.2.1.2.2.1.1.2 2 --> IF-MIB::ifIndex.2

Sie sehen also somit z. B., dass .1.3.6.1.2.1.1.4.0 übersetzt SNMPv2-MIB::sysContact.0 bedeutet. Dies ist ein wichtiger Hinweis auf die Bedeutung der OID. Der Rest ist dann Übung, Erfahrung und natürlich experimentieren.

Wenn Sie also die notwendigen OIDs festgestellt haben, geht es an die eigentliche Entwicklung des Plugins. Das geschieht in drei Schritten:

  • Legen Sie für die SNMP-Detection fest, welche OIDs welche Texte enthalten müssen, damit Ihr Plugin ausgeführt werden soll.
  • Deklarieren Sie, welche OID-Zweige für das Monitoring geholt werden müssen.
  • Schreiben Sie ein Checkplugin analog zu denjenigen für agentenbasierte Checks.

Die ersten beiden Schritte erfolgen durch die Registrierung einer SNMP-Sektion.

8.3. Die Registrierung der SNMP-Sektion

Die Registrierung der SNMP-Sektion wird erledigt durch den Aufruf von register.snmp_section(). Dies benötigt mindestens drei Argumente: den Namen der Sektion, die Angaben für die SNMP-Detection und die benötigten OID-Zweige für das eigentlich Monitoring. Hier ist ein Beispiel für ein fiktives Checkplugin mit dem Namen foo:

local/lib/check_mk/base/plugins/agent_based/foo.py
register.snmp_section(
    name = "foo",
    detect = startswith(".1.3.6.1.2.1.1.1.0", "foobar device"),
    tree = SNMPTree(
        base = '.1.3.6.1.4.1.35424.1.2',
        oids = [
            '4.0',
            '5.0',
            '8.0',
        ],
    ),
)

Die SNMP-Dection

Mit dem Schlüsselwort detect geben Sie an, unter welchen Bedingungen die Discoveryfunktion überhaupt ausgeführt werden soll. Dies ist hier der Fall, wenn der Wert der OID .1.3.6.1.2.1.1.1.0 (also die sysDescr) mit dem Text foobar device beginnt (wobei Groß-/Kleinschreibung grundsätlich nicht unterschieden wird). Neben startswith gibt es noch eine ganze Reihe weiterer möglichen Attribute. Dabei existiert von jedem auch eine negierte Form, welche mit not_ beginnt:

Attribut Negation Bedeutung
equals(oid, needle) not_equals(oid, needle) Der Wert der der OID ist gleich dem Text needle
contains(oid, needle) not_contains(oid, needle) Der Wert der der OID enthält an irgendeiner Stelle den Text needle
startswith(oid, needle) not_startswith(oid, needle) Der Wert der der OID beginnt mit dem Text needle
endswith(oid, needle) not_endswith(oid, needle) Der Wert der der OID endet mit dem Text needle
matches(oid, regex) not_matches(oid, regex) Wert der OID matcht auf den regulären Ausdruck regex, und zwar hinten und vorne geankert, also mit einem exakten Match. Wenn Sie nur einen Teilstring benötigen, ergänzen Sie einfach vorne bzw. hinten noch ein .*
exists(oid) not_exists(oid) Erfüllt, wenn die OID auf dem Gerät verfügbar ist. Der Wert darf leer sein.

Daneben gibt es noch die Möglichkeit, mehrere Tests mit all_of oder any_of zu verknüpfen. all_of erfordert mehrere erfolgreiche Attribute für eine positive Erkennung des Plugins. Folgendes Beispiel findet auf einem Gerät das Plugin, wenn in der sysDescr mit dem Text foo (oder FOO oder Foo) beginnt und die OID .1.3.6.1.2.1.1.2.0 den Text .4.1.11863. enthält:

detect = all_of(
    startswith(".1.3.6.1.2.1.1.1.0", "foo"),
    contains(".1.3.6.1.2.1.1.2.0", ".4.1.11863.")
)

any_of hingegen ist damit zufrieden, wenn auch nur eines der Kriterien erfüllt ist. Hier ist ein Beispiel, wo verschiedene Wert für die sysDescr erlaubt sind:

detect = any_of(
    startswith(".1.3.6.1.2.1.1.1.0", "foo version 3 system"),
    startswith(".1.3.6.1.2.1.1.1.0", "foo version 4 system"),
    startswith(".1.3.6.1.2.1.1.1.0", "foo version 4.1 system"),
)

Übrigens: wer sich ein bisschen mit regulären Ausdrücken auskennt, der kann spezielle dieses Beispiel auch in einer Zeile formulieren:

detect = matches(".1.3.6.1.2.1.1.1.0" "FOO Version (3|4|4.1) .*"),

Und noch ein wichtiger Hinweis: Die OIDs, die Sie bei der detect-Deklaration von einem Plugin angeben, werden im Zweifel von jedem Gerät geholt, welches per SNMP überwacht wird. Sein Sie daher sehr sparsam bei der Verwendung von herstellerspezifischen OIDs. Versuchen Sie, Ihre Erkennung unbedingt so zu machen, dass aussschließlich die sysDescr (.1.3.6.1.2.1.1.1.0) und die sysObjectID (.1.3.6.1.2.1.1.2.0) verwendet werden. Falls Sie dennoch eine weitere andere OID benötgen, dann reduzieren Sie die Anzahl der Geräte, wo diese angefragt wird auf ein Minimum, indem Sie zuvor mittels der sysDescr soviele Geräte wie möglich bereits ausschließen, z. B. so:

detect = all_of(
    startswith(".1.3.6.1.2.1.1.1.0", "foo"),
    contains(".1.3.6.1.4.1.4455.1.3", "bar"),
)

Hier wird die OID .1.3.6.1.4.1.4455.1.3 nur bei solchen Geräten geholt, die foo in ihrer sysDescr haben.

Was geschieht, wenn Sie die Deklaration falsch oder zumindest nicht ganz zielsicher gemacht haben?

  • Falls die Detection fälschlicherweise Geräte erkennt, auf denen die nötigen OIDs garnicht vorhanden sind, wir normalerweise Ihre Discoveryfunktion dann auch keine Services erzeugen. Es passiert also nichts „Schlimmes“. Allerdings wird das die Discovery auf solchen Geräten verlangsamen, da jetzt jedesmal nutzlos versucht wird, die entsprechenden OIDs abzufragen.
  • Falls die Detection eigentlich zulässige Geräte nicht erkennt, werden dort auch keine Services gefunden.

8.4. Die OID-Bereiche für das Monitoring

Die wichtigste Stelle der SNMP-Deklaration ist die Angabe, welche OIDs für das Monitoring geholt werden sollen. Betrachten wir erst den einfachen Fall – nämlich den, dass Sie nur aus einer Stelle aus dem Baum Daten benötigen. Hier geben Sie zwei Dinge an:

  1. Die Basis-OID
    tree = SNMPTree(
        base = '.1.3.6.1.4.1.35424.1.2',
        oids = [
            '4.0',
            '5.0',
            '8.0',
        ],
    ),

9. Hinweise für Nutzer der alten API

Sind Sie bereits erfahren bei der Entwicklung von Checkplugins mit der bisherigen API – derjenigen bis Version 1.6.0 von Checkmk? Dann finden Sie hier einige Hinweise über wichtige Änderungen zusammengefasst.

9.1. saveint() und savefloat()

Die beiden Funktionen saveint() und savefloat() sind weggefallen. Zur Erinnerung: saveint(x) liefert 0 wenn sich x nicht vernünftig in eine Zahl konvertieren lässt, z. B. weil es ein leerer String ist oder nicht nur aus Ziffern besteht.

Auch wenn es dafür einige wenige gute Anwendungsfälle gab, wurde es doch in der Mehrheit der Fälle falsch verwendet und hat dazu geführt, dass so viele Fehler verschleiert wurden.

Für den Fall, dass Sie bei einem Leerstring eine 0 bekommen möchten, also den häufigsten „guten“ Anwendungsfall von saveint(x), können Sie einfach Folgendes schreiben:

foo = int(x) if x else 0

Für savefloat() gilt alles analog.

10. Formatierung von Zahlen

10.1. Grundlegendes

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

Allen 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 Umrechung. Z.B. 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.

Bitte 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. Davon wird dann Ihr Checkplugin auch profitieren.

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

10.2. Zeiten, Zeitspannen, Frequenzen

Absolute Zeitangaben (Zeitstempel) werden mit render.date() oder render.datetime() formatiert. Die Angaben erfolgen immer in Sekunden ab dem 1. Januar 1970, 00:00:00 UTC – der sogenannten Epochenzeit. Dies ist auch das Format, mit dem die Pythonfunktion time.time() arbeitet. Vorteil an dieser Darstellung ist, dass sich damit sehr einfach rechnen lässt, also z. B. die Dauer eines Vorgangs, 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 dabei nur das Datum aus, render.datetime() fügt noch die Uhrzeit hinzu. Die Ausgabe erfolgt dabei gemäß der aktuellen Zeitzone desjenigen Checkmk-Servers, welcher den Check ausführt! Beispiele:

Aufruf Ausgabe
render.date(0) Jan 01 1970
render.datetime(0) Jan 01 1970 01:00:00
render.date(1600000000) Sep 13 2020
render.datetime(1600000000) Sep 13 2020 14:26:40

Bitte wundern Sie sich jetzt nicht, dass render.date(0) als Uhrzeit nicht 00:00, sondern 01:00 ausgibt! Das liegt daran, dass ich dieses Handbuch in der Zeitzone von Deutschland schreibe, 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 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.

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 z. B. die Taktrate einer CPU:

Aufruf Ausgabe
render.frequency(111222333444) 111 GHz

10.3. Bytes

Überall wo es um Arbeitsspeicher, Dateien, Festplatten, Dateisystem und dergleichen geht, ist die kanonische Einheit das Byte. Da Computer sowas meist in Zweierpotenzen organisieren, also z. B. in Einheiten zu 512, 1024 oder 65536 Bytes, hatte sich dabei von Beginn an eingebürgert, dass ein Kilobyte nicht 1000, sondern 1024 Bytes sind. An sich sehr praktisch, weil so meist runde Zahlen rauskamen. Der legendäre Commodore C64 hatte eben 64 Kilobyte Speicher und nicht 65,536.

Leider kamen irgendwann Festplattenhersteller auf die Idee, die Größen ihrer Platten in 1000'er-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 * 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 neue Präfixe auf Grundlage des Binärsystems festgelegt. Demnach ist heute offiziell ein Kilobyte 1000 Byte und ein Kibibyte 1024 Byte (2 hoch 10). Außerdem soll man Mebibyte und Gibitbyte und Tebibyte sagen (schon mal gehört?). Die Abkürzungen lauten (Achtung, hier auf einmal immer i, statt e!) KiB, MiB, GiB und TiB.

Checkmk passt sich an diesen Standard an und hilft Ihnen mit mehreren angepassten Renderfunktionen dafür, dass Sie immer korrekte Ausgaben machen. So gibt es speziell für Festplatten und Dateisysteme die Funktion render.disksize(), welche die Ausgabe in 1000'er-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öchte, die keine Platten- oder Dateigröße ist, dann verwenden Sie einfach das generische render.bytes(). Hier bekommen sie die Ausgabe in klassichen 1024'er-Potenzen in der neuen offiziellen Schreibweise:

Aufruf Ausgabe
render.bytes(1000) 1000 B
render.bytes(1024) 1.00 KiB
render.bytes(2000000) 1.91 MiB

10.4. 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 Renderfunktionen. 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. Achtung: trotzdem müssen Sie auch hier Bytes pro Sekunde übergeben! Beispiele:

Aufruf Ausgabe
render.nicspeed(12500000) 100 MBit/s
render.nicspeed(100000000) 800 MBit/s

render.networkbandwidth() ist für eine tatsächlich gemessene Übertragungsgeschwindigkeit im Netzwerk. Eingabewert sind wieder Bytes pro Sekunde (Oder „Oktette“, wie der Netzwerker sagen würde):

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 Renderfunktion render.iobandwidth(), die in Checkmk mit 1000'er-Potzenzen arbeitet:

Aufruf Ausgabe
render.iobandwidth(123) 123 B/s
render.iobandwidth(123456) 123 kB/s
render.iobandwidth(123456789) 123 MB/s

10.5. Prozentwerte

Die Funktion render.percent() stellt einen Prozentwert dar – intelligent gerundet. Es 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 z. B. zur Hälfte voll ist, müssen Sie nicht 0.5 sondern 50 übergeben.

Übrigens gibt es bei Prozentwerten eine Besonderheit beim Runden. Es macht hier nicht immer Sinn, mit „gültigen Ziffern“ zu arbeiten. Würde mann z. B. immer zwei gültige Ziffern ausgeben, so würden Prozenzwerte nahe hundert – z. B. 99.95% – immer auf 100 gerundet. Hier gibt sie Checkmk Mühe, dass dies nicht passiert und bei der Ausgabe keine wichtigen Informationen verloren gehen.

Aufruf Ausgabe
render.percent(0.4) 0.40%
render.percent(18.5) 18.5%
render.percent(99.99512) 99.9951%
render.percent(123) 123%

10.6. Zusammenfassung

Hier ist nochmal eine Übersicht über alle Renderfunktionen:

Funktion Eingabe Beschreibung Beispielausgabe
dateEpocheDatumDec 18 1970
datetimeEpocheDatum und UhrzeitDec 18 1970 10:40:00
timespanSekundenDauer / Alter3d 5m
frequencyHzFrequenz (z. B. Taktrate)110 MHz
disksizeBytesGröße von Festplatte, Basis 10001,234 GB
filesizeBytesGröße von Dateien, volle Genauigkeit1,334,560 B
bytesBytesGröße in Bytes, Basis 102423,4 KiB
nicspeedOctets/secGeschwindigkeit von Netzwerkkarten100 MBit/s
networkbandwidthOctets/secÜbertragungsgeschwindigkeit23.50 GBit/s
iobandwidthBytes/secIO-Bandbreiten124 MB/s
percentProzentProzentwert, sinnvoll gerundet99.997%

11. Weitere Aspekte

In diesem Artikel werden in Zukunft noch weitere Aspkete der Pluginentwicklung besprochen werden. Die wichtigsten sind:

  • Schwellwerte und andere Checkparameter

Und diese hier werden später auch noch beschrieben:

  • Checks im Cluster
  • Checks mit einem Regelsatz, welche die Discovery steuert
  • Counter und andere persistierte Daten
  • Host- und Servicelabels erzeugen
  • Includefunktionen, geteilter Code
  • Definition von Perf-O-Metern, Graphtemplates, Metriken
  • Manpages schreiben
  • Checks, die mehr als eine Sektion verwenden
  • Deklaratoren in den Sektionen, z. B. sep(...).
  • Mehrere Checks für die gleiche Sektion
  • Ein Check, der mehrere Sektionen auswertet

12. Ausblick

Wenn das klappt, sind Sie eigentlich fertig. Sie können das Ganze aber noch um etliche Zusatzfeatures erweitern, wie zum Beispiel:

  • Definitionen für die von den Services gelieferten Messdaten, damit schöne und gut beschriftete Graphen und „Perf-O-Meter“ erzeugt werden.
  • Ein Regelsatz, mit dem Sie die Parameter des Checkplugins konfigurieren können.
  • Ein Regelsatz, welcher das Agentenplugin für die Agentenbäckerei konfiguriert.
  • Ein Regelsatz, mit der der Spezialagent konfiguriert werden kann.
  • Eine Manualpage, welche das Checkplugin für den Anwender dokumentiert.
  • Ein MKP-Paket, in welchem das Plugin paketiert und einfach installierbar ist.

Artikel dazu folgen hier in Kürze...

13. Dateien und Verzeichniss

.