The Icinga Config Compiler: An Overview

The Icinga configuration format was designed to be easy to use for novice users while at the same time providing more advanced features to expert users. At first glance it might look like a simple key-value configuration format:

object Host "example.icinga.com" {
	import "generic-host"

	address = "203.0.113.17"
	address6 = "2001:db8::17"
}

However, it features quite a bit of functionality that elevates it to the level of scripting languages: variables, functions, control flow (if, for, while) and a whole lot more.

Icinga’s scripting language is used in several places:

  • configuration files
  • API filter expressions
  • auto-generated files used for keeping track of modified attributes

In this post I’d like to show how some of the config machinery works internally.

The vast majority of the config compiler is contained in the lib/config directory. It weighs in at about 4.5% of the entire code base (3059 out of 68851 lines of code as of July 2018). The compiler is made up of three major parts:

Lexer

The lexer (lib/config/config_lexer.ll, based on flex) takes the configuration source code in text form and breaks it up into tokens. In doing so the lexer recognizes all the basic building blocks that make up the language:

  • keywords (e.g. “object”, “for”, “break”) and operators (e.g. >, ==, in)
  • identifiers
  • literals (numbers, strings, booleans)
  • comments

However, it has no understanding of how these tokens fit together syntactically. For that it forwards them to the parser. All in all the lexer is actually quite boring.

Parser/AST

The parser (lib/config/config_parser.yy, based on GNU Bison) takes the tokens from the lexer and tries to figure out whether they represent a valid program. In order to do so it has production rules which define the language’s syntax. As an example here’s one of those rules for “for” loops:

| T_FOR '(' identifier T_FOLLOWS identifier T_IN rterm ')'
{
        BeginFlowControlBlock(context, FlowControlContinue | FlowControlBreak, true);
}
rterm_scope_require_side_effect
{
        EndFlowControlBlock(context);

        $$ = new ForExpression(*$3, *$5, std::unique_ptr($7), std::unique_ptr($10), @$);
        delete $3;
        delete $5;
}

Here’s a list of some of the terms used in the production rule example:

Symbol Description
T_FOR Literal text “for”.
identifier A valid identifier
T_FOLLOWS Literal text “=>”.
T_IN Literal text “in”.
BeginFlowControlBlock, EndFlowControlBlock These functions enable the use of certain flow control statements which would otherwise not be allowed in code blocks. In this case the “continue” and “break” keywords can be used in the loop’s body.
rterm_scope_require_side_effect A code block for which Icinga can’t prove that the last statement doesn’t modify the program state.

An example for a side-effect-free code block would be { 3 } because Icinga can prove that executing its last statement has no effect.

After matching the lexer tokens against its production rules the parser continues by constructing an abstract syntax tree (AST). The AST is an executable representation of the script’s code. Each node in the tree corresponds to an operation which Icinga can perform (e.g. “multiply two numbers”, “create an object”, “retrieve a variable’s value”). Here’s an example of an AST for the expression “2 + 3 * 5”:

Note how the parser supports operator precedence by placing the AddExpression AST node on top of the MultiplyExpression node.

Icinga’s AST supports a total of 52 different AST node types. Here are some of the more interesting ones:

Node Type Description
ArrayExpression An array definition, e.g. [ varName, "3", true ]. Its interior values are also AST nodes.
BreakpointExpression Spawns the script debugger console if Icinga is started with the -X command-line option.
ImportExpression Corresponds to the import keyword which can be used to import another object or template.
LogicalOrExpression Implements the || operator. This is one of the AST node types which don’t necessarily evaluate all of its interior AST nodes, e.g. for true || func() the function call never happens.
SetExpression This is essentially the = operator in its various forms, e.g. host_name = "localhost"

On their own the AST nodes just describe the semantical structure of the script. They don’t actually do anything in terms of performing any real actions.

VM

Icinga contains a virtual machine (in the language sense) for executing AST expressions (mostly lib/config/expression.cpp and lib/config/vmops.hpp). Given a reference to an AST node – such as root node from the example in the previous section – it attempts to evaluate that node. What that means exactly depends on the kind of AST node:

The LiteralExpression class represents bare values (e.g. strings, numbers and boolean literals). Evaluating a LiteralExpression AST node merely yields the value that is stored inside of that node, i.e. no calculation of any kind takes place. On the other hand, the AddExpression and MultiplyExpression AST nodes each have references to two other AST nodes. These are the operands which are used when asking an AddExpression or MultiplyExpression AST node for “their” value.

Some AST nodes require additional context in order to run. For example a script function needs a way to access its arguments. The VM provides these – and a way to store local variables for the duration of the script’s execution – through an instance of the StackFrame (lib/base/scriptframe.hpp) class.

Future Considerations

All in all the Icinga scripting language is actually fairly simple – at least when compared to other more sophisticated scripting engines like V8. In particular Icinga does not implement any kind of optimization.

A first step would be to get rid of the AST and implement a bytecode interpreter. This would most likely result in a significant performance boost – because it allows us to use the CPU cache much more effectively than with thousands, if not hundreds of thousands AST nodes scattered around the address space. It would also decrease memory usage both directly and indirectly by avoiding memory fragmentation.

However, for now the config compiler seems to be doing its job just fine and is probably one of the most solid parts of the Icinga codebase.

Gunnar Beutner

Autor: Gunnar Beutner

Vor seinem Eintritt bei NETWAYS arbeitete Gunnar bei einem großen deutschen Hostingprovider, wo er bereits viel Erfahrung in der Softwareentwicklung für das Servermanagement sammeln konnte. Bei uns kümmert er sich vor allem um verschiedene Kundenprojekte, aber auch eigene Tools wie inGraph oder Icinga2.

Außerhalb von NETWAYS: Projektwoche in der Berufsschule

Heute möchte ich einen Blick außerhalb der betrieblichen Ausbildung werfen und ein wenig über die Projektwoche in der Berufsschule berichten. Alle Auszubildenden, die die Berufsschule B3 in Fürth im Zweig Fachinformatik Systemintegration oder Anwendungsentwicklung besuchen, führen dort am Ende des 2. Ausbildungsjahres eine Projektwoche durch.

Innerhalb dieser Projektwoche arbeiten die Schüler einer Klasse in Gruppen von 12 – 15 Mitgliedern zusammen. Dabei wird vor allem darauf geachtet, dass die Stärken der Schüler gleichmäßig auf die Gruppen verteilt werden.

Das Projekt selbst bestand aus der Simulation eines Kundenauftrages an eine Firma, die sowohl die Umsetzung von IT-Infrastrukturen als auch Software-Entwicklung bietet. Die Belegschaft der Firma wurde dann wie folgt auf die Mitschüler aufgeteilt:

  • Abteilung Management: 3 Schüler
  • Abteilung Infrastruktur: 3 Schüler
  • Abteilung Entwicklung: 6 Schüler

Die Gewichtung ergab sich aus der Analyse des Lastenheftes, das vom Lehrerkollegium zuvor ausgearbeitet und ausgehändigt wurde. Darin ging es darum, dass für den Kunden eine Infrastruktur und Software aufgebaut werden soll, mit der der Kunde sämtliches IT-Inventar in seinen Räumlichkeiten verwalten kann. Der Kunde selbst wurde dabei durch unsere Schule und einen Teil des Lehrerkollegiums dargestellt. Folgende Punkte waren dabei besonders von Bedeutung:

  • Netzwerk mit VLANs (Trennung von Schul- und Administrationsnetzwerk) mit Access Points für WLAN
  • Web- und Datenbankserver, auf denen die Inventarisierungssoftware laufen soll
  • Mit der Software müssen folgende Aktionen machbar sein:
    • Auflisten von Inventar
    • Ändern, Löschen und Hinzufügen von Inventar
    • Zuordnung von Inventar zu Räumen und Klassenzimmern
    • Login mit User/Passwort und Vergabe von Rechten (Admin oder User)
  • Einrichten von Clients für die Nutzung des Webfrontends der Software
    • Aufrufbar über Browser
    • Ergonomie muss beachtet werden
  • Erstellung eines Pflichtenheftes, einer Kundendokumentation und eines Wartungsvertrages
    • Pflichtenheft und Wartungsvertrag muss von den Lehrkräften abgenommen werden
    • Kundendokumentation soll als Benutzerhandbuch fungieren
  • Abschließende Projekt-Präsentation vor Publikum mit abschließender Fragerunde
  • Zeitlicher Rahmen: Montag bis Donnerstag, Umsetzung des Projektes mit freier Zeiteinteilung, Freitag morgen Präsentation

Nachdem wir den Montagvormittag damit verbrachten, die Aufgabenstellung durchzuplanen, setzten wir im Laufe der Woche die folgenden Schritte um:

  • Erstellung Pflichtenheft
  • Aufbau Netzwerk
  • Aufbau LAMP-Stack auf Server
  • Aufbau der MySQL-Datenbank
  • Erstellung der Core Software aus php und javascript
  • Erstellung des Webfrontends mit html und php
  • Erstellung Wartungsvertrag
  • Erstellung und Planung Projekt-Präsentation
  • Ausführliches Testen der einzelnen Komponenten
  • Fehlersuche, Debugging und Korrekturen
  • Erstellung Kundendokumentation, direkt aufrufbar über die Hilfe-Funktion in der Software

Mit dem Ergebnis aus dieser Woche konnten wir dann auch unsere Lehrer überzeugen, die unsere Gruppe durchweg positiv bewerteten und sehen konnten, dass wir als Klasse viel Wissen aus den letzten beiden Schuljahren mitgenommen haben.

Als Fazit kann ich persönlich sagen, dass es auf jeden Fall eine tolle und konstruktive Erfahrung ist, einmal komplett eigenverantwortlich und mit eigener Zeiteinteilung eine solche Aufgabe zu bewältigen. Außerdem bringt dies die Schüler auch zwischenmenschlich und bzgl. der eigenen Persönlichkeit weiter, denn jeder lernt nicht nur die eigenen Stärken und die der Mitschüler kennen und schätzen, sondern muss auch mit Schwächen und Fehlschlägen zurechtkommen bzw. anderen aus diesen heraushelfen. Es ist eben doch ganz gut, wenn man ab und zu mal die eigene Komfortzone verlässt!

Wer sich nun angesprochen fühlt, auch mal in die IT-Welt zu schnuppern oder mit dem Gedanken spielt, eine Ausbildung im Bereich Informatik zu machen, dann schreibt uns doch einfach unter jobs@netways.de. Mehr Infos findet Ihr auch auf unserer Webseite oder in unserer Stellenausschreibung zum Azubi Fachinformatik. Mehr Informationen zum Thema Ausbildung Fachinformatiker findet Ihr auch auf der Webseite der IHK.

 

Bildquellen: 
https://www.unixmen.com/how-to-install-lamp-stack-ubuntu-17-04/
http://www.b3-fuerth.de/ 

Nicole Lang

Autor: Nicole Lang

Ihr Interesse für die IT kam bei Nicole in ihrer Zeit als Übersetzerin mit dem Fachgebiet Technik. Seit 2010 sammelt sie bereits Erfahrungen im Support und der Administration von Storagesystemen beim ZDF in Mainz. Ab September 2016 startete Sie Ihre Ausbildung zur Fachinformatikerin für Systemintegration bei NETWAYS, wo sie vor allem das Arbeiten mit Linux und freier Software reizt. In ihrer Freizeit überschüttet Sie Ihren Hund mit Liebe, kocht viel Gesundes, werkelt im Garten, liest Bücher und zockt auch mal gerne.

Continuous Integration with Golang and GitLab

Today you not only develop code and collaborate with other developers in your Git branches and forks. In addition continuous integration helps with “instant” compile and runtime tests. Which Git commit caused a failure, are there any performance changes with the recent code history?

Today I want to dive into our latest insights into Golang and building our code inside GitLab on each push and merge request. Golang extensively supports unit tests and also their analysis with coverage tests. This ensures that code isolated in test interfaces runs rock-solid and you can focus on the more important tasks and features.

In order to abstract the Go build and install calls, we add a Makefile for convenience. This also allows developers to manually use it, if they want.

$ vim Makefile

.PHONY: all test coverage

all: get build install

get:
        go get ./...

build:
        go build ./...

install:
        go install ./...

test:
        go test ./... -v -coverprofile .coverage.txt
        go tool cover -func .coverage.txt

coverage: test
        go tool cover -html=.coverage.txt

The “coverage” target runs the tests and stores the coverage results in “.coverage.txt”. This is then converted into HTML and can be opened with a browser for example.

Inside GitLab we need a CI runner configured for our project and job pipeline. This is already available in the NWS app. If you want to learn more about GitLab CI/CD, containers, jobs and pipelines, I’ll invite you to join me for the official GitLab training sessions 🙂

The CI configuration file is stored in “.gitlab-ci.yml” inside the Git repository and uses the Golang image for Debian Stretch in this example.

$ vim .gitlab-ci.yml

image: golang:1.10-stretch

stages:
  - build

before_script:
  - apt-get update && apt-get install -y make curl
  - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
  - mkdir -p /go/src/git.icinga.com/icingadb
  - ln -s /builds/icingadb/icingadb /go/src/git.icinga.com/icingadb/icingadb
  - cd /go/src/git.icinga.com/icingadb/icingadb
  - dep init && dep ensure

build:
  stage: build
  script:
    - make test

The “before_script” tasks can be moved into your own Docker image which can be made available in the GitLab container registry too. At some later point when dependencies are fixed, we’ll remove the “dep init” step and just use the locked versions from inside our Git repository.

The test coverage should also be highlighted in the job output. This can be extracted with a regular expression using the shell output from the tests. Nagivate into “Settings > CI/CD > General pipeline settings > Test coverage parsing” and add the following regex:

^total:\t+\(statements\)\t+(\d+\.\d+)%

Each pushed branch and merge request will then automatically be built and also shows the test coverage. There’s room for improvement here, the next months will ensure to add more unit and runtime tests 😎

Michael Friedrich

Autor: Michael Friedrich

Michael ist seit vielen Jahren Icinga-Entwickler und hat sich Ende 2012 in das Abenteuer NETWAYS gewagt. Ein Umzug von Wien nach Nürnberg mit der Vorliebe, österreichische Köstlichkeiten zu importieren - so mancher Kollege verzweifelt an den süchtig machenden Dragee-Keksi und der Linzer Torte. Oder schlicht am österreichischen Dialekt der gerne mit Thomas im Büro intensiviert wird ("Jo eh."). Wenn sich Michael mal nicht in der Community helfend meldet, arbeitet er am nächsten LEGO-Projekt oder geniesst das schöne Nürnberg mit #atasteofaustria 🙂

Die Extrawurst

Icinga Web 2 bietet eine Reihe von Möglichkeiten, Gruppen-Mitgliedschaften zuzuweisen. Am häufigsten werden Gruppenmitgliedschaften entweder via Web angelegt oder aus einem LDAP-Verzeichnis gezogen. Der bekannteste Vertreter dabei ist Microsofts Active Directory.

Weniger bekannt ist, dass man in eigenen Modulen Benutzer- sowie Gruppenmitgliedschafts-Backends bereitstellen kann. Manchmal braucht man aber gar nicht ein volles Backend, sondern nur ein paar kleine Korrekturen. Aus einem solchen Grund entstand diese Woche in einem Kundenprojekt ein neues Icinga Web 2 Modul: Extragroups.

Zusätzliche Gruppen für jeden

Einmal installiert, lassen sich in der config.ini des Moduls flexible Regeln definieren.

[Jeder ist ein Mimimi]
add_groups = "Mimimi"

Das allein hat erst mal keinen Effekt, also hinterlegen wir in der /etc/icingaweb2/roles.ini eine Rolle, welche unseren Usern eine entsprechende Einschränkung verpasst:

[Mimimi Demo]
groups = "Mimimi"
monitoring/filter/objects = "host_name=*mi*"

Sobald wir uns neu anmelden sind wir Mitglied der Gruppe “Mimimi” und sehen nur noch Hosts welche “mi” enthalten. Dabei ist nebensächlich, ob eine Benutzergruppe namens “Mimimi” existiert oder nicht.

Mustervergleich mit Benutzernamen

Lasst uns das Ganze ein wenig einschränken:

[Alle Heuler sollen Mimimis werden]
user_filter = "username=*heul*"
add_groups = "Mimimi"

Was hier passiert dürfte klar sein, wir machen nur noch unsere Heuler zu Mimimis.

Regeln basierend auf Umgebungsvariablen

Ihr möchtet alle Benutzer mit ein paar Ausnahmen in eine spezielle Gruppe geben, aber nur wenn diese remote arbeiten? Erstellt einfache eine auf deren IP-Range basierende Regel:

[Einschränkung wenn via VPN verbunden]
env_filter = "REMOTE_ADDR=192.0.2.*"
user_filter = "username!=tom&username!=admin"
add_groups = "Via VPN verbunden"

Ähnlich wie REMOTE_ADDR in diesem Beispiel können alle vom Web-Server bereitgestellten Umgebungsvariablen benutzt werden. Im nächsten Beispiel zeigen wir Netzwerk-Admins welche via Nagstamon angemeldet sind lediglich wirklich wichtige Hosts und Services:

[Filter für Nagstamon]
env_filter = "HTTP_USER_AGENT=Nagstamon*"
user_filter = "group=Network Admin"
add_groups = "VIP objects filter"

VORSICHT: Der User-Agent kann einfachst gefälscht werden. Dieses Beispiel dient dem Komfort unserer Benutzer: sie wollen in Nagstamon nicht mit allen Problemen belästigt werden. Für sicherheitskritische Regeln taugt der User-Agent nicht.

Mehrere Gruppen zuweisen

Manchmal will man mehrere Gruppen auf einmal zuweisen, ohne die ganze Regel zu klonen. Das lässt sich einfach bewerkstelligen, denn add_groups akzeptiert eine Komma-getrennte Liste:

[VPN-Benutzer einschränken]
; ..
add_groups = "Eingeschränkte Benutzer, Spezielle Dashboards, Business-Prozesse"

Soll die Gruppenliste noch dynamischer sein? Vorausgesetzt dass ein Webserver-Modul oder dessen Konfiguration diese Information bereitstellt, lässt sich jede Umgebungsvariable via {VARIABLE_NAME} nutzen:

[Gruppen des SSO-Moduls zuweisen]
add_groups = "{HTTP_AUTHZ_GROUPS}"

Auch in Strings die aus Umgebungsvariablen kommen funktioniert das Komma (,) als Trennzeichen. Und man kann natürlich Variablen-Platzhalter mit statischen Werten kombinieren:

[Eine Reihe von Gruppen]
add_groups = "Spezial-Gruppe, {REMOTE_GROUPS}, {HTTP_AUTHZ_GROUPS}"

Auf Gruppenmitgliedschaften basierende Regeln

Der user_filter erlaubt auch Regeln basierend auf bereits zugewiesene Gruppen. In unserem umfangreicheren letzten Beispiel erhalten alle Benutzer zusätzliche Gruppenmitgliedschaften via HTTP_AUTHZ_GROUPS. Na gut, jeder außer der guest und der admin-Benutzer.

Wenn sie Nagstamon nutzen während sie via VPN verbunden sind, sollen sie zusätzlich in die Gruppe Nagstamon Remote kommen.

Wird Nagstamon ohne VPN benutzt, sollen Mitglieder der Gruppen “Linux Benutzer” und/oder “Unix Benutzer” in die Gruppe “Nagstamon Lokal” kommen. Alle außer dem admin:

[Gruppen des SSO-Moduls zuweisen]
add_groups = "{HTTP_AUTHZ_GROUPS}"
user_filter = "user_name!=guest&username!=admin"

[Nagstamon via VPN]
env_filter = "REMOTE_ADDR=192.0.2.*&HTTP_USER_AGENT=Nagstamon*"
add_groups = "Nagstamon Remote"

[Nagstamon ohne VPN]
env_filter = "REMOTE_ADDR!=192.0.2.*&HTTP_USER_AGENT=Nagstamon*"
user_filter = "username!=admin&(group=Linux Benutzer|group=Unix Benutzer)"
add_groups = "Nagstamon Local"

Dabei gilt es zu beachten, dass der Filter basierend auf die group-Eigenschaft nur Gruppen sieht, welche von vorhergehenden Regeln zugewiesen wurden. Von anderen Backends bereitgestellte Gruppenmitgliedschaften sind nicht zugänglich.

Das war’s auch schon, viel Spaß!

Thomas Gelf

Autor: Thomas Gelf

Der gebürtige Südtiroler Tom arbeitet als Principal Consultant für Systems Management bei NETWAYS und ist in der Regel immer auf Achse: Entweder vor Ort bei Kunden, als Trainer in unseren Schulungen oder privat beim Skifahren in seiner Heimatstadt Bozen. Neben Icinga und Nagios beschäftigt sich Tom vor allem mit Puppet.

MSSQL databases from Icinga Web 2

Disclaimer: This guide is only for RedHat and CentOS 7, using the PHP SCL packages with the official icingaweb2 package.

Often when we help customers implement Icinga 2, Icinga Web 2 and Director, they use custom imports to pull in data to their monitoring config. Whenever data is in need to be pulled from a MSSQL database, this can be challenging. Their are multiple guides around the Internet, even from Microsoft.

This post should clear things how to achieve this with the official Icinga packages and their requirements.

Requirements

Basic Installation Icinga Web 2

First of all icingaweb2 and PHP needs to be installed – usually you should have this already…

# on RedHat
subscription-manager repos --enable rhel-7-server-optional-rpms
subscription-manager repos --enable rhel-server-rhscl-7-rpms
yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

# on CentOS
yum install centos-release-scl epel-release

yum install httpd icingaweb2

systemctl start httpd.service
systemctl enable httpd.service

echo "date.timezone = Europe/Berlin" > /etc/opt/rh/rh-php71/php.d/timezone.ini
systemctl start rh-php71-php-fpm.service
systemctl enable rh-php71-php-fpm.service

Please also see the official documentation.

MSSQL PDO driver for PHP

Their are multiple ways to install a MSSQL compatible driver, but with icingaweb2 and the ZendFramework below in mind, we need to use pdo_dblib with FreeTDS as driver implementation. I prepared a RPM spec file for you to build and install, it is based on the php-extras packages shipped with Fedora’s EPEL, only updated for PHP 7.1 SCL.

You can setup a small RPM build environment on a RedHat or CentOS machine with the required repositories.

sudo yum install rpm-build rpmdevtools yum-utils gcc gcc-c++ scl-utils scl-utils-build
rpmdev-setuptree

cd ~/rpmbuild/SPEC
wget https://github.com/lazyfrosch/rpm-php-extras/raw/epel7/php-extras.spec
cd ../SOURCES
spectool -gf ../SPECS/php-extras.spec

cd ~/rpmbuild
rpmbuild -bs SPECS/php-extras.spec
sudo yum-builddep SRPMS/rh-php71-php-extras*.src.rpm

rpmbuild --rebuild SRPMS/rh-php71-php-extras*.src.rpm

With the built RPM files under RPMS/ you are good to go to your Icinga machine. Basically you only need to copy rh-php71-php-mssql*.rpm over and install it there.

yum install rh-php71-php-mssql*.rpm
scl enable rh-php71 -- php -m

After restarting php-fpm the driver has been loaded.

systemctl restart rh-php71-php-fpm.service

Patching Icinga Web 2

In Icinga Web 2.5.1 and before there is an error in detecting MSSQL correctly, detection only works in PHP 5.x. A fix has been suggested in PR#3400.

To manually patch this, you only need to fix a single line in /usr/share/php/Icinga/Application/Platform.php

--- /usr/share/php/Icinga/Application/Platform.php.orig 2018-03-27 06:02:59.454240788 -0400
+++ /usr/share/php/Icinga/Application/Platform.php 2018-03-27 00:38:15.967639651 -0400
@@ -351,7 +351,7 @@
 */
 public static function hasMssqlSupport()
 {
- return static::extensionLoaded('mssql') && static::classExists('Zend_Db_Adapter_Pdo_Mssql');
+ return static::extensionLoaded('pdo_dblib') && static::classExists('Zend_Db_Adapter_Pdo_Mssql');
 }
 
 /**

I hope this helps you getting started, feel free to ask questions in the comments.

NETWAYS offers professional support for Icinga and other Open Source tools, check our Website about Support.

The image used in this article is from johnmartel.blogspot.de, we assume fair use by mentioning the author.

Markus Frosch

Autor: Markus Frosch

Markus arbeitet bei NETWAYS als Principal Consultant und unterstützt Kunden bei der Implementierung von Nagios, Icinga und anderen Open Source Systems Management Tools. Neben seiner beruflichen Tätigkeit ist Markus aktiver Mitarbeiter im Debian Projekt.

Icinga 2 – Monitoring automatisiert mit Puppet Teil 8: Integration von Icinga Web 2

This entry is part 8 of 8 in the series Icinga 2 Monitoring automatisiert mit Puppet

Zum Ausklang des Jahres 2017 gibt es nochmals einen Post zum Thema Puppet und Icinga. Es geht heute um das Ziel, einen Icinga-Server inklusive Icinga Web 2 mittels Puppet zu managen. Die Icinga IDO sowie eine Datenbank zur Authentifizierung am Icinga Web 2 sind beide als MySQL-Datenbanken realisiert. Kommandos von Icinga Web 2 werden zum Icinga-Core via Icinga-2-API übertragen.

Als Plattform kommt ein CentOS 7 zum Einsatz. Damit muss für die aktuellen Pakete zum Icinga Web 2 ab Version 2.5.0 der verwendete Apache den PHP-Code mittels FastCGI ausführen.

Voraussetzung ist, dass die erforderlichen Puppet-Module icinga-icinga2, icinga-icingaweb2, puppetlabs-mysql und deren jeweilige Abhängigkeiten installiert sein müssen.

Beginnen wir zuerst mit einigen Variablen, die wir setzen, um im nachfolgenden Code keine Redundanzen zu haben. Wir setzen hier wie die Datenbanken für die IDO und fürs Icinga Web 2 heißen sollen und mit welchem Account jeweils darauf zugegriffen werden soll. Zusätzlich kommt noch der Name und das Passwort des Benutzers für die Icinga-2-API hinzu.

$ido_db_name = 'icinga'
$ido_db_user = 'icinga'
$ido_db_pass = 'icinga'

$api_user    = 'icingaweb2'
$api_pass    = '12e2ef553068b519'

$web_db_name = 'icingaweb2'
$web_db_user = 'icingaweb2'
$web_db_pass = 'icingaweb2'

Der nun folgende Code ist für Puppet 4 und neuer gedacht, da das Feature bzgl. Reihenfolge der Deklarationen in der Datei vorausgesetzt wird.

Für CentOS werden im Vorfeld zusätzliche Repositories benötigt, EPEL für die Plugins und SCL für das FastCGI PHP in der Version 7. Die Klasse icinga2 kümmert sich nicht nur um die Grundkonfiguration, sondern außerdem auch um die Einbindung des Icinga-Repos.

package { ['centos-release-scl', 'epel-release']:
  ensure => installed,
}

class { '::icinga2':
  manage_repo => true,
}

Als nächstes kümmern wir uns um den MySQL-Server und die von uns benötigten Datenbanken für die IDO und das Icinga Web 2. Beide laufen auf dem selben Host wie Icinga 2 und Icinga Web 2.

include ::mysql::server

mysql::db { $ido_db_name:
  user     => $ido_db_user,
  password => $ido_db_pass,
  host     => 'localhost',
  grant    => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE VIEW', 'CREATE', 'ALTER', 'INDEX', 'EXECUTE'],
}

mysql::db { $web_db_name:
  user     => $web_db_user,
  password => $web_db_pass,
  host     => 'localhost',
  grant    => ['ALL'],
}

Nun können wir das IDO-Feature von Icinga 2 konfigurieren und aktivieren. Da es im Zusammenhang der Defined Resources für die Datenbank und das Feature zu einer Abhängigkeit kommt, die nicht via Top-Down-Dependency gelöst werden kann, muss hier mit require gearbeitet werden, damit die Datenbank vorher erzeugt wird.

class { '::icinga2::feature::idomysql':
  database      => $ido_db_name,
  user          => $ido_db_user,
  password      => $ido_db_pass,
  import_schema => true,
  require       => Mysql::Db[$ido_db_name],
}

Da Icinga Web 2 Kommandos mittels API an Icinga 2 senden soll, benötigen wir eine CA sowie die Aktivierung der Features API selbst. Die Certificate Authority soll eine von Icinga verwaltete CA sein. Das erfordert die Deklaration der Klasse icinga2::Pki::ca, die sich um genau dieses kümmert. Das Feature muss dann mit none für den Parameter pki werden, da sonst mit dem Default, die Puppet-Zertifikate verwendet werden und damit nicht mehr zur CA passen würden.

Zusätzlich erzeugen wir in einer Konfigurationsdatei noch einen API-User, der entsprechend eingeschränkte Rechte hat, um dann von Icinga Web 2 verwendet zu werden Kommandos zu übertragen.

class { '::icinga2::feature::api':
  pki => 'none',
}

include ::icinga2::pki::ca

::icinga2::object::apiuser { $api_user:
  ensure      => present,
  password    => $api_pass,
  permissions => [ 'status/query', 'actions/*', 'objects/modify/*', 'objects/query/*' ],
  target      => "/etc/icinga2/conf.d/api-users.conf",
}

Das Icinga-Repository ist schon aktiviert und wir ziehen die Installation der Pakete für Icinga Web 2 aus der Klasse icingaweb2 heraus. Damit profitieren wir davon, dass die abhängigen Pakete für PHP mit FastCGI zu diesem Zeitpunkt schon installiert werden und wir den Dienst rh-php71-php-fpm schon vor der Installation von icinga Web 2 mit allen benötigten PHP-Modulen starten können. Anders herum müsste dafür Sorge getragen werden die Dienst nach icingaweb2 nochmals für einen Neustart zu triggern.

Zusätzlich kommen noch die Standard-Plugins und der Apache aufs System. Bevor der Apache-Service deklariert wird, soll noch die erforderliche Apache-Konfiguration fürs Icinga Web 2 ins Konfigurationsverzeichnis des Webservers abgelegt werden. Dieses Beispiel für FastCGI ist erst im Module ab Version 2.0.1 von puppet-icingaweb2 enthalten.

TIPP: Die hier verwendete File-Resource mit der Quelle auf das Example aus dem offiziellen Modul sollte in Produktion nicht verwendet werden, sondern nur als Beispielvorlage für eine eigene Source dienen.

package { ['icingaweb2', 'icingacli', 'httpd', 'nagios-plugins-all']:
  ensure => installed,
}

file { '/etc/httpd/conf.d/icingaweb2.conf':
  ensure => file,
  source => 'puppet:///modules/icingaweb2/examples/apache2/for-mod_proxy_fcgi.conf',
  notify => Service['httpd'],
}

service { 'httpd':
  ensure => running,
  enable => true,
}

Das in Abhängigkeit vom Paket icingaweb2 installierte PHP mit dem FastCGI-Dienst kann nun konfiguriert und gestartet werden. Die hier verwendete file_line Resource kann bei bedarf durch eine mit Augeas gemanagte ersetzt werden.

file_line { 'php_date_time':
  path  => '/etc/opt/rh/rh-php71/php.ini',
  line  => 'date.timezone = Europe/Berlin',
  match => '^;*date.timezone',
}

~> service { 'rh-php71-php-fpm':
  ensure => running,
  enable => true,
}

Nachdem nun alle Voraussetzungen gegeben sind, kümmern wir uns abschließend um Icinga Web 2. Dies unterteilt sich in zwei Schritte. Icinga Web 2 ist als aller erstes ein Framework für Weboberflächen, die spezifischen Sachen fürs Monitoring ist in einem Modul implementiert. Da es viele weitere zusätzliche Module gibt, folgt auch das Puppet-Modul diesem Schema.

class { 'icingaweb2':
  manage_package => false,
  import_schema  => true,
  db_name        => $web_db_name,
  db_username    => $web_db_user,
  db_password    => $web_db_pass,
  require        => Mysql::Db[$web_db_name],
}

Zuerst wird das Framework mit dem zur Authentifizierung nötigen Datenbankzugriff konfiguriert und erst dann das Monitoring-Modul. Für dieses ist der IDO-Zugriff zwingend erforderlich und der Transportweg für die zusendenden Kommandos wird mittels API konfiguriert.

class { 'icingaweb2::module::monitoring':
  ido_host        => 'localhost',
  ido_db_name     => $ido_db_name,
  ido_db_username => $ido_db_user,
  ido_db_password => $ido_db_pass,
  commandtransports => {
    icinga2 => {
      transport => 'api',
      username  => $api_user,
      password  => $api_pass,
    }
  }
}

Bleibt als letzten allen unseren Bloglesern ein Frohes Neues Jahr zu wünschen!

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.