Icinga 2 Best Practice Teil 3: Services überwachen

This entry is part 3 of 7 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
Lennart Betz
Senior Consultant

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 1: Zonen und Agenten

This entry is part 1 of 7 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
Lennart Betz
Senior Consultant

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.