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

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

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 unbedingt den Artikel über das Monitoren via SNMP.

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

  • Die Renderfunktionen (korrekte Darstellung von Zahlen)
  • 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

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