Inhaltsverzeichnis

Monitoring ist neben Backup das wichtigste Gut in der professionellen IT, weswegen Monitoringsystem in der Regel hochverfügbar sein müssen. Ein Ansatz ist die Implementation eines Active/Passive-Clusters, auf welchem Icinga läuft. Die aktive Node übernimmt in der Regel die Aufgaben des Monitorings, während die passive Node inaktiv ist. Bei einem Ausfall der aktiven Node übernimmt die passive Node die Aufgabe - sie wird aktiv. Damit in einem solchen Fall nicht immer die andere IP verwendet werden muss, wird mithilfe von ucarp eine virtuelle IP für beide Server verwendet. Sofern eine Node nicht zur Verfügung steht, wird automatisch der Verkehr zur IP des anderen Servers durchgereicht.

Aufbau

Der Aufbau besteht aus zwei Servern, hier TVM-HAM01 und TVM-HAM002, die über eine Netzwerkschnittstelle (hier jeweils eth0) und eine einzigartige IP, hier 192.168.1.41 und 192.168.1.42, nach „Außen“ verfügen. Zwischen den beiden Servern besteht eine direkte Netzwerk-Verbindung (hier jeweils eth1), die zur Synchronisation eines gemeinsamen Speichers verwendet wird. Dieses „distributed replicated block device“ ist auf beiden Nodes immer synchron, da ein Schreibvorgang erst dann abgeschlossen ist, wenn er auch auf der zweiten Resource ausgeführt wurde. Diese Resource beinhaltet Daten, über die beide Systene verfügen müssen. Insbesondere die Nagios-/Icinga-Konfiguration sowie die temporären Icinga-Daten müssen hier liegen. Zusätzliche optionale Daten, wie z.B. eine Dokuwiki-Installation können hier ebenfalls Platz finden.

OS-Installation

Die Netzwertkschnittstellen der einzelnen Server sollten idealerweise statisch konfiguriert werden:

tvm-ham01tvm-ham02
# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE="eth0"
BOOTPROTO="none"
HWADDR="..."
NM_CONTROLLED="no"
ONBOOT="yes"
TYPE="Ethernet"
IPADDR="192.168.1.41"
NETMASK="255.255.255.0"
GATEWAY="192.168.1.1"
# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE="eth0"
BOOTPROTO="none"
HWADDR="..."
NM_CONTROLLED="no"
ONBOOT="yes"
TYPE="Ethernet"
IPADDR="192.168.1.42"
NETMASK="255.255.255.0"
GATEWAY="192.168.1.1"
# cat /etc/sysconfig/network-scripts/ifcfg-eth1
DEVICE="eth1"
BOOTPROTO="none"
HWADDR="..."
NM_CONTROLLED="no"
ONBOOT="yes"
TYPE="Ethernet"
IPADDR="10.0.0.1"
NETMASK="255.255.255.252"
# cat /etc/sysconfig/network-scripts/ifcfg-eth1
DEVICE="eth1"
BOOTPROTO="none"
HWADDR="..."
NM_CONTROLLED="no"
ONBOOT="yes"
TYPE="Ethernet"
IPADDR="10.0.0.2"
NETMASK="255.255.255.252"
# cat /etc/resolv.conf
nameserver 192.168.1.1
# cat /etc/resolv.conf
nameserver 192.168.1.1

Anschließend werden der Apache2-Webserver sowie PHP 5 installiert und aktiviert. In diesem Beispiel wird die Firewall der Einfachheit halber deaktiviert.

# yum install httpd php
# chkconfig httpd on
# chkconfig iptables off
# chkconfig ip6tables off
# service httpd start
# service iptables stop
# service ip6tables stop

UCARP

UCARP ist nicht Bestandteil von CentOS 6 - es wird das EPEL-Repository benötigt:

# rpm -ivh http://ftp-stud.hs-esslingen.de/pub/epel/6/i386/epel-release-6-7.noarch.rpm
# yum update

Nun kann ucarp installiert werden. Die angelegte Beispiel-Konfigurationsdatei wird angepasst:

# yum install ucarp
# cp /etc/ucarp/vip-001.conf.example /etc/ucarp/vip-001.conf
# vim /etc/ucarp/vip-001.conf

In der Konfigurtionsdatei wird neben der virtuellen IP-Adresse das lokale Interface und deren IP-Adresse konifguriert. Außerdem wird ein Passwort definiert, das zur Kommunikation über ucarp zwischen den Servern verwendet wird:

tvm-ham01tvm-ham02
VIP_ADDRESS="192.168.1.43"
ID=001
BIND_INTERFACE="eth0"
SOURCE_ADDRESS="192.168.1.41"
PASSWORD="CHANGEME"
VIP_ADDRESS="192.168.1.43"
ID=001
BIND_INTERFACE="eth0"
SOURCE_ADDRESS="192.168.1.42"
PASSWORD="CHANGEME"

Der Dienst wird dahingehend konfiguriert, dass er bei jedem Boot automatisch gestartet wird - initial wird der Dienst das erste Mal händisch gestartet:

# chkconfig ucarp on
# service ucarp start

Nun funktioniert die virtuelle IP-Adresse bereits. Um die Funktion von ucarp zu überprüfen, wird auf jeden der beiden Server eine Start-Webseite mit dem Hostname angelegt:

tvm-ham01tvm-ham02
# echo "tvm-ham01" > /var/www/html/index.html
# echo "tvm-ham02" > /var/www/html/index.html

Beim Aufruf der virtuellen IP-Adresse erscheint nun eine Seite mit dem Namen de aktuell aktiven Systems.

ucarp lässt sich rudimentär durch das Senden zweier Signale steuern - nach erfolgter Aktion schreibt ucarp in das Syslog:

USR1USR2
Status des Knoten herausfindenMaster zum Backup degradieren (falls Masternode)
kill -SIGUSR1 PID-VON-UCARP
kill -SIGUSR2 PID-VON-UCARP
tvm-ham01# tail -n 1 /var/log/messages
tvm-ham01 ucarp[2363]: [INFO] MASTER on eth0 id 1

tvm-ham02# tail -n 1 /var/log/messages
tvm-ham02 ucarp[2697]: [INFO] BACKUP on eth0 id 1
tvm-ham01# tail /var/log/messages
tvm-ham01 ucarp[2363]: [WARNING] Switching to state: BACKUP
tvm-ham01 ucarp[2363]: [WARNING] Spawning
[/usr/libexec/ucarp/vip-down eth0 192.168.1.43]

Die PID von ucarp kann man ganz einfach über einen der folgenden Aufrufe herausfinden:

# ps aux|grep ucarp|grep -v grep|tr -s ' '|cut -d ' ' -f 2
1517

# service ucarp status
ucarp (pid 1517) is running...

In Fehlerfall switcht UCARP automatisch binnen weniger Sekunden auf den nächsten Server. Mit dem Herunterfahren der Netzwerkschnittstellen auf dem Master-Server kann man einen solchen Ausfall mal schnell demonstrieren:

# ifconfig eth0 down; ifconfig eth1 down

DRBD

DRBD ist fester Bestandteil des Linux-Kernels - ab Version 2.6.33. Dummerweise kommt CentOS 6 mit 2.6.32 - DRBD ist hier nicht integriert und auch nicht mehr Bestandteil des „CentOS Extra“-Repositories, wie unter CentOS 5.

Es gibt im ELRepo jedoch die notwendigen Utilities und Kernelmodule:

# rpm --import http://elrepo.org/RPM-GPG-KEY-elrepo.org
# rpm -ivh http://elrepo.org/elrepo-release-6-4.el6.elrepo.noarch.rpm
# yum update
# yum install kmod-drbd84 drbd84-utils

Auf den beiden Servern befindet sich eine zweite Festplatte (/dev/sdb), die zwischen den beiden Systemen synchron gehalten werden soll. Zuerst wird die Festplatte auf beiden Systemen partioniert:

# fdisk /dev/sdb << EOF
n
p
1


w
EOF

Die Konfigurationsdatei von DRBD (etc/drbd.conf) wird auf beiden Systemen angepasst:

# vim /etc/drbd.conf
...

resource drbd1 {
  protocol C;

  syncer {
    rate 70M;
    al-extents 257;
  }
  on tvm-ham01.localdomain.loc {
    device    /dev/drbd1;
    disk      /dev/sdb1;
    address   10.0.0.1:7789;
    meta-disk internal;
  }
  on tvm-ham02.localdomain.loc {
    device    /dev/drbd1;
    disk      /dev/sdb1;
    address   10.0.0.2:7789;
    meta-disk internal;
  }
  
}

Es ist wichtig, dass für die Hostnames ein entsprechender Eintrag in der /etc/hosts vorhanden ist. Ansonsten schlägt die Initialisierung mit einer Fehlermeldung…

'drbd1' ignored, since this host (tvm-ham01.localdomain.loc) is not mentioned with an 'on' keyword.

…ab.

# cat /etc/hosts
...
10.0.0.1        tvm-ham01       tvm-ham01.localdomain.loc
10.0.0.2        tvm-ham02       tvm-ham02.localdomain.loc

Auf beiden Systemen werden jetzt die Metadaten des Volumes initialisiert und das Volume anschließend gestartet - danach wird das Volume vom ersten Server aus initial angelegt:

# drbdadm create-md drbd1
# drbdadm up drbd1
Command 'drbdmeta 1 v08 /dev/sdb1 internal apply-al' terminated with exit code 20
# drbdadm --overwrite-data-of-peer primary drbd1

Bei DRBD ab Version 8.4 wird anstelle des letzten Kommandos das folgende Kommando benötigt:

# drbdadm primary --force drbd1

Nachdem auf dem zweiten Server das Gerät ebenso angelegt wurde, ist der Status der Initialisierung einsehbar:

# drbdadm create-md drbd1
# drbdadm up drbd1
# drbdadm secondary drbd1
# cat /proc/drbd
...
 1: cs:SyncSource ro:Primary/Secondary ds:UpToDate/Inconsistent C r-----
    ns:3816448 nr:0 dw:0 dr:3817112 al:0 bm:232 lo:6 pe:1 ua:6 ap:0 ep:1 wo:b oos:1422976
        [=============>......] sync'ed: 72.9% (1388/5112)M
        finish: 0:00:28 speed: 50,460 (52,264) K/sec

Nachdem die Initialisierung abgeschlossen ist, kann die gespiegelte Festplatte (/dev/drbd1) mit einem Dateisystem versehen werden:

# mkfs.ext4 /dev/drbd1

Das Volume kann immer nur von einem Server exklusiv gemountet werden. Wenn das Volume bereits gemountet wurde, erscheint auf dem zweiten Server eine Fehlermeldung:

mount: block device /dev/drbd1 is write-protected, mounting read-only
mount: Wrong medium type

Damit das Dateisystem auf dem zweiten Server gemountet werden kann, muss das Volume erst ausgebunden und anschließend heruntergestuft werden:

umount /shared
# drbdadm secondary drbd1

Der zweite Server kann jetzt das Volume an sich reißen und es einbinden:

# drbdadm primary drbd1
# mount /shared

Failover

Damit jetzt im Fehlerfall auch automatisch die DRBD-Festplatte ausgehängt und auf dem anderen System eingehängt wird, werden zwei ucarp-Skripte auf Basis der mitgelieferten Beispiele erstellt:

# cp /usr/libexec/ucarp/vip-down /etc/vip-down
# cp /usr/libexec/ucarp/vip-up /etc/vip-up
# vim /etc/vip-up
#!/bin/sh
exec 2>/dev/null

/sbin/ip address add "$2"/32 dev "$1"

/sbin/drbdadm primary drbd1

/bin/mount /dev/drbd1 /shared
# vim /etc/vip-down
#!/bin/sh
exec 2>/dev/null

/sbin/ip address del "$2"/32 dev "$1"

/bin/umount /shared

/sbin/drbdadm secondary drbd1
# chmod +x /etc/vip-*

Nach einem Ausfall ist die Festplatte des ausgefallenen Hosts nicht mehr aktuell - ein sogenanntes „Split Brain“-Szenario:

# cat /proc/drbd
...
 1: cs:StandAlone ro:Primary/Unknown ds:UpToDate/DUnknown   r-----
    ns:12 nr:5453032 dw:5453044 dr:1370 al:1 bm:320 lo:0 pe:0 ua:0 ap:0 ep:1 wo:b oos:0

Dem anderen Server wird mitgeteilt, dass er jetzt der Master ist:

# drbdadm connect drbd1

Auf dem fehlerhaften Servern werden die Änderungen bezogen:

# drbdadm secondary drbd1
# drbdadm --discard-my-data connect drbd1

MySQL

Zunächst wird MySQL aus dem CentOS-Repository installiert. Für eventuelle spätere NagVis-Installationen wird gleich das MySQL-Modul für PHP installiert:

# yum install mysql-server php-mysql
# service mysqld start
# mysqladmin -u root password NEUES-PASSWORT

Anschließend wird die Konfigurationsdatei von MySQL gesichert und dahingehend angepasst, dass es seine Daten von der gemeinsamen Festplatte bezieht - auf der gemeinsamen Festplatte wird vorher ein Ordner für die MySQL-Datendateien angelegt. Vor der gesamten Aktion muss die Datenbank natürlich heruntergefahren werden:

# service mysqld stop
# mkdir -p /shared/mysql/data
# cp /etc/my.cnf /shared/mysql
# cp -R /var/lib/mysql /shared/mysql/data
# vim /shared/mysql/my.cnf
...
datadir=/shared/mysql/data
log-bin=mysql-bin
...
# mv /etc/my.cnf /etc/my.cnf.initial
# ln -s /shared/mysql/my.cnf /etc/my.cnf
# service mysqld start
# mysql -u root -p

Auf den anderen Server muss nur die Konfigurationsdatei „umgebogen“ werden:

# service mysqld stop
# mv /etc/my.cnf /etc/my.cnf.initial
# ln -s /shared/mysql/my.cnf /etc/my.cnf

Icinga

Icinga existiert zwar auch als Binärpaket im RPMForge-Repository, ich jedoch empfehle die händische Übersetzung. Das hat mehrere Gründe: zum Einen ist Aktualisierungspolitik (meiner Meinung nach) relativ restriktiv, was dazu führt, dass es lange dauert, bis neue Versionen von Icinga in das Repository aufgenommen werden - zum Anderen gestaltet sich die Anpassung an die redundante Verwendung der Anwendung durch zwei Server bedeutend komplexer als bei einer händischen Übersetzung.

Die hier beschriebene Übersetzung und Installation erfolgt auf der aktiven Instanz des Clusters.

Zuerst werden einige Entwicklungstools installiert, Icinga und die Nagios-Plugins aus dem Internet bezogen und entpackt, damit einer Kompilation nichts mehr im Wege steht:

# yum install make wget gcc gcc-c++ httpd gd gd-devel glibc glibc-common libjpeg libjpeg-devel libpng libpng-devel openldap-devel php net-snmp net-snmp-devel net-snmp-utils openssl openssl-devel libdbi libdbi-devel libdbi-drivers libdbi-dbd-mysql
# mkdir /usr/src/icinga
# cd /usr/src/icinga
# wget http://downloads.sourceforge.net/sourceforge/icinga/icinga-1.7.1.tar.gz
# wget http://downloads.sourceforge.net/sourceforge/nagiosplug/nagios-plugins-1.4.15.tar.gz
# tar xfz icinga-1.7.1.tar.gz
# tar xfz nagios-plugins-1.4.15.tar.gz

Zuerst wird Icinga übersetzt. Hierfür müssen vorab noch ein Benutzer und zwei Gruppen angelegt werden. Eine Gruppe ist für den Icinga-Dienst, die andere Gruppe berechtigt zusätzliche (Service-)Benutzer dazu, Icinga zu steuern. Zu dieser Gruppe wird der Apache-Benutzer hinzugefügt, damit Icinga über das Webfrontend gesteuert werden kann:

# useradd -m icinga
# passwd icinga
...
# groupadd icinga
# groupadd icinga-cmd
# usermod -a -G icinga-cmd icinga
# usermod -a -G icinga-cmd apache

Bei einigen Distributionen wird beim Anlegen eines Benutzers automatisch eine gleichnamige Gruppe angelegt - in einem solchen Fall wird eine Fehlermeldung angezeigt, die getrost ignoriert werden kann:

groupadd: Gruppe icinga bereits vorhanden

Die Konfiguration für die eigentliche Übersetzung erfolgt nun mittels:

# cd icinga-1.7.1
# ./configure --with-command-group=icinga-cmd --enable-idoutils --enable-ssl --prefix=/usr/local/icinga/ --exec-prefix=/usr/local/icinga/ --sysconfdir=/shared/icinga/etc/ --localstatedir=/shared/icinga/var/ --libexecdir=/shared/icinga/libexec/
...
*** Configuration summary for icinga-core 1.7.1 06-18-2012 ***:

 General Options:
 -------------------------
        Icinga executable:  icinga
        Icinga user/group:  icinga,icinga
       Command user/group:  icinga,icinga-cmd
        Apache user/group:  apache,apache
            Embedded Perl:  no
             Event Broker:  yes
          ido2db lockfile:  /shared/icinga/var/ido2db.lock
             ido sockfile:  /shared/icinga/var/ido.sock
          idomod tempfile:  /shared/icinga/var/idomod.tmp
           Build IDOUtils:  libdbi, instance_name=default
        Install ${prefix}:  /usr/local/icinga
                Lock file:  /shared/icinga/var/icinga.lock
                Temp file:  /tmp/icinga.tmp
                 Chk file:  /shared/icinga/var/icinga.chk
           HTTP auth file:  /shared/icinga/etc/htpasswd.users
            Lib directory:  ${exec_prefix}/lib
            Bin directory:  ${exec_prefix}/bin
         Plugin directory:  /shared/icinga/libexec
   Eventhandler directory:  /shared/icinga/libexec/eventhandlers
            Log directory:  /shared/icinga/var
   Check result directory:  /shared/icinga/var/spool/checkresults
           Temp directory:  /tmp
          State directory:  /shared/icinga/var
   Ext Cmd file directory:  /shared/icinga/var/rw
           Init directory:  /etc/rc.d/init.d
  Apache conf.d directory:  /etc/httpd/conf.d
             Mail program:  /bin/mail
                  Host OS:  linux-gnu
       Environment Prefix:  ICINGA_

Wichtig ist, dass die Werte mit den oben genannten übereinstimmen. Ein kleiner Vertipper kann hier schon zu sehr unschönen Ergebnissen später führen.

Nach der Konfiguration wird der Compiler angeworfen:

# make all
# make fullinstall
# make install-config

Nach der Übersetzung und Installation werden zwei Beispielkonfigurationsdateien umbenannt. In aller Regel sind diese weitesgehend korrekt - lediglich die Zugangsdaten für die MySQL-Datenbank müssen noch angepasst werden. Dies geschiet in der Konfigurationsdatei von ido2db - dem Dienst, der nachher die Ereignisse in die Datenbank einträgt:

# cd /shared/icinga/etc
# cp idomod.cfg-sample idomod.cfg
# cp ido2db.cfg-sample ido2db.cfg
# vim ido2db.cfg
...
db_servertype=mysql
db_port=3306
db_user=icinga
db_pass=icinga
...
# cp modules/idoutils.cfg-sample modules/idoutils.cfg

Anschließend muss auf der installierten MySQL-Datenbank noch ein Icinga-Benutzer mit einer eigenen Datenbank angelegt werden. Auf dieser Datenbank wird das Standardschema für ido2db installiert:

# mysql -u root -p
mysql> CREATE DATABASE icinga;
mysql> GRANT USAGE ON icinga.* TO 'icinga'@'localhost' 
   IDENTIFIED BY 'icinga'
   WITH MAX_QUERIES_PER_HOUR 0
   MAX_CONNECTIONS_PER_HOUR 0
   MAX_UPDATES_PER_HOUR 0;
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, DROP, CREATE VIEW, INDEX
   ON icinga.* TO 'icinga'@'localhost';
mysql> FLUSH PRIVILEGES;
mysql> quit

# cd /usr/src/icinga/icinga-1.7.1/module/idoutils/db/mysql
# mysql -u root -p icinga < mysql.sql

Das letzte fehlende Stück ist die klassische Weboberfläche von Icinga - die HTML- und CGI-Dateien müssen noch installiert werden:

# cd /usr/src/icinga/icinga-1.7.1
# make cgis
# make install-cgis
# make install-html
# make install-webconf

Damit die Weboberfläche auch verwendet werden kann, muss ein Standardbenutzer icingaadmin für Icinga eingerichtet werden. Anschließend muss der Apache-Webserver seine Konfiguration neu einlesen:

# htpasswd -c /shared/icinga/etc/htpasswd.users icingaadmin
# service httpd reload

Damit Icinga auch etwas überwachen kann, fehlen noch die Nagios-Plugins. Mithilfe dieser Plugins wird Icinga zur „eierlegenden Überwachungs-Sau“:

# cd /usr/src/icinga/nagios-plugins-1.4.15
# ./configure --prefix=/shared/icinga --with-cgiurl=/icinga/cgi-bin --with-htmurl=/icinga --with-nagios-user=icinga --with-nagios-group=icinga
# make
# make install

Zu aller letzt werden die neu installierten Dienste für den automatischen Start vorbereitet, konfiguriert und anschließend gestartet:

# chkconfig --add ido2db
# chkconfig --add icinga
# chkconfig ido2db on
# chkconfig icinga on
# service ido2db start
# service icinga start

Ein Teil des Icinga-Systems befindet sich auf der gemeinsamen Festplatte. Auf dem passiven System muss die Übersetzung jedoch nochmals bei deaktivierter gemeinsamer Festplatte ausgeführt werden, damit die restlichen Dateien ebenfalls vorhanden sind. Anschließend können die Inhalte unterhalb von /shared theoretisch entfernt werden, da bei der Aktivierung der Node ohnehin die DRBD-Festplatte gemountet und somit der ursprüngliche Inhalt unterhalb von /shared ausgeblendet wird:

# rm -r /shared

UCARP-Skript

Die vorhin angelegten ucarp-Skripte müssen noch angepasst werden, damit im Fall eines Ausfalls auch die MySQL-, ido2db- und Icinga-Dienste gestartet werden:

# vim /etc/vip-down
#!/bin/sh
exec 2>/dev/null

/etc/init.d/icinga stop
/etc/init.d/ido2db stop
/etc/init.d/mysqld stop

/sbin/ip address del "$2"/32 dev "$1"

/bin/umount /shared

/sbin/drbdadm secondary drbd1
# vim /etc/vip-up
#!/bin/sh
exec 2>/dev/null

/sbin/ip address add "$2"/32 dev "$1"

/sbin/drbdadm primary drbd1

/bin/mount /dev/drbd1 /shared

/etc/init.d/mysqld start
/etc/init.d/ido2db start
/etc/init.d/icinga start

Diese Aktion muss auf beiden Clusternodes ausgeführt werden und bedarf einen Neustart des ucarp-Dienstes:

# service ucarp restart

Anmerkungen

Das hier gezeigte Konzept ist natürlich nicht perfekt. Es gibt noch einige Punkte, an denen gewisser Verbesserungsbedarf besteht:

Wichtig ist auch, dass die Anwendungen, die von beiden Servern gleichzeitig verwendet werden, immer den selben Versionsstand aufweisen müssen. Insbesondere der Mischbetrieb von verschiedenen MySQL- und Icinga-Versionen kann sich als problematisch herausstellen.

Internetverweise