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 Check-Plugins schreiben

Dieser Artikel ist noch nicht fertig und nur ein Entwurf!

1. Einleitung

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

Und trotzdem 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 drei 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, SNMP nicht möglich oder sehr umständlich, für komplexere Dinge unkomfortabel.
Nagios-kompatibles Check-Plugin 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, SNMP nicht möglich oder sehr umständlich, 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-Check-Plugins 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. Verschiedene Arten von Agenten

Bevor wir uns in Geschehen stürzen, müssen wir uns zunächst einen Überblick über die verschiedenen Arten von Agenten befassen, mit denen Checkmk arbeitet:

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 Check-Plugin erfordert eine Erweiterung des Agenten in Form eines Agent-Plugins, damit dieser die nötigen Daten bereitstellt.
Spezialagent 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.
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 über WATO wie gewohnt per Regeln einrichten kann.

1.3. Voraussetzungen

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

  • Kenntnisse in der Programmiersprache Python oder wenigstens Übung in einer ähnlichen Sprache (wie z. B. PHP, Ruby, Java, etc.) nebst Lust, sich in Python einzuarbeiten.
  • 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 Check-Plugin programmieren. Als Beispiel nehmen wir eine einfache Überwachung für Linux. Da Checkmk selbst auf Linux läuft, ist es sehr wahrscheinlich, dass jeder auch auf ein Linuxsystem Zugriff hat.

Der Check soll erkennen, ob auf einem Linuxserver jemand einen USB-Stick eingesteckt hat. In diesem Fall soll der zugehörige Service kritisch werden. Vielleicht werden Sie sowas sogar nützlich finden, aber es ist hier wirklich nur ein vereinfachtes Beispiel und möglicherweise auch nicht ganz wasserdicht programmiert. Aber darum geht es 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. Wir erweitern den Linux-Agenten um ein kleines Skript, welches diesen Befehl aufruft.
  2. Wir schreiben in der Checkmk-Instanz ein kleines Checkplugin, welches diese Daten auswertet.

Und los geht's...

2.1. Den Agenten erweitern

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

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.

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 usb-APPLE_SD_Card_Reader_000000000820-0:0 -> ../../sdb
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 11:21 usb-APPLE_SD_Card_Reader_000000000820-0:0 -> ../../sdb
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

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  usb-APPLE_SD_Card_Reader_000000000820-0:0   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
usb-APPLE_SD_Card_Reader_000000000820-0:0
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-APPLE_SD_Card_Reader_000000000820-0:0
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.

Übrigens hab ich das -1 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 interpretierern würde.

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 vom 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 im Verzeichnis /usr/lib/check_mk_agent/plugins abzulegen.

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 einfach eine speziell formatierte Zeile, in der der Name unseres neuen Checks steht.

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 ein Plugin mit dem 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!), dann sieht unser Plugin so aus:

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

Als Dateiname hab ich jetzt einfach auch linux_usbstick verwendet, aber der ist eigentlich egal. 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
usb-APPLE_SD_Card_Reader_000000000820-0:0
wwn-0x5002538655584d30

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
usb-APPLE_SD_Card_Reader_000000000820-0:0
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 ausgaben:

root@linux# cmk -d heute | grep -A5 '^<<<linux_usbstick'
<<<linux_usbstick>>>
ata-APPLE_SSD_SM0512F_S1K5NYBF810191
usb-APPLE_SD_Card_Reader_000000000820-0:0
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.2. 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 leeres 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 sehen, steckt hier auch eine Versionsnummer der API für die Pluginprogrammierung – in unserem Fall v1:

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

Der nächste Schritt ist die sogenannten Parsefunktion. Diese hat die Aufgabe, die „rohen“ Agentendaten zu parsen und in eine logisch aufgeräumte Form zu bringen, die für alle weiteren Schritte einfach zu verarbeiten ist. Konvention ist, dass diese nach der Agentensektion benannt wird und mit parse_ beginnt. Sie bekommt als einziges Argument string_table. Bitte beachten Sie, dass Sie hier nicht frei in der Wahl des Arguments sind. Es muss wirklich so heißen.

Wir schreiben unsere Parsefunktion jetzt erstmal so, dass wir einfach nur die Daten, die sie bekommt, auf der Konsole ausgeben. Dazu nehmen wir einfach die print-Funktion (Achtung: seit Python 3 sind hier Klammern zwingend notwendig):

def parse_linux_usbstick(string_table):
    print(string_table)

Damit das Ganze irgendetwas bewirken soll, müssen wir unsere Parsefunktion und überhaupt die neue Agentensektion bei Checkmk bekannt machen. Dazu rufen wir eine Registrierfunktion auf:

register.agent_section(
    name = "linux_usbstick",
    parse_function = parse_linux_usbstick,
)

Hier ist es wichtig, dass der Name der Sektion wirklich exakt mit dem Sektionsheader in der Agentenausgabe übereinstimmt. Insgesamt sieht das jetzt so aus:

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

def parse_linux_usbstick(string_table):
    print(string_table)

register.agent_section(
    name = "linux_usbstick",
    parse_function = parse_linux_usbstick,
)

Wir haben jetzt gewissermaßen das einfachste mögliche Plugin gebaut, was noch keinen wirklich Nutzen hat, aber das wir immerhin schon testen können. Dazu stoßen wir auf der Kommandozeile eine Serviceerkennung (Option -I) von dem Host an, dessen Agenten wir vorhin präpariert haben. Wenn dessen Ausgabe auch wirklich eine Sektion linux_usbstick enthält, dann müssten wir unsere Debugausgabe sehen:

OMD[mysite]:~$ cmk -I myhost123
[['ata-APPLE_SSD_SM0512F_S1K5NYBF810191'], ['usb-APPLE_SD_Card_Reader_000000000820-0:0'], ['wwn-0x5002538655584d30']]

Etwas übersichtlicher wird die Ausgabe, wenn wir das einfache print durch ein Pretty-print aus dem Modul pprint ersetzen. Das ist für alle weitere Debugausgaben sehr empfehlenswert:

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

def parse_linux_usbstick(string_table):
    pprint.pprint(string_table)

register.agent_section(
    name = "linux_usbstick",
    parse_function = parse_linux_usbstick,
)

Das sieht dann so aus:

OMD[mysite]:~$ cmk -I myhost123
[['ata-APPLE_SSD_SM0512F_S1K5NYBF810191'],
 ['usb-APPLE_SD_Card_Reader_000000000820-0:0'],
 ['wwn-0x5002538655584d30']]

2.3. Die Parsefunktion schreiben

Wenn Sie genau hinsehen, dann erkennen Sie, dass es sich hier verschachtelte Listen handelt. Im Argument string_table bekommen Sie eine Liste, welche pro Zeile der Agentenausgabe eine Liste von Worten beheinhaltet. Dabei werden die Zeilen an Folgen von Leerzeichen getrennt. Da unsere Sektion pro Zeile nur ein Wort enthält, bestehen ergo die inneren Listen aus nur jeweils einem Eintrag.

Folgendes Beispiel macht die Struktur noch etwas klarer:

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

def parse_linux_usbstick(string_table):
    print("Number of lines: %d" % len(string_table))
    print("Number of words in first line: %d" % len(string_table[0]))
    print("Length of first word: %d" % len(string_table[0][0]))

register.agent_section(
    name = "linux_usbstick",
    parse_function = parse_linux_usbstick,
)

Die Ausgabe sieht dann so aus:

OMD[mysite]:~$ cmk -I myhost123
Number of lines: 3
Number of words in first line: 1
Length of first word: 36

Für unser Beispiel benötigen wir einfach nur eine einfache Liste der Devicenamen. Also machen wir unsere Parsefunktion so, dass sie aus jeder Zeile das eine Wort auspackt und in eine hübsche neue Liste verpackt:

def parse_linux_usbstick(string_table):
    parsed = []
    for line in string_table:
        parsed.append(line[0])
    pprint.pprint(parsed)

Die Debugausgabe sieht dann so aus (bitte schauen Sie genau hin, es gibt jetzt nur noch ein einziges paar eckiger Klammern):

['ata-APPLE_SSD_SM0512F_S1K5NYBF810191',
 'usb-APPLE_SD_Card_Reader_000000000820-0:0',
 'wwn-0x5002538655584d30']

Damit die Parsefunktion vollständig ist, müssen wir jetzt noch die Debugmeldung entfernen und – ganz wichtig – das Ergebnis mit return zurückgeben:

def parse_linux_usbstick(string_table):
    parsed = []
    for line in string_table:
        parsed.append(line[0])
    return parsed

2.4. Den Check deklarieren

So. Die Daten haben jetzt den Weg vom Agenten zu Checkmk und dort durch die Parsefunktion genommen und stehen als aufgeräumte einfache Liste von Strings zur Verarbeitung bereit. Theoretisch kann Checkmk daraus sogar mehrere verschiedene Checks ableiten. Ein Beispiel aus der Praxis ist z. B. fileinfo und fileinfo.groups – zwei unterschiedliche Checks, die beide die Daten aus der Sektion fileinfo verarbeiten.

Deswegen müssen Sie als nächstes den oder die Checks registrieren, die diese Daten verarbeiten sollen. Das geschieht mit einem zweiten register-Aufruf. Dabei müssen immer mindestens vier Dinge angeben:

  1. name: der Name des Checkplugins. Wenn es für die Sektion nur eine Art von Check gibt, nimmt man immer immer den Namen der Sektion.
  2. service_name: der Name des Services wie er dann im Monitoring erscheinen soll.
  3. discovery_function: Funktion zum Erkennen von Services dieses Typs (dazu gleich mehr).
  4. check_funktion: 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.5. Die Discoveryfunktion 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 für den betreffenden Host angelegt werden soll, bzw. welche Services des Typs, falls der Check auf einem Host mehrfach vorhanden sein kann (z. B. ein Service pro Dateisystem).

Für unseren Check brauchen wir dabei die einfache Variante: entweder ist er vorhanden oder nicht. Es gibt keine Liste von Checks oder sowas. Somit verhält er sich analog zum Check CPU load.

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.

Die Discoveryfunktion 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 unserem einfachen Fall bekommt sie genau ein Argument mit dem Namen section. In diesem bekommen wir das Ergebnis der Parsefunktion geliefert. Dies ist uns aber egal, denn auch wenn auf einem Host kein Blockdevice gefunden wurde, wollen wir einen Service anlegen.

Für jeden anzulegenden Service geben wir mit yield ein Objekt vom Typ Service zurück (also kein return!)

def discovery_linux_usbstick(section):
    yield Service()

2.6. Die Checkfunktion schreiben

Somit können wir nun zur eigentlichen Checkfunktion 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. Auch hier bekommen wir wieder unsere geparste Liste von Devicenamen.

Hier ist die Implementierung:

def check_linux_usbstick(section):
    for device in section:
        if device.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 device in section gehen wir in einer Schleife über alle gefundenen Blockgeräte durch.
  • Dann prüfen wir, ob das jeweilige Gerät mit usb-SCSI_DISK beginnt.
  • Falls ja, erzeugen wir mit Checkresult 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.7. 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.v0 import *

def parse_linux_usbstick(string_table):
    parsed = []
    for line in string_table:
        parsed.append(line[0])
    return parsed

def discover_linux_usbstick(section):
    yield Service()

def check_linux_usbstick(section):
    for device in section:
        if device.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.agent_section(
    name = "linux_usbstick",
    parse_function = parse_linux_usbstick,
)

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

2.8. Die Discovery testen

2.9. Den Check testen

UND HIER GEHT ES IN KÜRZE WEITER...

3. 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 Check-Plugins 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 Check-Plugin für den Anwender dokumentiert.
  • Ein MKP-Paket, in welchem das Plugin paketiert und einfach installierbar ist.

Artikel dazu folgen hier in Kürze...