Icinga2 Puppet4

aus PUG, der Penguin User Group
Wechseln zu: Navigation, Suche

Icinga2 und Puppet

Diese Anleitung wird einer meiner schlechtesten werden, soviel ist sicher. Es geht mehr darum Anderen das Gesuche zu ersparen und schneller zum Ergebnis zu kommen, insbesondere wenn man - wie ich - Programmierlegastheniker ist.

Ziel

Ziel ist es mittels Puppet(4) sowohl den Icinga2 Master als auch die Agents auszurollen. Hinzu kommt auch noch die Verwendung von Hiera - was eine sehr viel flexiblere Puppet Konfiguration ermöglicht - und der Zuhilfenahme von Profilen und Rollen. Gerade letzteres hat mich doch ganz schön Zeit gekostet, in Kombination mit Hiera.

Zutaten

Damit das alles am Ende klappt, benötigen wir:

  • Mindestens drei Hosts
    • Ein PuppetMaster Host
    • Ein Icinga2 Master
    • Ein Host für den Agent
  • Genug Zeit
  • Noch mehr Zeit
  • Ein wenig Ahnung von Puppet
  • Ein wenig Ahnung von Icinga2
  • Immer noch Zeit

Klar, theoretisch würde es auch mit einem Host klappen, aber so peilt man es ein wenig besser. Für die Anleitung habe ich mir drei LXC Container (Danke ProxMox) erstellt und mittels Standard Debian 8.0 befüllen lassen. Danach kommen noch ein paar Standard Werkzeuge drauf, wie Screen und Co. Es empfiehlt sich außerdem ein Cluster SSH zu verwenden, da die ersten Schritte überall gleich sind. Für Linux gibt es "mssh", für OSX csshX und Windows .... bestimmt auch etwas.

Konzept

Das Konzept sieht so aus, dass jedem Host eine(!) Rolle (Webserver / Mailserver / Jabber ..) zugewiesen wird, welche aus mehreren Profilen bestehen kann. Ein Beispiel:

  • role::webserver::nginx
    • common -> Enthält Standardpakete (screen/htop/fail2ban/ntpd...) / Konfigurationen (NTP Server / screenrc ..)
    • profile::webserver -> Kann z.B. für passende Firewallregeln sorgen ...
    • profile::webserver::nginx -> Wir wollen den Nginx als Webserver
    • profile::webserver::nginx::php -> Alles was für PHP5 nötig ist

Das ließe sich noch alles viel granulärer definieren, aber für den Anfang ...

Um auf das eigentliche Thema -- Icinga2 Master -- zurückzukommen, benötigen wir eine weitere Rolle:

  • monitoring::monitor::master:

Dort werden alle Profile enthalten sein, die notwendig sind, um einen vollständigen Icinga2 Master zu haben. Für die zu überwachenden Hosts benötigen wir keine Rolle, da der Icinga2 Agent (aka Host) über ein passendes Profil eingebunden wird.

Dann fangen wir mal zuerst mit dem PuppetMaster an.

Vim für Puppet

Bevor es losgeht, noch schnell dafür sorgen, dass wir die Puppet Manifests und YAML Dateien vernünftig editieren können:

Gnome-terminal.png
root@puppet-master ~ # mkdir -p /opt/puppetlabs/environments
root@puppet-master ~ # chown puppet: /opt/puppetlabs/environments
root@puppet-master ~ # usermod -s /bin/bash puppet
root@puppet-master ~ # su - puppet

puppet@puppet-master ~ $ mkdir /opt/puppetlabs/environments/.vim
puppet@puppet-master ~ $ ln -s /opt/puppetlabs/environments/.vim
puppet@puppet-master ~ $ mkdir -p ~/.vim/autoload ~/.vim/bundle;
puppet@puppet-master ~ $ curl -L  -Sso ~/.vim/autoload/pathogen.vim https://raw.github.com/tpope/vim-pathogen/master/autoload/pathogen.vim

puppet@puppet-master ~ $ cat <<EOF > ~/.vim/vimrc
call pathogen#infect()
syntax on
filetype plugin indent on
EOF

puppet@puppet-master ~ $ cd ~/.vim/bundle
puppet@puppet-master ~/.vim/bundle $ git clone https://github.com/rodjek/vim-puppet.git
puppet@puppet-master ~/.vim/bundle $ git clone https://github.com/godlygeek/tabular.git
puppet@puppet-master ~/.vim/bundle $ git clone https://github.com/hjpbarcelos/vim-yaml

Theoretisch könnte man auch einfach das $HOME von dem User "puppet" nach /opt/puppetlabs/environments/ ändern, aber das könnte unter Umständen andere Probleme nach sich ziehen, daher die Kombination mit Softlinks.

PuppetMaster

Ein wichtiger Grund weshalb wir gleich auf Puppet4 setzen ist der, dass sich insbesondere bei Hiera einiges getan hat, vor allem würde es Minimum ein Puppet 3.7 (glaube ich) benötigen, damit die essentiellen Dinge funktionieren.

Da bei Debian Jessie Puppet 3.7 enthalten ist, verwenden wir die Puppetlabs Pakete. Wir benötigen hierfür das PC1 Repository

Debian-term.png
root@puppet-master ~ # wget http://apt.puppetlabs.com/puppetlabs-release-pc1-jessie.deb; dpkg -i puppetlabs-release-pc1-jessie.deb && apt-get update

Puppet Server

Für den Puppetserver benötigt es nicht sonderlich viel:

Debian-term.png
root@puppet-master ~ # aptitude install puppetserver

Des weiteren muss DNS funktionieren, damit die IP Adressen jeweils auflösbar sind. Wer keinen DNS Server aufsetzen möchte oder kann, greift am Besten zu dnsmasq und pflegt die /etc/hosts.

Kommt nun die Basiskonfiguration, die alles notwendige enthält:

  • /etc/puppetlabs/puppet/puppet.conf:
Ascii.png
[master]
  vardir = /opt/puppetlabs/server/data/puppetserver
  logdir = /var/log/puppetlabs/puppetserver
  rundir = /var/run/puppetlabs/puppetserver
  pidfile = /var/run/puppetlabs/puppetserver/puppetserver.pid
  codedir = /etc/puppetlabs/code
  # storeconfigs = true
  # storeconfigs_backend = puppetdb
  hiera_config = /etc/puppetlabs/puppet/hiera.yaml
  environmentpath = /opt/puppetlabs/environments

[main]
  logdir = /var/log/puppetlabs/puppet
  ssldir = /opt/puppetlabs/puppet/ssl
  rundir = /var/run/puppetlabs
  environmentpath = /opt/puppetlabs/environments
  # http_proxy_host = 1.2.3.4
  # http_proxy_port = 3128

[agent]
  report = true
  ca_server = puppet-master.4lin.net
  server = puppet-master.4lin.net
  environment = test
  • /etc/default/puppetserver:

In dieser Datei wird unter anderem definiert, wie viel Ram sich der Java Prozess nehmen darf. Standard sind 256MB.

Ascii.png
[...]
JAVA_ARGS="-Xms2g -Xmx2g -XX:MaxPermSize=256m"
[...]

Auf meiner Test Instanz habe ich den Wert auf "128m" gesetzt und die VM selbst hat 4GB Ram, um den Puppetserver erfolgreich starten zu können.

Im Anschluss kann der Puppetserver gestartet werden. Der Dienst benötigt meist etwas Zeit, bis dieser gestartet ist. Also Geduld haben und ein Blick in die Logs werfen.

Eventuell kann es sein, dass der Puppetserver sich kein zweites Mal starten lässt, wegen fehlender Berechtigungen für das SSL Verzeichnis und dem User "puppet". Das lässt sich schnell korrigieren:

Debian-term.png
root@puppet-master ~ # chown puppet: -R /opt/puppetlabs/puppet/ssl/c*
root@puppet-master ~ # chown puppet: -R /opt/puppetlabs/puppet/ssl/p*

PuppetDB

Als nächstes benötigen wir PostgreSQL für die Puppetdatenbank "puppetdb". Der Puppetserver dient nur dafür um die Ressourcen bereitzustellen, die dann von dem Puppet-Agent abgerufen wird. Die Eigenschaften (Facts) des Hosts die der Agent auswertet, wird nirgends zentral gespeichert. Genau das ist die Aufgabe vom "puppetdb". Alle Facts werden vom Puppetserver an Puppetdb weitergegeben und liegen dann dort zentral vor. Damit ist es dann möglich mittels "@@" auf diese Informationen von jedem Puppet-Agent zu zugreifen. Leider unterstützt PuppetDB lediglich HSQL (nicht mehr unterstützt -> veraltet) und Postgresql. MySQL ist damit außen vor.

Debian-term.png
root@puppet-master ~ # aptitude install postgresql postgresql-contrib

Folgt die Basiskonfiguration für PostgreSQL:

Debian-term.png
root@puppet-master ~ # su - postgres
postgres@puppet-master ~ $ createuser -DRSP puppetdb
postgres@puppet-master ~ $ createdb -E UTF8 -O puppetdb puppetdb
postgres@puppet-master ~ $ exit

Gibt es eine Fehlermeldung...:

createdb: database creation failed: ERROR:  new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)
HINT:  Use the same encoding as in the template database, or use template0 as template.

... muss zuvor das Template1 mit UTF8 erstellt werden:

Debian-term.png
root@puppet-master ~ # su - postgres
postgres@puppet-master ~ $ psql
postgres=# UPDATE pg_database SET datistemplate = FALSE WHERE datname = 'template1';
postgres=# DROP DATABASE template1;
postgres=# CREATE DATABASE template1 WITH TEMPLATE = template0 ENCODING = 'UNICODE';
postgres=# UPDATE pg_database SET datistemplate = TRUE WHERE datname = 'template1';
postgres=# \c template1
You are now connected to database "template1" as user "postgres".
template1=# VACUUM FREEZE;
template1-# \q

Danach kann die Datenbank "puppetdb" wie oben gezeigt, erstellt werden. Nach der Datenbank wird als nächstes eine Erweiterung hinzugefügt, welche sich im jeweiligen "postgresql-contrib-x.y" Paket verbirgt:

Debian-term.png
root@puppet-master ~ # su - postgres
postgres@puppet-master ~ $ psql puppetdb -c 'create extension pg_trgm'
postgres@puppet-master ~ $ exit

Fehlt noch die Authentifizierung:

  • /etc/postgresql/9.4/main/pg_hba.conf:
Ascii.png
# Debian Standard
local   all             postgres                                peer
local   all             all                                     peer
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5

# Neu
local   all             all                                     md5

Die oberen Zeilen sind bereits vorhanden, die "local" Zeile wurde hinzugefügt.

Debian-term.png
root@puppet-master ~ # service postgresql restart
root@puppet-master ~ # psql -h localhost puppetdb puppetdb
Password for user puppetdb: 
psql (9.4.10)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

puppetdb=> \q

Damit ist der PostgreSQL Teil erledigt, folgt PuppetDB selbst:

Debian-term.png
root@puppet-master ~ # aptitude install puppetdb puppetdb-termini

Bei der Ersteinrichtung werden die SSL Zertifikate die zuvor von der Installation von Puppetserver bzw. puppet-agent erstellt worden sind, nach /etc/puppetlabs/puppetdb/ssl kopiert. Wurde zuerst puppetdb installiert oder eine neue CA aufgesetzt, kann das Setup mittels puppetdb ssl-setup erneut aufgerufen werden.

PuppetDB befüllt die Datenbank automatisch, es müssen lediglich die Daten für die Authentifizierung hinterlegt werden. Diese befinden sich in /etc/puppetlabs/puppetdb/database.ini

  • /etc/puppetlabs/puppetdb/database.ini:
Ascii.png
[database]
classname = org.postgresql.Driver
subprotocol = postgresql
subname = //localhost:5432/puppetdb
username = puppetdb
password = neuroses-clap-latchet-manasses
gc-interval = 60
log-slow-statements = 10
Debian-term.png
root@puppet-master ~ # service puppetdb start

Der Start dauert auch hier ein wenig ...

Nun kommt der letzte Teil für PuppetDB. Wir müssen dem Puppetserver noch mitteilen, dass die Daten an PuppetDB übergeben werden sollen. Dazu müssen die passenden Zeilen in der puppet.conf hinterlegt werden. Wurde die Konfiguration von "oben" verwendet, reicht es die '#' zu entfernen:

  • /etc/puppetlabs/puppet/puppet.conf:
Ascii.png
[...]
[Master]
  storeconfigs = true
  storeconfigs_backend = puppetdb

Wird nun der Puppetserver neugestartet, werden alle Tabelle angelegt und mit ersten Informationen gefüttert. Das reicht allerdings noch nicht ganz, sondern wir benötigen dann noch die puppetdb.conf, welche in der Regel noch nicht existiert:

  • /etc/puppetlabs/puppet/puppetdb.conf:
Ascii.png
[main]
server_urls = https://puppet-master.4lin.net:8081

Hiera

Normalerweise würde man nun damit beginnen, die Manifests für Module (Apt/Webserver...) anzulegen um diese dann für Nodes zu verwenden. Das Problem: schon bei nur wenigen Nodes (aka Hosts) würden früher oder später Zeilen doppelt geschrieben werden müssen, weil es Ausnahmen gibt. Zum Beispiel soll der Webserver den internen NTP Server verwenden, der NTP Server selbst jedoch von pool.ntp.org. Das wäre an dieser Stelle noch "einfach" zu lösen, doch je länger eine Puppet Infrastruktur "lebt", desto komplizierter wird es, Ausnahmen zu erstellen, ohne ganze Ketten von Profilen, Modulen und Nodes umzuschreiben. Diesem Problem soll Hiera entgegenwirken, in dem Redundanzen komplett vermieden werden, oder zumindest stark reduziert werden können. Es werden nur noch Module gepflegt mit Eigenschaften, die Attribute kommen jedoch aus separaten aus einer anderen Quelle. Das Schöne: In einer perfekten Hiera Welt benötigt es keine Node Manifeste und für Abweichungen nur eine Zeile. Genug geschwafelt.

hiera.yaml

Zuerst muss definiert werden, wo sich die hiera.yaml Datei befindet. Das wiederum steht in der puppet.conf

  • /etc/puppetlabs/puppet/puppet.conf:
Ascii.png
...
[Master]
  ...
  hiera_config = /etc/puppetlabs/puppet/hiera.yaml

Der Inhalt dieser Datei kann wie im folgendem Aussehen:


  • /etc/puppetlabs/puppet/hiera.yaml:
Ascii.png
---
:merge_behavior: deeper
:backends:
  - yaml
  - eyaml
  - json

:yaml:
  :datadir: "/opt/puppetlabs/environments/%{::environment}/hieradata"
:json:
  :datadir: "/opt/puppetlabs/environments/%{::environment}/hieradata"
:eyaml:
  :datadir: "/opt/puppetlabs/environments/%{::environment}/hieradata"
  :pkcs7_private_key: "/opt/puppetlabs/environments/%{::environment}/hieradata/keys/private_key.pkcs7.pem"
  :pkcs7_private_key: "/opt/puppetlabs/environments/%{::environment}/hieradata/keys/public_key.pkcs7.pem"

:hierarchy:
  - "role/%{::role}"
  - "node/%{::fqdn}"
  - "profile/%{::profile}"
  - "virtual/%{::is_virtual}"
  - "common"
  - "monitoring"

Dies ist bereits eine Variante, wie wir sie für Icinga2 benötigen. Die monitoring(.yaml) am Ende enthält Werte, die in verschiedenen Modulen wiederverwendet werden. Es gibt eine Vielzahl von weiteren Kombinationen, die für dieses Beispiel zu weit weg führen würden. Es sind in jedem Fall zwei Dinge zu beachten:

  1. Es wird hauptsächlich Yaml verwendet, was bedeutet, dass auf die Einrückung geachtet wird, also auf die Anzahl der Leerzeichen bzw. Tabulator Stops.
  2.  %{::fqdn} ist der Verweis auf ein Fakt, welches beim Aufruf des Puppet Agents auf dem Host bekannt sein muss:
Debian-term.png
root@puppet-master ~ # facter -p fqdn
puppet-master.4lin.net

Fakten die nicht vorhanden sind, müssen zuvor erstellt und auf den Nodes eingerichtet werden:

Debian-term.png
root@puppet-master ~ # facter -p role

Eine weitere Besonderheit ist "Eyaml". Eyaml bietet die Möglichkeit sensitive Informationen via Gnupg zu verschlüsseln. Was verschlüsselt werden soll, ist dabei unerheblich. Es können einfach nur Passwörter sein, oder auch Zertifikate.

Debian-term.png
root@puppet-master ~ # aptitude install hiera-eyaml
root@puppet-master ~ # su - puppet
puppet@puppet-master ~ $ puppetserver gem install hiera-eyaml

In diesem Beispiel, haben wir drei Umgebungen:

  • live - Für die produktiven Server (kann man auch production nennen)
  • dev - Für Server in der Entwicklung
  • test - Für Testzwecke

Für den Anfang konzentrieren wir uns auf "test". Für Eyaml erstellen wir also passende Ordner und legen dort die Schlüssel ab:

Debian-term.png
root@puppet-master ~ # su - puppet
puppet@puppet-master ~ $ cd  /opt/puppetlabs/environments/
puppet@puppet-master /opt/puppetlabs/environments $ mkdir -p test/hieradata && cd test/hieradata
puppet@puppet-master /opt/puppetlabs/environments/test/hieradata $ eyaml createkeys
puppet@puppet-master /opt/puppetlabs/environments/test/hieradata $ chmod -R 0500 keys/
puppet@puppet-master /opt/puppetlabs/environments/test/hieradata $ chmod 0400 keys/*.pem

Damit haben wir die Schlüssel für eyaml erstellt und Dank der hiera.yaml weiß Puppet auch, wo sie zu finden sind.

Ein kurzer Test:

Debian-term.png
root@puppet-master ~ # su - puppet
puppet@puppet-master /opt/puppetlabs/server/data/puppetserver/puppet/test/hieradata $ eyaml encrypt -s 'Spinat ist grün'

Eyaml spuckt dann zwei Variationen vom verschlüsselten Text aus. Hat es nicht geklappt, liegt es sehr wahrscheinlich daran, dass man sich im falschem Verzeichnis befindet, sodass die Schlüssel nicht gefunden werden.

Ein weiteres Beispiel:

Debian-term.png
root@puppet-master ~ # su - puppet
puppet@puppet-master /opt/puppetlabs/server/data/puppetserver/puppet/test/hieradata $ eyaml edit common.eyaml
Ascii.png
---
klartext: kann man lesen

spinat::ist:: DEC::PKCS7[grün]!

Wurde der Text gespeichert und der Editor beendet, wird der Inhalt von spinat::ist:: verschlüsselt, der obere Teil jedoch nicht. Einfach mal einen Blick in die Datei mit "cat" werfen ;-). Ein wichtiger Hinweis an dieser Stelle: Eyaml fügt IDs ein, zum Beispiel: DEC(1). Diese darf man nicht kopieren, sondern löscht sie beim Kopieren der Zeilen:

  • FALSCH:
spinat::ist:: DEC(1)::PKCS7[grün]!
spargel::ist:: DEC(1)::PKCS7[weiß]!
  • RICHTIG:
spinat::ist:: DEC(1)::PKCS7[grün]!
spargel::ist:: DEC::PKCS7[weiß]!

Eyaml fügt die IDs dann automatisch ein.

Basis

Für die ersten Schritte verwenden wir die common.yaml und lassen die /etc/apt/sources.list von Puppet verwalten. Dazu installieren wir das puppetlabs-apt Modul und erstellen die Grundstruktur. Damit die Übersicht nicht zu sehr leidet, lasse ich den Arbeitspfad ($PWD) in den Shells weg. Der Hauptarbeitsordner ist: /opt/puppetlabs/server/data/puppetserver/puppet/test/

Debian-term.png
puppet@puppet-master ~ # puppet module install puppetlabs-apt

Als nächstes muss die Umgebung (environment) "test" vorbereitet werden:

  • /opt/puppetlabs/server/data/puppetserver/puppet/test/environment.conf:
Ascii.png
modulepath = /opt/puppetlabs/environments/test/modules:/opt/puppetlabs/server/data/puppetserver/.puppetlabs/etc/code/modules

Die environment.conf kann unter anderem enthalten, in welchen Pfaden nach Modulen gesucht werden soll. Dabei lässt sich ebenfalls eine Hierarchie aufbauen, sodass von test auch auf Module in dev oder live zugegriffen werden kann. Bei älteren Puppet Version (<3.8) muss unter anderem "parser = future" enthalten sein, da andernfalls verschiedene neue Puppet Aufrufe nicht klappen. Beim aktuellen Puppet ist das der neue Standard.

  • /opt/puppetlabs/server/data/puppetserver/puppet/test/manifests/site.pp:
Ascii.png
hiera_include('classes','')
node default {
}

Da wir auf Node Manifests verzichten, lassen wir über die site.pp die Klassen via Hiera einbinden. Damit lässt sich zum Beispiel das Puppet Modul über die Klasse 'apt' aufrufen.

Und nun folgt das erste Beispiel über die common.yaml

  • /opt/puppetlabs/server/data/puppetserver/puppet/test/hieradata/common.yaml:
Ascii.png
---
classes:
  - 'apt'
apt::purge:
  sources.list.d: true
  sources.list: true
apt::sources:
  'debian_stable':
    comment: 'This is the Debian stable mirror'
    location: 'http://ftp.debian.org/debian'
    release: 'jessie'
    repos: 'main contrib non-free'
    include:
      src: false
      deb: true
  'debian_security':
    comment: 'This is the Debian stable security mirror'
    location: 'http://security.debian.org'
    release: 'jessie/updates'
    repos: 'main contrib'
    include:
      src: false
      deb: true
  'debian_updates':
    comment: 'This is the Debian stable update mirror'
    location: 'http://ftp.debian.org/debian'
    release: 'jessie-updates'
    repos: 'main contrib'
    include:
      src: false
      deb: true
  'puppetlabs':
    location: 'http://apt.puppetlabs.com'
    repos: 'PC1'
    release: 'jessie'
    key:
      id: '6F6B15509CF8E59E6E469F327F438280EF8D349F'
      server: 'pgp.mit.edu'

Die common.yaml wird als (vor-)letztes aufgerufen und bedarf keiner weiteren facts. Im späteren Verlauf würde man die Apt Quellen nicht unbedingt in der common.yaml pflegen, sondern eher unter hieradata/os/debian.yaml (Dank {os/%{::family} wäre das möglich) . In unserem Fall lässt sich aber so zügig testen, ob Puppet an sich funktioniert.

Auch nochmal der Hinweis an dieser stelle: Es ist unbedingt darauf zu achten, dass die Yaml Syntax eingehalten wird.

Beim Aufruf von puppet agent -t sollten zwei Dinge passieren:

  1. Puppet löscht den Inhalt von sources.list und alles in sources.list.d/
  2. Puppet legt für jedes Repository eine Datei mit passendem Namen an
Debian-term.png
puppet agent -t
Info: Using configured environment 'test'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Loading facts
Info: Caching catalog for puppet-master.4lin.net
Info: Applying configuration version '1489700295'
Notice: /Stage[main]/Apt/File[sources.list]/content:
--- /etc/apt/sources.list	2017-03-16 21:38:03.353839833 +0000
+++ /tmp/puppet-file20170316-7253-muxbvl	2017-03-16 21:38:17.169393864 +0000
@@ -1,21 +1 @@
 # Repos managed by puppet.
-# Puppetlabs PC1 jessie Repository
[...]

Es wird die Umgebung "test" verwendet, da dies für den Puppet Agent so in der /etc/puppetlabs/puppet/puppet.conf definiert wurde. Später sollte man auf dem Puppet Server natürlich auf "live" oder "production" übergehen.

Sollte etwas nicht klappen, so empfehle ich immer Syntax Fehler gezielt einzustreuen, um zu sehen, ob Puppet die Dateien überhaupt aufruft. Es reicht eine Klammer zu entfernen, in der site.pp oder dann danach in der common.yaml ein Hochkomma zu entfernen. Stolpert dann der Puppet Agent über diese Syntaxfehler, ist automatisch klar, dass der Fehler nicht an der Umgebung liegt, sondern eher an den Klassen, bzw. deren Verwendung.

Hat alles geklappt, so kann man nun die zweite VM/Container Instanz hochfahren und dort den Puppet Agent einrichten.

Puppet Agent - Icinga Master

Hier wird noch nicht so viel passieren, nur der Puppet Agent muss eingerichtet werden. Dazu benötigen wir:

  • Ein(e) VM/LXC Container
  • Puppet Apt Quellen für PC1
  • /etc/apt/sources.list:
Ascii.png
deb http://apt.puppetlabs.com jessie PC1
Debian-term.png
# aptitude update
# aptitude install puppetlabs-release-pc1 lsb-release
# aptitude update
# aptitude install puppet-agent

Das Paket puppetlabs-release-pc1 benötigen wir, damit der Apt Schlüssel für das Apt Repository vorhanden ist und Apt nicht meckert. Außerdem wird die $PATH Variable um /opt/puppetlabs/bin/ erweitert. lsb-release ist nicht immer installiert, wird aber vom Puppet Agent benötigt.

Ist der Puppet Agent auf dem System, sollte man sich neu einloggen, damit wir die neue $PATH Variable bekommen und gesetzt wird.

Damit kann es an das Konfigurieren gehen:


  • /etc/puppetlabs/puppet/puppet.conf:
Ascii.png
[agent]
  report = true
  ca_server = puppet-master.4lin.net
  server = puppet-master.4lin.net
  environment = test

Das genügt bereits für den Anfang. Wichtig ist noch, dass der FQDN stimmt, andernfalls würde gleich das Zertifikat falsch erzeugt werden. Die /etc/resolv.conf sollte daher geprüft werden.

Debian-term.png
root@icinga-master-01 ~ # puppet  agent -t --noop
Info: Creating a new SSL key for icinga-master-01.4lin.net
Info: Caching certificate for ca
Info: csr_attributes file loading from /etc/puppetlabs/puppet/csr_attributes.yaml
Info: Creating a new SSL certificate request for icinga-master-01.4lin.net
Info: Certificate Request fingerprint (SHA256): 0B:FA:13:97:A1:A5:FC:EA:8E:93:31:9E:53:4E:7E:48:52:78:A6:16:50:95:2F:9B:7D:11:09:33:3F:50:96:18
Info: Caching certificate for ca
Exiting; no certificate found and waitforcert is disabled

Auf der Puppet-master Seite, müssen wir das Zertifikat nun unterschreiben lassen:

  • Auf Puppet Master:
Debian-term.png
root@puppet-master ~ # puppet cert sign icinga-master-01.4lin.net

Signing Certificate Request for:
  "icinga-master-01.4lin.net" (SHA256) 0B:FA:13:97:A1:A5:FC:EA:8E:93:31:9E:53:4E:7E:48:52:78:A6:16:50:95:2F:9B:7D:11:09:33:3F:50:96:18
Notice: Signed certificate request for icinga-master-01.4lin.net
Notice: Removing file Puppet::SSL::CertificateRequest icinga-master-01.4lin.net at '/opt/puppetlabs/puppet/ssl/ca/requests/icinga-master-01.4lin.net.pem'

root@puppet-master ~ # puppet cert list --all
+ "icinga-master.4lin.net" (SHA256) DB:45:97:7E:71:2E:CA:2B:EE:F3:F9:65:AB:45:52:BD:BB:D9:F6:EA:52:3A:E2:7A:52:8C:B5:34:0D:B1:6F:A2
+ "puppet-master.4lin.net" (SHA256) B9:AA:DE:37:43:91:AA:B9:9F:1E:6E:1B:5C:29:8A:BA:9D:E6:2F:3D:C3:79:0F:69:77:67:FF:32:9D:67:24:35 (alt names: "DNS:puppet", "DNS:puppet-master.4lin.net")

Damit können wir nun wieder auf icinga-master-01 den Agent erneut starten:

  • Auf Icinga Master:
Debian-term.png
root@icinga-master-01 ~ # puppet  agent -t
Info: Caching certificate for icinga-master-01.4lin.net
Info: Caching certificate for icinga-master-01.4lin.net
Info: Using configured environment 'test'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Loading facts
Info: Caching catalog for icinga-master-01.4lin.net
Info: Applying configuration version '1489774490'
Notice: /Stage[main]/Apt/Apt::Source[debian_stable]/Apt::Setting[list-debian_stable]/File[/etc/apt/sources.list.d/debian_stable.list]/ensure: defined content as '{md5}7b431f7079129d235f07fee6d842172a'
[...]

Damit haben wir nun sowohl einen Puppet Master und eine Node die wir mir dem Puppet Agent Konfigurieren lassen können. Dadurch dass wir nur die common.yaml aktuell verwenden, werden auch auf dieser VM nur die Apt Quellen angetastet.

Rollen

Wie oben bereits angedeutet, wollen wir Rollen und Profile (1:n) verwenden. Da es das "Fact" "role" nicht gibt (
$ facter -p role
), müssen wir dieses Faktum erzeugen.

Dazu erstellen wir eine "Modulstruktur" unterhalb von test/ die das Faktum "role" auf die Nodes bringt. Das klappt allerdings nur, wenn wir die Funktion "pluginsync = true" auf beiden Seiten aktivieren.

Hinweis
Hinweis

pluginsync = true sorgt ab der aktuellsten Version (4.x) / Puppet Agent 1.9.3 für: Warning: Setting 'pluginsync' is deprecated. Ticket: https://tickets.puppetlabs.com/browse/PUP-5708. Sobald ich die Alternative kenne, werden die Seiten angepasst.

  • Puppet-Master: /etc/puppetlabs/puppet/puppet.conf:
  • Icinga-Master-01: /etc/puppetlabs/puppet/puppet.conf:
Ascii.png
[main]
[...]
  pluginsync = true

Danach den Puppetserver Dienst neustarten:

Debian-term.png
root@puppet-master ~ # service puppetserver restart

Auf dem icinga-master reicht es den Agent einfach nur aufzurufen, aber das kann später erfolgen.

Als nächsten ersten wir die "role" Modulstruktur:

Debian-term.png
puppet@puppet-master ~ test/ $ mkdir -p modules/role/lib/facter/
puppet@puppet-master ~ test/ $ mkdir -p modules/role/manifests

Und legen im lib Ordner zwei Dateien an:

  • Icinga-Master: test/modules/role/lib/facter/role.rb:


Ascii.png
# https://rnelson0.com/2014/07/14/intro-to-roles-and-profiles-with-puppet-and-hiera/


# https://rnelson0.com/2014/07/14/intro-to-roles-and-profiles-with-puppet-and-hiera/
# ([a-z]+)[0-9]+, i.e. www01 or logger22 have a role of www or logger
if Facter.value(:hostname) =~ /^([a-z]+)[0-9]+$/
  Facter.add('role') do
  setcode do
    $1
  end
  end

# ([a-z]+), i.e. www or logger have a role of www or logger
elsif Facter.value(:hostname) =~ /^([a-z]+)$/
  Facter.add('role') do
  setcode do
    $1
  end
  end

# ([a-z]+), i.e. www-01 or logger-01 have a role of www or logger
elsif Facter.value(:hostname) =~ /^([a-z]+)-[0-9]+$/
  Facter.add('role') do
  setcode do
    $1
  end
  end

# ([a-z]+), i.e. mon-master-01 or logger-slave-02 have a role of www-master or logger-slave
elsif Facter.value(:hostname) =~ /^([a-z]+)-([a-z]+)-[0-9]+$/
  Facter.add('role') do
  setcode do
  $1+'-'+$2
  end
  end

# Set to hostname if no patterns match
else
  Facter.add('role') do
    setcode do
      'default'
    end
  end
end

Diese Version ist gegenüber der Originalversion etwas erweitert worden, damit auch Namen klappen wie "www-01", oder auch "icinga-master-01". Aus dem Hostname wird dann die Rolle "www", oder "icinga-master".


  • Icinga-Master: test/modules/role/lib/facter/puppet_classes.rb:
Ascii.png
classes_file  = '/opt/puppetlabs/puppet/cache/state/classes.txt'
classes_hash  = {}
modules_array = []
File.foreach(classes_file) do |l|
  modules_array << l.chomp.gsub(/::.*/, '')
end
modules_array = modules_array.sort.uniq
modules_array.each do |i|
  classes_array = []
  classes_array << i
  File.foreach(classes_file) do |l|
    classes_array << l.chomp if l =~ /^#{i}/
      classes_array = classes_array.sort.uniq
  end
  classes_hash[i] = classes_array
end

Facter.add(
  :puppet_modules) do
  confine :kernel => 'Linux'
  setcode do
    modules_array.sort.uniq.join(', ').to_s
  end
  end
Facter.add(
  :puppet_classes) do
  confine :kernel => 'Linux'
  setcode do
    classes_hash.map { |_k, v| v }.sort.uniq.join(', ').to_s
  end
  end

Diese Datei kann dafür genutzt werden, aus den zugeordneten / verwendeten Puppet Klassen die beiden neuen Fakten "puppet_classes" und "puppet_modules" zu erstellen, die sich dann für die Profile nutzen lassen. Wird also einer Node die Klasse "nginx" zu gewiesen, kann daraus das Profil "webserver" abgeleitet werden. Eventuell muss bei der "puppet_classes.rb" der Pfad zu der "classes.txt" Datei angepasst werden. Dazu einfach mittels "find" im Puppet Ordner suchen lassen.

Profile

Der Profilanteil dieser Anleitung lässt sich mit wenigen Sätzen erklären:

Es geht lediglich darum, Puppet Manifests zu erstellen, die alles notwendige enthalten, um aus einer Node einen Webserver zu erstellen. Die Kunst der Profile besteht darin, soweit wie möglich zu abstrahieren, sodass die Profile keine starren Zuordnungen enthalten. Es gibt aber auch Abseits Gründe, warum Profile Sinn ergeben: nicht jedes Puppet Modul bietet die Möglichkeit alles mittels Hiera zu definieren, sondern Profile bieten dann die Schnittstelle zwischen Hiera und dem Modul. Ein sehr bekanntes Beispiel ist das Apache Modul, bei dem VirtualHost zwar in Hiera definiert werden, aber erst die entsprechenden Manifest Zeilen es in die Tat umsetzen. Dazu dann später mehr.

Icinga-Master

Um mir die Tipparbeit zu erleichtern, gelten folgende Konventionen:

  • Puppet Dateien werden immer als User "puppet" editiert
  • Ein sehr großer Teil entstammt einem fertigen Testsystem, daher wird vieles per Copy/Paste übernommen, sofern sinnvoll.
  • Ich versuche die Profile so aufzulisten, sodass der Puppet Agent nach jedem Profil Abschnitt sauber durchlaufen kann.
  • Manifest/Konfigurations Dateien enthalten immer einen Kopf, damit klar wird, durch welches Modul eine Veränderung vorgenommen wurde
  • Dateien sollten definitiv per Git gepflegt werden
  • Der Ort ist, wenn nicht anders definiert: /opt/puppetlabs/environments/test/
  • Die Unterordner sind:
    • hieradata/ -> Alles was mit Yaml zu tun hat
    • modules/ -> Wo alle eigenen Module liegen
    • modules/role -> Rollenmanifests
    • modules/profile -> Profilmanifests
  • Es gibt weniger zu lesen, sondern mehr zu tippen / kopieren :-)
  • Es ist nicht perfekt, aber nachvollziehbar und Anregungen nehme ich sehr sehr gern an
  • Der Icinga-master-01 bildet sozusagen alles ab, was es im Moment gibt, aber eventuell gibt es sinnvollere Wege ;-)

Rolle/Profile

  • Der Icinga-Master hat die Rolle: role::icinga2_server
  • Die Profile sind:
    • profile::base
    • profile::mysql::server
    • profile::mysql::client
    • profile::icinga2::server
    • profile::apache2::server
    • profile::apache2::php
    • profile::icinga2::web
    • profile::graphite::graphite
    • profile::grafana::grafana
    • profile::influxdb::influxdb
    • profile::monitoring

Profile base

  • Enthält alles, was auf allen Nodes konfiguriert werden soll
  • Puppet Modul für Sudo und NTP installieren:
Debian-term.png
# puppet module install saz-sudo
# puppet module install puppetlabs-ntp
# puppet module install puppetlabs-concat
# puppet module install camptocamp-augeas
# puppet module install herculesteam-augeasproviders_shellvar
  • modules/profile/manifests/base.pp:
Ascii.png
# source modules/profile/manifests/base.pp
# == Class: profile::base
class profile::base {

  contain 'profile::base::apt'
  contain 'profile::base::sudo'

  class { '::ntp':
    servers => [ 'ptbtime1.ptb.de', 'ptbtime2.ptb.de', '1.rhel.pool.ntp.org'],
  }
  @@host { $::fqdn:
    ensure       => present,
    ip           => $::ipaddress,
    host_aliases => $::hostname,
  }

  file { '/backup':
    ensure => directory,
    owner  => root,
    group  => root,
    mode   => '0750',
  }

  package { ['git', 'htop', 'screen' ]:
    ensure =>  $ensure,
  }

  Host <<| |>>
}
  • modules/profile/manifests/base/apt.pp:
Ascii.png
# source modules/profile/manifests/base/apt.pp
# == Class: profile::base::apt
class profile::base::apt {
  # include base aptitude class
  include ::apt

  package { ['apt-transport-https' ]:
    ensure =>  $ensure,
  }

  # get keys from hiera and create them
  $keys = hiera_hash('profile::apt::keys', undef)
  if ($keys) {
    create_resources('apt::key', $keys)
  }

  # get repos from hiera and create them
  $repositories = hiera_hash('profile::apt::repositories', undef)
  if ($repositories) {
    create_resources('apt::source', $repositories)
  }
}
  • modules/profile/manifests/base/sudo.pp:
Ascii.png
# source modules/profile/manifests/base/sudo.pp
# == Class: profile::base::sudo
class profile::base::sudo {
  include ::sudo
  include ::sudo::configs
}
  • hieradata/common.yaml:
Ascii.png
---
classes:
 - 'profile::base'
 - 'profile::base::sudo'

########### Apt settings #############
apt::purge:
  sources.list.d: true
  sources.list: true

########### Apt sources #############
apt::sources:
  'debian_stable':
    comment: 'hieradata/common.yaml'
    location: 'http://ftp.debian.org/debian'
    release: 'jessie'
    repos: 'main contrib non-free'
    include:
      src: false
      deb: true
  'debian_security':
    comment: 'hieradata/common.yaml'
    location: 'http://security.debian.org'
    release: 'jessie/updates'
    repos: 'main contrib'
    include:
      src: false
      deb: true
  'debian_updates':
    comment: 'hieradata/common.yaml'
    location: 'http://ftp.debian.org/debian'
    release: 'jessie-updates'
    repos: 'main contrib'
    include:
      src: false
      deb: true
  'debian_backports':
    comment: 'hieradata/common.yaml'
    location: 'http://httpredir.debian.org/debian'
    release: 'jessie-backports'
    repos: 'main contrib'
    include:
      src: false
      deb: true
  'puppetlabs':
    comment: 'hieradata/common.yaml'
    location: 'http://apt.puppetlabs.com'
    repos: 'PC1'
    release: 'jessie'
    key:
      id: '6F6B15509CF8E59E6E469F327F438280EF8D349F'
      server: 'pgp.mit.edu'
  'debmon':
    comment: 'hieradata/common.yaml'
    location: 'http://debmon.org/debmon'
    release: 'debmon-jessie'
    repos: 'main'
    include:
      src: false
      deb: true
    key:
      id: '7E55BD75930BB3674BFD6582DC0EE15A29D662D2'
      source: 'http://debmon.org/debmon/repo.key'
  'icinga':
    comment: 'hieradata/common.yaml'
    location: 'http://packages.icinga.org/debian'
    release: 'icinga-jessie'
    repos: 'main'
    key:
      id: 'F51A91A5EE001AA5D77D53C4C6E319C334410682'
      source: 'http://packages.icinga.com/icinga.key'
    include:
      src: false
      deb: true


  • hieradata/monitoring.yaml:
Ascii.png
---
########### Sudo settings #############

sudo::configs:
    'nagios':
        'content'   : "%nagios ALL=(ALL) NOPASSWD: ALL"
        'priority'  : 10

############ Icinga2 common vars ############
icinga_vars:
  os: Linux
  client_endpoint: "%{::fqdn}"
  domain: "%{::domain}"
  hostname: "%{::hostname}"
  address: "%{::ipaddress}"
  cores: "%{::processorcount}"
  virtual_machine: "%{::is_virtual}"
  distro: "%{::operatingsystem}"
  disks:
    'disk /':
      disk_partitions: '/'

############ Icinga2 common object settings ############
icinga2::object::host:
  "%{::fqdn}":
    address:  "%{::ipaddress}"

Mysql Server

Alles rund um den MySQL / Mariadb


Debian-term.png
# puppet module install puppetlabs-mysql
  • modules/profile/manifests/mysql/base.pp:
Ascii.png
# source modules/profile/manifests/mysql/base.pp
# == Class: profile::mysql::server::base
class profile::mysql::server {

}
  • modules/profile/manifests/mysql/server.pp:
Ascii.png
# source modules/profile/manifests/mysql/server.pp
# == Class: profile::mysql::server::base
class profile::mysql::server {
  include ::mysql::server
  include ::mysql::server::backup
}
Backup / root Passwort

Alles Passwörter werden verschlüsselt über eyaml abgelegt. Wichtig ist die Endung "*.eyaml" und sollte nicht mit der *.yaml verwechselt werden:

Debian-term.png
$ cd hieradata/
$ touch role/icinga-master.eyaml
$ eyaml edit role/icinga-master.eyaml
  • role/icinga-master.eyaml:
Ascii.png
mysql::server::root_password: DEC::PKCS7[dbrootsecret]!
mysql::server::backup::backuppassword: DEC::PKCS7[dbbackupsecret]!

Beim Aufruf vom Puppet Agent werden sowohl die Werte aus der Yaml, als auch auch der Eyaml Datei zusammengeführt.

Im Falle vom MySQL Puppet Modul werden die Daten auch in /root/.my.cnf abgelegt.

MySQL Client

  • modules/profile/manifests/mysql/client.pp:
Ascii.png
# source modules/profile/manifests/mysql/client.pp
# == Class: profile::mysql::server::client
class profile::mysql::client {
  include ::mysql::client
}

Profile der Rolle zuordnen

Nun sind wir an dem Punkt, an dem wir anfangen die Profile der Rolle zuzuordnen. Dazu erstellen wir das Manifest "modules/role/manifests/icinga2_server.pp" und fügen die Profile hinzu. Ich habe schon alle Profile hinzugefügt die noch kommen, aber noch auskommentiert. Lediglich profile::base und MySQL (allerdings nehmen wir MariaDB 10.x) ist soweit benutzbar. Wichtig wäre noch zu wissen, dass die profile::monitoring automatisch über Hiera eingebunden wird, wie dies bei der common der Fall ist. Daher legen wir dort Werte ab, die für mehrere Module genutzt werden (können).

  • modules/role/manifests/icinga2_server.pp:
Ascii.png
# Role icinga2_master
class role::icinga2_server {
  contain profile::base
  contain profile::mysql::server
  contain profile::mysql::client
  # contain profile::icinga2::master
  # contain profile::apache2::server
  # contain profile::apache2::php
  # contain profile::icinga2::web
  # contain profile::graphite::graphite
  # contain profile::grafana::grafana
  # contain profile::influxdb::influxdb
}

Wie bereits angedeutet Werte, die Modul übergreifend sind. Auch hier habe ich wieder kräftig kopiert und es sind einige Werte enthalten, die in dieser Anleitung (noch) keinen Nutzen haben.

  • hieradata/monitoring.yaml:
Ascii.png
---

##########################
### general settings
###########################

'monitoring::domain': '4lin.net'
'monitoring::admin_email': "monitor@%{hiera('monitoring::domain')}"

#########################
## Firewall settings
########################

'monitoring::ip_whitelist':
  - "%{hiera('monitoring::icinga::ipaddress')}"
  - "%{hiera('monitoring::mysql::ipaddress')}"
  - "%{hiera('monitoring::influxdb::ipaddress')}"
  - "%{hiera('monitoring::puppet::ipaddress')}"

#########################
## SMTP Settings
#########################
'monitoring::smtp::fqdn': "mx-01.%{hiera('monitoring::domain')}"
'monitoring::smtp::ssl_type': 'STARTTLS'
'monitoring::smtp::port': 25

##########################
## icinga related settings
##########################

'monitoring::icinga::fqdn': "mon.%{hiera('monitoring::domain')}"
'monitoring::icinga::mysql_db': 'icinga2_ido_db'
'monitoring::icinga::mysql_user': 'icinga2_ido_db'

'monitoring::icinga::ipaddress': '192.168.43.13'
'monitoring::icinga::influxdb::db' : 'icinga2'
'monitoring::icinga::influxdb::user' : 'icinga2'

##########################
## icingaweb2 related settings
##########################

'monitoring::icingaweb2::mysql_db': 'icingaweb2_db'
'monitoring::icingaweb2::mysql_user': 'icingaweb2_db'
'monitoring::icingaweb2_director::mysql_db': 'icingaweb2_director_db'
'monitoring::icingaweb2_director::mysql_user': 'icingaweb2_director_db'

##############################
#### InfluxDB Details
################################

'monitoring::influxdb::ip':  "graph.%{hiera('monitoring::domain')}"

##############################
### Grafana Details
###############################

'monitoring::grafana::db::host': "%{hiera('monitoring::mysql::ipaddress')}"
'monitoring::grafana::mysql_db': 'grafana_db'
'monitoring::grafana::mysql_user': 'grafana_db'
'monitoring::grafana::user': 'admin'
'monitoring::grafana::url': "graph.%{hiera('monitoring::domain')}"
'monitoring::grafana::influxdb::user': 'admin'
'monitoring::grafana::influxdb::password': 'admin'
'monitoring::grafana::port': 3000
'monitoring::grafana::elasticsearchurl': "127.0.0.1"
'monitoring::grafana::influxdb::ipaddress': "graph.%{hiera('monitoring::domain')}"
'monitoring::grafana::influxdbdatabase': "icinga2"
'monitoring::grafana::install_method': 'repo'
'monitoring::grafana::version': '4.2.0'

In der hieradata/role/icinga-master.yaml kommt nun alles zusammen. Da sie bei mir sehr umfangreich geworden ist, wäre es nicht verkehrt einzelne Bestandteile auszulagern.

  • hieradata/role/icinga-master.yaml:
Ascii.png
# Icinga2 Master Monitoring Node
---
classes:
  - role::icinga2_server

############# MySQL base settings #############

mysql::server::restart                   : 'true'
mysql::server::backup::backupuser        : 'dbbackup'
mysql::server::backup::backupdir         : '/backup/mysql'
mysql::server::backup::backupcompress    : 'true'
mysql::server::backup::backuprotate      : 90
mysql::server::backup::file_per_database : 'true'
mysql::server::backup::time              : ['*', '00']
mysql::server::package_name              : 'mariadb-server-10.0'
mysql::client::package_name              : 'mariadb-client-10.0'

############# MySQL override options #############

mysql::server::override_options :
  'mysqld':
    bind-address             : '0.0.0.0'
    server-id                : 1
    auto-increment-increment : 2
    auto-increment-offset    : 1

############# MySQL user #############

mysql::server::users:
  '@localhost':
    ensure        : 'absent'
  '@mysql':
    ensure        : 'absent'
  'root@127.0.0.1':
    ensure        : 'present'
############# MySQL databases #############

mysql::server::databases:
  test:
    ensure  : 'absent'

Damit haben wir einen funktionierenden MySQL Server, mit MariaDB als Backend beim Ausführen des Puppet Agents auf icinga-master-01.

Dann kann es ja mit den anderen Profilen weitergehen :-)

Profil Icinga2

Nachfolgende Zeilen entstammen zu einem sehr großen Anteil von hier: https://github.com/Icinga/puppet-icinga2/tree/master/examples/example3 Ohne diese enorme Hilfe, hätte ich noch ewig benötigt, alles zusammen zu bekommen. Daher mein Dank an https://github.com/kwisatz !

Das icinga2-puppet Modul besorgen wir uns über Puppet:

Debian-term.png
# puppet module install puppet-icinga2
Profil Icinga2 Server

Dieses Profil sorgt für alles Grundlegende, was benötigt wird. Es empfiehlt sich die README durchzulesen. Es wurde lediglich um einige Zeilen erweitert, dazu unten mehr:

  • modules/profile/manifests/icinga2/server.pp:
Ascii.png
class profile::icinga2::server (
  $ido_db_name = hiera('monitoring::icinga::mysql_db')
  $ido_db_user = hiera('monitoring::icinga::mysql_user')
  $ido_db_pass = hiera('monitoring::icinga::mysql_password')
  $ido_db_host = hiera('monitoring::mysql::ipaddress')
) {

  user { 'nagios':
    groups => ssl-cert
  }

  mysql::db { "$ido_db_name":
    user     => "$ido_db_user",
    password => "$ido_db_pass",
    host     => "$ido_db_host",
    grant    => ['ALL'],
    require  => Package['mysql-client'],
  }
  class { '::icinga2': }

  icinga2::object::zone { 'global-templates':
    global => true,
  }

  icinga2::object::zone { 'director-global':
    global => true,
  }

  file { 'icinga2_global_templates':
    path    => '/etc/icinga2/zones.d/global-templates',
    ensure  => directory,
    purge   => true,
    recurse => true,
  }->
  File <<| ensure != 'directory' and tag == 'icinga2::scripts::file' |>> { }

  # Collect all hosts into their respective directories.
  file { 'icinga2_masterzone':
    path    => '/etc/icinga2/zones.d/master',
    ensure  => directory,
    purge   => true,
    recurse => true,
  }->
  file { 'icinga2_hosts':
    path    => '/etc/icinga2/conf.d/hosts',
    ensure  => directory,
    purge   => true,
    recurse => true,
  }->
  Icinga2::Object::Host <<| |>> { }

  # Export master zone and endpoint for all agents to collect
  @@icinga2::object::zone { 'master':
    endpoints => [ "$::fqdn", ],
  }
  @@icinga2::object::endpoint { "$::fqdn":
    host => "$::ipaddress_eth0",
  }

  # Collect and realize all agent zones and endpoints
  Icinga2::Object::Endpoint <<| |>> { }
  Icinga2::Object::Zone <<| |>> { }

  # Collect services and notifications exported on agent nodes
  # (and not created by the Apply Rules included below)
  file { 'icinga2_services':
    path    => '/etc/icinga2/conf.d/services',
    ensure  => directory,
    purge   => true,
    recurse => true,
  }->
  Icinga2::Object::Service <<| |>> { }

  file { 'icinga2_notifications':
    path    => '/etc/icinga2/conf.d/notifications',
    ensure  => directory,
    purge   => true,
    recurse => true,
  }->
  Icinga2::Object::Notification <<| |>> { }

  # Collect check and notification commands that are not created by Apply
  file { 'icinga2_commands':
    path    => '/etc/icinga2/conf.d/commands',
    ensure  => directory,
    purge   => true,
    recurse => true,
  }->
Icinga2::Object::Checkcommand <<| |>> { }->
Icinga2::Object::NotificationCommand <<| |>> { }

# Define apply rules that
# contain profile::icinga2::applyrules

# Create API users from Hiera
# $myIcinga2ApiUser = hiera('icinga2::object::apiuser', {})
# create_resources( 'icinga2::object::apiuser', $myIcinga2ApiUser)

# Create Icinga hosts from Hiera
# $myIcinga2Hosts = hiera('icinga2::object::host', {})
# create_resources( 'icinga2::object::host', $myIcinga2Hosts)

# Create Icinga hostgroups from Hiera
# $myIcinga2HostGroup = hiera('icinga2::object::hostgroup', {})
# create_resources( 'icinga2::object::hostgroup', $myIcinga2HostGroup)

# Create Icinga servicegroups from Hiera
# $myIcinga2ServiceGroup = hiera('icinga2::object::servicegroup', {})
# create_resources( 'icinga2::object::servicegroup', $myIcinga2ServiceGroup)

# Note: these manifests are not included in this example
# contain profile::icinga2::hostgroups
# contain profile::icinga2::users
# contain profile::icinga2::timeperiods
# contain profile::icinga2::notifications
# contain profile::icinga2::checkcommands
}

Die Zeilen mit "$my*" sind eingefügt worden, um in Hiera Ressourcen definieren zu können, für die es keine Modul Hiera spezifischen Objekte gibt. Mittels $myIcinga2Hosts können so für den Icinga-Master Hosts erzeugt werden, die keinen Icinga-Agent haben, z.B: weil sie nur per SNMP erreichbar sind. Dies kann man zwar auch anderweitig lösen, z.B. über die Einbindung weitere Profile, aber in diesem Fall habe ich mich für diesen Weg entschieden. Im Augenblick sind sie noch nicht aktiv, das kommt später.

An dieser Stelle sollte noch kein Puppet Agent ausgeführt werden, denn es fehlt noch so einiges.

  • hieradata/role/icinga-master.yaml:
Ascii.png
############# Icinga2 settings #############

icinga2::constants:
  NodeName: "%{::fqdn}"
  ZoneName: 'master'
  # Change TicketSalt !!
  TicketSalt: '1234567890ß0945766325458937237545934583489502387450854903758972347809348230974wezriuwzeriuwrfoo'

icinga2::features:
  - 'idomysql'
  - 'api'
  - 'checker'
  - 'mainlog'
  - 'statusdata'
  - 'command'

icinga2::manage_database: true
icinga2::restart_cmd: 'service icinga2 reload'
icinga2::plugins:
  - 'nscp'
  - 'plugins'
  - 'plugins-contrib'
  - 'windows-plugins'
  - 'manubulon'

icinga2::feature::idomysql::database: "%{hiera('monitoring::icinga::mysql_db')}"
icinga2::feature::idomysql::user: "%{hiera('monitoring::icinga::mysql_user')}"
icinga2::feature::idomysql::host: "%{hiera('monitoring::mysql::ipaddress')}"
icinga2::feature::idomysql::import_schema: true
icinga2::feature::api::accept_commands: true
icinga2::feature::api::endpoints: {}
icinga2::feature::api::zones: {}
  • Datenbankberechtigung:
  • hieradata/role/icinga-master.yaml:
Ascii.png
mysql::server::grants:
  "icinga2_ido_db@%/icinga2_ido_db.*":
    ensure     : 'present'
    privileges : ['ALL']
    table      : 'icinga2_ido_db.*'
    user       : 'icinga2_ido_db@%'
  • Datenbank anlegen:
  • hieradata/role/icinga-master.yaml:
Ascii.png
mysql::server::databases:
  test:
    ensure  : 'absent'
  icinga2_ido_db:
    ensure  : 'present'
  • Datenbank User anlegen:

Die User Passwörter (nicht das MySql root Passwort!) können leider nicht als Klartext in Hiera abgelegt werden, wie es in einem Manifest der Fall wäre. Das Attribut "password" existiert da leider nicht. Daher gäbe es zwei Möglichkeiten den Benutzer zu erstellen:

  1. Attribute password_hash: Hierbei wird zuvor ein Passwort-Hash erstellt, welches dann als Hiera Attribut eingegeben werden kann. Der Hash sollte dann dennoch über Eyaml verschlüsselt gespeichert werden.
  2. User im Manifest Profil: Eine andere Methode wäre es, den Datenbank User im Profil -- z.B. icinga2_server --- erzeugen zu lassen. Dann könnte das Passwort über Hiera bezogen werden, als Klartext. Das Passwort hätte dann gleich erneut verwendet werden können.

Die Ablage wäre dann natürlich wieder in Eyaml.

Die Erste Methode hat den Nachteil, dass das Passwort zweimal abgelegt werden muss; einmal als Hash, einmal als Klartext. Das Klartextpasswort benötigen für den Icinga2 Daemon. Ein weiterer Umstand ist, dass der komplette User mysql::server::users: Teil in Eyaml liegen muss.

Wie auch immer, ich empfinde die erste Methode als angenehmer, da sie mir flexibler erscheint.

  • MySQL Hash erzeugen:
Debian-term.png
root@icinga-master-01 ~ # mysql -e 'select password("dbidosecret")'
+-------------------------------------------+
| password("dbidosecret")                   |
+-------------------------------------------+
| *994EDBCADFBB7973CC6B1346421BE0FE842F4C7E |
+-------------------------------------------+


  • EYAML:
  • hieradata/role/icinga-master.eyaml:
Ascii.png
############# MySQL user #############

mysql::server::users:
  '@localhost':
    ensure        : 'absent'
  '@mysql':
    ensure        : 'absent'
  'root@127.0.0.1':
    ensure        : 'present'
  'icinga2_ido_db@%':
    ensure        : 'present'
    password_hash : DEC::PKCS7[*994EDBCADFBB7973CC6B1346421BE0FE842F4C7E]!

############# Icinga secrets #############

icinga2::feature::idomysql::password: DEC::PKCS7[dbidosecret]!

Ab diesem Punkt kann der Puppet Agent auf "icinga-master-01" ausgeführt werden und am Ende läuft ein Icinga2 der per MySQL seine Daten an die Datenbank überreicht. Wenn etwas nicht klappen sollte, einfach einen Blick in die Logs werfen.

Icinga2 Api User

Bevor es sich lohnt das Icingaweb2 anzugehen, sollte zuerst der API Teil aktiviert werden. Das ist in diesem Fall sehr einfach:

Die bereits vorhandenen $myIcinga2ApiUser Zeilen aktivieren:

  • modules/profile/manifests/icinga2/server.pp:
Ascii.png
...
$myIcinga2ApiUser = hiera('icinga2::object::apiuser', {})
create_resources( 'icinga2::object::apiuser', $myIcinga2ApiUser)
...
  • role/icinga-master.yaml:
Ascii.png
...
icinga2::features:
  - 'api'
...
############# Icinga2 API User #############
icinga2::object::apiuser:
  'root':
    target: '/etc/icinga2/conf.d/api-user.conf'
    apiuser_name: 'root'
    password: "%{hiera('icinga2::object::apiuser::root::password')}"
    permissions:
      - '*'
  • EYAML:
  • role/icinga-master.eyaml:
Ascii.png
icinga2::object::apiuser::root::password: DEC::PKCS7[icingaapirootsecret]!

Damit wird die Datei /etc/icinga2/conf.d/api-user.conf erstellt mit dem ersten User Root und dem passendem Passwort aus der EYAML Datei.

Profil Apache2 Server

Zeit um einen Webserver zu installieren.

  • Puppet Modul für Apache installieren:
Debian-term.png
# puppet module install puppetlabs-apache
  • modules/role/manifests/icinga2_server.pp:
Ascii.png
...
contain profile::apache2::server
...
  • modules/profile/manifests/apache2/server.pp:
Ascii.png
# source modules/profile/manifests/apache2/server.pp
# == Class: profile::apache2::server
class profile::apache2::server {
  class { 'apache':
    default_vhost        => false,
  }

  $myApache2Vhosts = hiera('apache::vhost', {})
  create_resources('apache::vhost', $myApache2Vhosts)

  class { 'apache::mod::alias': }
  class { 'apache::mod::rewrite': }
  class { 'apache::mod::env': }
  class { 'apache::mod::headers': }
  class { 'apache::mod::setenvif': }
  class { 'apache::mod::deflate': }
}

Auch hier zu beachten, die Zeilen von $myApache2Vhosts, welche sich aus der Hiera Datei alles passende holen.


  • hieradata/role/icinga-master.yaml:
Ascii.png
...
############# Apache2 settings #############

apache::default_mods: false
apache::mpm_module: prefork
apache::vhost:
  "%{::fqdn}":
    servername: "%{::fqdn}"
    serveraliases:
      - 'icinga-master.%{::domain}'
    serveradmin: 'webmaster@%{::domain}'
    port: 80
    docroot: '/var/www'
    redirect_status: 'permanent'
    redirect_dest: "https://%{::fqdn}/"
  "%{::fqdn}_ssl":
    servername: "%{::fqdn}"
    serveraliases:
      - 'icinga-master.%{::domain}'
    serveradmin: 'webmaster@%{::domain}'
    ssl: true
    ssl_cert: '/etc/ssl/certs/ssl-cert-snakeoil.pem'
    ssl_key: '/etc/ssl/private/ssl-cert-snakeoil.key'
    port: 443
    docroot: '/var/www'

Nach einem Puppet Agent durchlauf ist ein Apache2 installiert, der von Port 80 auf 443 (SSL) umleitet. Genutzt werden die TestSSL Zertifikate, die dann später durch LetsEncrypt ersetzt werden sollten.

Profil Apache2 PHP

Für Icingaweb2 benötigen wir PHP5, als aktivieren wir das passende Profil.

  • modules/role/manifests/icinga2_server.pp:
Ascii.png
...
contain profile::apache2::php
...
  • modules/profile/manifests/apache2/php.pp:
Ascii.png
# source modules/profile/manifests/apache2/php.pp
# == Class: profile::apache2::php
class profile::apache2::php {

  contain ::apache::mod::php
  package { php5-curl:
    ensure => installed,
  }
}

Ein erneuter Puppet Agent Lauf bringt PHP5 auf den Server und aktiviert es auch sogleich.

Profil Icinga2 Web

Nun kommt Icingaweb2. Dieses Profil ist wieder ein wenig aufwendiger. Die Basis kommt von hier: https://github.com/Icinga/puppet-icingaweb2#real-world-example

  • Puppet Module für Ini Dateien und Repos:
Debian-term.png
# puppet module install puppetlabs-inifile
# puppet module install puppetlabs-vcsrepo
  • Puppet Modul für Icingaweb2:
Debian-term.png
$ cd modules/
$ git clone https://github.com/Icinga/puppet-icingaweb2 icingaweb2
  • Profil selbst:
Ascii.png
# source modules/profile/manifests/icinga2/web.pp
# == Class: profile::icinga2::web
class profile::icinga2::web() {

  package { git:
    ensure => installed,
  }

  # Icinga2 results saved ido
  $ido_db_name = hiera('icinga2::feature::idomysql::database')
  $ido_db_user = hiera('icinga2::feature::idomysql::user')
  $ido_db_pass = hiera('icinga2::feature::idomysql::password')
  $ido_db_host = hiera('icinga2::feature::idomysql::host')

  $web_db_name = hiera('icingaweb2::db_name')
  $web_db_user = hiera('icingaweb2::db_user')
  $web_db_pass = hiera('icingaweb2::db_password')
  $web_db_host = hiera('icingaweb2::db_host')

  class { '::icingaweb2':
    initialize          => true,
    install_method      => 'package',
    manage_apache_vhost => true,
    # Icinga2 DB
    ido_db_name         => $ido_db_name,
    ido_db_pass         => $ido_db_pass,
    ido_db_user         => $ido_db_user,
    # Icingaweb2 DB
    web_db_name         => $web_db_name,
    web_db_pass         => $web_db_pass,
    web_db_user         => $web_db_user,
    require             => Class['::mysql::server'],
  } ->

  #  class { '::icingaweb2::mod::graphite':
  #    install_method    => 'git',
  #    require       => Package['git'],
  #  } ->
  #
  augeas { 'php.ini':
    context => '/files/etc/php.ini/PHP',
    changes => ['set date.timezone Europe/Berlin',],
  }
  contain ::icingaweb2::mod::monitoring
  #  contain ::icingaweb2::mod::graphite
  #  contain ::icingaweb2::mod::director
  #  contain ::icinga2::feature::graphite
  #  contain ::icinga2::feature::influxdb

  # For director module
  #  $myResourceDatabase = hiera('icingaweb2::config::resource_database', {})
  #create_resources( 'icingaweb2::config::resource_database', $myResourceDatabase)

}
  • hieradata/role/icinga-master.yaml:
Ascii.png
...
############## MySQL grants #############
mysql::server::databases:
  ...
  'icingaweb2_db@%/icingaweb2_db.*':
    ensure     : 'present'
    privileges : ['ALL']
    table      : 'icingaweb2_db.*'
    user       : 'icingaweb2_db@%'
  • hieradata/role/icinga-master.yaml:
Ascii.png
############# MySQL databases #############
mysql::server::databases:
  ...
  icingaweb2_db:
    ensure  : 'present'
    charset : 'utf8'
Debian-term.png
root@icinga-master-01 ~ # mysql -e 'select password("dbicingawebsecret")'
+-------------------------------------------+
| password("dbicingawebsecret")             |
+-------------------------------------------+
| *1FC0E6B8BE974EB5617E25EFE49902AB9441205F |
+-------------------------------------------+
  • EYAML
  • hieradata/role/icinga-master.eyaml:
Ascii.png
############# MySQL user #############
...
mysql::server::users:
...
  'icingaweb2_db@%':
    ensure        : 'present'
    password_hash : DEC::PKCS7[*1FC0E6B8BE974EB5617E25EFE49902AB9441205F]!

############# Icinga secrets #############
...
icingaweb2::db_pass: DEC::PKCS7[dbicingawebsecret]!

Damit haben wir nun ein funktionierendes Icingaweb2, welches dann unter https://icinga-master-01/icingaweb2/ erreichbar ist. Die Standardzugangsdaten sind wie gehabt:

  • User: icingaadmin
  • Password: icinga
Profil Icinga2 Graphite

Um nun ein paar Performance Graphen in unserem Icingaweb2 zu erhalten, gibt es unterschiedliche Methoden. Die Variante die nun kommt, würde ich mittlerweile als "veraltet" ansehen, da Grafana auf dem Vormarsch ist und es erste Anläufe gibt, die Bilder in Icingaweb2 zu bekommen. Wie auch immer, ich führe es dennoch auf.

Ein paar Worte zur Apache Konfiguration:

Leider funktioniert Graphite nur als VirtualHost und nicht als Unterordner von einem vorhandenem VirtualHost, wie zum Beispiel icinga-master-01.4lin.net/graphite. Daher gibt es einen neuen VirtualHost graph.4lin.net mit den gleichen Kenndaten, lediglich um WSGI erweitert. Man muss also daran denken (oder Puppet), den DNS Namen auf die gleiche IP Adresse zu legen (oder CNAME).

Das Modul habe ich mir selbst zusammen geschustert, da mir alle Puppet Module für Graphite/Carbon einfach zu aufwendig waren. Zu selten muss etwas geändert werden ....

  • modules/role/manifests/icinga2_server.pp:
Ascii.png
...
  contain profile::graphite::graphite
...
  • modules/profile/manifests/graphite/graphite.pp:
Ascii.png
# source modules/profile/manifests/graphite/init.pp
# == Class: profile::graphite::graphite
class profile::graphite::graphite {

    contain ::apache::mod::wsgi
    contain ::graphite
    contain ::icingaweb2::mod::graphite
}

Nicht über den doppelten Namen wundern. Man sollte es eigentlich mal in profile::graphite::basic umbenennen ...

  • modules/graphite/manifests/init.pp:
Ascii.png
class graphite {

  ## Hiera Lookups

  # Graphite
  $storage_dir = hiera('graphite::storage_dir', '/var/lib/graphite')
  $secret_key = hiera('graphite::secret_key', 'S3CrEt_k3Y')
  $whisper_dir = hiera('graphite::whisper_dir', '/var/lib/graphite/whisper')
  $log_dir = hiera('graphite::log_dir','/var/log/graphite')
  $index_file = hiera('graphite::index_file', '/var/lib/graphite/search_index')
  $engine = hiera('graphite::engine','django.db.backends.mysql')

  $time_zone = hiera('graphite::time_zone','Europe/Berlin')
  $web_port = hiera('graphite::web::port')

  $db_name = hiera('graphite::db_name', 'graphite_db')
  $db_user = hiera('graphite::db_user', 'graphite_db')
  $db_pass = hiera('graphite::db_pass', 'secret')
  $db_host = hiera('graphite::db_host', 'localhost')

  # Carbon
  $carbon_data_dir = hiera('graphite::carbon::data_dir', '/var/lib/graphite/whisper')
  $carbon_storage_dir = hiera('graphite::carbon::storage_dir', '/var/lib/graphite/whisper')
  $carbon_log_dir = hiera('graphite::carbon::log_dir', '/var/log/graphite')

  $icingaweb_base_url = hiera('icingaweb2::mod::graphite::graphite_base_url')

  # Packages
  $packages = [
    'python-mysqldb',
    'graphite-carbon',
    'python-django',
    'python-django-tagging',
    'python-cairo',
    'python-tz',
    'python-pyparsing',
    'python-memcache',
    'python-rrdtool',
    'graphite-web',
  ]
  package { $packages:
    ensure => latest,
    }

  # Alternative zu service Deklaration unten 
  # shellvar { 'graphite-carbon_default':
  #  ensure   => present,
  #  target   => '/etc/default/graphite-carbon',
  #  variable => 'CARBON_CACHE_ENABLED',
  #  value    => 'true',
  #  comment  => 'We want graphite-carbon to run',
  # }

    ::mysql::db { $db_name:
      user     => $db_user,
      password => $db_pass,
      host     => $db_host,
      grant    => ['ALL'],
    }

    file { '/etc/graphite/local_settings.py':
      notify  => Service['carbon-cache'],
      content => template('graphite/local_settings_py.erb'),
      owner   => root,
      group   => www-data,
      mode    => '0640',
      require => Package['graphite-carbon'],
    }

    file { '/etc/carbon/storage-schemas.conf':
      notify  => Service['carbon-cache'],
      content => template('graphite/carbon_storage_schemas_conf.erb'),
      owner   => root,
      group   => root,
      mode    => '0640',
      require => Package['graphite-carbon'],
    }

    file { '/etc/carbon/carbon.conf':
      notify  => Service['carbon-cache'],
      content => template('graphite/carbon_conf.erb'),
      owner   => root,
      group   => root,
      mode    => '0640',
      require => Package['graphite-carbon'],
    }

    file { '/etc/icingaweb2/modules/graphite/config.ini':
      content => template('graphite/icingaweb2_mod_graphite_config_ini.erb'),
      owner   => root,
      group   => root,
      mode    => '0644',
    }
    exec { 'graphite-manage syncdb --noinput':
      cwd   =>    '/var/tmp',
      path  =>    ['/usr/bin', '/usr/sbin/',],
      creates =>  hiera('graphite::index_file'),
      require =>  Package['graphite-carbon'],
    }

    service { "carbon-cache":
      # provider => systemd,
      ensure => running,
      enable => true,
      require => Package['graphite-carbon'],
    }
}
  • modules/graphite/templates/local_settings_py.erb:
Ascii.png
## Managed by Puppet ##
# modules/graphite/templates/local_settings_py.erb

SECRET_KEY = <%= @secret_key %>
TIME_ZONE  = <%= @time_zone %>
LOG_RENDERING_PERFORMANCE = True
LOG_CACHE_PERFORMANCE = True
LOG_METRIC_ACCESS = True
GRAPHITE_ROOT = '/usr/share/graphite-web'
CONF_DIR = '/etc/graphite'
STORAGE_DIR = '<%= @storage_dir %>'
CONTENT_DIR = '/usr/share/graphite-web/static'
WHISPER_DIR = '<%= @whisper_dir %>'
LOG_DIR = '<%= @log_dir %>'
INDEX_FILE = '<%= @index_file %>'
DATABASES = {
    'default': {
        'NAME': '<%= @db_name %>',
        'ENGINE': '<%= @engine %>',
        'USER': '<%= @db_user %>',
        'PASSWORD': '<%= @db_pass %>',
        'HOST': '<%= @db_host %>',
        'PORT': '3306'
    }
}
  • modules/graphite/templates/carbon_storage_schemas_conf.erb:
Ascii.png
# Managed by Puppet
# modules/graphite/templates/carbon_storage_schemas_conf.erb

#  [name]
#  pattern = regex
#  retentions = timePerPoint:timeToStore, timePerPoint:timeToStore, ...

# Carbon's internal metrics. This entry should match what is specified in
# CARBON_METRIC_PREFIX and CARBON_METRIC_INTERVAL settings
[carbon]
pattern = ^carbon\.
retentions = 60:90d

[default_1min_for_1day]
pattern = .*
retentions = 60s:1d

[icinga_internals]
pattern = ^icinga2\..*\.(max_check_attempts|reachable|current_attempt|execution_time|latency|state|state_type)
retentions = 5m:7d

[icinga_default]
pattern = ^icinga2\.
retentions = 1m:2d,5m:10d,30m:90d,360m:4y
  • modules/graphite/templates/carbon_conf.erb:
Ascii.png
# Managed py Puppet #
# modules/graphite/templates/carbon_conf.erb

[cache]
STORAGE_DIR    = <%= @carbon_storage_dir %>
CONF_DIR       = /etc/carbon/
LOG_DIR        = <%= @carbon_log_dir %>
PID_DIR        = /var/run/
LOCAL_DATA_DIR = <%= @carbon_data_dir %>
ENABLE_LOGROTATION = True
USER = _graphite
MAX_CACHE_SIZE = inf
MAX_UPDATES_PER_SECOND = 500
MAX_CREATES_PER_MINUTE = 50
LINE_RECEIVER_INTERFACE = 0.0.0.0
LINE_RECEIVER_PORT = 2003
ENABLE_UDP_LISTENER = False
UDP_RECEIVER_INTERFACE = 0.0.0.0
UDP_RECEIVER_PORT = 2003
PICKLE_RECEIVER_INTERFACE = 0.0.0.0
PICKLE_RECEIVER_PORT = 2004
LOG_LISTENER_CONNECTIONS = True
USE_INSECURE_UNPICKLER = False
CACHE_QUERY_INTERFACE = 0.0.0.0
CACHE_QUERY_PORT = 7002
USE_FLOW_CONTROL = True
LOG_UPDATES = False
LOG_CACHE_HITS = False
LOG_CACHE_QUEUE_SORTS = True
CACHE_WRITE_STRATEGY = sorted
WHISPER_AUTOFLUSH = False
WHISPER_FALLOCATE_CREATE = True

[relay]
LINE_RECEIVER_INTERFACE = 0.0.0.0
LINE_RECEIVER_PORT = 2013
PICKLE_RECEIVER_INTERFACE = 0.0.0.0
PICKLE_RECEIVER_PORT = 2014
LOG_LISTENER_CONNECTIONS = True
RELAY_METHOD = rules
REPLICATION_FACTOR = 1
DESTINATIONS = 127.0.0.1:2004
MAX_DATAPOINTS_PER_MESSAGE = 500
MAX_QUEUE_SIZE = 10000
USE_FLOW_CONTROL = True

[aggregator]
LINE_RECEIVER_INTERFACE = 0.0.0.0
LINE_RECEIVER_PORT = 2023
PICKLE_RECEIVER_INTERFACE = 0.0.0.0
PICKLE_RECEIVER_PORT = 2024
LOG_LISTENER_CONNECTIONS = True
FORWARD_ALL = True
DESTINATIONS = 127.0.0.1:2004
REPLICATION_FACTOR = 1
MAX_QUEUE_SIZE = 10000
USE_FLOW_CONTROL = True
MAX_DATAPOINTS_PER_MESSAGE = 500
MAX_AGGREGATION_INTERVALS = 5
  • modules/graphite/templates/icingaweb2_mod_graphite_config_ini.erb:
Ascii.png
; Managed by Puppet
; modules/graphite/templates/icingaweb2_mod_graphite_config_ini.erb

[graphite]
metric_prefix = icinga2
base_url = <%= @icingaweb_base_url %>
legacy_mode = false
;service_name_template = "icinga2.$host.name$.services.$service.name$.$service.check_command$.perfdata.$metric$.value"
;host_name_template = "icinga2.$host.name$.host.$host.check_command$.perfdata.$metric$.value"
;graphite_args_template = "&target=$target$&source=0&width=300&height=120&hideAxes=true&lineWidth=2&hideLegend=true&colorList=$colorList$&areaMode=$areaMode$&areaAlpha=$areaAlpha$"
graphite_iframe_w = 800px
graphite_iframe_h = 700px
graphite_area_mode = all
graphite_area_alpha = 0.1
graphite_summarize_interval = 10min
graphite_color_list = 049BAF,EE1D00,04B06E,0446B0,871E10,CB315D,B06904,B0049C

host_name_template = icinga2.$host.name$.host.$host.check_command$.perfdata.$metric$.value
service_name_template = icinga2.$host.name$.services.$service.name$.$service.check_command$.perfdata.$metric$.value
graphite_args_template = &target=$target$&source=0&width=300&height=120&hideAxes=false&lineWidth=2&hideLegend=true&colorList=049BAF&bgcolor=white&fgcolor=black
graphite_large_args_template = &target=$target$&source=0&width=800&height=700&colorList=049BAF&lineMode=connected&bgcolor=white&fgcolor=black

Nach den vielen Templates, kommt nun die Hiera Konfiguration:

Debian-term.png
root@icinga-master-01 ~ # mysql -e 'select password("dbgraphitesecret")'
+-------------------------------------------+
| password("dbgraphitesecret")              |
+-------------------------------------------+
| *467829391D367414291A5E8000D52F24C9CBCBDC |
+-------------------------------------------+
  • EYAML:
  • hieradata/role/icinga-master.eyaml:
Ascii.png
...
mysql::server::users:
  ...
  'graphite_db@%':
    ensure        : 'present'
    password_hash : DEC::PKCS7[*467829391D367414291A5E8000D52F24C9CBCBDC]!

# Graphite
graphite::db_pass: DEC::PKCS7[dbgraphitesecret]!
graphite::secret_key: DEC::PKCS7['S3Cr3T']!
  • hieradata/role/icinga-master.yaml:
Ascii.png
############# Icinga2 settings #############
...
icinga2::features:
  ...
  - 'graphite'
...
icinga2::feature::graphite::host: "graph.4lin.net"
icinga2::feature::graphite::enable_send_thresholds: true
icinga2::feature::graphite::enable_send_metadata: true
...
############# Icingaweb2 settings #############
...
icingaweb2::mod::graphite::graphite_base_url:  'https://graph.4lin.net/render?'


############# Graphite settings #############

graphite::db_name: 'graphite_db'
graphite::db_user: 'graphite_db'
graphite::db_host: 'localhost'
graphite::time_zone: '"Europe/Berlin"'
graphite::storage_dir: '/var/lib/graphite/whisper'
graphite::whisper_dir: '/var/lib/graphite/whisper'
graphite::log_dir: '/var/log/graphite'
graphite::index_file: '/var/lib/graphite/search_index'
graphite::engine: 'django.db.backends.mysql'
graphite::web::port: '3000'

graphite::carbon::data_dir: '/var/lib/graphite/whisper'
graphite::carbon::storage_dir: '/var/lib/graphite'
graphite::carbon::log_dir: '/var/log/carbon'
...
############# Apache2 settings #############
...
  "graph.%{::domain}":
    servername: "graph.%{::domain}"
    serveraliases:
      - "graph-01.%{::domain}"
    serveradmin: "webmaster@%{::domain}"
    port: 80
    docroot: '/var/www'
    redirect_status: 'permanent'
    redirect_dest: "https://graph.%{::domain}/"

  "graph.%{::domain}_ssl":
    servername: "graph.%{::domain}"
    serveraliases:
      - "graph-01.%{::domain}"
    serveradmin: "webmaster@%{::domain}"
    ssl: true
    ssl_cert: '/etc/ssl/certs/ssl-cert-snakeoil.pem'
    ssl_key: '/etc/ssl/private/ssl-cert-snakeoil.key'
    port: '443'
    docroot: '/var/www'
    aliases:
      -
        alias:  '/content/'
        path:   '/usr/share/graphite-web/static/'
    directories:
      -
        provider: 'location'
        path: '/content/'
        sethandler: 'None'
    # Note: %%{} is required for escaping
    custom_fragment: |
                WSGIProcessGroup _graphite
                WSGIDaemonProcess _graphite processes=5 threads=5 display-name='%%{}{GROUP}' inactivity-timeout=120 user=_graphite group=_graphite
                WSGIImportScript /usr/share/graphite-web/graphite.wsgi process-group=_graphite application-group=%%{}{GLOBAL}
                WSGIScriptAlias / /usr/share/graphite-web/graphite.wsgi

Damit haben wir nun Graphite am Start und nachdem Icinga2 neugestartet wurde (falls noch nicht geschehen), dauert es auch nicht lang, bis in dem Ordner /var/lib/graphite/whisper/ "icinga2" auftaucht. Damit sind dann die Werte auch über die Webseite darstellbar. Dank Puppet wurde auch alles konfiguriert, um die Graphen auch in icingaweb2 einzubinden.

Icinga2 Checks - Basics

Bevor wir den Webserver einbinden, möchte ich kurz auf das Konstrukt eingehen, wie Icinga2 mit Puppet einhergeht. Das Puppet Manifest legt folgende Ordner an:

  • /etc/icinga2/zones.d/global-templates/ -> Dateien dort werden auf allen Icinga2 Instanzen verteilt
  • /etc/icinga2/zones.d/master/ -> Dateien die nur den Icinga-Master betreffen
  • /etc/icinga2/conf.d/commands/ -> Eigene Checkkommandos
  • /etc/icinga2/conf.d/services/ -> Services
  • /etc/icinga2/conf.d/notifications -> Benachrichtigungen
  • /etc/icinga2/conf.d/hosts -> Hosts

Wird ein neuer Host zu Puppet hinzugefügt und hat das entsprechende Icinga Profil eingebunden, so wird außer Icinga2 zu installieren auch die Information an die PuppetDB weitergegeben. Wird danach der Puppet Agent auf dem Icinga Master aufgerufen, holt sich dieser wiederum alles aus der Datenbank, um einen neuen Host zu Icinga2 hinzuzufügen. Puppet muss in diesem Fall daher zweimal aufgerufen werden:

  1. Auf dem neu zu überwachenden Host
  2. Auf dem Icinga2 Master

Das Gleiche gilt auch bei Änderungen an den Icinga2 Variablen wie zum Beispiel host.var.foo, die keinerlei Änderungen an dem Host selbst durchführen. Da die Variable dem Host zugewiesen wird, kann der Icinga2 Master noch nichts davon wissen, erst nachdem der Puppet Agent diese Information in der zentralen Datenbank abgelegt hat.

Icinga2 Checks - Checkcommands

In dieser Datei werden eigene Checkcommands untergebracht. Das Beispiel hier entstammt meiner erste Testumgebung und soll lediglich aufzeigen, wie so etwas aussehen kann. Es gibt dabei zwei Besonderheiten:

  • Es gibt statische Checks, die lediglich auf dem Icinga2 Master zum Einsatz kommen, wie zum Beispiel check_nwc_health mit samt sämtlicher Perl Module die notwendig sind.
  • Checks die in der Datenbank abgelegt werden, sind leicht an dem "@@" zu erkennen. Diese Checks können damit auch von anderen Puppet Instanzen bezogen werden, da sie exportiert werden.

Programme/Tools etc. die für einen Checkkommando benötigt werden, habe ich in dem Ordner modules/icinga_checks/files/checks/ abgelegt, wenn es dafür keine Debian Pakete gibt. Dort finden sich z.B. check_iostat, oder auch check_nwc_health. Die exportieren Checkkommandos finden sich dann in zones.d/global-templates/ wieder.

  • modules/profile/manifests/icinga2/server.pp:
Ascii.png
...
# contain profile::icinga2::notifications
 contain profile::icinga2::checkcommands
...
  • modules/profile/manifests/icinga2/checkcommands.pp:
Ascii.png
# class profile::icinga2::checkcommands
# modules/profile/manifests/icinga2/checkcommands.pp
class profile::icinga2::checkcommands {

  # installing packages
  $checkdeps = [ 'libnet-snmp-perl','libcrypt-hcesha-perl',
  'libcrypt-des-perl','libdigest-hmac-perl','libcrypt-rijndael-perl',
  'sysstat','smartmontools','libconfig-json-perl','libredis-perl'
  ]

  package { $checkdeps:
    ensure => installed,
    }

    file { '/usr/lib/nagios/plugins/check_nwc_health':
      ensure => file,
      mode   => '755',
      owner  => 'root',
      group  => 'root',
      source => [
        "puppet:///modules/icinga_checks/checks/check_nwc_health",
      ],
    }

    file { '/usr/lib/nagios/plugins/check_iostat':
      ensure  => file,
      mode    => '755',
      owner   => 'root',
      group   => 'root',
      source  => [
        "puppet:///modules/icinga_checks/checks/check_iostat",
      ],
      require =>  Package['sysstat'],
    }

    file {'/usr/lib/nagios/plugins/check_icmp':
      ensure => file,
      mode   => '4755',
      owner   => 'root',
      group   => 'root',
    }

    file {'/usr/lib/nagios/plugins/check_ping':
      ensure => file,
      mode   => '4755',
      owner   => 'root',
      group   => 'root',
    }

    file {'/usr/lib/nagios/plugins/contrib':
      ensure => directory,
      mode   => '755',
      owner   => 'root',
      group   => 'root',
    }

    @@icinga2::object::checkcommand { 'smart_attributes':
      import    => [
        'plugin-check-command',
      ],
      command   => [
        'PluginDir + /contrib/check_smart_attributes/check_smart_attributes',
      ],
      arguments => {
        '-d' => '$smart_device$',
        '-dbj' => '$smart_dbj$',
        '-ucfgj' => '$smart_ucfgj$',
        '-nosudo' => '$smart_nosudo$',
      },
      vars      => {
        'smart_device' => '/dev/sda',
        'smart_dbj' => '/usr/lib/nagios/plugins/contrib/check_smart_attributes/check_smartdb.json',
      },
      target    =>  '/etc/icinga2/zones.d/global-templates/check-smart-attributes-command.conf',
    }
    # Ceph Checks

    @@icinga2::object::checkcommand { 'ceph_health':
      import    => [
        'plugin-check-command',
      ],
      command   => [
        'PluginDir + /check_ceph_health',
      ],
      arguments => {
        '-e' => '$ceph_bin$',
        '-c' => '$ceph_conf$',
        '-m' => '$ceph_mon_address$',
        '-i' => '$ceph_client_id$',
        '-n' => '$ceph_client_name$',
        '-k' => '$ceph_client_keyring$',
        '-w' => '$ceph_whitelist_health_regex$',
        '-d' => '$ceph_health_detail$',
      },
      vars      => {
        'ceph_bin' => '/usr/bin/ceph',
        'ceph_conf' => '/etc/ceph/ceph.conf',
        'ceph_client_id' => 'nagios',
        'ceph_client_keyring' => '/var/lib/nagios/ceph-nagios.keyring',
      },
      target    =>  '/etc/icinga2/zones.d/global-templates/check-ceph-health-command.conf',

    }

    @@icinga2::object::checkcommand { 'ceph_mon':
      import    => [
        'plugin-check-command',
      ],
      command   => [
        'PluginDir + /check_ceph_mon',
      ],
      arguments => {
        '-e' => '$ceph_bin$',
        '-c' => '$ceph_conf$',
        '-m' => '$ceph_mon_address$',
        '-I' => '$ceph_mon_id$',
        '-i' => '$ceph_client_id$',
        '-k' => '$ceph_client_keyring$',
      },
      vars      => {
        'ceph_bin' => '/usr/bin/ceph',
        'ceph_conf' => '/etc/ceph/ceph.conf',
        'ceph_client_id' => 'nagios',
        'ceph_client_keyring' => '/var/lib/nagios/ceph-nagios.keyring',
      },
      target    =>  '/etc/icinga2/zones.d/global-templates/check-ceph-mon-command.conf',

    }

    @@icinga2::object::checkcommand { 'ceph_osd':
      import    => [
        'plugin-check-command',
      ],
      command   => [
        'PluginDir + /check_ceph_osd',
      ],
      arguments => {
        '-e' => '$ceph_bin$',
        '-c' => '$ceph_conf$',
        '-m' => '$ceph_mon_address$',
        '-I' => '$ceph_osd_id$',
        '-H' => '$ceph_osd_host$',
        '-i' => '$ceph_client_id$',
        '-k' => '$ceph_client_keyring$',
        '-o' => '$ceph_osd_out$',
      },
      vars      => {
        'ceph_bin'            => '/usr/bin/ceph',
        'ceph_conf'           => '/etc/ceph/ceph.conf',
        'ceph_client_id'      => 'nagios',
        'ceph_client_keyring' => '/var/lib/nagios/ceph-nagios.keyring',
        'ceph_osd_host'        => '$address$',
      },
      target    =>  '/etc/icinga2/zones.d/global-templates/check-ceph-osd-command.conf',

    }

    @@icinga2::object::checkcommand { 'ceph_rgw':
      import    => [
        'plugin-check-command',
      ],
      command   => [
        'PluginDir + /check_ceph_rgw',
      ],
      arguments => {
        '-c' => '$ceph_conf$',
        '-e' => '$radosgw_admin_bin$',
        '-d' => '$ceph_perf_detail$',
        '-B' => '$ceph_perf_byte$',
        '-i' => '$ceph_client_id$',
      },
      vars      => {
        'radosgw_admin_bin' => '/usr/bin/radosgw-admin',
        'ceph_conf' => '/etc/ceph/ceph.conf',
      },
      target    =>  '/etc/icinga2/zones.d/global-templates/check-ceph-rgw-command.conf',

    }

    @@icinga2::object::checkcommand { 'ceph_df':
      import    => [
        'plugin-check-command',
      ],
      command   => [
        'PluginDir + /check_ceph_df',
      ],
      arguments => {
        '-e' => '$ceph_bin$',
        '-c' => '$ceph_conf$',
        '-m' => '$ceph_mon_address$',
        '-i' => '$ceph_client_id$',
        '-n' => '$ceph_client_name$',
        '-k' => '$ceph_client_keyring$',
        '-d' => '$ceph_pool_detail$',
        '-W' => '$ceph_pool_warning$',
        '-C' => '$ceph_pool_critical$',
      },
      vars      => {
        'ceph_bin' => '/usr/bin/ceph',
        'ceph_conf' => '/etc/ceph/ceph.conf',
        'ceph_client_id' => 'nagios',
        'ceph_client_keyring' => '/var/lib/nagios/ceph-nagios.keyring',
        'ceph_pool_warning' => '85',
        'ceph_pool_critical' => '95',
      },
      target    =>  '/etc/icinga2/zones.d/global-templates/check-ceph-df-command.conf',

    }
}
Icinga2 Checks - Services / applyrules

Im Gegensatz zu der offiziellen icinga2-puppet Anleitung, wird für die Services nicht notwendigerweise auf auf Puppet spezifische Konstrukte (icinga2::object::service) verwendet. Der Autor von Example3 hat als Grund dafür angegeben, dass mit dem icinga2-puppet einfach noch nicht alles möglich ist, was Icinga selbst kann, mit seinen diversen Macros und Variablen. Daher werden die services / applyrules einfach per Datei verteilt, allerdings auch exportiert.

Die Dateien dazu liegen bei mir unter: modules/icinga_checks/files/services/

  • modules/profile/manifests/icinga2/server.pp:
Ascii.png
...
# Define apply rules that
  contain profile::icinga2::applyrules
...
  • modules/profile/manifests/icinga2/applyrules.pp:
Ascii.png
# classes profile::icinga2::applyrules
# modules/profile/manifests/icinga2/applyrules.pp
class profile::icinga2::applyrules {

  # We attempt to export them with the respective services where possible.
  # However, that only works if the service is unique on the infrastructure and would
  # not lead to duplicate resources.
  #
  # All multi-use (apply) services are defined here.
  #
  # We do not use "icinga2::object::service" but files with the "icinga2::config::file" tag. See the
  # example's README on why this is the case.

    file { '/etc/icinga2/conf.d/services/check_nwc_health.conf':
      ensure => file,
      owner => nagios,
      group => nagios,
      tag       => 'icinga2::config::file',
      source    => [
        "puppet:///modules/icinga_checks/services/service_check_nwc_health.conf",
      ],
    }
       file { '/etc/icinga2/conf.d/services/check_ping.conf':
      ensure => file,
      owner => nagios,
      group => nagios,
      tag       => 'icinga2::config::exported',
      source    => [
        "puppet:///modules/icinga_checks/services/service_check_ping.conf",
      ],
    }

      file { '/etc/icinga2/conf.d/services/check_iostat.conf':
      ensure => file,
      owner => nagios,
      group => nagios,
      tag       => 'icinga2::config::exported',
      source    => [
        "puppet:///modules/icinga_checks/services/service_check_iostat.conf",
      ],
    }
      file { '/etc/icinga2/conf.d/services/service_check_ntp.conf':
      ensure => file,
      owner => nagios,
      group => nagios,
      tag       => 'icinga2::config::exported',
      source    => [
        "puppet:///modules/icinga_checks/services/service_check_ntp.conf",
      ],
    }
  # Collect any files exported and tagged elsewhere (can be created inside
  # services or master zone)
  # We need to use a different tag then icinga itself (icinga2::config::file)
  # or the agent will try to collect any resources tagged so on himself.
  File <<| ensure != 'directory' and tag == 'icinga2::config::exported' |>> {
    require => [
      File['icinga2_masterzone'],
      File['icinga2_services'],
    ],
  }
}
  • modules/icinga_checks/files/services/service_check_ping.conf:
Ascii.png
# Managed by Puppet
# modules/icinga_checks/files/services/service_check_ping.conf

apply Service "ping4" {
  import "generic-service"
  check_command = "ping4"
  assign where host.address
  ignore where host.vars.noping4
}

apply Service "ping6" {
  import "generic-service"
  check_command = "ping6"
  assign where host.address6
  ignore where host.vars.noping6
}
  • modules/icinga_checks/files/services/service_check_iostat.conf:
Ascii.png
# Mangaged by puppet
# modules/icinga_checks/files/services/service_check_iostat.conf

apply Service for (blockdevice => config in host.vars.blockdevices) {
    import "generic-service"
    check_command = "iostat"
    vars += config
        display_name = "iostat " + blockdevice
        vars.instance = blockdevice
    if (host.name != NodeName) {
        command_endpoint = host.name
    }
    assign where host.vars.os == "Linux"
    ignore where host.vars.noagent
}


  • modules/icinga_checks/files/services/service_check_ntp.conf:
Ascii.png
# Mangaged by puppet
# modules/icinga_checks/files/services/service_check_ntp.conf

apply Service "ntp-time" {
  import "generic-service"
  check_command = "ntp_time"
    if (host.name != NodeName) {
        command_endpoint = host.name
    }
  assign where host.vars.distro == "Debian"
  ignore where host.vars.noagent
}
  • modules/icinga_checks/files/services/service_check_nwc_health.conf:
Ascii.png
# Mangaged by puppet
# modules/icinga_checks/files/services/service_check_nwc_health.conf

apply Service "if-" for (interface_name => interface_config in host.vars.interfaces) {
  import "generic-service"
  display_name = "IF-" + interface_name
  enable_notifications = 0
  notes = interface_config.description
  check_command = "nwc_health"
  vars.instance = interface_name
  vars.nwc_health_mode = "interface-usage"
  vars.nwc_health_name = interface_name
  vars.nwc_health_timeout = 120
  check_interval = 300
  retry_interval = 300
  vars += interface_config
}

apply Service "cpu" {
  import "generic-service"
  display_name = "CPU-Load"
  enable_notifications = 0
  check_command = "nwc_health"
  vars.nwc_health_mode = "cpu-load"
  vars.nwc_health_timeout = 120
  assign where host.vars.load == "snmp"
}

apply Service "memory" {
  import "generic-service"
  display_name = "memory usage"
  enable_notifications = 0
  check_command = "nwc_health"
  vars.nwc_health_mode = "memory-usage"
  vars.nwc_health_timeout = 120
  assign where host.vars.mem == "snmp"
}

apply Service "interface-available" {
  import "generic-service"
  display_name = "Interfaces available"
  enable_notifications = 0
  check_interval = 28800
  retry_interval = 30000
  check_command = "nwc_health"
  vars.nwc_health_timeout = 120
  vars.nwc_health_mode = "interface-availability"
  assign where host.vars.intcheck == "snmp"
}

apply Service "uptime" {
  import "generic-service"
  display_name = "uptime"
  enable_notifications = 0
  check_command = "nwc_health"
  vars.nwc_health_mode = "uptime"
  vars.nwc_health_timeout = 120
  assign where host.vars.uptime == "snmp"
}

Sollte ein neustart von Icinga im übrigen fehlschlagen, könnte es daran liegen, dass Check oder Services bereits vorhanden sind. Dies trifft zum Beispiel auf check_ping zu, der bereits in der icinga2/conf.d/services.conf steck. Dann entweder die Datei unschädlich machen, oder nur den Inhalt auskommentieren.

Icinga2 Checks - Hosts ohne Icinga2

Sind Hosts zu prüfen, auf denen kein Icinga2 laufen kann, genügen folgende Zeilen:

  • modules/profile/manifests/icinga2/server.pp:
Ascii.png
...
 $myIcinga2Hosts = hiera('icinga2::object::host', {})
 create_resources( 'icinga2::object::host', $myIcinga2Hosts)
...

Damit lassen sich nun Hosts in Hiera erstellen, die dem Icinga Master zugewiesen werden:

  • hieradata/node/icinga-master-01.4lin.net.yaml:
Ascii.png
# Only host specified things
---
icinga2::object::host:
  www.heise.de:
    target: '/etc/icinga2/zones.d/master/www.heise.de.conf'
    display_name: 'Heise.de'
    address: '193.99.144.80'
    notes: 'Heise Verlag'
    notes_url: 'https://heise.de'
    check_command: 'hostalive'
    vars:
      os: 'None'
      http_vhosts:
        'www.heise.de':
          http_vhost: 'www.heise.de'
          http_ssl: 'true'

Dadurch dass in der hiera.yaml definiert wurde, den Ordner node/ nach <fqdn>.yaml zu durchsuchen (::fqdn), findet Puppet nun unsere neu angelegt Datei und kann somit die Icingakonfiguration erstellen, die dann unter zones.d/master/www.heise.de.conf zu finden ist:

Ascii.png
# This file is managed by Puppet. DO NOT EDIT.

object Host "www.heise.de"  {
  address = "193.99.144.80"
  display_name = "Heise.de"
  check_command = "hostalive"
  notes = "Heise Verlag"
  notes_url = "https://heise.de"
  vars.os = "None"
  vars.http_vhosts["www.heise.de"] = {
    http_vhost = "www.heise.de"
    http_ssl = true
  }
}

In diesem Fall, kann man mittels des check_http und einer passenden Applyrule sämtliche Vhost prüfen :-) Auf die gleiche Weise können SNMP Hosts etc. eingebunden werden.

Icinga2 Checks - Hosts mit Icinga2

Kommen wir zu unserem Webserver. Jeder Host der mittels Icinga Agent überwacht werden soll, muss die Rolle monitorednode eingebunden haben. Des Weiteren ist es wichtig zu wissen, dass für die Verschlüsselung zwischen den Icinga2 Instanzen die Zertifikate von Puppet verwendet werden.

Das nachfolgende Beispiel beruht wieder zu fast 100% auf Example3 :-)

Beachten: Da ich nun schon zweimal darauf reingefallen bin, hier ein Hinweis: Am Ende der agent.pp findet ihr die Zeile "Icinga2::Object::Endpoint". Dort muss euer Icinga2 Master Hostname stehen, oder ein Äquivalent. Passt der Eintrag nicht, so wird auf der Agent Seite in der icinga2/zone.conf der Endpoint fehlen. Damit startet Icinga natürlich nicht, da er nicht weiß, wie er den Master erreichen kann.

  • Alles was für den Agent notwendig ist:
  • modules/profile/manifests/icinga2/agent.pp:
Ascii.png
class profile::icinga2::agent {

  # By default, the icinga module only installs monitoring-plugins-base
  ensure_packages([
    'monitoring-plugins-standard',
    'nagios-plugins-contrib',
    'libmonitoring-plugin-perl',
    'sysstat',
  ], {
    install_options => ['--no-install-recommends'],
    })

    # Options valid for all agents, thus defined inside the manifest
    class { '::icinga2':
      manage_repo => false,
      confd       => false,
      features    => [ 'checker','mainlog' ],
    }

    # Leave this here or put it in a yaml file common
    # to icinga agent nodes only.
    class { '::icinga2::feature::api':
      pki             => 'puppet',
      accept_config   => true,
      accept_commands => true,
      endpoints       => {},
      zones           => {},
    }

    icinga2::object::zone { 'global-templates':
      global => true,
    }

    # All nodes export resources for icinga monitoring
    # The vars (set in the various nodes hiera files) are used to Apply Services
    # to these hosts. (See profile::icinga::server)
    @@::icinga2::object::host { $::fqdn:
      display_name            => $::fqdn,
      address                 => $::ipaddress,
      check_command           => 'hostalive',
      vars                    => hiera_hash('icinga_vars', {}),
      target                  => "/etc/icinga2/zones.d/master/${::fqdn}.conf"
    }

    # Create virtual resources for this agent node
    @@::icinga2::object::endpoint { "$::fqdn":
      host => "$::ipaddress",
    }

    @@::icinga2::object::zone { "$::fqdn":
      endpoints => [ "$::fqdn", ],
      parent    => 'master',
    }

    # Collect and realize info about self and master, but no other nodes.
    Icinga2::Object::Endpoint <<| title == $::fqdn or title == 'icinga-master-01.4lin.net' |>> { }
    Icinga2::Object::Zone <<| title == $::fqdn or title == 'master' |>> { }
    # ::icinga2::pki::puppet
    }
  • Die Rolle monitorednode die das Profil icinga::agent einbindet
  • modules/role/manifests/monitorednode.pp:
Ascii.png
class role::monitorednode {
  contain profile::icinga2::agent
}
  • Unsere Rolle für den Webserver
  • modules/role/manifests/webserver.pp:
Ascii.png
# class  role::webserver
# modules/role/manifests/webserver.pp
class role::webserver inherits role::monitorednode {
    contain profile::nginx::server
}
  • Wir wollen Nginx
  • Fehlende Module nachinstallieren:
Debian-term.png
root@puppet-master # puppet module install puppet-nginx
root@puppet-master # puppet module install puppet-collectd
  • modules/profile/manifests/nginx/server.pp:
Ascii.png
# class profile::nginx::server
# modules/profile/manifests/nginx/server.pp
class profile::nginx::server {
    
    # Needed for Nginx Repo
    package { ['apt-transport-https']:
      ensure => $ensure,
    }

    # This profile can be used by many nodes and thus the node configuration is
    # in the hiera file for the respective node!
    class { '::nginx':
        manage_repo => true,
        package_source => 'nginx-stable'
    }->
    class { '::collectd::plugin::nginx':
        url => 'http://localhost:8433/nginx-status',
    }

    # Icinga: install check into PluginContribDir
    # (PluginContribDir could be a fact "icinga2 variable get PluginContribDir",
    # but for that to work, puppet would probably have to run twice…)
    file { '/usr/lib/nagios/plugins/check_nginx_status.pl':
        ensure  => file,
        mode    => '+x',
        source => [
            'puppet:///modules/1024/icinga/plugins/check_nginx_status.pl',
        ],
        require => Package['monitoring-plugins-standard'],
    }

}
  • Zum Schluss die Hiera Node
  • hieradata/node/web-01.4lin.net.yaml:
Ascii.png
---
classes:
  - 'role::webserver'

icinga_vars:
  client_endpoint: "%{::fqdn}"
  role: http-server
  vhosts:
    web.4lin.net:
      uri: '/ping'
  checks:
    'nginx-status':
      nginx_status_host_address: '127.0.0.1'
      nginx_status_port: '8433'
      nginx_status_url: '/nginx-status'
      nginx_status_warn: '100,50,100'
      nginx_status_critical: '200,100,200'
}

Im Grunde was es das nun. Nun kann man den Puppet Agent auf web-01.4lin.net genauso einrichten, wie bereits schon beim icinga-master-01.4lin.net. Also zuerst das PC1 Repository von Puppetlabs einbinden, das passende Paket installiere und die puppet.conf anpassen. Natürlich muss dann noch das Zertifikat unterschrieben werden, bevor der richtige Puppetlauf starten kann. Am Ende des Durchlaufs kann der Puppet Agent auf dem Icinga Master gestartet werden, der darauf hin ein passendes Icinga2 Host Objekt erzeugt.

Profil InfluxDB

Weil Graphite eben keine Schönheit ist, wollen wir stattdessen Grafana. Um Grafana in vollem Umfang genießen zu können, benötigen wir dafür InfluxDB, welches von Icinga2 nativ gefüttert werden kann.

  • modules/role/manifests/icinga2_server.pp:
Ascii.png
...
  contain profile::influxdb::influxdb


  • modules/profile/manifests/influxdb/influxdb.pp:
Ascii.png
# Class profile::influxdb::influxdb
# modules/profile/manifests/influxdb/influxdb.pp
class profile::influxdb::influxdb {
  class { 'influxdb::server':
    meta_bind_address      => "${::fqdn}:8088",
    meta_http_bind_address => "${::fqdn}:8091",
    http_bind_address      => "${::fqdn}:8086",
  }
}
  • Puppet Module für Influx:
Debian-term.png
root@puppet-master # puppet module install golja-influxdb
  • hieradata/role/icinga-master.yaml:
Ascii.png
...
############# Influx settings #############

influxdb::params:http_pprof_enabled:  true
...

############# Icinga2 settings #############
...
icinga2::features:
  - 'influxdb'
...
icinga2::feature::influxdb::enable_send_thresholds: true
icinga2::feature::influxdb::enable_send_metadata: true
icinga2::feature::influxdb::host: "graph.%{::domain}"

icinga2::feature::influxdb::host_tags:
  fqdn:     '$host.name$'
  hostname: '$host.vars.hostname$'
  domain:   '$host.vars.domain$'

icinga2::feature::influxdb::service_tags:
  fqdn:     "$host.name$"
  hostname: '$host.vars.hostname$'
  domain:   "$host.vars.domain$"
  service:  "$service.name$"
  instance: "$service.vars.instance$"