Um in hochkomplexen Monitoring-Umgebungen mittels Nagios
die noetigen Anpassungen, wie beispielsweise dem Hinzufuegen von weiteren Servern oder Abaendern von bestehenden Konfigurationen, zeitlich moeglichst gering zu halten, ist eine feste, intelligente Struktur in der Konfiguration absolut unabdingbar. Monitoring ist schließlich dazu da, Fehlerlokalisation zu erleichtern, vielleicht gar zu automatisieren und somit freie Zeitraeume fuer andere wichtige Tasks zu schaffen und eben nicht dazu, die freie Zeit damit zu verbringen, die Monitoring-Umgebung staendig pflegen zu muessen.
Nagios bringt von Hause aus nuetzliche, objektorientierte Prinzipien mit, die man sich in großen, wachsenden Umgebungen zunutze machen muss.
Viele Systemadministratoren machen sich zu Beginn leider nicht die Muehe, genauer zu planen, was denn eigentlich ueberwacht werden soll oder welche Moeglichkeiten sich bei der Konfiguration ergeben. Das hat zur Folge, dass die Installation, die eigentlich zum Evaluieren gedacht war, spaeter auch produktiv eingesetzt wird und die vielen, kleinen
Fehler, die man zu Beginn unweigerlich macht, in den produktiven Einsatz mit hinuebergefuehrt werden. Spaeter, wenn die
Monitoring-Umgebung waechst, werden diese kleinen, unueberlegten Fehler meist zu einer zeitraubenden Angelegenheit, die aufgrund der Komplexitaet der Konfiguration auch nicht mehr ohne grossen Zeitaufwand zu beheben sind.
Die Nagios-Installation funktioniert. Man moechte nun einen ersten Host hinzufuegen. Der gemeine Administrator lernt also, wie man einen Host und dazu gehoerige Services definiert. In der Konfiguration sieht das dann meistens so aus:
define host{
host_name webserver-123
alias Webserver 123
address 192.168.1.42
parents switch-456
check_command check-host-alive
check_interval 5
retry_interval 1
max_check_attempts 5
check_period 24x7
process_perf_data 0
retain_nonstatus_information 0
contact_groups webserver-admins
notification_interval 30
notification_period 24x7
notification_options d,u,r
}
define service{
host_name webserver-123
service_description HTTP
check_command check-http!80
max_check_attempts 5
check_interval 5
retry_interval 3
check_period 24x7
notification_interval 30
notification_period 24x7
notification_options w,c,r
contact_groups webserver-admins
}
Wir sehen hier also den Host ‘webserver-123′ und den dazugehoerigen Service ‘HTTP’, der ueberprueft, ob der Webserver denn auch auf localhost:80 lauscht. Soweit so gut: 31 Zeilen Konfiguration lassen sich noch recht gut ueberschauen und im Zweifelsfall auch anpassen. Nun ist es aber nicht mit dem Ueberwachen eines einzigen Services erledigt.
Neben dem Ueberpruefen, ob HTTP funktioniert sind vielleicht auch noch andere Informationen nuetzlich, wie beispielsweise die Auslastung der CPU oder RAM, dem freien Speicherplatz auf den HDDs, dem Load der Maschine, wieviele Prozesse am laufen sind, die Anzahl der eigenloggten User, dem Datendurchsatz auf den einzelnen Interfaces und vieles,
vieles mehr.
700-1000 Zeilen pro zu ueberwachendem Host in der Konfiguration werden ohne grosse Probleme erreicht (inklusive dem Aerger, den man spaeter hat aufgrund des Zeitaufwands, den man einplanen muss, wenn Anpassungen anstehen). Dafuer gibt es jedoch eine Loesung.
Nagios folgt einem strikt objektorientierten Ansatz. Man hat also die Moeglichkeit einen (oder auch mehrere) generische Templates zu definieren, von dem die tatsaechlich zu ueberwachenden Hosts und Services ihre Einstellungen erben. Man lagert also alle Optionen in das generische Template aus und definiert den jeweiligen Host oder Service nur mit den Optionen, die auch wirklich fuer die spezifische Maschine notwendig sind. Zuerst folgt die Definition des Templates, danach die des Hosts.
define host{
name generic-host ; The name of this host template
notifications_enabled 1 ; Host notifications are enabled
event_handler_enabled 1 ; Host event handler is enabled
flap_detection_enabled 1 ; Flap detection is enabled
failure_prediction_enabled 1 ; Failure prediction is enabled
process_perf_data 1 ; Process performance data
retain_status_information 1 ; Retain status information across program restarts
retain_nonstatus_information 1 ; Retain non-status information across program restarts
check_period 24x7 ; By default, Linux hosts are checked round the clock
check_interval 5 ; Actively check the host every 5 minutes
retry_interval 1 ; Schedule host check retries at 1 minute intervals
max_check_attempts 3 ; Check each Linux host 10 times (max)
check_command check-host-alive ; Default command to check Linux hosts
notification_period 24x7 ; Linux admins hate to be woken up, so we only notify during the day
; Note that the notification_period variable is being overridden from
; the value that is inherited from the generic-host template!
notification_interval 30 ; Resend notifications every 2 hours
notification_options d,u,r ; Only send notifications for specific host states
contact_groups nagios-admins ; Notifications get sent to the admins by default
register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
}
define host{
use generic-host
host_name webserver-01
alias Webserver 01
address 192.168.1.42
}
Die Anzahl der Konfigurationsparameter fuer einen einzelnen Host ist somit von 17 auf 6 Zeilen reduziert worden. Das gleiche Prinzip laesst sich natuerlich auch auf Services anwenden:
define service{
name generic-service ; The 'name' of this service template
active_checks_enabled 1 ; Active service checks are enabled
passive_checks_enabled 1 ; Passive service checks are enabled/accepted
parallelize_check 1 ; Active service checks should be parallelized (disabling this can lead to major performance problems)
obsess_over_service 1 ; We should obsess over this service (if necessary)
check_freshness 0 ; Default is to NOT check service 'freshness'
notifications_enabled 1 ; Service notifications are enabled
event_handler_enabled 1 ; Service event handler is enabled
flap_detection_enabled 1 ; Flap detection is enabled
failure_prediction_enabled 1 ; Failure prediction is enabled
process_perf_data 0 ; Process performance data
retain_status_information 1 ; Retain status information across program restarts
retain_nonstatus_information 1 ; Retain non-status information across program restarts
is_volatile 0 ; The service is not volatile
check_period 24x7 ; The service can be checked at any time of the day
max_check_attempts 3 ; Re-check the service up to 3 times in order to determine its final (hard) state
normal_check_interval 5 ; Check the service every 10 minutes under normal conditions
retry_check_interval 1 ; Re-check the service every two minutes until a hard state can be determined
contact_groups nagios-admins ; Notifications get sent out to everyone in the 'admins' group
notification_options c,r ; Send notifications about warning, unknown, critical, and recovery events
notification_interval 20 ; Re-notify about service problems every hour
notification_period 24x7 ; Notifications can be sent out at any time
register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL SERVICE, JUST A TEMPLATE!
}
define service{
use generic-service
service_description HTTP
check_command check_http!80
host_name webserver-01
}
Der Vorteil liegt auf der Hand: Nehmen wir an, die IT-Abteilung ist gewachsen und es wurden einzelne Teams fuer spezifische Server eingeteilt. Jetzt haben wir also nicht mehr nur die Kontaktgruppe “nagios-admin”, sondern auch die Gruppe “webserver-admin”. Der objektorientierte Ansatz macht mir die Aenderungen leicht, indem ich die Kontaktgruppe lediglich in der Template-Definition hinzufuege und eben nicht in jeder einzelnen Host- und Service-Definition. Grade fuer Administratoren, die mit awk und sed (unverstaendlicherweise) auf Kriegsfuss stehen, ist Letzteres eine enorm zeitaufwendige Arbeit, die man sich so ersparen kann.
Jetzt ist die Monitoring-Umgebung also gewachsen. Wir ueberwachen eine Vielzahl verschiedenster Dienste und wurden letzte Nacht aufgrund einer Warnung, dass sich der Disk Space auf /dev/sda3 verringert, nachts um halb 4 aus dem Bett geklingelt. Das Problem ist nun nicht derart unternehmenskritisch, als das wir dafuer unseren wohlverdienten
Schlaf unterbrechen muessten und koennte auch problemlos morgens um 9 vom Office aus behoben werden. In unserer generischen Service-Definition, unserem Template, haben wir jedoch eine 24×7 Benachrichtung definiert. Wir muessen uns also ueberlegen, wie wir erreichen koennen, das fuer einzelne Checks andere Bedingungen gelten. Dafuer bieten sich folgende zwei Loesungen an:
Wir erinnern uns: In unserem Service-Template haben wir folgendes definiert:
notification_period 24x7 ; Notifications can be sent out at any time
Dies gilt fuer alle Services, die ihre Optionen von diesem Template erben. Um die Option des Templates zu ueberschreiben, koennen wir in der spezifischen Service-Definition die jeweilige Option wie folgt
ueberschreiben:
define service{
use generic-service
service_description DISK3
check_command check_nrpe!5667!check_disk3
host_name webserver-01
notification_period 8x5
}
Wir haetten somit unser Ziel erreicht und wuerden bezueglich etwaigiger Speicherplatz-Probleme nur noch waehrend der Arbeitszeit benachrichtigt werden. Allerdings ist diese Art und Weise eher eine “von hinten durch die Brust ins Auge”-Loesung. Wir haben schließlich wieder die Service Definition aufgeblaeht und was passiert, wenn wir weitere Optionen aus dem Service-Template veraendern moechten? Richtig, die Konfiguration von spezifischen Hosts und Services waechst wieder auf ein unertraegliches, wartungsfeindliches Mass an. Problematisch ist jetzt nicht mehr nur der Zeitaufwand, sondern auch das uneinheitliche Bild in der Konfiguration. Man muss schlicht und ergreifend wissen,
dass man die notification_period fuer den Check in der Service-Definition explizit veraendert hat.
Wuerde beispielsweise beschlossen werden, dass /dev/sda3 jetzt doch eher zu den unternehmenskritischen Diensten gehoert und unter keinen Umstaenden in Platznoete geraten darf, dann steht dem Administrator, der schon lange oder gar noch nie nichts mehr an der Konfiguration gemacht hat, zuerst einmal eine laengere Suche nach der Option im Heuhaufen bevor. Besser ist es daher, sich mehrere Service-Templates fuer einzelne Service-Kategorien zu erstellen und diese vom generischen Template erben zu lassen. Man kann also auch Templates von anderen Templates
erben lassen.
Bevor man damit beginnt, eine Vielzahl von verschiedenen
Service-Templates zu erstellen, sollte man konsolidieren. Ich
beispielsweise fahre sehr gut mit nur 4 weiteren Service-Templates, die
allesamt verschiedene Gewichtungen beinhalten bezueglich der
Intervalle, in denen der jeweilige Dienst ueberprueft wird und den
Zeitraeumen, in denen dies geschehen soll und ihre Einstellungen vom
oben beschriebenen Service-Template erben:
define service{
name normal-service ; The name of this service template
use generic-service ; Inherit default values from the generic-service definition
max_check_attempts 3 ; Re-check the service up to 4 times in order to determine its final (hard) state
normal_check_interval 5 ; Check the service every 5 minutes under normal conditions
retry_check_interval 1 ; Re-check the service every minute until a hard state can be determined
register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL SERVICE, JUST A TEMPLATE!
}
define service{
name important-service ; The name of this service template
use generic-service ; Inherit default values from the generic-service definition
max_check_attempts 2 ; Re-check the service up to 4 times in order to determine its final (hard) state
normal_check_interval 1 ; Check the service every 5 minutes under normal conditions
retry_check_interval 1 ; Re-check the service every minute until a hard state can be determined
register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL SERVICE, JUST A TEMPLATE!
}
define service{
name minor-service ; The name of this service template
use generic-service ; Inherit default values from the generic-service definition
max_check_attempts 5 ; Re-check the service up to 4 times in order to determine its final (hard) state
normal_check_interval 10 ; Check the service every 5 minutes under normal conditions
retry_check_interval 5 ; Re-check the service every minute until a hard state can be determined
notification_period 24x7 ; Notifications can be sent out at any time
register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL SERVICE, JUST A TEMPLATE!
}
define service{
name unimportant-service ; The name of this service template
use generic-service ; Inherit default values from the generic-service definition
max_check_attempts 10 ; Re-check the service up to 4 times in order to determine its final (hard) state
normal_check_interval 60 ; Check the service every 5 minutes under normal conditions
retry_check_interval 10 ; Re-check the service every minute until a hard state can be determined
notifications_enabled 0 ; Service notifications are disabled
register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL SERVICE, JUST A TEMPLATE!
}
Man ist somit also problemlos in der Lage, bestimmte Services im Gegensatz zu anderen weniger zu gewichten. Wichtige,
unternehmenskritische Dienste koennen im Minutenintervall ueberprueft werden, andere weniger wichtigere Dienste aber beispielsweise nur stuendlich. Wir erreichen somit die noetige Flexibilitaet, um Dienste entsprechend ihrer Bedeutung ueberpruefen zu koennen.
Nun haben wir eine bereits sehr gute Basis erreicht. Wir sind in der Lage, Hosts und Services mittels einiger, weniger Zeilen in der Konfiguration anzulegen und abzuaendern. Allerdings muessen wir weiterhin fuer jeden Host die Services einzeln anlegen, was zur Folge hat, dass fuer die 6 Zeilen lange Host-Definition etwa 100 Zeilen Service-Definition folgen. Hier gilt es zu optimieren, indem wir zuerst einmal Hostgruppen nach folgendem Schema einrichten:
define hostgroup{
hostgroup_name webserver
alias Our company's webserver
members webserver-01,webserver-02,webserver-03
}
Der Weg zu Hostgruppen-basierten Services ist jetzt nicht mehr weit. Anstatt den HTTP Service-Check fuer jeden einzelnen Host mittels des Hostnames definieren, nehmen wir jetzt einfach den Namen der Hostgruppe. Das sieht dann wie folgt aus:
define service{
use important-service
service_description HTTP
check_command check_http!80
hostgroup_name webserver
}
Anstatt jeden einzelnen Service fuer die entsprechenden Hosts konfigurieren zu muessen, koennen wir mit Hilfe der Hostgruppen die Service-Definitionen global abhandeln. Wir haben jetzt also nur noch eine Service-Definition anstatt eine fuer jeden Host!
Das hat nicht nur den Vorteil, dass man mit geringem Zeitaufwand die komplette Installation pflegen kann, sondern auch, dass einem der Ueberblick ueber die Umgebung nicht so leicht entgleitet. Mehrere hundert Hosts und tausende Services sind in einer hochkomplexen Umgebung keine Seltenheit, da ist Vereinheitlichung schlicht und ergreifend eine
Notwendigkeit.
Abschließend noch ein, zwei Worte zur Performance von Nagios. Diese laesst sich, wer haette es geahnt, nicht so einfach in Zahlen ausdruecken. Eine Aussage, wie beispielsweise “Nagios kann 831 Hosts und 5.542 Services zeitnah ueberwachen” ist schlicht und ergreifend nicht moeglich. Zu komplex sind die Abhaengigkeiten von der Hardware, der Art und Anzahl der eingesetzten Checks, sowie der Intervalle, in denen die Dienste ueberprueft werden. Muss Nagios nach jedem Check noch
ein externes Kommando aufrufen, sind weitere Verzoegerungen in Kauf zu nehmen.
Nun ist der gemeine Nagios-Administrator meist von der Idee angefixt, moeglichst alles zu ueberwachen: Laeuft der syslog-ng auf all meinen Servern? Kann ich den Hostnamen auf all meinen 10 eingesetzten Nameservern aufloesen? Und wann ist eigentlich mal wieder Towel-Day?
Nun spiegelt der Load des Nagios-Servers die Performance meist nur unzureichend wieder. Akkurate Aussagen lassen sich mittels des Load-Wertes jedenfalls nicht machen. Vielleicht laeuft ja auch noch ein anderer, performancekritischer Dienst auf der Maschine. Einen ersten Blick sollte man also ins Web-Interface werfen und sich die
Performance-Werte zu Gemuete fuehren:

Interessant sind hier zuerst einmal die Werte “Check Latency - Maximum” und “Check Latency - Average” fuer unsere Services. Diese Werte spiegeln die Zeit wieder, wie lange ein auszufuehrender Check in der Warteschlage von Nagios verzoegert wird. Wenn ich also einen Check habe, der alle fuenf Minuten ueberprueft werden soll und eine
durchschnittliche Check Latency von ca. zehn Sekunden besteht, dann wird dieser Check eben erst wieder nach fuenf Minuten und zehn Sekunden ausgefuehrt.
Ein weiterer, guter Indikator fuer Performanceprobleme ist die Tabelle auf der linken Seite, die angibt, wieviele Checks der Nagios-Server in einer, in fuenf in 15 Minuten, sowie in einer Stunde ausfuehren konnte.
In grossen Installation wird es aufgrund der Masse an Checks, sowie der Vielzahl von verschiedenen Plugins kaum dazu kommen, eine Latenz im Millisekundenbereich zu erreichen. Zu Beginn jedoch mit wenigen hundert Services sollte dieser Wert sich entsprechend dort ansiedeln.
Eine weitere Moeglichkeit, Performancegrenzen von Nagios auszumachen ist das Monitoring des eigenen Nagios-Servers. Ich beispielsweise setze dazu ein kleines Bash-Script von Matteo Corti als Check ein, welches mir alle 10 Minuten die Check Latency inklusive Performancedaten zurueckgibt. Ich bin also in der Lage, diese grafisch ueber laengere
Zeitraeume darzustellen, beispielsweise mittels PNP oder Nagiosgrapher und kann somit beim Einbinden neuer Checks auch leicht zeitversetzt Performance-Engpaesse ausmachen und diese ggf. beheben. Grad umfangreiche Bash-Scripte versetzten Nagios eine ungemein laestige Performancebremse. Ein Plugin in C oder Perl ist Shellscripts meist vorzuziehen.
Hier das Performance-Monitoring Shellscript zum Ueberwachen der Latenz von Matteo Corti, welches u.a. auch auf Nagiosexchange zum Download angeboten wird:
#!/bin/bash\\
# (c) Matteo Corti, ETH Zurich, 2007
#
# check_nagios_latency
#
# Checks the Nagios latency
VERSION=0.9.3
################################################################################
# Functions
################################################################################
# Prints usage information
# Params
# $1 error message (optional)
usage() {
if [ -n "$1" ] ; then
echo "Error: $1" 1>&2
fi
echo
echo "Usage: check_nagios_latency [-hvV?] -w warning -c critical [-n path]"
echo
echo " -c critical threshold"
echo " -h, -? this help message"
echo " -n path nagiostats path"
echo " -v verbose output"
echo " -w warning threshold"
echo " -V version"
echo
echo "Report bugs to: Matteo Corti <matteo.corti@id.ethz.ch>"
echo
exit 3
}
################################################################################
# Checks if a given program is available and executable
# Params
# $1 program name
check_prog() {
if [ -z "$PROG" ] ; then
PROG=`which $1`
fi
if [ -z "$PROG" ] ; then
echo "LATENCY CRITICAL - cannot find $1"
exit 2
fi
if [ ! -x "$PROG" ] ; then
echo "LATENCY CRICTICAL - $PROG is not executable"
exit 2
fi
}
################################################################################
# Main
################################################################################
# process command line options
while getopts "vh?Vc:w:n:" opt; do
case $opt in
c ) CRITICAL=$OPTARG; ;;
h | \? ) usage ; exit 3; ;;
n ) PROG=$OPTARG ;;
V ) echo "check_nagios_latency version ${VERSION}"; exit 3; ;;
v ) VERBOSE=1; ;;
w ) WARNING=$OPTARG; ;;
esac
done
shift $(($OPTIND - 1))
################################################################################
# sanity checks
###############
# Check options
if [ -z "${CRITICAL}" ] ; then
usage "No critical threshold specified"
fi
if [ -z "${WARNING}" ] ; then
usage "No warning threshold specified"
fi
######################
# Check number formats
if ! echo $WARNING | grep -qE '^[0-9]+(\.[0-9]+)?$' ; then
echo "LATENCY UNKOWN - Wrong number: $WARNING"
exit 3
fi
if ! echo $CRITICAL | grep -qE '^[0-9]+(\.[0-9]+)?$' ; then
echo "LATENCY UNKOWN - Wrong number: $WARNING"
exit 3
fi
#######################
# Check needed programs
check_prog nagiostats
LATENCY=`$PROG | grep "Active Service Latency" |cut -f3 -d'/' | awk '{print $1}' | tr -d '\n'`;
if [ -n "${VERBOSE}" ] ; then echo "latency: ${LATENCY}"; fi
####################
# Perform the checks
PERF="Latency=${LATENCY};${WARNING};${CRITICAL};;"
COMPARISON=`echo "if($LATENCY>$CRITICAL) 1 else 0;" | bc`
if [ $COMPARISON -eq 1 ] ; then
echo "LATENCY CRITICAL ${LATENCY}s | $PERF"
exit 2
fi
COMPARISON=`echo "if($LATENCY>$WARNING) 1 else 0;" | bc`
if [ $COMPARISON -eq 1 ] ; then
echo "LATENCY WARNING ${LATENCY}s | $PERF"
exit 1
fi
echo "LATENCY OK ${LATENCY}s| $PERF"
exit 0;
Dieser Beitrag ist unter einer Creative-Commons Lizenz
veroeffentlicht worden. Er darf frei kopiert, veroeffentlicht und veraendert werden, sofern der Name des Autors (Mike Adolphs) genannt wird und keine kommerziellen Absichten verfolgt werden (Google AdSense in Blogs, Foren und Wikis ausgenommen). Ueber Feedback freut sich der Autor genauso, wie ueber Verbesserungsvorschlaege und Benachrichtigungen im Falle von
offensichtlichen Fehlern.