Ein- oder zweimal im Jahr kommt es bei mir vor, dass sich von irgend einem meiner Rechner mal wieder die Festplatte verabschiedet und sich entschließt, plötzlich und unerwartet vor ihren Schöpfer zu treten. Und das obwohl sich die Rechner kaum von der Stelle bewegen, nur durchschnittlich oft benutzt und immer sorgfältig behandelt werden. Da ich somit schon viel zu oft irgendwelche Daten verloren habe, musste ich irgendwann zu der Einsicht kommen, dass ein Backup nur dann ein gutes Backup ist, wenn es vollautomatisch und ohne jegliches Zutun von Anwenderseite durchgeführt werden kann. Insbesondere darf es nicht sein, dass ein Backup erst manuell angestoßen werden muss (zum Beispiel durch Einstecken einer Backupplatte), da dies in der Realität viel zu oft vergessen wird. Beim letzten Plattencrash hat es ausgerechnet meinen Hauptcomputer erwischt, wodurch ich ganze zwei Monate zurückfiel, weil eben genauso lange das letzte Backup her war.
Damit sich ein solcher Fall nicht nochmal ereignen kann, beschloss ich einen gerade günstig gekauften Minirechner (400 MHz VIA CPU, 10 EUR gebraucht) als vollautomatischen Backupserver zu konfigurieren. Das Prinzip ist einfach: Mit Hermes habe ich ohnehin einen Rechner, der rund um die Uhr läuft, den ich aber nicht als Backupserver verwenden will. Er weckt jede Nacht um vier Uhr den Backupserver auf (Cronjob) und sorgt per SSH dafür, dass ein vordefiniertes Backupprogramm angestoßen wird. Der Backupserver kümmert sich dann darum, alle Computer im Netzwerk hochzufahren, zu sichern und wieder herunterzufahren. Anschließend schaltet er sich von selbst aus.
Der Aufbau sieht also wie folgt aus:
Abb.: Systemlandschaft mit Backupserver |
[Backupserver]---USB---[Festplatte 1.5 TB] | +---LAN-----+-------------+ | | [Router] [Heimserver] | +--LAN--+---+---+-------+ | | | | [PC1] [PC2] [PC3] [PC4] ... [Heimserver] weckt [Backupserver]. |
...
Natürlich werden nur die Rechner aufgeweckt, die nicht schon ohnehin eingeschaltet sind. Der Heimserver muss also nicht aufgeweckt werden, da er immer an ist. Die Existenz der anderen Rechner kann mit Ping überprüft werden. Damit wird auch überprüft, ob nach einem Wake-On-Lan der Rechner tatsächlich hochgefahren wurde. Beim Abschalten gilt umgekehrt, dass der Heimserver nicht heruntergefahren darf, da somit die automatischen Backups nicht mehr angestoßen werden könnten. Alle anderen Computer dürfen im Prinzip heruntergefahren werden, jedoch nur, wenn kein Benutzer mehr angemeldet ist. Arbeitet noch jemand an dem Rechner, soll der Rechner also einfach eingeschaltet bleiben.
Hinzu kommt, dass manche meiner Rechner nicht wake-on-lan fähig oder nicht ans Netzwerk angebunden sind. Hierbei handelt es sich um historische Rechner mit DR-DOS oder ATARI TOS statt Linux. Aber auch die AW4416 Audio Workstation von Yamaha zählt hierzu. Diese Geräte können nicht vollautomatisch gesichert werden. Für sie muss der Zugriff auf den Backuprechner per FTP oder SFTP möglich sein, um manuelle Backups durchführen zu können.
Voraussetzung für den obigen Plan ist, dass alle Rechner mit einer festen IP-Adresse oder einem festen Domainnamen angesprochen werden können. Hierfür gibt es verschiedene Lösungsansätze; in meinem Fall ist es so, dass dnsmasq als kombinierter DHCP/DNS-Server jedem Rechner anhand seiner MAC-Adresse eine IP und eine Subdomain zuordnet.
Hinzu kommt ein eigenes Python-Script, welches das wakeonlan Kommando (muss unter Debian nachinstalliert werden!) einhüllt. Denn wakeonlan funktioniert nur mit einer MAC-Adresse und die kenne ich in der Regel nicht auswendig. Stattdessen greift das Skript auf eine zentral im Netzwerk abgelegte Konfigurationsdatei zurück, die jeder MAC-Adresse einen Namen zuordnet.
Zuordnung von Namen zu MAC-Adressen |
192.168.178.004 00:50:45:5D:89:0C * jake 192.168.178.005 00:40:63:C9:BD:F3 * backup 192.168.178.020 00:A0:D1:9B:79:6E * blues-mobil |
Demnach sind folgende Aufrufe identisch:
$ wake-on-lan 00:40:63:C9:BD:F3
$ ./wakehost backup
Hier der Quellcode des Skripts:
wakehost.py |
#! /usr/bin/env python #encoding=utf-8 # wakehost.py # Simple wakeonlan wrapper in order to wake named hosts # # Copyright 2010 Dennis Schulmeister <dennis(at)developer-showcase.de> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # Configuration values HOST_FILE_URL = "http://hermes/hosts.txt" TIMEOUT = 10 # Imported modules import subprocess, sys, urllib2 # Function definitions def main(): ''' Main routine of this script. Returns system return code. ''' try: host_file = urllib2.urlopen(HOST_FILE_URL, None, TIMEOUT) except urllib2.URLError as ex: print str(ex) return 1 try: hostname = sys.argv[1] except IndexError: hostname = "" error = False if hostname: mac = "" for line in host_file: line = line.lstrip().rstrip() host = line.split(" ") if host[3] == hostname: mac = host[1] break if mac: try: subprocess.call(["wakeonlan", mac]) except OSError: print "OS Error: Unable to run wakeonlan" error = True else: print "Unknown host: %s" % hostname error = True else: for line in host_file: line = line.lstrip().rstrip() host = line.split(" ") print "%s: %s" % (host[1], host[3]) host_file.close() if error: return 1 else: return 0 # Program start if __name__ == "__main__": sys.exit(main()) |
Aufgabe des Heimservers ist es, das Backup der Cronjob täglich anzustoßen. Das Ergebnis des Backups soll dann per Mail an den Administrator geschickt werden.
Da die Kommunikation mit dem Backupserver nur via SSH funktioniert, muss zunächst ein Schlüsselpaar generiert werden. Der Public Key findet sich dann in der Datei ~/.ssh/id_rsa.pub.
$ ssh-keygen
Anschließend wird ein neuer Cronjob eingeplant:
/etc/cron.d/DES-backup |
# Start periodic backup on backup server # Runs mon, wed, fri at 3:30am # des, Sun Jul 11 2010 #-------------------------------------------------------- # 0=sun, 1=mon, 2=tue, 3=wed, 4=tue, 5=fri, 6=sat, 7=sun #-------------------------------------------------------- # NOTE: many_small contains hermes but not blues-mobil! #-------------------------------------------------------- 00 4 * * * dennis /home/dennis/bin/run_backup.sh -q many_silent 00 14 * * 3 dennis /home/dennis/bin/run_backup.sh -q host_jake |
Dieser Job startet ein Shellscript, das als Parameter mindestens einen Namen des auszuführenden Backupprogramms entgegennimmt. Dieses muss auf dem Backupserver angelegt sein (siehe unten), wodurch sich Art und Umfang des Backups bestimmt.
Das Skript selbst unterteilt sich in zwei Dateien, um die eigentliche Skriptfunktion (das Anstoßen des Backups) vom E-Mailversand des Backupergebnisses zu trennen. Zwar verschickt Cron automatisch alle Ausgaben eines Kommandos per E-Mail, diese landen bei mir aber nicht an der richtigen Stelle. (Cron schickt die Ausgaben an den ausführenden Benutzer, dennis in diesem Fall. Ich will jedoch, dass die Mails an root gehen, weil sie dann automatisch in einen speziellen IMAP-Ordner für Systemmeldungen einsortiert werden können. Mehr dazu etwas weiter unten.) Deshalb werden durch den Parameter -q sämtliche Assgaben unterdrückt und stattdessen vom Skript per E-Mail verschickt. Will man das Skript interaktiv ausführen, lässt man den Parameter natürlich weg; die Ausgaben werden trotzdem per E-Mail verschickt.
~/bin/run_backup.sh (Aufruf und E-Mailversand) |
#! /bin/bash ## des - Sun Jul 11, 2010: Start backup server in order to run a full backup ## This script is called automaticaly by a cron job and sends the results back ## to the system adminstrator. BIN=$(dirname $0) quiet=0 if [ -z "$1" ]; then echo "Usage: $0 [-q] <backup-target> <backup-target> ..." exit 1 fi until [ -z "$1" ]; do case $1 in -q) quiet=1 ;; *) target=$1 mailfile=$(tempfile) if [ $quiet == "0" ]; then $BIN/_run_backup.sh $target | tee -a "$mailfile" else $BIN/_run_backup.sh $target &>> "$mailfile" fi mail -s "[HERMES] Results of backup for target $target" root < "$mailfile" rm "$mailfile" sleep 60 ;; esac shift done |
Die eigentliche Funktion steckt in _run_backup.sh. Hier wird erst geprüft, ob der Backupserver mit ping erreicht werden kann und der Server bei Bedarf hochgefahren. Anschließend wird das geforderte Backupprogramm ausgeführt und der Rechner wieder heruntergefahren. Über ein Lockfile wird sichergestellt, dass nicht zwei Backups gleichzeitig laufen können.
~/bin/_run_backup.sh (Eigentliches Anstarten des Backups) |
#! /bin/bash ## des - Sun Jul 11, 2010: Start backup server in order to run a full backup ## This script is called automaticaly by a cron job. set +e BIN=$(dirname $0) LOCK_FILE="$BIN/.lock_backup" # Find out backup target if [ -n "$1" ]; then echo "Target: $1" target=$1 else echo "Target: NONE --> Doing full backup" target="all" fi echo "backup target: $target" | mail -s "[HERMES] Starting backup for target $target" root # Check no other backup is currently running if [ -e "$LOCK_FILE" ]; then echo "ERROR: Cannot aquire lock file. Is another backup running?" echo "Delete $LOCK_FILE if an old backup aborted unforseen." exit 1 else touch $LOCK_FILE fi # Check whether server is already running ping -q -c 5 -w 5 backup > /dev/null SERVER_ON=$? # Start server if necessary if [ "$SERVER_ON" != 0 ]; then echo "Backup server not running. Wake up." $BIN/wakehost backup COUNT=0 while [ $COUNT -lt 18 ]; do let COUNT=COUNT+1 echo -n "*" ping -q -c 5 backup &> /dev/null SUCCESS=$? if [ "$SUCCESS" == 0 ]; then break else sleep 10 fi done echo if [ "$SUCCESS" != 0 ]; then echo "ERROR: Backup server didn't wake up!" exit 1 fi sleep 10 else echo "Backup server already running." fi # Do backup echo ssh backup /home/dennis/bin/backup-$target echo # Shutdown server if it wasn't running before USER_COUNT=$(ssh backup who | wc -l) if [ "$USER_COUNT" -eq "0" ]; then echo "Noone else logged in. Shutdown backup server." ssh backup sudo poweroff else echo "$USER_COUNT users still loged in. Can't shutdown backup server." fi # Release lock file rm $LOCK_FILE exit 0 |
Jetzt fehlt nurnoch ein kleiner Kniff, damit die beim Backup verschickten Mails in einem gesonderten Ordner meines Postfaches auftauchen. Hier kommt die Tatsache zum Tragen, dass Hermes auch der IMAP-Server für meine Mails ist und diese daher in einem Maildir liegen, das per fdm regelmäßig aus verschiedenen Quellen befüllt wird.
Zunächst wird ein neuer Systembenutzer und ein Alias für root auf diesen Benutzer benötigt. Somit wird erreicht, dass die Mails an den Administrator im Postfach des neuen Benutzers landen. Dieses kann dann von fdm via IMAP ausgelsen werden, um die Mails durch eine neue Regel in einen eigenen Ordner einzusortieren:
$ sudo adduser sysmail
Password:
$ sudo nano /etc/aliases
root:sysmail
Folgende Zeilen müssen in ~/.fdm.conf ergänzt werden:
Anpassungen an ~/.fdm.conf |
account "hermes-sysmail" imap server "localhost" user "sysmail" pass "..." action "md_system" maildir "${maildir}/.System" match account "hermes-sysmail" or "^(To:|Cc:|Bcc:).*root@" in headers or "^(To:|Cc:|Bcc:|X-Envelope-To:).*login@" in headers or "^(To:|Cc:|Bcc:|X-Envelope-To:).*system@" in headers or "^(To:|Cc:|Bcc:|X-Envelope-To:).*security@" in headers or "^(To:|Cc:|Bcc:|X-Envelope-To:).*sicherheit@" in headers action "md_system" |
Zuletzt muss der Heimserver noch wei ein normaler Client eingerichtet werden, damit auch er automatisch gesichert werden kann. Diese Prozedur unterscheidet sich nicht von der unten beschriebenen Konfiguration der Clients.
xxx