Serverraum-Überwachung mit Nagios oder Icinga

Icinga und Nagios sind Computer Überwachungswerkzeuge. Icinga ist ein Fork von Nagios und gilt als rückwärtskompatibel zu diesem. Die nachfolgenden Beispielen beziehen sich auf die Nagios API sind daher aber auch kompatibel zu Icinga.

Beide Überwachungswerkzeuge nutzen Plugins, instantiiert als Services, um die Prozessorauslastung, Speicherbelegung, spezielle Software Prozesse oder physikalische Werte, wie die Temperatur, zu überwachen. Die jeweilige Dokumentation gibt hier weitere Informationen.

Plugins werden genutzt um überwachende Services zu erzeugen. Dies sind Programme mit einem definierten Rückgabewert (z.B. 0=OK, 1=Warning, 2=Critical, 3=Unknown). Deren Standardausgabe wird von Nagios genutzt um Informationen über deren Zustand zu bekommen. Die Nagios Developer Guidelines geben hier weitere Auskünfte.

Nach der Grundinstallation von Nagios kann damit begonnen werden ein eigens Plugin zu entwickeln. Als erstes sollten die Bindings für die gewünschte Programmiersprache installiert werden. Anschließend kann das eigene Plugin unter Beachtung der Nagios Developer Guidelines geschrieben werden.

Basis Nagios/Icinga Skript

Für dieses Beispiel nutzen wir die Python Bindings. Das Skript nutzt das Temperature oder PTC Bricklet um die Temperatur zu messen und zu warnen falls zu hohe Temperaturen gemessen werden.

Das kleine Skript, check_tf_temp.py genannt, besitzt folgende Schnittstelle:

usage: check_tf_temp.py [-h] -u UID -t {temp,ptc} [-H HOST] [-P PORT]
                        [-m {none,high,low,range}] [-w WARNING] [-c CRITICAL]
                        [-w2 WARNING2] [-c2 CRITICAL2]

optional arguments:
 -h, --help            show this help message and exit
 -u UID, --uid UID     UID from Temperature Bricklet
 -t {temp,ptc}, --type {temp,ptc}
                       Type: temp = Temperature Bricklet, ptc = PTC Bricklet
 -H HOST, --host HOST  Host Server (default=localhost)
 -P PORT, --port PORT  Port (default=4223)
 -m {none,high,low,range}, --modus {none,high,low,range}
                       Modus: none (default, only print temperature), high,
                       low or range
 -w WARNING, --warning WARNING
                       Warning temperature level (temperatures above this
                       level will trigger a warning message in high mode,
                       temperature below this level will trigger a warning
                       message in low mode)
 -c CRITICAL, --critical CRITICAL
                       Critical temperature level (temperatures above this
                       level will trigger a critical message in high mode,
                       temperature below this level will trigger a critical
                       message in low mode)
 -w2 WARNING2, --warning2 WARNING2
                       Warning temperature level (temperatures below this
                       level will trigger a warning message in range mode)
 -c2 CRITICAL2, --critical2 CRITICAL2
                       Critical temperature level (temperatures below this
                       level will trigger a critical message in range mode)

Der Großteil der Schnittstelle sollte selbsterklärend sein. Diese unterstützt drei Modi:

  • high: Nachricht wird abgegeben wenn die gemessene Temperatur über WARNING oder CRITICAL liegt
  • low: Nachricht wird abgegeben wenn die gemessene Temperatur unter WARNING oder CRITICAL liegt
  • range: Nachricht wird abgegeben falls die Temperatur über WARNING oder CRITICAL der unter WARNING2 oder CRITICAL2 liegt

Das Skript sollte global ausführbar sein, z.B. durch speichern unter /usr/local/bin.

Das folgende Beispiel verbindet zu einer Ethernet Extension mit dem Hostnamen ServerMonitoring und zu einem Temperature Bricklet mit der UID SCT31. Eine Warning Nachricht wird bei Temperaturen über 26°C abgegeben und eine Critical Nachricht bei Temperaturen über 27°C:

check_tf_temp.py -H ServerMonitoring -u SCT31 -t temp -m high -w 26 -c 27

Das folgende Beispiel erzeugt ein Warning bei Temperaturen unter 10°C oder über 30°C und Critical Nachrichten bei Temperaturen unter 8°C und über 35°C:

check_tf_temp.py -H ServerMonitoring -u SCT31 -t temp -m range -w 10 -w2 30 -c 8 -c2 35

Um diese Funktion mit einem PTC Bricklet anstatt mit dem Temperature Bricklet zu nutzen muss die UID und der Typ des Bricklets entsprechend angepasst werden. Das Kommando sieht dann wie folgt aus:

check_tf_temp.py -H ServerMonitoring -u fow -t ptc -m range -w 10 -w2 30 -c 8 -c2 35

Das check_tf_temp.py Skript kann einfach an andere Tinkerforge Sensoren angepasst werden. Die read Methode ist der Hauptteil des Skripts. Diese liest das Bricklet aus und vergleicht die gemessene Temperatur mit den Warning und Critical Grenzwerten. Falls notwendig generiert sie eine Meldung und den dazu passenden Rückgabewert. Das gesamte Skript sieht wie folgt aus (download):

#!/usr/bin/env python
# -*- coding: utf8 -*-

'''
Based on Wiki project:
http://www.tinkerunity.org/wiki/index.php/EN/Projects/IT_Infrastructure_Monitoring_-_Nagios_Plugin
'''

import sys
import argparse
from tinkerforge.bricklet_ptc import PTC
from tinkerforge.bricklet_ptc_v2 import PTCV2
from tinkerforge.ip_connection import IPConnection
from tinkerforge.bricklet_temperature import Temperature
from tinkerforge.bricklet_temperature_v2 import TemperatureV2

OK = 0
WARNING = 1
CRITICAL = 2
UNKNOWN = 3

TYPE_PTC = 'ptc'
TYPE_TEMPERATURE = 'temp'

class CheckTFTemperature(object):
    def __init__(self, host='localhost', port=4223):
        self.host = host
        self.port = port
        self.ipcon = IPConnection()

    def connect(self, type, uid):
        self.ipcon.connect(self.host, self.port)
        self.connected_type = type

        if self.connected_type == TYPE_PTC:
            ptc = PTC(uid, self.ipcon)

            if ptc.get_identity().device_identifier == PTCV2.DEVICE_IDENTIFIER:
                ptc = PTCV2(uid, self.ipcon)

            self.func = ptc.get_temperature
        elif self.connected_type == TYPE_TEMPERATURE:
            temperature = Temperature(uid, self.ipcon)

            if temperature.get_identity().device_identifier == TemperatureV2.DEVICE_IDENTIFIER:
                temperature = TemperatureV2(uid, self.ipcon)

            self.func = temperature.get_temperature

    def disconnect(self):
        self.ipcon.disconnect()

    def read_temperature(self):
        return self.func()/100.0

    def read(self, warning, critical, mode='none', warning2=0, critical2=0):
        temp = self.read_temperature()

        if mode == 'none':
            print "temperature %s °C" % temp
        else:
            if mode == 'low':
                warning2 = warning
                critical2 = critical

            if temp >= critical and (mode == 'high' or mode == 'range'):
                print "CRITICAL : temperature too high %s °C" % temp
                return CRITICAL
            elif temp >= warning and (mode == 'high' or mode == 'range'):
                print "WARNING : temperature is high %s °C" % temp
                return WARNING
            elif temp <= critical2 and (mode == 'low' or mode == 'range'):
                print "CRITICAL : temperature too low %s °C" % temp
                return CRITICAL
            elif temp <= warning2 and (mode == 'low' or mode == 'range'):
                print "WARNING : temperature is low %s °C" % temp
                return WARNING
            elif (temp < warning and mode == 'high') or \
                 (temp > warning2 and mode == 'low') or \
                 (temp < warning and temp > warning2 and mode == 'range'):
                print "OK : %s°C " % temp
                return OK
            else:
                print "UNKNOWN : can't read temperature"
                return UNKNOWN

if __name__ == '__main__':
    parse = argparse.ArgumentParser()
    parse.add_argument(
        '-u',
        '--uid',
        help = 'UID from Temperature Bricklet', required=True)
    parse.add_argument(
        '-t',
        '--type',
        help = 'Type: temp = Temperature Bricklet, ptc = PTC Bricklet',
        type = str,
        choices = [TYPE_TEMPERATURE, TYPE_PTC],
        required = True)
    parse.add_argument(
        '-H',
        '--host',
        help = 'Host Server (default=localhost)',
        default = 'localhost')
    parse.add_argument(
        '-P',
        '--port',
        help = 'Port (default=4223)',
        type = int,
        default = 4223)
    parse.add_argument(
        '-m',
        '--modus',
        help = 'Modus: none (default, only print temperature), high, low or range',
        type = str,
        choices = ['none', 'high','low','range'],
        default = 'none')
    parse.add_argument(
        '-w',
        '--warning',
        help = 'Warning temperature level (temperatures above this level will trigger a warning message in high mode, temperature below this level will trigger a warning message in low mode)',
        required = False,
        type = float)
    parse.add_argument(
        '-c',
        '--critical',
        help = 'Critical temperature level (temperatures above this level will trigger a critical message in high mode, temperature below this level will trigger a critical message in low mode)',
        required = False,
        type = float)
    parse.add_argument(
        '-w2',
        '--warning2',
        help = 'Warning temperature level (temperatures below this level will trigger a warning message in range mode)',
        type = float)
    parse.add_argument(
        '-c2',
        '--critical2',
        help = 'Critical temperature level (temperatures below this level will trigger a critical message in range mode)',
        type = float)

    args = parse.parse_args()

    tf = CheckTFTemperature(args.host, args.port)
    tf.connect(args.type, args.uid)

    exit_code = tf.read(
                    args.warning,
                    args.critical,
                    args.modus,
                    args.warning2,
                    args.critical2)

    tf.disconnect()

    sys.exit(exit_code)

Um das Skript mit Nagios auszuführen muss es zuerst registriert werden. Dazu wird dieses mit den folgenden Zeilen in einer Command Konfigurationsdatei (z.B. /usr/local/nagios/etc/checkcommands.cfg oder /etc/icinga/commands.cfg) registriert:

define command {
    command_name    check_tf_temp
    command_line    /usr/local/bin/check_tf_temp.py -H ServerMonitoring -u SCT31 -t temp -m high -w 26 -c 27
}

Anschließend kann dieses Kommando einem Service zugeordnet werden. Ein neuer Service kann in eine Service Konfigurationsdatei mit den folgenden Zeilen:

define service {
    use                             generic-service
    host_name                       localhost
    service_description             Check Temperature
    check_command                   check_tf_temp
    check_interval                  1
}

erzeugt werden. Mögliche Konfigurationsdateien befinden sich unter /usr/local/nagios/etc/services.cfg, /etc/icinga/objects/services_icinga.cfg oder an anderen Positionen. Die jeweilige Dokumentation gibt hier Aufschluss.

Das war es. Es sollte ein neuer Service im Web-Interface angezeigt werden, der vor zu hohen Temperaturen warnt.

Icinga Screenshot