Icinga 2 Best Practice Teil 1: Zonen und Agenten

This entry is part 1 of 6 in the series Icinga 2 Best Practice

Heute bin ich endlich mal wieder dran einen Blogpost zu schreiben und wie jedes Mal, kann ich mich nicht entscheiden über welches Thema ich berichten möchte. Eigentlich sollte hier nach meinem Plan ein Post über den Rewrite des Icinga 2 Moduls für Puppet stehen. Aber wie schon häufiger habe ich mich in nahezu letzter Sekunde umentschieden und möchte heute mit einer Reihe starten, in der es um die Organisation von Icinga 2 Host- und Service-Objekten gehen wird. Den Start macht der erste Teil zur Konfiguration des Icinga 2 Agenten.
Im Icinga-Kosmos gibt es im Grunde zwei unterschiedliche Arten von Plugins. Bei der Ersten handelt es sich um Plugins, die ihre Werte über eine Netzwerkverbindung ermitteln z.B. check_http. Die zweite Art sind dann solche, die ihre Werte lokal auf dem zu überwachenden Host ermitteln müssen wie check_disk oder check_load. Mit dem Agenten ist uns seit geraumer Zeit eine Möglichkeit in die Hand gegeben mit der diese letzteren Plugins lokal ausgeführt werden können. Hierbei wird vom eigentlichen Icinga 2 Server eine persistente, verschlüsselte und an Hand von Zertifikaten authentifizierte Verbindung zum zu überwachenden Host aufgebaut. Über diese stößt dann der Server auf dem Agenten die Ausführung des gewünschten Plugins an.
Die eigentlich Installation, z.B. mit dem node wizard ist hier bitte der Online-Dokumentation zu entnehmen. Bzgl. der Konfiguration ist am Agenten zu hinterlegen wer hier überhaupt mit einander kommuniziert.

# cat /etc/icinga2/zones.conf
object Endpoint "agent.example.org" {
}
object Endpoint "server.example.org" {
  host = "x.x.x.x"
}
object Zone "agent.example.org" {
  endpoints = ["agent.example.org"]
  parent = "master"
}
object Zone "master" {
  endpoints = ["server.example.org"]
}

Jedes Endpoint-Objekt ist eine laufende Icinga-Instanz. Einer Zone muss immer mindestens ein Endpoint zugeordnet sein. Daten werden zwischen Zonen ausgetauscht. Hier sorgt das parent-Attribut, dass Checkresults an die Master-Zone und damit an deren Endpoints geliefert werden. Für das Objekt server.example.org vom Type Endpoint ist das Attribut host mit der zugehörigen IP-Adresse gesetzt, damit ist konfiguriert, dass der Agent die Verbindung zum Server aufbaut.
Auch der andere Kommunikationspartner in der master-Zone muss wissen von wem er Daten empfangen und an wen er selbst welche senden darf. Dort schreiben wir jedoch lediglich die Information zur eigenen Seite in die Datei zones.conf.

# cat /etc/icinga2/zones.conf
object Endpoint "server.example.org" {
}
object Zone "master" {
  endpoints = ["server.example.org"]
}

Das agentenzugehörige Endpoint- und Zonen-Objekt kommt mit dem Host-Objekt zusammen in eine “normale” Konfigurationsdatei für zu überwachende Objekte, z.B. unterhalb von /etc/icinga2/conf.d.

object Endpoint "agent.example.org" {
}
object Zone "agent.example.org" {
  endpoints = ["agent.example.org"]
  parent = "master"
}
object Host "agent.example.org" {
  import "generic-host"
  display_name = "agent"
  ...
}

Dies erleichtert einem in zweierlei Hinsicht die Konfiguration:

1) Es stehen alle Informationen an einer Stelle und können beim Wegfall des Hosts auch dort zusammen entfernt werden ohne noch zusätzlich an anderen Orten suchen zu müssen.
2) Betreibt man Satelliten, wird so das Endpoint- und Zonen-Objekt via Synchronisation der Konfiguration automatisch auf den entsprechenden Satelliten verteilt bzw. entfernt.

Auf dieser Seite verzichte ich auf die Angabe einer IP mittels host, d.h. es erfolgt kein Versuch eines Verbindungsaufbaus zum Agenten. Der Server warte also darauf das der Agent von sich aus die Kommunikation initiiert. Zu überwachende Hosts werden auch mal gewartet und sind dann nicht erreichbar. So erfolgen keine überflüssigen Verbindungsversuche und schreiben das Logfile voll. Ein weiterer Tipp ist, die Namen für Endpoint- und Host-Objekt identisch zu wählen. Damit sieht eine Servicedefinition z.B. für load wie folgt aus:

apply Service "load" {
  import "generic-service"
  check_command = "load"
  command_endpoint = host.name
  assign where host.vars.os == "Linux"
}   

Da check_load auf einem Endpoint (Agenten) auszuführen ist, wird mit dem Service-Attribut command_endpoint festgelegt auf welchem dies zu erfolgen hat. Ein Service ist immer einem Host zugeordnet, so kann aus dem Service heraus auf Attribute des zugehörigen Hosts zugegriffen werden. Da für command_endpoint ein existierender Endpoint anzugeben ist, wir diesem per Konvention den selben Namen wie dem Host-Objekt gaben, kann hier nun host.name benutzt werden.

TIPP: Kommen keine Ergebnisse vom Agenten auf dem Icinga-Server (Master) an, fehlt auf Seite des Agenten die Angabe der parent-Zone. Wird keine Konfiguration zum Agenten synchronisiert, fehlt die parent-Zone auf der Seite des Servers.

Zum Thema synchronisieren von Konfigurationsdateien und globalen Zonen, dann in Teil 2.

Lennart Betz

Autor: Lennart Betz

Der diplomierte Mathematiker arbeitet bei NETWAYS im Bereich Consulting und bereichert seine Kunden mit seinem Wissen zu Icinga, Nagios und anderen Open Source Administrationstools. Im Büro erleuchtet Lennart seine Kollegen mit fundierten geschichtlichen Vorträgen die seinesgleichen suchen.

Icinga 2 Best Practice Teil 2: Konfiguration synchronisieren

This entry is part 2 of 6 in the series Icinga 2 Best Practice

Im heutigen Teil 2 dieser Serie, befassen wir uns mit Konfiguration und deren Synchronisation zwischen Icinga 2 Instanzen in verteilten Umgebungen. Hierbei ist eine solche verteilte Umgebung schon gegeben, wenn der Agent zum Einsatz kommt, also immer wenn Endpoints und Zones beteiligt sind. In Teil 1 wurde Tipps zur Konfiguration der Verbindungsdaten gegeben, heute vertiefen wir, was auf dem Agenten passiert und was dort zusätzlich zum Icinga 2 Core benötigt wird.
Zuerst werden die jeweiligen Plugins auf den Agenten benötigt, seien es für Linux die Monitoring-Plugins oder auf der Windows-Seite die nativen aus dem Icinga-Projekt oder die Plugins vom NSClient++. Bei Icinga, auch in der neuen Version 2, ist das Objekt vom Typ CheckCommand das Bindeglied zwischen Host- bzw. Service-Objekt und dem konfigurierten Plugin, was zur Statusermittlung aufzurufen ist. Wobei ein lokal auszuführender Host-Check üblicherweise nicht Praxisrelevant ist. Das CheckCommand beschreibt wie das Plugin aufzurufen ist, den Ort im Dateisystem wie auch mit welchen Optionen das Plugin ausgeführt wird.

object CheckCommand "mem" {
  command = [ PluginContribDir + "/check_mem.pl" ]

  arguments = {
    "-u" = {
      set_if = "$mem_used$"
      description = "Check USED memory"
    }
...

Für die Pfadangabe zum Plugin ist immer die Verwendung einer Konstanten zu empfehlen, da sich der Ort von Plattform zu Plattform unterscheiden kann und eine Konstante ist für jede Instanz und damit für jeden Agenten gesondert setzbar. Standardmäßig sollten Konstanten in constants.conf im Icinga-Konfigurationsverzeichnis definiert werden.
Beim Plugin check_mem.pl im obigen Beispiel handelt es sich nicht um eines vom Monitoring-Plugin-Projekt, sondern um eines was gesondert auf den Agenten zu installieren ist. Ausserdem benötigen die Icinga-Instanzen der Agenten obige Definition. Für das CheckCommand mem wird diese schon in der ITL (Icinga Template Library) mit geliefert und muss nicht selbst erstellt werden. Auf den jeweiligen Agenten ist lediglich die Definition in icinga2.conf als include einzubinden. Sie befindet sich im PluginContrib-Bereich der ITL.

include <plugins>
include <plugins-contrib>  

In plugins hingegen befinden sich die Definitionen zu Plugins aus dem Monitoring-Projekt. Bei Windows empfiehlt sich die Definitionen der nativen (windows-plugins) bzw. NSClient++-Plugins (nscp) einzubinden.
Was aber nun, wenn man nun für ein Plugin selbst ein CheckCommand-Objekt anlegen muss? Es jeweils auf allen Agenten in die ITL legen? Nein, der ITL-Bereich ist ausschließlich für vom Projekt gepflegte Objekte, eigene würden beim nächsten Update wieder verschwinden. Ausserdem ist eine zusätzliche Pflege von sich ändernden Konfigurationsdateien für jeden Agenten nicht erstrebenswert. Hier kommen nun globale Zonen ins Spiel, die einem Konfiguration auf beliebige Endpoints synchronisieren und automatisch laden.

object Zone "linux-commands" {
  global = true
}
object Zone "windows-commands" {
  global = true
}

Im Gegensatz zum Server bzw. Master und Satelliten-Systemen, ist auf den Windows- und Linux-Agenten nur die entsprechende Zone in der zones.conf einzutragen. In beiden werden auch wirklich nur CheckCommands hinterlegt, Services und Templates werden in der Regel nicht auf Agenten benötigt und sind damit Informationen die besser nicht leicht zugänglich sowie zentral gehalten werden.
Windows kennt leider keinen graceful restart bzw. reload wie Unixsysteme und somit ist hier die Trennung sinnvoll, da Windows einen Stop des Dienstes und nachgelagertem Start nur ausführen muss, wenn es wirklich nötig ist.

Teil 3 dieser Reihe wird sich dann mit praktischen Definition von Services beschäftigen und wie diese ausschließlich aus den zugehörigen Host-Objekten parametresiert werden.

Lennart Betz

Autor: Lennart Betz

Der diplomierte Mathematiker arbeitet bei NETWAYS im Bereich Consulting und bereichert seine Kunden mit seinem Wissen zu Icinga, Nagios und anderen Open Source Administrationstools. Im Büro erleuchtet Lennart seine Kollegen mit fundierten geschichtlichen Vorträgen die seinesgleichen suchen.

Icinga 2 Best Practice Teil 3: Services überwachen

This entry is part 3 of 6 in the series Icinga 2 Best Practice

Nun in Teil 3 dieser Serie werden wir uns näher damit beschäftigen wie in Icinga 2 Services überwacht werden bzw. wie es zu konfigurieren ist, dass bestimmte Services nur auf bestimmten Hosts überwacht werden. Hier bietet Icinga 2 als Neuerung eine regelbasierte Zuweisung an Host-Objekte die definierten Eigenschaften genügen.

apply Service "ping4" {
  import "generic-service"

  check_command = "ping"

  assign where host.address || host.address6
}

So wird hier ein Service ping4 an alle Hosts “gebunden”, die das Attribut address oder address6 definiert haben. Nach diesem recht einfachem Beispiel wenden wir uns auch gleich etwas komplizierterem zu, der Überwachung von Dateisystemen auf einem Linux-System.

apply Service for (filesystem => config in host.vars.disks) {
  import "generic-service"

  check_command = "disk"
  command_endpoint = host.name

  vars += config

  assign where host.vars.os == "Linux"
  ignore where typeof(config) != Dictionary
}

Da hier über das CheckCommand disk, das Plugin check_disk zur Anwendung gelangt, das lokal auf dem zu überwachenden System laufen muss, wird es via command_endpoint auf genau diesem Endpoint angetriggert (siehe hierzu Teil 1 dieser Serie). Ein Host kann mehrere unterschiedlich Dateisysteme beherbergen, deshalb sind diese im Host-Objekt mit dem Custom-Attribute vars.disks zu definieren. Ausserdem muss zusätzlich, wie in dem assign-Statement gefordert, vars.os auf Linux gesetzt sein.

object Host "host.example.org" {
  ...
  vars.os = "Linux"

  vars.disks["disk /"] = {
    disk_partition = "/"
  }
  vars.disks|"disk /tmp"] = {
    disk_partition = "/tmp"
  }
}

Bekanntlich handelt es sich bei vars um ein Dictionary und vars.disks ist eine Darstellungsform eines Keys in diesem Dictionary. Eine andere Form einen Schlüssel anzusprechen ist der Index mit []-Klammern, wie in vars.disks[“disk /”]. Das heißt wir haben hier ein Dictionary in einem Dictionary. Und um es noch auf die Spitze zu treiben weisen wir den einzelnen Keys als Wert wieder jeweils ein Dictionary zu, Perl lässt grüßen. Wozu nun das Ganze? Mit apply Service for wird der Inhalt von vars.disks durchlaufen. Da es sich hierbei um ein Dictionary handelt, wird hier ein for-each verwendet, zu sehen an filesystem => config. Beides sind hier unsere Laufvariablen für die Schleife. Der Variablen filesystem wird jeweils der Key zu gewiesen, also beim ersten Durchlauf “disk /” und beim Zweiten “disk /tmp”, in config dann demnach der zugehörige Wert. Diesen Wert, selbst ein Dictionary, kann als Konfigurations-Dictionary bezeichnet werden, der den jeweiligen Pluginaufruf von disk parametrisiert. Die möglichen Parameter für disk sind sehr gut der Online-Dokumentation zu entnehmen. Wie dies funktioniert und warum die Zeile vars += config hierzu eine zentrale Rolle spielt, wird in Teil 4 erklärt werden. Der Name des Services entspricht standardmäßig dem Inhalt von filesystem.
Selbstverständlich besitzt jedes Linux-System ein Root-Dateisystem und sagen wir, bei uns auch ein eigenes für /tmp. Natürlich möchte man nun nicht für alle seine Hosts immer diese obigen 7 Zeilen angeben müssen, deshalb definieren wir mit diesen ein Host-Template mit der Bezeichnung linux-host. Nun haben Regeln die dumme Eigenheit ihre Ausnahmen zu haben, z.B. hat der Host host.example.org im Gegensatz zu allen anderen Hosts eben kein Dateisystem /tmp. Was dann?

object Host "host.example.org" {
  import "generic-host"

  vars.disks["disk /tmp"] = false
}

Hier wird nun für /tmp die Definition aus dem Template nachträglich überschrieben. Das ignore-Statement in unserer Service-Definition sorgt dafür, dass alle Dateisysteme, denen kein Konfiguration-Dictionary zugewiesen ist, auch nicht als Service in unserer Icinga-Konfiguration landen. Bei typeof handelt es sich um eine Funktion, die die Typesierung einer Variablen ermittelt.

Bis zum nächsten Mal, ihr müsst unbedingt schauen wie es weiter geht. In Teil 4 folgt die etwas theoretische Erklärung wie solche Services jeweils einzeln unterschiedlich parametrisiert werden.

Lennart Betz

Autor: Lennart Betz

Der diplomierte Mathematiker arbeitet bei NETWAYS im Bereich Consulting und bereichert seine Kunden mit seinem Wissen zu Icinga, Nagios und anderen Open Source Administrationstools. Im Büro erleuchtet Lennart seine Kollegen mit fundierten geschichtlichen Vorträgen die seinesgleichen suchen.

Icinga 2 Best Practice Teil 4: Host Templates und Services

This entry is part 4 of 6 in the series Icinga 2 Best Practice

Heute soll es um die Strukturierung von Services und deren Zuordnungen zu Gruppen von Hosts gehen. Ein Host Template kann zur Zusammenfassung von Informationen einer Gruppe von Hosts dienen und damit mehrere unterschiedliche Services für den jeweiligen Host anziehen. Wir wollen in den folgenden Beispielen Linux Hosts überwachen. Dort neben, wie schon in Teil 3 beschrieben, der Belegung der Dateisysteme auch in Abhängigkeit in welchem Netzsegment der Host angeschlossen ist, ob die Zeit synchron zum Zeitserver läuft.

template Host "linux-host" {
  import "generic-host"

  vars.os = "Linux"
  vars.disks["disk /"] = {
    disk_partition = "/"
  }
}

apply Service "time" {
  import "generic-service"

  check_command = "ntp_time"
  command_endpoint = host_name

  assign where host.vars.os == "Linux"
}

Es gibt zwei Netze mit je eigenem Zeitserver. Um dieses abzubilden, definieren wir für jedes Netz ein eigenes Host-Template:

template Host "dmz-net" {
  vars.ntp_address = "172.16.2.99"
}
template Host "lan-net" {
  vars.ntp_address = "172.16.1.99"
}

Diese beiden Templates enthalten nur netzspezifische Informationen, in unserem Beispiel auch nur den jeweilig zuständigen Zeitserver. Der Service-Check time mit dem Plugin check_ntp_time ermittelt die Differenz zwischen der lokalen Zeit des Hosts und der Zeit des NTP-Servers, der in ntp_address angegeben ist. Nun müssen wir für einen Host im internen Netzwerk lan-net nur noch beide Templates zusammen bringen:

object Host "host.example.org" {
  import "linux-host"
  import "lan-net"
  import "postgres-dbms"

  address = "172.16.1.11"
}

Habe wir weitere Services, die abhängig vom Netzsegment unterschiedlich zu konfigurieren sind, können diese Informationen den Netz-Templates hinzugefügt werden. Ein weiteres Beispiel wäre hier die Überwachung unterschiedlicher Domain Name Services. Diese Konzept der Stapelung von Host templates kann natürlich noch weitergeführt werden, z.B. auf Applikationen wie einen Postgresql basierendes Datenbank-Management-Systems bezogen. Ggf. muss jedoch auf die Reihenfolge der Importe geachtet werden, wenn Werte überschrieben werden sollen.

Lennart Betz

Autor: Lennart Betz

Der diplomierte Mathematiker arbeitet bei NETWAYS im Bereich Consulting und bereichert seine Kunden mit seinem Wissen zu Icinga, Nagios und anderen Open Source Administrationstools. Im Büro erleuchtet Lennart seine Kollegen mit fundierten geschichtlichen Vorträgen die seinesgleichen suchen.

Icinga 2 Best Practice Teil 5: Autosign von Zertifikatsanfragen in verteilten Umgebungen

This entry is part 5 of 6 in the series Icinga 2 Best Practice

Ein jeder kennt das Problem, im Unternehmensnetz gibt es unterschiedliche netzwerkbezogene Sicherheitszonen, die mittels Perimeter voneinander getrennt sind. Für das Monitoring bedeutet dies im Idealfall, man stellt in jeder dieser Zonen einen Icinga-Satelliten bereit, der die von ihm ermittelten Ergebnisse an eine zentrale Instanz weiter meldet, den Icinga-Master. Damit ist gewährleistet, was Firewall-Admins berechtigterweise verlangen, lediglich Punkt-zu-Punkt-Verbindungen zu erlauben. Auch die Richtung des Verbindungsaufbaus ist mit Icinga 2 wählbar.
Setzt man zusätzlich auch Icinga 2 in der Ausprägung Agent ein, benötigt dieser ein signiertes Zertifikat um mit seinem jeweiligen Satelliten oder direkt mit dem Master zu kommunizieren. Im letzten Fall entstehen hieraus keine Probleme. In der Regel wird die CA, in einer Umgebung mit mehreren Satelliten auf dem Master betrieben. Bei lediglich einem Satelliten könnte die CA auch auf genau diesem Satelliten laufen, was jedoch Sicherheitsbedenken hervorruft. Ein Zertifikat aus einer niedrigen Sicherheitszone könnte verwendet werden, um mit einer höheren zu kommunizieren. Gleiches gilt natürlich auch bei der Benutzung lediglich einer CA, aber die Netzwerksicherheit soll ja dieses Risiko Minimieren.
Bleibt das Problem auf einem neuinstallierten Agenten mittels Autosigning ein beglaubigtes Zertifikat zu erhalten. Hier kann eine eigene CA auf jedem Satelliten Abhilfe schaffen. Der jeweilige Agent benötigt nun nur eine Verbindung zu seinem Satelliten um einen Request zu senden und keine Kommunikation zum Master. Wie wird nun dieses genau bewerkstelligt?

  • Erstellen einer CA auf dem Satelliten
  • Der Satellit bekommt ein Zertifikat signiert von seiner eigene CA
  • Der Satellit benötigt sein eigenes RootCA-Zertifikat und das vom Master
  • Der Master bekommt umgekehrt ebenfalls das RootCA vom Satelliten
Lennart Betz

Autor: Lennart Betz

Der diplomierte Mathematiker arbeitet bei NETWAYS im Bereich Consulting und bereichert seine Kunden mit seinem Wissen zu Icinga, Nagios und anderen Open Source Administrationstools. Im Büro erleuchtet Lennart seine Kollegen mit fundierten geschichtlichen Vorträgen die seinesgleichen suchen.