Während wir unseren Icinga2-Core mittlerweile funktionsfähig haben, wollen wir auch mal einen Blick auf unsere Checks werfen.

Hier hat man dann die Wahl zwischen dem klassischen UI, welches man noch von Nagios oder Icinga1 kennt, oder dem moderneren IcingaWeb2.
Ich empfehle hier IcingaWeb2. Nicht nur wegen dem fancy Aussehen, sondern weil es auch noch ein CLI Tool mitbringt. :)

IcingaWeb2 ist eine PHP basierte Webanwendung.
Wir benötigen also neben dem reinen IcingaWeb Paket auch noch einen Webserver und PHP.
Da ich mich mittlerweile vom Apache abgewandt und vermehrt nginx einsetze, werde ich das auch hier nutzen.

Beim schreiben ist mir aufgefallen, dass dieser eine Part doch umfangreicher als gedacht geworden ist.
Ich werde also ggf. einen zweiten Teil schreiben müssen, da mir selbst ein paar Dinge fehlen.

Falls jemand Anregungen haben sollte, der kann gern die Kommentarfunktion nutzen, oder mir einfach eine EMail schreiben.

Vorbereitungen

Bevor wir an die hiera Konfiguration gehen, müssen wir uns erst einmal die benötigten puppet-Module besorgen.
Da ich als Distribution etwas Debian basiertes nutze, beziehe ich mich hier auch auf dessen Paketmanagement, für rpm basierendes kann ich auf Wunsch gern etwas nachliefern.
Diese lassen sich einfach folgendermaßen in das eigene Module Verzeichniss installieren (ggf. mit sudo arbeiten):

  • apt puppet module install puppetlabs-apt
  • php-fpm git clone https://github.com/bodsch/puppet-phpfpm /etc/puppet/modules/phpfpm
  • nginx puppet module install jfryman-nginx (alternativ muss man das modul von github clonen)
  • memcached puppet module install saz-memcached (alternativ muss man das modul von github clonen)
  • icingaweb2 git clone https://dev.icinga.org/projects/puppet-icingaweb2 /etc/puppet/modules/icingaweb2

Nach der Installation der benötigten Pakete (und deren Abhängigkeiten) können wir endlich los legen.

apt

Wir benötigen einige Pakete, die nicht im offiziellen Zweigen zu finden sind, daher fangen wir damit an, die nötigen Repositories zu integrieren:

# --------------------------------------------------------------
# = APT
apt::sources:
  dotdeb:
    location: http://ftp.hosteurope.de/mirror/packages.dotdeb.org
    repos: all
    include_src: false
    key: 6572BBEF1B5FF28B28B706837E3F070089DF5277
  debmon:
    location: http://debmon.org/debmon
    release: debmon-%{::lsbdistcodename}
    repos: main
    include_src: false
    key: 7E55BD75930BB3674BFD6582DC0EE15A29D662D2

Bei der Integration in das puppet-Modul nutzen wir ein spezielles Feature von puppet, das ‚UFO‘:

# APT
include apt

Package <| |> {
  require => Class[ 'apt' ]
}

Die Package <| |> ... Notation zwingt puppet vor dem Aufruf von jedem package{ ... }, welcher der Installation von Paketen dient, die Klasse apt aufzurufen.
Dadurch werden die Repositories zuerst angezogen und erst danach versucht das Paket zu installieren.
Das Repository dotdeb stellt eine aktuelle PHP Version zur Verfügung, debmon beinhaltet das icingaweb2 Paket.

Memcached

Da es das einfachste ist, den memcached gleich zum Anfang.
Die hiera Konfiguartion:

# --------------------------------------------------------------------------
# == memcached
memcached::listen_ip: '127.0.0.1'
memcached::max_connections: 512
memcached::logfile: /var/log/memcached.log
memcached::max_memory: 64

Hier reicht ein simples include memcached und fertig.

PHP

PHP-FPM ist etwas kniffliger.

Hintergrundinformationen
Traditionell (im Apache Kontext) wird PHP als Modul (mod_php)genutzt.
D.h. bei jedem Aufruf wird der PHP-Interpreter gestartet und dann dem Request das ganze wieder weggeworfen. Und das passiert bei jedem Request, der beim Apachen ankommt.
Das kostet Zeit und Ressouren .. Zudem läuft der Interpreter immer mit den Rechten des Webservers.
Beim php-fpm läuft das ganze im Hintergrund als eigener Prozess. Der PHP-Interpreter wird nur ein einziges Mal gestartet und ist über einen UNIX-Socket oder eine IP:Port erreichbar.
Somit entfällt das ständige Neustarten des Interpreters und – als Zugabe sozusagen – man kann den PHP-Interpreter als eigenen User starten, was noch einmal ein Mehrwert bei der Sicherheit bringen kann. Vom Aufbau eines Pools für verschiedene Applikationen will ich gar nicht erst anfangen.
Interessanterweise ist man auf dem Einsatz von php-fpm angewiesen, wenn man sich vom Apachen abwendet.

Unsere hiera Konfiguration sollte ungefähr so aussehen:

# --------------------------------------------------------------------------
# == PHP-FPM
phpfpm::logdir: /var/log/php-fpm

phpfpm:
  ensure: present
  poold_purge: true
  error_log: "%{hiera('phpfpm::logdir')}/error.log"

phpfpm::log_level: notice
phpfpm::emergency_restart_threshold: 10
phpfpm::emergency_restart_interval: 30
phpfpm::process_control_timeout: 5
phpfpm::process_max: 32

phpfpm::pools::defaults:
  listen: /var/run/php5-fpm-$pool.sock
  user: www-data
  group: www-data
  slowlog: /var/log/php-fpm/$pool/slow.log
  request_slowlog_timeout: 30s
  request_terminate_timeout: '120s'
  catch_workers_output: 'yes'

phpfpm::pools:
  worker-01:
    php_flag:
      display_errors: 'off'
    php_admin_flag:
      expose_php: 'off'
    php_admin_value:
      upload_max_filesize: '8M'
      max_execution_time: 300
      log_errors: 'off'
      date.timezone: '"Europe/Berlin"'
      memory_limit: '256M'
      error_log: /tmp/php-fpm-worker-01-error.log
      session.save_handler: 'memcached'
      session.save_path: '"127.0.0.1:11211"'

Hier wird über hiera die Basiskonfiguration des PHP-FPM definiert.
Zusätzlich richten wir einen eigenen worker (worker-01) ein, der unter /var/run/php5-fpm-worker-01.sock einen Socket zur Kommunikation zur Verfügung stellt.
Logfiles werden unterhalb von /var/log/php-fpm/worker-01/ weggeschrieben.
Als Session Handler verwenden wir den zuvor installierten Memcached.

Die Integration in unser puppet-Modul geschieht dann folgendermaßen:

# PHP-FPM
include phpfpm
$pools          = hiera_hash( phpfpm::pools, undef )
$pools_defaults = hiera_hash( phpfpm::pools::defaults, undef )

if( $pools ) {
  if( $pools_defaults ) { create_resources( phpfpm::pool, $pools, $pools_defaults ) }
  else { create_resources( phpfpm::pool, $pools ) }
}

nginx

Als nächstes folgt die Installation des nginx.

# --------------------------------------------------------------------------
# == nginx
nginx::configtest_enable: true
nginx::confd_purge: true
nginx::vhost_purge: false
nginx::server_tokens: 'off'
nginx::http_access_log: '/var/log/nginx/access.log  main'
nginx::nginx_error_log: '/var/log/nginx/error.log  info' # notice | debug | info | ...
nginx::worker_processes: 1
nginx::keepalive_timeout: 65
nginx::log_format:
  main: '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"'
nginx::conf::proxy_buffers: '64k'

nginx::nginx_vhosts:
  icingaweb2:
    ensure: present
    server_name:
      - "%{::fqdn}"
      - "mon.%{::domain}"
    listen_port: 80
    www_root: /usr/share/icingaweb2/public
    index_files:
      - index.php
      - index.html

nginx::nginx_locations:
  icingaweb2:
    ensure: present
    vhost: icingaweb2
    location: '~ ^/icingaweb2/index\.php(.*)$'
    location_custom_cfg:
      fastcgi_pass: unix:/var/run/php5-fpm-worker-01.sock
      fastcgi_index: index.php
      include: fastcgi_params
      fastcgi_param SCRIPT_FILENAME: /usr/share/icingaweb2/public/index.php
      fastcgi_param ICINGAWEB_CONFIGDIR: /etc/icingaweb2
  icingaweb2_2:
    ensure: present
    vhost: icingaweb2
    location: '~ ^/icingaweb2(.+)?'
    location_custom_cfg:
      alias: /usr/share/icingaweb2/public
      index: index.php
      try_files:  '$1 $uri $uri/ /icingaweb2/index.php$is_args$args'
  favicon:
    vhost: icingaweb2
    location: '= /favicon.ico'
    location_custom_cfg:
      log_not_found: 'off'
      access_log: 'off'
      expires: 'max'

Die Integration im puppet-Modul erfolgt dann über ein einfaches include nginx

Icinga Web 2

Die entsprechende hiera Konfiguration ist ziemlich kurz. Als Installationsquelle wählen wir package wo durch das passende Paket genutzt wird. Es gibt auch noch git zur Auswahl, welches dann logischerweise ein git clone des Code-Repositorys ausführt. Hierzu muss dann aber auch noch das Paket git installiert werden.
IcingaWeb2 nutzt zum einen den Datenbestand, den icinga2 in seine IDO-Datenbank schreibt um die Resultate und eine Historie anzuzeigen, zum anderen ein davon unabhängiges Datenbankschema um Benutzer zu pflegen.

# --------------------------------------------------------------------------
# == icingaweb2
icingaweb2::install_method: package
icingaweb2::ido_db          : "%{hiera('icinga2::ido_db')}"
icingaweb2::ido_db_host     : "%{hiera('icinga2::ido_db_host')}"
icingaweb2::ido_db_name     : "%{hiera('icinga2::ido_db_name')}"
icingaweb2::ido_db_pass     : "%{hiera('icinga2::ido_db_pass')}"
icingaweb2::ido_db_user     : "%{hiera('icinga2::ido_db_user')}"
icingaweb2::ido_type        : "%{hiera('icinga2::ido_type')}"
icingaweb2::web_db          : "%{hiera('icinga2::web_db')}"
icingaweb2::web_db_host     : "%{hiera('icinga2::web_db_host')}"
icingaweb2::web_db_name     : "%{hiera('icinga2::web_db_name')}"
icingaweb2::web_db_pass     : "%{hiera('icinga2::web_db_pass')}"
icingaweb2::web_db_prefix   : "%{hiera('icinga2::web_db_prefix')}"
icingaweb2::web_db_user     : "%{hiera('icinga2::web_db_user')}"

Die puppet-Modul integration ist denkbar einfach: include icingaweb2.

icingaweb

Nach dem Aufruf von http://$WEBBROWSER/icingaweb2/

Ab diesem Zeitpunkt haben wir die aktuellen Möglichkeiten des IcingaWeb2 puppet-Modules fast ausgeschöpft.
Es gibt noch die Möglichkeit Rollen für ein Berechtigungskonzept zu konfigurieren oder den LiveStatus einzubinden:

icingaweb2::config::roles:
  full-admins:
    role_users: bodsch
    role_permissions: '*'
  users:
    role_users: foo,bar
    role_permissions: 'monitoring/command/*'
    
icingaweb2::config::resource_livestatus:
  livestatus:
    resource_socket: /var/run/icinga2/cmd/livestatus

Mit der Erweiterung in unserem puppet-Modul:

# IcingaWeb2 - Roles
$icingaweb_roles = hiera_hash( icingaweb2::config::roles, undef )
if( $icingaweb_roles ) { create_resources( icingaweb2::config::roles, $icingaweb_roles ) }

# IcingaWeb2 - LiveStatus
$icingaweb_livestatus = hiera_hash( icingaweb2::config::resource_livestatus, undef )
if( $icingaweb_livestatus ) { create_resources( icingaweb2::config::resource_livestatus, $icingaweb_livestatus ) }

WAS bei dem bis jetzt erstellten Setup noch fehlt ist die Benutzerauthentifizierung!
Initial würde man das bestimmt über die Datenbank machen, allerdings wurden die entsprechenden Tabellen nicht über puppet angelegt.
Möglicherweise ein Bug, oder Zeit für einen Pull-Request ;)

Per Hand kann man das folgendermaßen machen:

root@mon:~# mysql icinga2_auth < /usr/share/icingaweb2/etc/schema/mysql.schema.sql

Anschließend muß man sich noch seinen User - auch per Hand - in die Datenbank schreiben.
Dazu muß das Passwort entsprechend verschlüsselt werden:

openssl passwd -1 "vollgeheimes-ding"
echo "insert into icingaweb_user values ( 'bodsch', '1', '\$1\$FI6haHHl\$yQ4wO0ZdkxXfUNs2rOEjM.', now(), now() );" | mysql icinga2_auth

Man beachte die escapten Dollarzeichen! Das ist WICHTIG!

Jetzt fehlt nur noch noch das aktivieren der ersten Module