Server Überwachung via SMS und Mail

Heute ergab sich die Anforderung eine Server Überwachung im Netzwerk eines Kunden zu integrieren. Der Grund dafür ist ein Problem zu bemerken und zu beheben, eher der Kunde überhaupt bemerkt, dass er eins hat.

Für eine schnelle Reaktion ist es also wichtig, dass die Nachricht über den Ausfall eines Servers sofort gesendet und auch empfangen wird. Wert ständig ein Smartphone mit sich trägt, dem eröffnen sich zwei Benachrichtigungsverfahren: Email & SMS. Für eine Email spricht der kostengünstige Versand, dagegen aber ein relativ lahmer Transportweg. Eine SMS ist zwar im Versand teurer, dafür in der Zustellung schneller. Da für uns eine schnelle Benachrichtigung wichtig ist und die Kosten zu verschmerzen sind, integrieren wir der Einfachheit halber eine doppeltes Benachrichtigungsverfahren – sicher ist sicher 🙂

Wahl des SMS Providers

Die Wahl eines SMS Providers schwankte bei uns aus Verbundenheit zu Vodafone und als Qnap-Standard-Provider Clickatell. Bei Vodafone muss man seine Handy-Nr für den Gateway freischalten lassen (via SMS) und anschließend für jede empfangene SMS über diesen Gateway 0,20 Euro bezahlen. Das Verfahren heisst „Vodafone SMS-to-eMail“ und wird im InfoDok 354 beschrieben. Kurz erklärt sendet man aus seiner Applikation heraus eine Email an „handynr@vodafone-sms.de“, also in meinem Fall z.B. „01621770699@vodafone-sms.de“. Das klappt soweit ganz gut, ist aber ziemlich teuer.

Clickatell ist der von QNAP empfohlene SMS Provider. Nach einer Anmeldung über die Webseite erhält man Zugang zu verschiedenen Schnittstellen, über die SMS versendet werden können: Email, POST & GET Request, Webservice und … . Zum Testen werden einen 10 Frei-SMS zur Verfügung gestellt ohne das weitere Kosten entstehen. Möchte man den Dienst weiter nutzen, so müssen SMS via Kreditkarte oder auch Paypal gekauft werden. Die kleinste Einheit ist dabei 400 SMS zum Preis von 17,60 Euro zu kaufen, was einem Stückpreis von 0,044 Euro entspricht (für Vodafone & T-Mobile Kunden, sonst das doppelte).

Ich hab mich für Clickatell entschieden, da dieser Dienst mich nur 4 Cent pro SMS kostet und ich ihn auch direkt für QNAP Benachrichtigungen nutzen kann. Die Vielfalt der Schnittstellen bietet mir mehr Möglichkeiten zum Ausprobieren und herumspielen. Einfacher dagegen ist Vodafone, bei dem man nur eine Freischalt-SMS schicken muss, dafür aber auch anschließend 20 Cent pro SMS zahlt.

Szenario

Mein Szenario ist, dass ich vier Server von meinem Script überwacht haben möchte. Drei davon laufen virtuell auf dem Server 1 (SRV01). Die Überwachung soll nicht rund um die Uhr laufen, da in der Nacht die VMs gesichert und dazu heruntergefahren werden. Ich möchte einmal benachrichtigt werde,n wenn ein Server ausgefallen ist und auch dann, wenn er wieder verfügbar ist. Irgendwo muss also ein Status gespeichert werden.

Implementierung

Meine Lösung ist in PHP implementiert. Auf den Quellcode werde ich nicht groß eingehen, da er doch recht verständlich ist und von oben nach unten durchgelesen werden kann. Im Nachhinein habe ich für diesen Beitrag noch eine kleine Logger Klasse eingebaut, die den Code etwas sauberer aussehen lassen soll.

Initial wollte ich den Status der Maschinen über die PHP Funktion exec(‚ping…‘) testen, aber aufgrund von Benutzerbeschränkungen des Apache Users auf dem QNAP können einige Binarys nicht ausgeführt werden. Die Alternative ist via fsockopen() eine Verbindung zu einem Port auf der Maschine aufzubauen. Voraussetzung dafür ist, dass es auch einen offenen Port gibt. Da jeder dieser Server aber eben auch einen Dienst nach außen hin anbietet, ist auf jeden von den Servern auch mindestens ein Port offen. Wer auf seinem Server einen offenen Port sucht, kann in Windows das DOS-Command „netstat“ verwenden. Ich verwenden auf dem SRV01 den VM-Ware Port, auf SRV02 den DNS Port, auf SRV04 den SMTP Port und auf SRV_ALT den MSSQL-Server Port.

Den Status merkt sich das Script in einer Lock-Datei, die ins Filesystem geschrieben wird. In diese Datei wird der Zeitpunkt des letzten Ausfalls geschrieben. Um das Mehrfache versenden von Mails und SMS zu verhindern, ist eine Sperre/Lag/Verzögerung von 1 Stunde eingebaut. Fällt ein System mehrfach innerhalb dieses Zeitraums aus, so wird nur einmal eine Benachrichtigung verschickt.

Als Schnittstelle zum Clickatell SMS Gateway habe ich mich für den Mail-Versand entschieden.

Aufgerufen wird das Script via Eintrag in der /etc/config/crontab jeden Tag von 6 bis 22 Uhr alle 15 Minuten.

[code language=“bash“]

0,15,30,45 6-22 * * * /usr/bin/wget http://10.10.0.10/sms.php

[/code]

In diesem Post habe ich bereits erklärt, wie man auf dem QNAP NAS Cronjobs einträgt.

Das Script

[code language=“php“]
<?php
error_reporting(E_ALL);

define("LAG", 3600);
define("FROM_MAIL", "from@domain.com");
define("TO_MAIL", "to@domain.com");
define("SMS_PROVIDER_TO", "sms@messaging.clickatell.com");
define("SEND_MAIL_ANYTIME", false);
define("SMS_ENABLED", true);
define("SERVER_TIMEOUT_SEK", 2);

define("SMS_PROVIDER_MAIL_BODY", "user:xxxxxxxxxx
password:yyyyyyyyyy
api_id:zzzzzzzzzz
to: 49173123456789
reply:host@domain.com
text:");

/*
* script
*/

// aray(serverip, port, notification msg, vmware-host-system)
$hosts = array(
array("10.10.0.1", "8333", "SRV01", "SRV01 is not reachable", true),
array("10.10.0.2", "53", "SRV02", "SRV02 is not reachable", false),
array("10.10.0.4", "25", "SRV04", "SRV04 is not reachable", false),
array("10.10.0.249", "5900", "SRV_ALT", "SRV_ALT is not reachable", false),
);

foreach($hosts as $entry) {
if(!checkHost($entry[0], $entry[1], $entry[2], $entry[3]) && $entry[4] === true) {
break;
}
flush();
}

/*
* do not make changes beyond this magic line
* -_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
*/

function checkHost($host, $port, $uniqueKey) {
$log = new Logger($uniqueKey);

$log->log("START AT ".date("d.m.Y H:i:s"));
$log->log("CONNECT TO ".$host.":".$port);

$iErr = – 1;
$lckFile = $uniqueKey . ".lck";
$errorMsg = $uniqueKey." is ";

$fp = fsockopen($host, $port, $iErr, $error, SERVER_TIMEOUT_SEK);

if($fp == true) {
fclose($fp);
$log->log("CONNECTED SUCCESFUL");

$lastOutageTime = 0;
if(file_exists($lckFile)) {
$lastOutageTime = file_get_contents($lckFile);
}
if(SMS_ENABLED == true && $lastOutageTime > (time() – LAG)) {
$log->log("SEND SYSTEM ALIVE SMS");
$errorMsg .= "UP";
sendSMS($errorMsg);
sendMail($errorMsg);
}
unlink($lckFile);

} else {
$log->log("CONNECTION FAILED");

$lastOutageTime = 0;
$errorMsg .= "DOWN";
if(file_exists($lckFile)) {
$lastOutageTime = file_get_contents($lckFile);
}

$log->log("LAST OUTAGE ON: " . date("d.m.Y H:i:s", $lastOutageTime));
if(SMS_ENABLED == true && $lastOutageTime < (time() – LAG)) {
$log->log("SEND SMS AND MAIL");

sendSMS($errorMsg);
sendMail($errorMsg);
} else if(SEND_MAIL_ANYTIME == true) {
$log->log("SEND MAIL");
sendMail($errorMsg);
}

file_put_contents($lckFile, $lastOutageTime == 0 ? time() : $lastOutageTime);
}
$log->log("FINISHED");
$log->close();

return $fp;
}

function sendMail($content) {
mail(TO_MAIL, $content, SMS_PROVIDER_MAIL_BODY . $content, "from:" . FROM_MAIL);
}

function sendSMS($content) {
mail(SMS_PROVIDER_TO, $content, SMS_PROVIDER_MAIL_BODY . $content, "from:" . FROM_MAIL);
}

class Logger {

private $system = "n/a";
private $logFile = "no.log";

public function __construct($sys = "N/A") {
$sys = str_replace(" ", "_", $sys);
$this->system = $sys;
$this->logFile = $sys.".log";
$this->start();
}

public function log($msg) {
echo "[" . $this->system. "] \t ".$msg. "\r\n";
}

public function start() {
ob_start();
ob_clean();
echo "<pre>";
}
public function close() {
echo "</pre>";
file_put_contents($this->logFile, file_get_contents($this->logFile).ob_get_contents());
}
}

?>
[/code]

Schlusswort

Bisher läuft das Script einwandfrei und zuverlässig. Die SMS kommen ca. 30 Sekunden nach dem Abschicken an, sodass sich die SMTP Schnittstelle als praxistauglich erwiesen hat.

Die Schwäche des Scripts ist, dass für die Benachrichtigung ein funktionstüchtiger Netzwerkzugang samt Internetverbindung vorausgesetzt ist. Fällt der Router oder das Internet aus, so wird auch keine Benachrichtigung verschickt. Das ist sicher für manche Anwendungsfälle ein K.O.-Kriterium, doch ist in meinem Fall ein zu akzeptierender Umstand.