Check_MK: Automation via Web Service

Wenn man Dinge automatisieren möchte kann man das über die Webservices von Check_MK machen. Dazu gibt es einen schönen Artikel von Check_MK selbst: https://mathias-kettner.de/checkmk_multisite_automation.html

Leider ist es aus der URL nicht leicht rauszufinden welche Variablen man mitgeben muss um z.B. einen Service zu Ack’en. Es gibt aber einen Trick um das einfach herauszufinden.

In den Global Settings unter User Interface -> Debug mode aktivieren.

Danach gewünschte Aktion ausführen und sich die Debug Ausgabe ansehen:

Hier sind jetzt alle Variablen aufgelistet die bei dem Request übergeben werden.

Viel Spaß beim automatisieren 😉

Check_MK: Agent Monitoring via SSH

Falls eine unverschlüsselte Abfrage des Check_MK Agemten nicht in Frage kommt ist es möglich den Agenten über SSH abzurufen.

SSH Key erzeugen in der OMD Umgebung

OMD[dev1]:~$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/omd/sites/dev1/.ssh/id_rsa): 
Created directory '/omd/sites/dev1/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /omd/sites/dev1/.ssh/id_rsa.
Your public key has been saved in /omd/sites/dev1/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:1Gxpgs9G9f4nK5uIvhe1iKU8xII1UzxFGZ7aApkYsNI dev1@cmkdev.m.local
The key's randomart image is:
+---[RSA 2048]----+
|  ...  o.o=o     |
| . . o++o=.+     |
|. E .o=++.O .    |
| .  . .*o*...    |
|       +S+.o..   |
|       .=.o ..   |
|         . .  o .|
|         ..... + |
|       .+o. oo.  |
+----[SHA256]-----+

Auf Zielsystem User anlegen, per sudo auf den Agenten berechtigen und SSH Publickey anlegen

User monitoring anlegen

root@target:~# adduser monitoring
Lege Benutzer »monitoring« an ...
Lege neue Gruppe »monitoring« (1003) an ...
Lege neuen Benutzer »monitoring« (1002) mit Gruppe »monitoring« an ...
Erstelle Home-Verzeichnis »/home/monitoring« ...
Kopiere Dateien aus »/etc/skel« ...
Geben Sie ein neues UNIX-Passwort ein: 
Geben Sie das neue UNIX-Passwort erneut ein: 
passwd: password updated successfully
Changing the user information for monitoring
Enter the new value, or press ENTER for the default
  Full Name []: Monitoring
  Room Number []: 
  Work Phone []: 
  Home Phone []: 
  Other []: 
Sind diese Informationen korrekt? [J/n] j

/etc/sudoers File anpassen

monitoring     ALL = NOPASSWD: /usr/bin/check_mk_agent

/home/monitoring/.ssh/authorized_keys anlegen

command="sudo /usr/bin/check_mk_agent" ssh-rsa AAAAB3NzaC..................GOXzCLX dev1@cmkdev.m.local

Rechte anpassen

chmod 640 /home/monitoring/.ssh/
chmod 600 /home/monitoring/.ssh/authorized_keys

xinetd Port 6556 abschalten

/etc/xinetd.d/check_mk anpassen

disable = yes

Anschließend „service xinetd restart“

Check_MK bekannt machen das Agent per SSH abgefragt werden muss

In WATO muss hierfür eine Regel angelegt werden.

Zu finden unter: Host & Service Parameters -> Datasource Programs -> Individual program call instead of agent access

Command line to execute:

ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no monitoring@$HOSTADDRESS$

Speichern und Regeln deployen, danach kann man mit WATO die Services suchen, etc.

Viel Spaß 😉

 

Python: Snippet – Python Code aus Textdateien ausführen/importieren

Check_MK speichert alle Daten in einfachen Dateien direkt als ausführbaren Python Code.

Um die abgelegten Dictionarys etc. in seinen eigenen Skripten weiterverwenden zu können kann man sich die mit eval() oder exec() laden.

eval() kann verwendet werden um z.B.  ein Dictionary in eine Variable zu laden, exec() kann auch ganze Funktionen etc. laden.

Beispiel eval():

dict.txt

{"foo":"bar","aaa":"bbb"}

import_dict.py

#!/usr/bin/env python

with open("dict1.txt","r") as f:
    x = eval(f.read().replace("\n",""))

print x

print x['foo']

Ergebnis:

max@cmkdevel:~/dev$ python import_dict.py
{'foo': 'bar', 'aaa': 'bbb'}
bar

Beispiel exec()

code.txt

max['foo'] = {
             "foo": "bar",
             "fxx": "boo",
             }


def hello(name):
    print "Hallo " + name

import_code.py

#!/usr/bin/env python

max = {}

with open("code.txt","r") as f:
    exec(f.read())


print max
print max['foo']
print max['foo']['foo']
hello("max")

Ergebnis:

max@cmkdevel:~/dev$ python import_code.py 
{'foo': {'foo': 'bar', 'fxx': 'boo'}}
{'foo': 'bar', 'fxx': 'boo'}
bar
Hallo max

 

 

Check_MK: Inventory erweitern mit SNMP Daten

Check_MK hat seit Version 1.2.5i1 das Feature Inventory an Board. Per Default werden von Systemen CPU, Memory, Harddisks, Softwarepakete und vieles mehr eingesammelt. Die Anleitung dient dazu das Inventory zu erweitern und eigene Informationen per SNMP einzusammeln und per View durchsuchbar zu machen.

Ich habe mittlerweile mehrere eigene Ergänzungen gemacht, darunter CDP Neighbors, Dot1X Authentications, Wireless Accesspoints und Hardware Inventory von Cisco Geräten.

Als Beispiel zeige ich das Cisco Hardware Inventory mit Seriennummern.

Bestandteile für eine Inventory Erweiterung:

  1. Daten sammeln -> Erweiterung für den Inventory Collector unter  ~/local/share/check_mk/inventory/
  2. Daten darstellen -> Erweiterung für die Inventory Views unter ~/local/share/check_mk/web/plugins/views/

1. Daten sammeln

Die Inventory Daten werden innerhalb einer Baumstruktur abgelegt. Man kann für seine eigenen Daten neue Knoten erzeugen. z.B. unter networking das hw_inventory wird im Tree dargestellt als networking.hw_inventory: . Der Doppelpunkt zeigt an das Daten unterhalb des Knotens folgen.

Das Inventory Plugin besteht aus einer Funktion und dem inv_info Directory welches alle Inventory Plugins kennt. In inv_info  im Key snmp_scan_function ist genauso wie bei den Check_MK SNMP Checks angegeben ob Check_MK überhaupt Daten finden wird bei einem SNMP Bulkwalk. snmp_info beinhaltet die OID und alle Untertabellen mit Daten die von Check_MK eingesammelt werden sollen.

Beispiel: ~/local/share/check_mk/inventory/my_hw_inventory.py

#!/usr/bin/python

port = {
0: "-",
1: "other",
2: "unknown",
3: "chassis",
4: "backplane",
5: "container",
6: "powerSupply",
7: "fan",
8: "sensor",
9: "module",
10: "port",
11: "stack",
12: "cpu"

}

def inv_hw_inventory(info, params):
    
    node = inv_tree("networking.hw_inventory:")

    for x in info[0]:
        
        #print x
        
        # If Serial Number is longer than 1 char
        if len(x[10]) >= 1:
        
            fru = "true" if "1" in x[15] else "false"
            
            classx = port[int(x[4])] if len(x[4]) is 1 else port[0]
            
            node.append(
            {
            "index": x[0],
            "description": x[1],
            "vendor_type": x[2],
            "contained_in_id": x[3],
            "class": classx,
            "parent_rel_pos": x[5],
            "name": x[6],
            "hw_rev": x[7],
            "fw_rev": x[8],
            "sw_rev": x[9],
            "serial_number": x[10],
            "manufactor": x[11],
            "model": x[12],
            "alias": x[13],
            "asset_id": x[14],
            "is_fru": fru,
            }
            )
        

inv_info["inv_hw_inventory"] = {
"inv_function": inv_hw_inventory,
"snmp_info": [(".1.3.6.1.2.1.47.1.1.1.1",[OID_END, # entPhysicalIndex
                                        "2", # entPhysicalDescr
                                        "3", # entPhysicalVendorType
                                        "4", # entPhysicalContainedIn
                                        "5", # entPhysicalClass
                                        "6", # entPhysicalParentRelPos
                                        "7", # entPhysicalName
                                        "8", # entPhysicalHardwareRev
                                        "9", # entPhysicalFirmwareRev
                                        "10", # entPhysicalSoftwareRev
                                        "11", # entPhysicalSerialNum                                        
                                        "12", # entPhysicalMfgName
                                        "13", # entPhysicalModelName
                                        "14", # entPhysicalAlias
                                        "15", # entPhysicalAssetID
                                        "16", # entPhysicalIsFRU
                                             ]),
             ],
"snmp_scan_function": lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.9.1"),
}

Mit der inv_tree Funktion wird ein neuer Inventory Baum eröffnet (siehe Code). Alle eingesammelten Informationen werden dann an diesen neuen Baum mit append als Dictionary hinzugefügt.

Die eigene Inventory Funktion kann man testen mit:

OMD[dev1]:~/local/share/check_mk/web/plugins/views$ cmk -v -i testswitch
Doing HW/SW-Inventory for testswitch...inv_hw_inventory snmp_extended_info snmp_os snmp_info ..unchanged..1603 entries..OK

Wenn in der Inventory Funktion ein pprint.pprint(info) eingebaut wird können auch die Rohdaten betrachtet werden.

2. Daten darstellen

Hierfür muss ein View gebaut werden der die Informationen des neuen Inventory Baums darstellt.

Beispiel ~/local/share/check_mk/web/plugins/views/my_inv_hw_inventory.py

#!/usr/bin/python

inventory_displayhints.update({

        ".networking.hw_inventory:"                          : { "title" : _("HW Inventory"), "render" : render_inv_dicttable,  "view" : "invhwi_of_host", "keyorder": 
                                                               ["index","name","description","model","contained_in_id","serial_number","hw_rev","fw_rev","sw_rev","class","manufactor","asset_id","alias","is_fru"] },
        ".networking.hw_inventory:*.index"                   : { "title" : _("Index") },
        ".networking.hw_inventory:*.description"             : { "title" : _("Description") },
        ".networking.hw_inventory:*.contained_in_id"         : { "title" : _("Contained in Index") },
        ".networking.hw_inventory:*.class"                   : { "title" : _("Class") },
        ".networking.hw_inventory:*.name"                    : { "title" : _("Name") },
        ".networking.hw_inventory:*.hw_rev"                  : { "title" : _("HW REV") },
        ".networking.hw_inventory:*.fw_rev"                  : { "title" : _("FW REV") },
        ".networking.hw_inventory:*.sw_rev"                  : { "title" : _("SW REV") },
        ".networking.hw_inventory:*.serial_number"           : { "title" : _("Serial Number") },
        ".networking.hw_inventory:*.manufactor"              : { "title" : _("Manufactor") },
        ".networking.hw_inventory:*.model"                   : { "title" : _("Model") },
        ".networking.hw_inventory:*.alias"                   : { "title" : _("Alias") },
        ".networking.hw_inventory:*.asset_id"                : { "title" : _("Asset ID") },
        ".networking.hw_inventory:*.is_fru"                  : { "title" : _("Is FRU") },
})

declare_invtable_view("invhwi", ".networking.hw_inventory:", _("HW Inventory"), _("HW Inventory"))

Danach sollte man mit „omd restart apache && cmk -v -R“ Check_MK und den Apachen neu starten, damit die neuen Views sichtbar werden. Damit Daten angezeigt werden muss 1 mal das Inventory auf dem Gerät gelaufen sein.

Im Views Menü sollte dann unser neuer View unter Inventory auftauchen:

Man kann sich alle Daten anzeigen lassen oder gezielt nach einzelnen Informationen oder Geräten suchen.

Wenn man im Host View ist kann man über das obere Menü auch direkt zu den Inventory Daten kommen, wurde der Button noch nicht genutzt kann er sichtbar gemacht werden mit dem „…“ Button.

Die Daten werden für den Host als Baumstruktur dargestellt.

Viel Spaß beim Erweitern 😉

Check_MK: Werte simulieren in einem SNMPwalk

Wer in Check_MK mittels gespeicherter SNMP Walks Checks entwickelt möchte unter umständen auch gerne sich ändernde Werte haben und keine statischen aus dem Dump.

Check_MK hat 3 eingebaute Funktionen um steigende Counter etc. zu simulieren.

Ausschnitt aus dem Check_MK Code lib/python/cmk_base/agent_simulator.py (GPLv2):

def agentsim_uptime(rate = 1.0, period = None): # period = sinus wave
    if period == None:
        return int(our_uptime() * rate)
    else:
        a = (rate * period) / (2.0 * math.pi)
        u = our_uptime()
        return int(u * rate + int(a * math.sin(u * 2.0 * math.pi / period)))


def agentsim_enum(values, period = 1): # period is in seconds
    hit = int(our_uptime()) / period % len(values)
    return values[hit]


def agentsim_sinus(base = 50, amplitude = 50, period = 300):
    return int(math.sin(our_uptime() * 2.0 * math.pi / period) * amplitude + base)



Wie kann man die Simulatoren verwenden?

Im gespeicherten SNMP Walk müssen die Simulatoren eingefügt werden statt des statisch exportieren Werts. Die SNMP Walks liegen unter ~/var/check_mk/snmpwalks/<hostname>

Beispiel:

.1.3.6.1.4.1.9.9.189.1.3.5.1.4.10101.1.3 0
.1.3.6.1.4.1.9.9.189.1.3.5.1.4.10101.2.1 0
.1.3.6.1.4.1.9.9.189.1.3.5.1.4.10101.2.2 %{uptime(rate=10)}
.1.3.6.1.4.1.9.9.189.1.3.5.1.4.10101.2.3 %{sinus()}
.1.3.6.1.4.1.9.9.189.1.3.5.1.4.10101.3.1 %{enum([10,20,30,40,50,60,70,80,90])}
.1.3.6.1.4.1.9.9.189.1.3.5.1.4.10101.3.2 0
.1.3.6.1.4.1.9.9.189.1.3.5.1.4.10101.3.3 31008
.1.3.6.1.4.1.9.9.189.1.3.5.1.4.10101.4.1 5353
.1.3.6.1.4.1.9.9.189.1.3.5.1.4.10101.4.2 0

 

 

Check_MK: Cisco MLS QoS Check

Cisco MLS QoS Check ist ein Check_MK kompatibler Check zum Überwachen der der Hardware Queues auf Cisco Switches.

Per Default werden keine Queues / Thresholds überwacht, diese müssen durch eine WATO Rule definiert werden.

Für jedes Interfaces wird ein Service angelegt, mehrere Queues auf einem Interface werden in einem Service dargestellt.

Die Queues werden einzeln als Graphen dargestellt:

 

 

Check_MK: Livestatus Schnittstelle

Check_MK stellt alle Informationen per Livestatus Schnittstelle bereit. So gut wie alles was man auf der Webseite des Monitorings sehen kann kommt aus Livestatus. Der Sprachsyntax für Abfragen ist LQL -> Livestatus Query Language.

Per Default ist die Livestatus Schnittstelle nur auf dem Host selbst über einen Socket abrufbar. Es gibt aber die Möglichkeit per xinetd die Schnittstelle auch per LAN zur Verfügung zu stellen.

Livestatus Schnittstelle auf LAN (per TCP) aktivieren

OMD stoppen

OMD[dev1]:~$ omd stop
Removing Crontab...OK
Stopping dedicated Apache for site dev1....OK
Stopping Check_MK Micro Core...killing 19502....OK
Stopping rrdcached...waiting for termination...OK
Stopping mknotifyd...killing 19481...
Stopping Livestatus Proxy-Daemon...killing 19474....OK
Stopping mkeventd...killing 19466....OK

OMD Config starten

OMD[dev1]:~$ omd config

OMD starten

OMD[dev1]:~$ omd start
Starting mkeventd...OK
Starting Livestatus Proxy-Daemon...OK
Starting mknotifyd...OK
Starting rrdcached...OK
Starting Check_MK Micro Core...OK
Starting dedicated Apache for site dev1...OK
Starting xinetd...OK
Initializing Crontab...OK
ACHTUNG
Nach dem Aktivieren der Livestatus Schnittstelle ist sie von jedem Host aus erreichbar! Wenn dies nicht gewünscht ist muss man das in der xinetd Konfig einschränken.

Erlaubte Hosts auf Livestatus einschränken

Dazu muss ~/etc/xinet.d/mk-livestatus angepasst werden beim Parameter only_from, anschließend muss der xinetd Service neu gestartet werden mit „omd restart xinetd“.

service livestatus
{
  type		= UNLISTED
  socket_type	= stream
  protocol	= tcp
  wait		= no

        # limit to 100 connections per second. Disable 3 secs if above.
  cps             = 100 3

        # set the number of maximum allowed parallel instances of unixcat.
        # Please make sure that this values is at least as high as 
        # the number of threads defined with num_client_threads in
        # etc/mk-livestatus/nagios.cfg
        instances       = 500

        # limit the maximum number of simultaneous connections from
        # one source IP address
        per_source      = 250 

        # Disable TCP delay, makes connection more responsive
  flags           = NODELAY
# configure the IP address(es) of your Nagios server here:
  only_from       = 127.0.0.1 10.0.20.1 10.0.20.2

# ----------------------------------------------------------
# These parameters are handled and affected by OMD
# Do not change anything beyond this point.

# Disabling is done via omd config set LIVESTATUS_TCP [on/off].
# Do not change this:
  disable		= no

# TCP port number. Can be configure via LIVESTATUS_TCP_PORT
  port		= 6557

# Paths and users. Manual changes here will break some omd
# commands such as 'cp', 'mv' and 'update'. Do not toutch!
  user		= dev1
  server		= /omd/sites/dev1/bin/unixcat
  server_args     = /omd/sites/dev1/tmp/run/live
# ----------------------------------------------------------
}

Livestatus Schnittstelle nutzen

Libraries und Beispiele von Check_MK

Check_MK hat in seinen DOCs für Python, Perl und C++ Libraries und Beispiele an Board. Zu finden sind Sie unter ~/share/doc/check_mk/livestatus/api/

Hier ein einfaches Beispiel für Python:

#!/usr/bin/env python

import os
import sys
import livestatus

try:
    omd_root = os.getenv("OMD_ROOT")
    socket_path = "unix:" + omd_root + "/tmp/run/live"

except:
    sys.stderr.write("This example is indented to run in an OMD site\n")
    sys.stderr.write("Please change socket_path in this example, if you are\n")
    sys.stderr.write("not using OMD.\n")
    sys.exit(1)

department = "testx"

hosts = livestatus.SingleSiteConnection(socket_path).query_table("GET hosts\nColumns: name\nFilter: custom_variables ~~ TAGS %s" % department)

for host in hosts:
    print host[0]

Test Run:

OMD[dev1]:~$ python live.py 
router

PHP Library

Check_MK hat selbst keine Library für PHP an Board, ich habe aber bereits folgende erfolgreich eingesetzt.

https://github.com/aashley/nagios-livestatus-client

Beispiel von der Github Seite (siehe Link oben)

<?php

use Nagios\Livestatus\Client;

$options = array(
    'socketType' => 'tcp',
    'socketAddress' => '10.253.14.22',
    'socketPort' => '6557',
);

$client = new Client($options);

$response = $client
    ->get('hosts')
    ->column('host_name')
    ->column('state')
    ->execute();

foreach ($response as $host) {
    print $host[0] . ": " . $host[1] . "\n";
}

$response = $client
    ->get('hosts')
    ->column('host_name')
    ->column('state')
    ->executeAssoc();

foreach ($response as $host) {
    print $host['host_name'] . ": " . $host['state'] . "\n";
}

Auf dem Check_MK Host

root@cmkdev:~# echo -e "GET hosts\nColumns: host_name\n" | unixcat /omd/sites/dev1/tmp/run/live
cluster
node1
router
node3
node2
localhost
Hinweis: Auf Ubuntu muss check-mk-livestatus installiert sein damit unixcat zur Verfügung steht.

In der OMD Umgebung

In der OMD Umgebung steht das Tool lq zur Verfügung welches ebenfalls ein unixcat macht.

OMD[dev1]:~$ lq "GET hosts\nColumns: host_name\n" 
cluster
node1
router
node3
node2
localhost

per TCP von Remote Host

Der Standardport ist tcp/6557.

root@cmkdev2:~# echo -e "GET hosts\nColumns: host_name\n" | netcat 10.211.55.13 6557
cluster
node1
router
node3
node2
localhost

Welche Tabellen / Spalten (Columns) gibt es?

Das lässt sich per Livestatus abfragen:

OMD[dev1]:~$ lq "GET columns\nFilter: table = hosts"

description;name;table;type
Whether passive host checks are accepted (0/1);accept_passive_checks;hosts;int
Whether the current problem has been acknowledged (0/1);acknowledged;hosts;int
Type of acknowledgement (0: none, 1: normal, 2: sticky);acknowledgement_type;hosts;int
An optional URL to custom actions or information about this host;action_url;hosts;string
The same as action_url, but with the most important macros expanded;action_url_expanded;hosts;string
Whether checks of the object are enabled (0/1);active_checks_enabled;hosts;int
IP address;address;hosts;string
An alias name for the host;alias;hosts;string
Logical command name for active checks;check_command;hosts;string
Logical command name for active checks, with macros expanded;check_command_expanded;hosts;string
Whether to check to send a recovery notification when flapping stops (0/1), not supported by CMC;check_flapping_recovery_notification;hosts;int
Whether freshness checks are enabled (0/1);check_freshness;hosts;int
Number of basic interval lengths between two scheduled checks;check_interval;hosts;float
The current check option, forced, normal, freshness (0-2), not supported by CMC;check_options;hosts;int
Time period in which this object will be checked. If empty then the check will always be executed.;check_period;hosts;string
Type of check (0: active, 1: passive);check_type;hosts;int
Whether checks of the object are enabled (0/1);checks_enabled;hosts;int
A list of all direct children of the host;childs;hosts;list
... gekürzt ...

Hilfe beim Zusammenstellen eines LQL Query

Da LQL Queries sehr komplex werden können kann man das Check_MK zur Hilfe nehmen. Dazu muss in den Global Settings „Debug Livestatus queries“ aktivieren.

Anschließend kann man sich einen View zusammenstellen mit den gewünschten Daten, der LQL Query wird unten an der Webseite angezeigt der für die Abfrage verwendet wurde.

Mehr dazu bei Check_MK / Mathias Kettner

Mathias Kettner hat Livestatus entwickelt und bietet auch eine sehr gute Dokumentation dazu an:

https://mathias-kettner.de/checkmk_livestatus.html

Have fun 😉

 

Check_MK: Cluster Checks bauen

Hier ein kleines Beispiel wie man einen Cluster Check implementieren kann.

Testumgebung:

3 Nodes node1,node2 und node3 welche als Clusterobjekt cluster zusammengefasst sind. Die Daten kommen von Piggybacks.

Clustercheck ist mycluster und ist ein Beispiel Check.

Schritt 1: Nodes anlegen

In WATO 3 Nodes anlegen mit der IP 127.0.0.1 (Testumgebung, Daten kommen von Piggybacks), Agent: No Agent 

Schritt 2: Clusterobjekt anlegen

Schritt 4: Clustercheck bauen, Piggybacks & Inventory

Unser eigener Check wird in ~/local/share/check_mk/checks/ abgelegt, er heisst mycluster.

#!/usr/bin/env python

def inventory_mycluster(info):
    for line in info:
        #pprint.pprint(line)
        if "CLUSTER" in line[0]:
            return [(None, None)]


def check_mycluster(item, params, info):
    active_nodes = 0
    for node in info:
        key, value = node
        if "CLUSTER" in key:
            if "ACTIVE" in value:
                active_nodes += 1

    if active_nodes <= 1:
        status = 2
    else:
        status = 0
    return status, "%d active nodes" % active_nodes


check_info['mycluster'] = {
    'inventory_function'    : inventory_mycluster,
    'check_function'        : check_mycluster,
    'service_description'   : 'MYCluster',
}

Piggybacks ablegen in /var/lib/check_mk_agent/spool/mycluster_test

<<<<node1>>>>
<<<mycluster>>>
CLUSTER	ACTIVE
<<<<>>>>
<<<<node2>>>>
<<<mycluster>>>
CLUSTER	ACTIVE
<<<<>>>>
<<<<node3>>>>
<<<mycluster>>>
CLUSTER	ACTIVE
<<<<>>>>
<<<<node4>>>>
<<<mycluster>>>
CLUSTER	ACTIVE
<<<<>>>>

Inventory durchführen:

OMD[dev1]:~/local/share/check_mk/checks$ cmk -v -II cluster
Discovering services on cluster:
node1:
Using piggyback information from host localhost.
    1 mycluster

node2:
Using piggyback information from host localhost.
    1 mycluster

node3:
Using piggyback information from host localhost.
    1 mycluster

Reload durchführen:

OMD[dev1]:~/local/share/check_mk/checks$ cmk -R
Generating configuration for core (type cmc)...OK
Packing config...OK
Restarting monitoring core...OK

Schritt 5: Service zum Cluster Service machen

Eine neue WATO Regel erstellen „Clustered services“:

WATO Änderung aktivieren, die Services verschwinden bei den Nodes und werden nur noch unter dem Cluster Objekt angezeigt.

 

 

Viel Spaß 🙂

DNS Blacklist Check

Check um zu Überprüfen ob ein Mailserver auf einer Blacklist ist. Der Check lässt sich auch in Check_MK einbinden, dazu das Script in ~/local/lib/nagios/plugins/ ablegen und eine Regel „Classical active and passive Monitoring checks“ für den Mailserver erstellen. Eine native Check_MK Implementierung folgt noch 😉

#!/usr/bin/env python
# -*- encoding: utf-8; py-indent-offset: 4 -*-

#
# check_dnspl.py - Check IP against Blacklist
# Use it on your own risk!
#
# Written 2017 - Maximilian Thoma
#
# This program is free software; you can redistribute it and/or modify it under the terms of the GNU
# General Public License as published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program; if not,
# write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
#

import getopt
import sys
import socket

# Define blacklists to be checked
blacklists = [
    'all.s5h.net',
    'b.barracudacentral.org',
    'bl.emailbasura.org',
    'bl.spamcannibal.org',
    'bl.spamcop.net',
    'blacklist.woody.ch',
    'bogons.cymru.com',
    'cbl.abuseat.org',
    # 'cdl.anti-spam.org.cn',
    'combined.abuse.ch',
    'db.wpbl.info',
    'dnsbl-1.uceprotect.net',
    'dnsbl-2.uceprotect.net',
    'dnsbl-3.uceprotect.net',
    'dnsbl.anticaptcha.net',
    'dnsbl.cyberlogic.net',
    'dnsbl.dronebl.org',
    'dnsbl.inps.de',
    'dnsbl.sorbs.net',
    'drone.abuse.ch',
    'drone.abuse.ch',
    'duinv.aupads.org',
    'dul.dnsbl.sorbs.net',
    'dyna.spamrats.com',
    'dynip.rothen.com',
    'exitnodes.tor.dnsbl.sectoor.de',
    'http.dnsbl.sorbs.net',
    'ips.backscatterer.org',
    'ix.dnsbl.manitu.net',
    'korea.services.net',
    'misc.dnsbl.sorbs.net',
    'noptr.spamrats.com',
    'orvedb.aupads.org',
    'pbl.spamhaus.org',
    'proxy.bl.gweep.ca',
    'psbl.surriel.com',
    'relays.bl.gweep.ca',
    'relays.nether.net',
    'sbl.spamhaus.org',
    'short.rbl.jp',
    'singular.ttk.pte.hu',
    'smtp.dnsbl.sorbs.net',
    'socks.dnsbl.sorbs.net',
    'spam.abuse.ch',
    'spam.dnsbl.sorbs.net',
    'spam.spamrats.com',
    'spambot.bls.digibase.ca',
    'spamrbl.imp.ch',
    'spamsources.fabel.dk',
    'ubl.lashback.com',
    'ubl.unsubscore.com',
    'virus.rbl.jp',
    'web.dnsbl.sorbs.net',
    'wormrbl.imp.ch',
    'xbl.spamhaus.org',
    'z.mailspike.net',
    'zen.spamhaus.org',
    'zombie.dnsbl.sorbs.net',
]


def check_if_valid_host_ip(ip):
    try:
        socket.inet_aton(ip)
        return True
    except socket.error:
        return False


def revert_ip(ip):
    x = ip.split('.')
    return x[3] + '.' + x[2] + '.' + x[1] + '.' + x[0]


def bls(olist):
    x = ''
    for bl in olist:
        x += bl + " "
    return x


def log(debug, s):
    if debug:
        print s


def usage():
    print "check_dnsbl.py - Check IP against DNS blacklists.\n" \
          " -H, --host <hostname or ip> Hostname or IP\n" \
          " -d, --debug Debug Modus\n" \
          " -h, --help Help"


def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "H:dh", ['host=', 'debug', 'help'])
    except getopt.GetoptError as err:
        print str(err)
        sys.exit(2)

    found_h = False
    host = None
    debug = False

    for o, a in opts:
        if o in ('-H', '--host'):
            host = a
            found_h = True
        if o in ('-d', '--debug'):
            debug = True
        if o in ('-h', '--help'):
            usage()
            sys.exit(2)

    if not found_h:
        print "-H is not given"
        usage()
        sys.exit(2)

    # print host
    # print debug



    # Check if valid Host IP
    if check_if_valid_host_ip(host) is not True:
        try:
            resolved_ip = socket.gethostbyname(host)
        except socket.gaierror:
            sys.stderr.write('Unable to make an DNS lookup, provided IP or hostname is invalid.')
            sys.exit(2)
        if check_if_valid_host_ip(resolved_ip) is not True:
            sys.stderr.write('Error no valid IP address.')
            sys.exit(2)
        else:
            ip = resolved_ip
    else:
        ip = host

    # Revert IP
    rip = revert_ip(ip)

    # Init variables
    negative_result_buffer = []

    for bl in blacklists:
        # Init Result
        result = ''
        # Build query string
        q = rip + '.' + bl
        log(debug, q)

        # Query DNS
        try:
            result = socket.gethostbyname(q)
            log(debug, "Result: %s" % result)
        except socket.error:
            log(debug, "No result")
            pass

        if "127.0.0" in result:
            log(debug, "Found 127.0.0 in result.")
            negative_result_buffer.append(bl)

    if len(negative_result_buffer) == 0:
        print "OK - %s (%s) is not listed at: %s" % (host, ip, bls(blacklists))
        sys.exit(0)
    else:
        print "CRITICAL - %s (%s) ist listed at: %s" % (host, ip, bls(negative_result_buffer))
        sys.exit(2)


if __name__ == "__main__":
    main()

 

Check_MK: Host custom variables

Check_MK bietet auch die Möglichkeit die Host Variablen zu erweitern um z.B. ein Host Description Feld hinzuzufügen.

Die Erweiterung muss in ~/local/share/check_mk/web/plugins/wato/ abgelegt werden. Die Dateiendung ist zwingend .py.

OMD[dev1]:~/local/share/check_mk/web/plugins/wato$ cat host_custom_vars.py 

#!/usr/bin/env python

declare_host_attribute(
    NagiosTextAttribute(
        # Sektion / Gruppe
        "MY_HOST_CUSTOM_VARIABLES",
        # Variable mit beginnenden Unterstrich
        "_HOST_MAGIC",
        # Feldbeschreibung
        _("Host Magic"),
        # Hilfe
        _("Hilfe"),
    ),
    show_in_table=True,
    show_in_folder=True,
)

Nach dem Anlegen eines eigenen Attributs muss der Apache neu gestartet werden mit z.B. „omd restart apache“.

Die Host custom variables sind auch per Livestatus abrufbar:

OMD[dev1]:~/local/share/check_mk/web/plugins/wato$ lq "GET hosts\nColumns: host_name host_address\nFilter: host_custom_variables ~ HOST_MAGIC (^|[ ])magic($|[ ])"
localhost;127.0.0.1

 

Check_MK: Service custom variables

Die Service Variablen lassen sich genau so Erweitern wie die Host Variablen.

Dazu muss man eine neue Definition erstellen im Folder ~/local/share/check_mk/web/plugins/wato/. Die Dateiendung muss .py sein.

#!/usr/bin/env python

register_rule(
    # Sektion
    "Test",
    # Variable
    "extra_service_conf:_test_test",
    # Parameter
    TextUnicode(
        title = _("TEST TEST"),
        help = _("Test test"),
        size = 80,
        attrencode = True,
    ),
    itemtype = 'service',
    match = 'first',
)

Die Variable in der das gespeichert wird ist: extra_service_conf:_test_test, in Livestatus ist es dann unter service_custom_variables als TEST_TEST abrufbar.

Danach ein „omd restart apache && cmk -R“ um die Änderung zu aktivieren.

Jetzt kann mann unter „Parameters for this service“ einen Wert für den Service setzen.

Die selbst festgelegten Parameter können auch per Livestatus abgefragt werden:

OMD[dev1]:~/local/share/check_mk/web/plugins/wato$ lq "GET services\nColumns: service_host_name service_service_description\nFilter: service_custom_variables ~ TEST_TEST (^|[ ])test123($|[ ])"
localhost;CPU utilization

 

Check_MK: Script um IP Adresse statisch bei einem Host einzutragen

Wenn die Hosts nur mit DNS-Namen angelegt sind wird regelmäßig der DNS-Cache aktualisiert, dies verzögert den Deploy Prozess. Das Script ließt den DNS Cache aus und setzt für die Hosts die IP Adresse statisch, ist die IP nicht im Cache löst er diese direkt per DNS auf.

#!/usr/bin/env python

#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful, but WITHOUT ANY 
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with 
# this program; if not, write to the Free Software Foundation, Inc., 
# 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
#

import json
import os
import subprocess
import sys
import livestatus
import socket

url = "http://cmkdev/dev1/check_mk/webapi.py"
user = "checkmk_automation"
passw = "password"
department = "hosttag"

##########################################################################################
### DON'T CHANGE ANYTHING BELOW
##########################################################################################

curl_base_cmd = url + "?_username=%s&_secret=%s" %(user,passw)

def execute_curl_cmd(curl_cmd):
    try:
        p = subprocess.Popen(curl_cmd, shell=True, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        o, e = p.communicate()
        out  = json.loads(o)
    except Exception, e:
        sys.stderr.write("ERROR: '%s' while executing '%s'\n" % (e, curl_cmd))
        sys.exit(1)
        return {}

    if out["result_code"] == 0:
        return out["result"]
    else:
        sys.stderr.write("ERROR: while executing WEB API command: '%s'\ncurl: '%s'\n" % \
                         (out["result"], curl_cmd))
        sys.exit(1)
        return {}

def edit_wato_host(curl_base_cmd, req):
    create_cmd = ('curl --insecure \'' + curl_base_cmd + \
                  '&action=edit_host\' ' + \
                  '-d \'%s\'') % req
    return execute_curl_cmd(create_cmd)

try:
    omd_root = os.getenv("OMD_ROOT")
    socket_path = "unix:" + omd_root + "/tmp/run/live"
    ip_cache = omd_root + "/var/check_mk/ipaddresses.cache"

    with open(ip_cache,'r') as inf:
        ipc = eval(inf.read())

except:
    sys.stderr.write("This example is indented to run in an OMD site\n")
    sys.stderr.write("Please change socket_path in this example, if you are\n")
    sys.stderr.write("not using OMD.\n")
    sys.exit(1)

hosts_arr = livestatus.SingleSiteConnection(socket_path).query_table("GET hosts\nColumns: name\nFilter: custom_variables ~~ TAGS %s" % department)

for host in hosts_arr:

    cached_ip = ipc.get((host[0], 4))

    if cached_ip != None:

        print host[0]
        print cached_ip

        req = 'request={"hostname": "%s", "attributes":{"ipaddress": "%s"}}' %(host[0],cached_ip)

        print req

        edit_wato_host(curl_base_cmd,req)

    else:

        try:
            ip = socket.gethostbyname(host[0])
            print "%s - %s" % (host[0],ip)

        except:
            print "no dns record for host %s " % host[0]

        req = 'request={"hostname": "%s", "attributes":{"ipaddress": "%s"}}' %(host[0],ip)

        print req

        edit_wato_host(curl_base_cmd,req)

 

Check_MK: yield statt return in der Check Funktion

Check_MK erwartet bei der Check Funktion ein Tuple mit 2 (ohne Performance Daten) bzw. 3 Werten (mit Performance Daten).

<STATUS>, <MESSAGE>, <PERFOMANCEDATA>

Wenn man in einem Service mehrere Werte aggregieren möchte z.B. man hat mehrere Lüfter im System möchte aber nicht für jeden einen eigenen Service sondern nur einen einzigen, so muss man die Werte von mehreren vergleichen und dann den entsprechenden Status zurückgeben. Da diese ganzen Vergleiche aufwändig sind und fehleranfällig löst Check_MK so eine Situation mit dem yield Generator. Diesem werden alle Werte einfach übergeben, er setzt nach einer Logik den Servicestatus und fügt alle einzelnen Status in den Output des Services.

Der Status des Checks wird in folgender Reihenfolge angezeigt, d.H. ist einer der Status UNKNOWN wird der komplette Service als UNKNOWN angezeigt:

  1. Unknown
  2. Critical
  3. Warn
  4. OK

Beispiel alte Lösung mit 1 return

def check_mycheck(item, params, info):
    warn = False
    crit = False
    unkn = False

    msg = ""

    for i in info:
        fan, status = i
        status = int(status) 

        if status == 2:
            crit = True
            msg += " %s: CRIT -" % fan
        elif status == 1:
            warn = True
            msg += " %s: WARN -" % fan
        elif status == 3:
            unkn = True
            msg += " %s: UNKN -" % fan
        else:
            msg += " %s: OK -" % fan

    if unkn == True:
        return 3, msg
    elif crit == True:
        return 2, msg
    elif warn == True:
        return 1, msg
    else:
        return 0, msg

Ausgabe:

Beispiel neue Lösung mit yield

def check_mycheck(item, params, info):
    for i in info:
        fan, status = i
        status = int(status)
        yield status, "Status von: %s" % fan

Ausgabe:

Check_MK: Piggyback Checkergebnisse anderer Hosts bereitstellen

Es gibt die Möglichkeit Check Ergebnisse wenn ein Host nicht direkt erreichbar ist für Check_MK oder man bestimmte Dinge z.B. Erreichbarkeit eines TCP Services von einem anderen Host aus testen möchte, als Piggyback über den Agenten eines anderen Hosts mitzugeben.

In den Beispiel haben wir einen Host (router) der ein Check Ergebnis von einem anderen Host (localhost) bekommen soll. Check_MK verwendet hierfür folgenden Syntax:

<<<<hostname>>>>

Check Ergebnisse, Ausgaben von Local Checks, etc.

<<<<>>>>

Das Piggyback File muss auf dem Host mit Agenten unter /var/lib/check_mk_agent/spool abgelegt werden. Ich habe die Ausgabe für ein Agent Check + einen Local Check eingebaut.

root@cmkdev:/var/lib/check_mk_agent/spool# cat piggy_router 

<<<<router>>>>
<<<mycheck>>>
foo 1
bar 2

<<<local>>>
0 Mein_piggyback_localcheck - TEST TEST TEST

<<<<>>>>

Dazu ein einfacher Check der in der Zeile den 2 Parameter als Status für den ersten zurückgibt:

OMD[dev1]:~/local/share/check_mk/checks$ cat mycheck 
#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-


def inventory_mycheck(info):
    if len(info) > 1:
        yield '', None


def check_mycheck(item, params, info):
    for i in info:
        yield int(i[1]), "Status von: %s" % i[0]



check_info['mycheck'] = {
    'inventory_function'    : inventory_mycheck,
    'check_function'        : check_mycheck,
    'service_description'   : 'MyCheck',
}

Inventory, Reload und Test:

OMD[dev1]:~$ cmk -v -II router
Discovering services on router:
router:
Using piggyback information from host localhost.
    1 local
    1 mycheck

OMD[dev1]:~$ cmk -R
Generating configuration for core (type cmc)...OK
Packing config...OK
Restarting monitoring core...OK

OMD[dev1]:~$ cmk -v router
Check_MK version 1.4.0p8
Using piggyback information from host localhost.
Mein_piggyback_localcheck OK - TEST TEST TEST
MyCheck              CRIT - Status von: foo(!), Status von: bar(!!)
OK - execution time 0.0 sec|execution_time=0.002 user_time=0.000 system_time=0.010 children_user_time=0.000 children_system_time=0.000

Check_MK weist auf der Shell darauf hin das Piggyback Informationen verwendet wurden:

Using piggyback information from host localhost.

 

Viel Spaß beim piggybacken 😉

Check_MK: Mit gespeicherten SNMP Walks Checks entwickeln

Es ist keine schlechte Idee seine Checks auf einer Testinstanz zu entwickeln, geht etwas schief wird das Produktionssystem davon nicht beeinflusst.

Check_MK bietet die Möglichkeit einen SNMP Walk zu exportieren und diesen für die Checkentwicklung zu verwenden.

Schritt 1: Export eines SNMP Walks in Check_MK

OMD[test1]:~$ cmk -v --snmpwalk router
router:
Walk on ".1.3.6.1.2.1"...2939 variables.
Walk on ".1.3.6.1.4.1"...1539 variables.
Successfully Wrote /omd/sites/test1/var/check_mk/snmpwalks/router.

Mit dem Befehl „cmk –snmpwalk <hostname>“ lässt sich auf dem Produktivsystem ein Check_MK kompatibler SNMP Walk erzeugen der in der Site unter ~/var/check_mk/snmpwalks/ abgelegt wird.

Schritt 2: Check_MK Testinstanz erzeugen

root@max:~# omd create dev
Adding /opt/omd/sites/dev/tmp to /etc/fstab.
Creating temporary filesystem /omd/sites/dev/tmp...OK
Restarting Apache...OK
Created new site dev with version 2017.07.11.cee.

  The site can be started with omd start dev.
  The default web UI is available at http://max/dev/

  The admin user for the web applications is cmkadmin with password: xxxxxxxxxxx
  (It can be changed with 'htpasswd -m ~/etc/htpasswd cmkadmin' as site user.
)
  Please do a su - dev for administration of this site.

root@max:~# omd start dev
Starting mkeventd...OK
Starting liveproxyd...OK
Starting mknotifyd...OK
Starting rrdcached...OK
Starting cmc...OK
Starting apache...OK
Initializing Crontab...OK
root@max:~#
  1. omd create dev
  2. omd start dev

Schritt 3: Kopieren des Walks

root@max:~# cp /omd/sites/prod/var/check_mk/snmpwalks/router /omd/sites/dev/var/check_mk/snmpwalks/router

Schritt 4: Host anlegen & SNMP walk Regel erstellen

Der Host muss genau so heissen wie das Walk-File, als IP-Adresse 127.0.0.1 angeben und bei Agent Type SNMP.

Anschließend noch eine Regel erstellen das die Walks verwendet werden für den Host.

Danach die Änderungen deployen.

Schritt 5: Inventory durchführen & Aktivieren

  1. Inventory: cmk -v -II router
  2. Aktivieren: cmk -v -O
  3. cmk -v -D router
OMD[dev]:~$ cmk -v -II router
Discovering services on router:
router:
    1 hr_cpu
    4 hr_fs
    1 hr_mem
    2 if64
    1 snmp_info
    1 snmp_uptime
    1 ucd_cpu_load

OMD[dev]:~$ cmk -v -O
Waiting for exclusive lock on /omd/sites/dev/etc/check_mk/main.mk.
Generating configuration for core (type cmc)...
Not importing state from Nagios, /omd/sites/dev/var/nagios/retention.dat not found.
/omd/sites/dev/var/check_mk/core/config written.
OK
Baking agents...VANILLA...linux_rpm:uptodate...linux_deb:uptodate...linux_tgz:uptodate...aix_tgz:uptodate...solaris_tgz:uptodate...solaris_pkg:uptodate...windows_msi:uptodate...OK
GENERIC...linux_rpm:uptodate...linux_deb:uptodate...linux_tgz:uptodate...aix_tgz:uptodate...solaris_tgz:uptodate...solaris_pkg:uptodate...windows_msi:uptodate...OK
router...does not use agent, skipping.
OK
Packing config...OK
Reloading monitoring core...OK
OMD[dev]:~$

OMD[dev]:~$ cmk -D router

router                                                                         
Addresses:              127.0.0.1
Tags:                   /wato/, ip-v4, ip-v4-only, lan, prod, site:dev, snmp, snmp-only, wato
Host groups:            
Contact groups:         all
Type of agent:          SNMP (use stored walk)
Services:
  checktype    item     params                                                                                                                                                                                                                        description         groups
  ------------ -------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------- ------
  ucd_cpu_load None     (5.0, 10.0)                                                                                                                                                                                                                   CPU load                  
  hr_cpu       None     {'levels': (80.0, 90.0)}                                                                                                                                                                                                      CPU utilization           
  hr_fs        /        {'trend_range': 24, 'show_levels': 'onmagic', 'inodes_levels': (10.0, 5.0), 'magic_normsize': 20, 'show_inodes': 'onlow', 'levels': (80.0, 90.0), 'show_reserved': False, 'levels_low': (50.0, 60.0), 'trend_perfdata': True} Filesystem /              
  hr_fs        /boot    {'trend_range': 24, 'show_levels': 'onmagic', 'inodes_levels': (10.0, 5.0), 'magic_normsize': 20, 'show_inodes': 'onlow', 'levels': (80.0, 90.0), 'show_reserved': False, 'levels_low': (50.0, 60.0), 'trend_perfdata': True} Filesystem /boot          
  hr_fs        /distros {'trend_range': 24, 'show_levels': 'onmagic', 'inodes_levels': (10.0, 5.0), 'magic_normsize': 20, 'show_inodes': 'onlow', 'levels': (80.0, 90.0), 'show_reserved': False, 'levels_low': (50.0, 60.0), 'trend_perfdata': True} Filesystem /distros       
  hr_fs        /opt/omd {'trend_range': 24, 'show_levels': 'onmagic', 'inodes_levels': (10.0, 5.0), 'magic_normsize': 20, 'show_inodes': 'onlow', 'levels': (80.0, 90.0), 'show_reserved': False, 'levels_low': (50.0, 60.0), 'trend_perfdata': True} Filesystem /opt/omd       
  if64         2        {'state': [u'1'], 'errors': (0.01, 0.1), 'speed': 1000000000}                                                                                                                                                                 Interface 2               
  if64         3        {'state': [u'1'], 'errors': (0.01, 0.1), 'speed': 100000000}                                                                                                                                                                  Interface 3               
  hr_mem       None     (150.0, 200.0)                                                                                                                                                                                                                Memory used               
  snmp_info    None     None                                                                                                                                                                                                                          SNMP Info                 
  snmp_uptime  None     {}                                                                                                                                                                                                                            Uptime                    
OMD[dev]:~$ 

Viel Spaß beim Entwickeln 😉

Check_MK: SNMP based Check

Neben der Möglichkeit lokale Scripts per Agent oder Agent Plugins zu verwenden gibt es noch die Option Werte direkt per SNMP von Geräten auszulesen. Bei diesem Beispiel ist auch eine Erweiterung dabei damit in WATO eine Regel erstellt werden kann mit den Parametern für den Check. Performance Werte sind in dem Beispiel ebenfalls integriert.

Die anderen HowTos sind unter Agent Plugin Check und Local Check zu finden.

 

Aufbau eines SNMP Checks

Der SNMP Check besteht genau so wie der Agent based Check aus drei Bestandteilen.

  1. Inventory Funktion
  2. Check Funktion
  3. check_info Directory

Hier ein Beispiel Check dazu, der Check wird abgelegt in ~/local/share/check_mk/checks/:

#!/usr/bin/python

default_mycanon = (500,20000)

def inventory_mycanon(info):

    for i in info:
        idx, frag, pages = i
        yield idx, default_mycanon


def check_mycanon(item, params, info):

    perf=[]

    for i in info:
        idx, frag, pages_s = i

        if idx == item:
            warn, crit = params
            pages = int(pages_s)

    perf = [("pages",pages, warn, crit), ("page_rate",get_rate("page_rate", time.time(), pages), warn, crit)]


    if pages >= crit:
        status = 2
    elif pages >= warn:
        status = 1
    else:
        status = 0

    return status, "%d pages (w:%d/c:%d) %s" % (pages, warn, crit, get_item_state("buh")), perf


check_info["mycanon"] = {
    "inventory_function":   inventory_mycanon,
    "check_function":       check_mycanon,
    "service_description":  "Printer FooBar",
    "snmp_scan_function":   lambda oid: "canon" in oid(".1.3.6.1.2.1.1.1.0").lower(),
    "snmp_info":            ( ".1.3.6.1.4.1.1602.1.11.1.4.1", ["2", "3", "4"]),
    "has_perfdata":         True,
    "group":                "mycanon",
}

Im Gegensatz zu den anderen Checks beginne ich in umgekehrter Reihenfolge mit den einzelnen Bausteinen weil in dem check_info Dictionary definiert ist wo die Inventory Funktion und die Check Funktion ihre Daten her bekommen.

Das check_info Dictionary

check_info["mycanon"] = {
    "inventory_function":   inventory_mycanon,
    "check_function":       check_mycanon,
    "service_description":  "Printer FooBar",
    "snmp_scan_function":   lambda oid: "canon" in oid(".1.3.6.1.2.1.1.1.0").lower(),
    "snmp_info":            ( ".1.3.6.1.4.1.1602.1.11.1.4.1", ["2", "3", "4"]),
    "has_perfdata":         True,
    "group":                "mycanon",
}

Genauso wie bei dem Agent Check muss auch beim SNMP Check die Inventory Funktion und die Check Funktion angegeben werden. Die Service Description ist der Name unter dem später der Check in Check_MK im Host auftaucht.

Die snmp_scan_function wird verwendet um festzustellen ob Check_MK überhaupt auf diesem Gerät tätig wird. Nur wenn der Scan erfolgreich ist wird per SNMP BULK WALK die OIDs die in snmp_info angegeben sind abgerufen.

Bei dem Testgerät handelt es sich um einen Canon Drucker, bei einem SNMP Get auf die OID .1.3.6.1.2.1.1.1.0 gibt er folgenden String zurück „Canon iR C1021 /P“. Die Lambda Funktion überprüft ob in der OID .1.3.6.1.2.1.1.1.0 der String „canon“ vorkommt. Dabei wird die Rückgabe von oid(„.1.3.6.1.2.1.1.1.0“) mit lower() in Lowercase umgewandelt um den Vergleich zu vereinfachen.Hier gibt es noch verschiedene andere Möglichkeiten um zu bestimmen ob der Check zum Gerät passt. Am besten sich dazu die Check_MK eigenen Checks ansehen unter: ~/share/check_mk/checks

In snmp_info werden die OIDs angegeben die Abgerufen werden. Wenn es nur eine OID ist so ist ein Tuple mit OID und den entsprechenden einzelnen Untertabellen anzugeben. Sind es mehrere OIDs sind die Tuples in eine Liste zu verpacken.

"snmp_info":            ( ".1.3.6.1.4.1.1602.1.11.1.4.1", ["2", 
                                                           "3",
                                                           "4"
                        ]),

In dem Beispiel rufen wir jetzt drei Untertabellen der OID .1.3.6.1.4.1.1602.1.11.1.4.1 ab. Diese werden an den Check für die Inventory Funktion und die Check Funktion als verschachtelte Liste übergeben.

Um das zu Testen geben wir die info in der Inventory Funktion mit pprint aus:

def inventory_mycanon(info):
    pprint.pprint(info)

Ausgabe auf der Shell:

OMD[test1]:~/local/share/check_mk/checks$ cmk -v -II printer-canon-c1021
Discovering services on printer-canon-c1021:
printer-canon-c1021:
[[u'101', u'', u'1'],
 [u'102', u'', u'1'],
 [u'104', u'', u'1'],
 [u'108', u'', u'8930'],
 [u'113', u'', u'8930'],
 [u'114', u'', u'2994'],
 [u'123', u'', u'11147'],
 [u'127', u'', u'18618'],
 [u'133', u'', u'7877'],
 [u'148', u'', u'10741'],
 [u'201', u'', u'1459'],
 [u'202', u'', u'1459'],
 [u'222', u'', u'1053'],
 [u'230', u'', u'406'],
 [u'231', u'', u'406'],
 [u'232', u'', u'406']]

In der Liste sind weitere Listen die den Werten aus der SNMP Anfrage entsprechen. Aus der ersten Liste [u’101′, u“, u’1′] ist der 1. Wert aus der 2. Untertabelle, der 2. Wert aus der 3. Untertabelle und der 3. Wert aus der 4. Untertabelle. (siehe oben bei snmp_info)

Inventory Funktion

default_mycanon = (500,20000)

def inventory_mycanon(info):

    for i in info:
        idx, frag, pages = i
        yield idx, default_mycanon

Die Inventory Funktion erkennt neue Services und legt diese in Check_MK an. Der Funktion wird das SNMP BULK WALK Ergebnis übergeben als Liste. (siehe oben) Diese einfache Inventory Funktion legt für jede Subliste einen neuen Service an. Dem yield Generator wird ein Tuple übergeben mit eindeutigem Servicenamen und den Default Parametern für den Check.

Check Funktion

def check_mycanon(item, params, info):

    perf=[]

    for i in info:
        idx, frag, pages_s = i

        if idx == item:
            warn, crit = params
            pages = int(pages_s)

    perf = [("pages",pages, warn, crit), ("page_rate",get_rate("page_rate", time.time(), pages), warn, crit)]


    if pages >= crit:
        status = 2
    elif pages >= warn:
        status = 1
    else:
        status = 0

    return status, "%d pages (w:%d/c:%d)" % (pages, warn, crit), perf

Die Check Funktion beinhaltet die Logik welche überprüft in welchem Zustand sich ein Service befindet (0=OK, 1=WARNING, 2=CRITICAL, 3=UNKNOWN).

Der Check Funktion werden 3 Variablen übergeben: item, params, info

Variable item

Der Variable wird der Servicename übergeben

Variable params

Hier werden die Parameter übergeben die beim Inventory oder durch eine WATO Regel für den Service gesetzt sind.

Variable info

Hier wird die komplette Ausgabe des Agent Check Plugins als Liste übergeben so wie bei der Inventory Funktion.

Schritt 1: Aus info Liste das richtige Item suchen

for i in info:
       idx, frag, pages_s = i

       if idx == item:
           warn, crit = params
           pages = int(pages_s)

Die Liste wird durchgegangen und mit einer IF Anweisung wird verglichen ob es sich um den richtigen Datensatz handelt, wenn True wird das Parameter Tuple in die Variablen warn und vrit gespeichert. Der dritte Wert der Liste ist die Seitenanzahl und die muss noch von einem Unicode String umgewandelt werden zu einem Integer int(pages_s).

Schritt 2: Performance Werte

perf = [("pages",pages, warn, crit), ("page_rate",get_rate("page_rate", time.time(), pages), warn, crit)]

Die Performance Werte werden als Liste übergeben. In der Liste sind wiederum Tupels.

Der Tuple ist wie folgt aufgebaut:

(„<name_des_counters>“, <aktueller_wert>, <warn_wert>, <crit_wert>)

Nettes Feature in Check_MK:

Es gibt die Funktion get_rate welche Werte speichern kann und eine Rate pro Sekunde errechnen kann. Dazu ist eine eindeutige Identifier zu bestimmen und es muss die aktuelle Zeit mitgegeben werden damit Check_MK die Rate berechnen kann. Das gleiche gibt es auch für Average Werte, auch ist es möglich Werte ohne Berechung einfach zu speichern bis zur nächsten Check Ausführung (set_item_state(), get_item_state()).

Schritt 3: Status Logik

Der Status des Checks wird als 0 = OK, 1 = WARNING, 2 = CRITICAL oder 3 = UNKNOWN übergeben.

if pages >= crit:
    status = 2
elif pages >= warn:
    status = 1
else:
    status = 0

Schritt 4: Return oder Yield des Checkergebnisses

return status, "%d pages (w:%d/c:%d)" % (pages, warn, crit), perf

Als letztes wird ein Tuple zusammengebaut das aus 3 Werten besteht:

STATUS, MESSAGE, PERFORMANCEVALUES

Es gibt die Möglichkeit auch das dem yield Generator zu übergeben, statt return wird dann yield verwendet. Check_MK aggregiert dann alle Ergebnisse nacheinander. Dazu kommt in einem anderen Artikel noch weitere Informationen.

WATO Erweiterung für eigene Parameter

Ich habe in unserem Beispiel default Parameter definiert die für jeden Check angelegt werden. Wenn man jetzt für einen Host, Service oder Host/Service die Werte anpassen möchte und es bequem über WATO erledigen will muss man WATO erweitern.

WATO Erweiterungen befinden sich unter ~/lokal/share/check_mk/web/plugins/wato/ und müssen die Dateiendung .py haben. Auch muss zwingend der Apache neu gestartet werden. Das geht auf der OMD Konsole mit „omd restart apache“.

#!/usr/bin/python

register_check_parameters(
    # Category
    "Printers",
    # Group
    "mycanon",
    # Rulename
    "Printer pages max values",
    #Parameter
    Tuple(
        title = "Printer pages max value",
        help = "Define critical and warning values",
        elements = [
            Integer(title="Warn at", unit="pages"),
            Integer(title="Critical at", unit="pages"),
        ]
    ),
    #Item
    TextAscii(
        title = "Item Name",
        help = "Help for the item"
    ),
    #Match
    match_type = "first"
)

Wir haben als default Parameter ein Tuple gespeichert mit 2 Werten, Warn und Crit. Dementsprechend muss unsere WATO Regel ebenfalls ein Tuple mit 2 Werten erzeugen.

Die Funktion register_check_parameters() erzeugt eine neue möglichkeit Regeln zu definieren.

Die Funktion erwartet 6 Parameter:

1 Kategorie – Die Kategorie ist die Rubrik unter der die neue Regel angezeigt wird.

2 Group – Die Gruppe macht die Zuordnung zum Check. Im Check im check_info gibt es hierfür im Dictionary den Key „group“. (siehe oben im Beispiel)

3 Rulename – Der Rulename wird in der Übersicht angezeigt

4 Parameter – Hier werden die Parameter definiert.

5 Item – Es gibt hier 2 Möglichkeiten:

None – Die Regel kann nur für den Host definiert werden und gilt für alle Services die auf die Group matchen.

TextAscii( title = „Item Name“, help = „Help for the item“ ) –  Hier kann ein Servicename definiert werden, die Regel gilt dann nur für einen bestimmten Servicenamen

6 Matchtype – Es gibt 2 Matchtypen:

first – Die erste Regel die Zutrifft wird angewendet.

dict – Eine Kombination aus mehreren Werten ist möglich aus mehreren Regeln. Der Typ ist zwingend wenn der äußere Parametertyp Dictionary ist.

Der Parameter Block

#Parameter
    Tuple(
        title = "Printer pages max value",
        help = "Define critical and warning values",
        elements = [
            Integer(title="Warn at", unit="pages"),
            Integer(title="Critical at", unit="pages"),
        ]
    ),

Das äußere Tuple() ist eine von Check_MK bereitgestellte Funktion, es gibt noch viele weitere (z.B. TextAscii, Integer, Float, ListOf, Dictionary, etc.) Weitere Beispiele findet ihr direkt im Check_MK Code unter ~/share/check_mk/web/plugins/wato/.

Das Tuple() hat in diesem Fall 3 Übergabewerte:

title – Gibt den Titel der Werte an

help – Wenn die Hilfe aktiviert ist (Buch rechts oben in der Ecke) werden die Hilfstexte angezeigt

elements – Sind die Werte die in WATO eingegeben werden können, diese können beliebig oft, entsprechend dem Datenformat, verschachtelt werden. In elements befinden sich unsere 2 Integer() Werte Warn und Crit.

Zu den WATO Erweiterungen wird noch ein gesonderter Artikel folgen den ich hier verlinken werde.

Fertig ??

Jetzt können wir den Check mal Testen, dazu in WATO ein „Tabula Rasa“ für den Host machen, der neue Check sollte dann auftauchen.

Viel Spaß beim Spielen 🙂

 

Check_MK: Helferlein und Treasures

Check_MK hat ein paar kleine Helferlein & Treasures an Board die nicht jeder kennt.

mkcheck

Mkcheck ist ein Script das ein Template für einen neuen eigenen Check erzeugt.

Zu finden ist es unter ~/share/doc/check_mk/helpers/mkcheck

OMD[test1]:~$ ~/share/doc/check_mk/helpers/mkcheck -h

DESCRIPTION:
  This script creates a basic check file or if '-m'
  specified a manual page.
  You have to specify at least one name.

  The created check or manual page are saved to
  the current directory.

  It is recommended to run this script in the checks folder
  if you want to create a check or in the manual page folder
  if you want to create a man page.

USAGE: mkcheck [OPTS] ARGS

OPTIONS:
 -h, --help        Show this help
 -s                Adds SNMP info and scan function
 -p                Adds parse function
 -m                Creates the manual page instead of
                   the check

Beispiel für einen SNMP Check:

OMD[test1]:~$ ~/share/doc/check_mk/helpers/mkcheck -sp my_snmp_check
OMD[test1]:~$ cat my_snmp_check 

#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2017             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# tails. You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.


# example output


def parse_my_snmp_check(info):
    pprint.pprint(info)
    parsed = {}
    return parsed


def inventory_my_snmp_check(parsed):
    return []


def check_my_snmp_check(item, params, parsed):
    return 3, 'not yet implemented'


check_info['my_snmp_check'] = {
    'parse_function'        : parse_my_snmp_check,
    'inventory_function'    : inventory_my_snmp_check,
    'check_function'        : check_my_snmp_check,
    'service_description'   : 'DESCR',
    'snmp_info'             : ('', []),
    'snmp_scan_function'    : lambda oid: False,
}

figheader

Figheader ist ein Helper der Kommentarblöcke im Check_MK Style erzeugt. figlet muss auf dem System installiert sein.

Zu finden unter ~/share/doc/check_mk/helpers/figheader

OMD[test1]:~$ ~/share/doc/check_mk/helpers/figheader whoohoo
#.
#   .--whoohoo-------------------------------------------------------------.
#   |                     _                 _                              |
#   |           __      _| |__   ___   ___ | |__   ___   ___               |
#   |           \ \ /\ / / '_ \ / _ \ / _ \| '_ \ / _ \ / _ \              |
#   |            \ V  V /| | | | (_) | (_) | | | | (_) | (_) |             |
#   |             \_/\_/ |_| |_|\___/ \___/|_| |_|\___/ \___/              |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'

wato_import.py

Wato_import.py ist ein Script aus der Schatzkiste und dient zum Import einer CSV Liste in WATO.

Zu finden unter ~/share/doc/check_mk/treasures/wato_import.py

OMD[test1]:~/share/doc/check_mk/treasures$ python wato_import.py 
Run this script inside a OMD site
    Usage: ./wato_import.py csvfile.csv
    CSV Example:
    wato_foldername;hostname|tag1 tag2;host_alias;ipaddress|None

cmk-delete-host

Cmk-delete-host ist ebenfalls aus der Schatzkiste und dient zum Löschen von Hosts von der Kommandozeile aus. Dazu wird die Automation Schnittstelle verwendet.

Zu finden unter ~/share/doc/check_mk/treasures/cmk-delete-host

extract_inventory.py

Extract_inventory.py ist aus der Schatzkiste und exportiert die Inventory Daten als CSV.

Zu finden unter ~/share/doc/check_mk/treasures/inventory/extract_inventory.py

OMD[test1]:~/share/doc/check_mk/treasures/inventory$ ./extract_inventory.py 
creating relation inv_raw_generic(OS) for localhost
creating relation inv_raw_generic(Linux) for localhost
creating relation inv_raw_arp for localhost
creating relation devices for localhost
creating relation inv_raw_file for localhost

mmm@max:/var/tmp# ls -la
insgesamt 32
drwxrwxrwt  3 root  root  4096 Jul 14 11:41 .
drwxr-xr-x 13 root  root  4096 Feb  3  2014 ..
-rw-r--r--  1 test1 test1  377 Jul 14 11:41 devices
-rw-r--r--  1 test1 test1   75 Jul 14 11:41 inv_raw_arp
-rw-r--r--  1 test1 test1  114 Jul 14 11:41 inv_raw_file
-rw-r--r--  1 test1 test1   59 Jul 14 11:41 inv_raw_generic(Linux)
-rw-r--r--  1 test1 test1   43 Jul 14 11:41 inv_raw_generic(OS)

mmm@max:/var/tmp# cat devices 
"import_id", "import_data_source_id", "import_org_level_2_id", "device_key", "device_manufacturer", "device_model", "serial_number", "operating_system", "inventory_date", "installation_date", "cpu_socket_count", "cpu_chip_count", "cpu_core_count", "cpu_speed", "cpu_name"
"localhost", "sla", "default", "localhost", "", "", "", "", "2017-07-14", "", "", "1", "4", "3200.0", ""

Inoffizielle Notification Scripts

In den Treasures befinden sich noch einige Notification Scripts welche man gut als Vorlage für eigene Scripte verwenden kann. Eigene Notification Scripte sind unter ~/local/share/check_mk/notifications abzulegen.

OMD[test1]:~/share/doc/check_mk/treasures/notifications$ ls -la
insgesamt 64
drwxr-xr-x  2 root root  4096 Jul 11 13:55 ./
drwxr-xr-x 21 root root  4096 Jul 11 13:55 ../
-rwxr-xr-x  1 root root  2829 Okt 12  2016 braintower*
-rwxr-xr-x  1 root root 24497 Jul  5 14:53 glpi.py*
-rwxr-xr-x  1 root root  3842 Okt 12  2016 mobilant*
-rwxr-xr-x  1 root root  5114 Okt 12  2016 multitech*
-rwxr-xr-x  1 root root  2059 Okt 12  2016 opcmsg*
-rw-r--r--  1 root root   344 Okt 12  2016 README
-rwxr-xr-x  1 root root   632 Okt 12  2016 slack*
-rwxr-xr-x  1 root root   803 Okt 12  2016 snmp_trap*

Andere Dinge

In der Schatzkiste und in vielen Ordnern im Check_MK Root verbergen sich noch viele coole Sachen am besten geht ihr mal selbst auf die Suche 🙂

~/share/doc/check_mk/treasures

~/share/doc/check_mk/helpers