Backup

Backup

Ich denke, über den Sinn von Backups muss hier nichts erzählt werden. Dennoch empfehle ich zunächst die Lektüre folgender Quellen: [1], [2], [3] und [4].

Internes Backup

Auf dem Caipirinha-Server hatte ich zunächst viele Jahre lange einfach mit tar gesichert und dabei für Dokumente, Konfigurationsdateien und Projektdaten einzelne tar-, tgz- und tbz-Dateien erzeugt, und zwar Voll-Backups. Da sich nun aber auf dem Caipirinha-Server doch täglich einige Dateien ändern, bin ich nach einer längeren Durchsicht verschiedener Methoden darauf gekommen, Backups mit dar zu machen. dar funktioniert ähnlich wie tar, kann aber differentielle Backups machen. Aber betrachten wir zunächst einmal das Backup-Skript, welches über einen cron-Job jede Nacht ausgeführt wird:

/root/bin/backup_new.sh:

#!/bin/bash
# Automated backup script for important data on CAIPIRINHA
# Gabriel Rüeck, 20.09.2010

# Set appropriate umask.
umask 0077

# Create a directory for the backups if it does not already exist.
BACKUP_DIR='/backup/Backup-'$(date +%b)
test -d $BACKUP_DIR || mkdir $BACKUP_DIR

# Delete old backup directories.
find /backup -maxdepth 1 -daystart -mtime +62 -and -type d -name "Backup-*" -exec rm -rf {} \;

# Get the day of the month and determine whether full or incremental backups will be done. The first day will always result in a full backup.
DAY=$(date +%d)
if [ $DAY -eq 1 ]; then
   # Do a full backup every beginning of a month.
   dar -Q       -R/home/public/Audio       -c${BACKUP_DIR}/Audio-full       -u"user.Beagle" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q    -D -R/home/public/Bilder      -c${BACKUP_DIR}/Bilder-full      -u"user.Beagle" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/Dokumente   -c${BACKUP_DIR}/Dokumente-full   -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P"c't" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/GroupOffice -c${BACKUP_DIR}/GroupOffice-full -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/Linux       -c${BACKUP_DIR}/Linux-full       -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -X"*.iso" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R/home/public/Mail        -c${BACKUP_DIR}/Mail-full        -u"user.Beagle" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/Software    -c${BACKUP_DIR}/Software-full    -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -X"*.iso" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/Spiele      -c${BACKUP_DIR}/Spiele-full      -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -X"fearcombat_en_*" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public             -c${BACKUP_DIR}/Rest-full        -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P"Audio" -P"Bilder" -P"BitTorrent" -P"Datenbank*" -P"Dokumente" -P"GroupOffice" -P"Linux" -P"Mail" -P"Software" -P"Spiele" -P"Streaming" -P"Video" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R/etc                     -c${BACKUP_DIR}/etc-full         -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R/srv/www/htdocs          -c${BACKUP_DIR}/www-full         -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P"mrtg" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q       -R/var/spool/cron/tabs     -c${BACKUP_DIR}/cron_tables-full -u"user.Beagle" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'

   dar -Q -y    -R/root                    -c${BACKUP_DIR}/root_files-full  -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P".adobe" -P".strigi" -P".mozilla" -P".thumbnails" -P".tvbrowser" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R/home                    -c${BACKUP_DIR}/user_files-full  -u"user.Beagle" -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -Z"*.mpg" -Z"*.avi" -Z"*.mkv" -P"ftp" -P"mailowner" -P"public" -P"tmp" -P"*/.adobe" -P"*/.strigi" -P"*/.google" -P"*/.mozilla" -P"*/.thumbnails" -P"*/.tvbrowser" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'

   # Check the integrity of the backup files.
   dar -Q -t${BACKUP_DIR}/Audio-full       >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Bilder-full      >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Dokumente-full   >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/GroupOffice-full >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Linux-full       >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Mail-full        >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Software-full    >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Spiele-full      >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Rest-full        >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/etc-full         >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/www-full         >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/cron_tables-full >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'

   dar -Q -t${BACKUP_DIR}/root_files-full  >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/user_files-full  >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
else
   # Do an incremental backup on all remaining days of the month.
   dar -Q       -R/home/public/Audio       -c${BACKUP_DIR}/Audio-${DAY}       -u"user.Beagle" -A${BACKUP_DIR}/Audio-full       >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q    -D -R/home/public/Bilder      -c${BACKUP_DIR}/Bilder-${DAY}      -u"user.Beagle" -A${BACKUP_DIR}/Bilder-full      >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/Dokumente   -c${BACKUP_DIR}/Dokumente-${DAY}   -u"user.Beagle" -A${BACKUP_DIR}/Dokumente-full   -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P"c't" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/GroupOffice -c${BACKUP_DIR}/GroupOffice-${DAY} -u"user.Beagle" -A${BACKUP_DIR}/GroupOffice-full -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/Linux       -c${BACKUP_DIR}/Linux-${DAY}       -u"user.Beagle" -A${BACKUP_DIR}/Linux-full       -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -X"*.iso" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R/home/public/Mail        -c${BACKUP_DIR}/Mail-${DAY}        -u"user.Beagle" -A${BACKUP_DIR}/Mail-full        >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/Software    -c${BACKUP_DIR}/Software-${DAY}    -u"user.Beagle" -A${BACKUP_DIR}/Software-full    -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -X"*.iso" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public/Spiele      -c${BACKUP_DIR}/Spiele-${DAY}      -u"user.Beagle" -A${BACKUP_DIR}/Spiele-full      -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -X"fearcombat_en_*" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y -D -R/home/public             -c${BACKUP_DIR}/Rest-${DAY}        -u"user.Beagle" -A${BACKUP_DIR}/Rest-full        -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P"Audio" -P"Bilder" -P"BitTorrent" -P"Datenbank*" -P"Dokumente" -P"GroupOffice" -P"Linux" -P"Mail" -P"Software" -P"Spiele" -P"Streaming" -P"Video" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R/etc                     -c${BACKUP_DIR}/etc-${DAY}         -u"user.Beagle" -A${BACKUP_DIR}/etc-full         -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R/srv/www/htdocs          -c${BACKUP_DIR}/www-${DAY}         -u"user.Beagle" -A${BACKUP_DIR}/www-full         -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P"mrtg" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q       -R/var/spool/cron/tabs     -c${BACKUP_DIR}/cron_tables-${DAY} -u"user.Beagle" -A${BACKUP_DIR}/cron_tables-full >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
                                                                                              
   dar -Q -y    -R/root                    -c${BACKUP_DIR}/root_files-${DAY}  -u"user.Beagle" -A${BACKUP_DIR}/root_files-full  -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P".adobe" -P".strigi" -P".mozilla" -P".thumbnails" -P".tvbrowser" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R/home                    -c${BACKUP_DIR}/user_files-${DAY}  -u"user.Beagle" -A${BACKUP_DIR}/user_files-full  -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -Z"*.mpg" -Z"*.avi" -Z"*.mkv" -P"ftp" -P"mailowner" -P"public" -P"tmp" -P"*/.adobe" -P"*/.strigi" -P"*/.google" -P"*/.mozilla" -P"*/.thumbnails" -P"*/.tvbrowser" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'

   # Check the integrity of the backup files.
   dar -Q -t${BACKUP_DIR}/Audio-${DAY}       >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Bilder-${DAY}      >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Dokumente-${DAY}   >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/GroupOffice-${DAY} >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Linux-${DAY}       >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Mail-${DAY}        >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Software-${DAY}    >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Spiele-${DAY}      >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/Rest-${DAY}        >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/etc-${DAY}         >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/www-${DAY}         >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/cron_tables-${DAY} >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'

   dar -Q -t${BACKUP_DIR}/root_files-${DAY}  >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/user_files-${DAY}  >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
fi

# MySQL login information
readonly MYSQL_USERNAME='root'
readonly MYSQL_PASSWORD='geheimes_passwort'

# Create a directory for the database backups if it does not already exist.
MYSQL_BACKUP_DIR='/backup/MySQL-Backup_KW'$(date +%U)
test -d $MYSQL_BACKUP_DIR || mkdir $MYSQL_BACKUP_DIR
PGSQL_BACKUP_DIR='/backup'

# Delete old backup directories.
find /backup -maxdepth 1 -daystart -mtime +31 -and -type d -name "MySQL-Backup_KW*" -exec rm -rf {} \;
find /backup -maxdepth 1 -daystart -mtime +31 -and -type f -name "postgres_KW*" -exec rm {} \;

# Database backups will be done once per week, in the early morning of Sunday.
WEEKDAY=$(date +%u)
if [ $WEEKDAY -eq 7 ]; then
   # Scan the MySQL Server for valid databases and create a dump file for each database found.
   DATABASE_LIST=$(mysql -e 'show databases' -u$MYSQL_USERNAME -p$MYSQL_PASSWORD)
   DATABASE_LIST=$(echo $DATABASE_LIST | cut -d " " -f2-)
   for DATABASE in $DATABASE_LIST
   for DATABASE in CCPM_Test PCBA nonCCPM_Test
       do mysqldump -BC --hex-blob -u$MYSQL_USERNAME -p$MYSQL_PASSWORD $DATABASE | gzip -n9 | cat - > ${MYSQL_BACKUP_DIR}/${DATABASE}.sql.gz
   done

   # Backup the Postgres Server
   su -l postgres -c "pg_dumpall -c | gzip -n9 | cat - > $PGSQL_BACKUP_DIR/postgres_KW$(date +%U).sql.gz"
fi

Alle Backups werden auf eine separate Festplatte geschrieben, die als /backup eingebunden ist. Diese Festplatte befindet sich im Server selbst, und deswegen bietet ein Backup dorthin lediglich Schutz gegen Ausfall der Systemplatten oder versehentliches Löschen von Daten in den entsprechenden Verzeichnissen der Systemplatte, nicht aber gegen Blitzschlag, Sabotage oder Diebstahl der Maschine.

Nach dem Aufrufen werden zunächst die umask entsprechend gesetzt. Danach prüft das Skript, ob ein Verzeichnis /backup/Backup-xyz existiert, wobei xyz die Abkürzung des aktuellen Monatsnamens ist. Existiert ein solches Verzeichnis nicht, dann wird es erzeugt. Nun prüft das Skript, ob solche Verzeichnisse existieren, die älter als 62d sind. Diese werden allesamt gelöscht, denn auf der Backup-Festplatte ist natürlich nur begrenzt viel Platz. Der weitere Verlauf des Skripts hängt nun davon ab, ob der aktuelle Tag der erste Tag eines Monats ist.

Am ersten Tag eines Monats wird ein Voll-Backup gemacht, an allen Folgetagen des Monats ein differentielles Backup; und zwar differentiell zum Voll-Backup des Monatsanfangs. Dabei wird der dar-Befehl eingesetzt, dessen Optionen auf der man-Page (man dar) gut beschrieben sind. Einige Punkte sind hierbei hervorzuheben:

  • Die Option -u”user.Beagle” bewirkt, dass die erweiterten Attribute jeder Datei, welche durch Beagle geändert worden sind, nicht berücksichtigt werden. Dies ist natürlich nur für diejenigen wichtig, die auch den Beagle-Dienst auf ihrem Linux-Server laufen haben. Dieser Suchdienst durchforstet periodisch alle Dateien in den eingestellten Verzeichnissen und legt seine Daten als erweitertes Attribut ab. Dadurch ändert sich jedes Mal die Datei, jedenfalls aus Sicht von dar, und wenn man die erweiterten Attribute nicht ausnimmt, kann es vorkommen, dass dar dann täglich ein Vollbackup macht, obwohl sich lediglich die Beagle-Informationen, nicht aber die Dateiinhalte geändert haben. Alles selbst erlebt.
  • Am Ende jedes dar-Kommandos wird die Ausgabe von dar nach /dev/null geschickt, denn das Skript soll im fehlerfreien Fall keine Ausgabe erzeugen. Um aber festzustellen, ob ein Fehler aufgetreten ist und in welcher Zeile des Skripts ein solcher Fehler aufgetreten ist, gibt es die Sequenz “[ $? -eq 0 ] || echo ...” nach jedem dar-Befehl. Das ist zwar nicht besonders elegant, dafür aber zweckmäßig.
  • Viele Verzeichnisse enthalten sowohl komprimierbare Dateien als auch Dateien, die bereits komprimiert sind (zip, jpg, tgz, …). Hier macht es Sinn, beim dar-Kommando die Option “-y” zu nutzen, dann aber für die entsprechenden Dateien die Komprimierung mit der Option “-Z” abzuschalten. Sonst verschwendet man nur Rechenzeit, weil dar versucht, eine bereits komprimierte Datei nochmals mit bzip2 zu komprimieren.

Nach den Backups wird die Integrität der Backup-Dateien geprüft.

An allen Folgetagen des Monats wird nur eine differentielle Sicherung durchgeführt, und zwar differentiell in Bezug auf die Sicherung vom Monatsanfang. Damit erreicht man an allen Folgetagen des Monats kürzere Backup-Zeiten. Zum nächsten Monatsanfang läßt man dann alle geänderten Dateien in ein neues Voll-Backup einfließen, und die differentiellen Backups werden dann wieder klein.

Danach kommt die Sicherung der MySQL– und der Postgres-Datenbanken. Die Datenbanken werden jeden Sonntag gesichert. Dieser Teil des Backup-Skripts folgt also einem anderen Rhythmus als der erste Teil mit den dar-Kommandos. Für die MySQL-Datenbanken wird zunächst ein Verzeichnis namens /backup/MySQL-Backup_KWxy erstellt, wobei xy die aktuelle Kalenderwoche ist. Existiert ein solches Verzeichnis nicht, dann wird es erzeugt. Alte Verzeichnisse werden nach einiger Zeit (31d) wieder gelöscht. Für die MySQL-Datenbanken wird eine Liste aller Datenbanken erstellt, jede Datenbank mit dem Kommando mysqldump ausgelesen, komprimiert und in dem zu der aktuellen Kalenderwoche gehörigen Verzeichnis abgelegt. Die Postgres-Datenbanken werden mit dem Befehl pg_dumpall ausgelesen, komprimiert und mit einem entsprechenden Namen, der die aktuelle Kalenderwoche beinhaltet, in /backup abgelegt. Da diese Sicherung als Benutzer postgres abläuft, muss das Verzeichnis /backup entweder für alle Benutzer beschreibbar sein, was ein gewisses Sicherheitsrisiko darstellt. Oder man erzeugt alternativ ein separates Verzeichnis in /backup, welches dem Benutzer postgres gehört und sichert die Dateien dort hinein.

Backup einer entfernten Maschine

Auf dem Caipirinha-Server werden auch Backups der entfernten Maschine rueeck.name erstellt. Dazu dient dieses Skript:

/root/bin/backup_1blu.sh:

#!/bin/bash
# Dieses Skript synchronisert wichtige Verzeichnisse des 1blu-Servers mit einem temporären Verzeichnis und erzeugt Backups.
# Gabriel Rüeck, 10.08.2010

readonly SOURCE='v37829.1blu.de'
readonly SYNCDIR='/backup/1blu/sync/'

# Wichtige Verzeichnisse synchronisieren
rsync -azq --rsh=ssh --delete --exclude="coppermine/" --exclude="mediawiki/" --exclude="mrtg/" --exclude="phpPgAdmin/" --exclude="webalizer/"    ${SOURCE}:/srv/www/htdocs/    ${SYNCDIR}/srv/
rsync -azq --rsh=ssh --delete    ${SOURCE}:/home/public/GroupOffice       ${SYNCDIR}
rsync -azq --rsh=ssh --delete    ${SOURCE}:/etc                           ${SYNCDIR}
rsync -azq --rsh=ssh --delete    ${SOURCE}:/var/spool/cron/tabs           ${SYNCDIR}
rsync -azq --rsh=ssh --delete    ${SOURCE}:/root                          ${SYNCDIR}

# Set appropriate umask.
umask 0077

# Create a directory for the backups if it does not already exist.
BACKUP_DIR='/backup/1blu/dar/Backup-'$(date +%b)
test -d $BACKUP_DIR || mkdir $BACKUP_DIR

# Delete old backup directories.
find /backup/1blu/dar -maxdepth 1 -daystart -mtime +62 -and -type d -name "Backup-*" -exec rm -rf {} \;

# Get the day of the month and determine whether full or incremental backups will be done. The first day will always result in a full backup.
DAY=$(date +%d)
if [ $DAY -eq 1 ]; then
   # Do a full backup every beginning of a month.
   dar -Q -y -D -R${SYNCDIR}GroupOffice -c${BACKUP_DIR}/GroupOffice-full  -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R${SYNCDIR}etc         -c${BACKUP_DIR}/etc-full          -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q       -R${SYNCDIR}tabs        -c${BACKUP_DIR}/cron_tables-full  >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R${SYNCDIR}root        -c${BACKUP_DIR}/root_files-full   -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P".adobe" -P".strigi" -P".mozilla" -P".thumbnails" -P".tvbrowser" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R${SYNCDIR}srv         -c${BACKUP_DIR}/srv_files-full    -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'

   # Check the integrity of the backup files.
   dar -Q -t${BACKUP_DIR}/GroupOffice-full >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/etc-full         >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/cron_tables-full >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/root_files-full  >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/srv_files-full   >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
else
   # Do an incremental backup on all remaining days of the month.
   dar -Q -y -D -R${SYNCDIR}GroupOffice -c${BACKUP_DIR}/GroupOffice-${DAY} -A${BACKUP_DIR}/GroupOffice-full  -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R${SYNCDIR}etc         -c${BACKUP_DIR}/etc-${DAY}         -A${BACKUP_DIR}/etc-full          -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q       -R${SYNCDIR}tabs        -c${BACKUP_DIR}/cron_tables-${DAY} -A${BACKUP_DIR}/cron_tables-full  >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R${SYNCDIR}root        -c${BACKUP_DIR}/root_files-${DAY}  -A${BACKUP_DIR}/root_files-full   -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" -P".adobe" -P".strigi" -P".mozilla" -P".thumbnails" -P".tvbrowser" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -y    -R${SYNCDIR}srv         -c${BACKUP_DIR}/srv_files-${DAY}   -A${BACKUP_DIR}/root_files-full   -Z"*.zip" -Z"*.bz2" -Z"*.gz" -Z"*.tgz" -Z"*.jpg" -Z"*.png" >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'

   # Check the integrity of the backup files.
   dar -Q -t${BACKUP_DIR}/GroupOffice-${DAY} >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/etc-${DAY}         >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/cron_tables-${DAY} >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/root_files-${DAY}  >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
   dar -Q -t${BACKUP_DIR}/srv_files-${DAY}   >/dev/null; [ $? -eq 0 ] || echo 'In Zeile '$LINENO' ist ein Fehler aufgetreten!'
fi

# MySQL login information
readonly MYSQL_USERNAME='root'
readonly MYSQL_PASSWORD='geheimes_passwort'

# Create a directory for the database backups if it does not already exist.
MYSQL_BACKUP_DIR='/backup/1blu/mysql/Backup-KW'$(date +%U)
test -d $MYSQL_BACKUP_DIR || mkdir $MYSQL_BACKUP_DIR
PGSQL_BACKUP_DIR='/backup/1blu/pgsql'

# Delete old backup directories.
find /backup/1blu/mysql -maxdepth 1 -daystart -mtime +62 -and -type d -name "Backup-KW*" -exec rm -rf {} \;
find /backup/1blu/pgsql -maxdepth 1 -daystart -mtime +62 -and -type f -name "Backup-KW*" -exec rm {} \;

# Database backups will be done once per week, in the early morning of Sunday.
WEEKDAY=$(date +%u)
if [ $WEEKDAY -eq 7 ]; then
   # Save MySQL databases
   DATABASE_LIST=$(ssh ${SOURCE} "mysql -e 'show databases' -u$MYSQL_USERNAME -p$MYSQL_PASSWORD")
   DATABASE_LIST=$(echo $DATABASE_LIST | cut -d " " -f2-)   for DATABASE in $DATABASE_LIST
       do ssh ${SOURCE} "mysqldump -BC --hex-blob -u${MYSQL_USERNAME} -p${MYSQL_PASSWORD} ${DATABASE} | gzip -n9" > ${MYSQL_BACKUP_DIR}/${DATABASE}.sql.gz
   done

   # Backup the Postgres Server
   ssh ${SOURCE} "su -l postgres -c 'pg_dumpall -c | gzip -n9'" > $PGSQL_BACKUP_DIR/postgres_KW$(date +%U).sql.gz
fi

Dieses Skript ist ähnlich aufgebaut wie dasjenige im Abschnitt Backup#Internes_Backup. Zu Beginn des Skripts werden die zu sichernden Verzeichnisse der entfernten Maschine mit dem rsync-Kommando in ein lokales “Schattenverzeichnis” (/backup/1blu/sync/) synchronisiert. Das Sichern mit rsync hat mehrere Vorteile:

  • rsync funktioniert gut über schwankende oder instabile Netzwerkverbindungen.
  • Es werden immer nur die geänderten Dateien übermittelt.
  • Die Übermittlung erfolgt verschlüsselt mit ssh.

Man kann erkennen, dass sich der Benutzer root des Caipirinha-Servers offensichtlich ohne Passwort als Benutzer root auf der Maschine v37829.1blu.de einloggen kann. Wie das funktioniert, ist beispielsweise in [5] und in [6] beschrieben. Danach werden die Unterverzeichnisse in /backup/1blu/sync/ mit dem Kommando dar nach dem gleichen Prinzip gesichert wie im Abschnitt Backup#Internes_Backup beschrieben. Danach folgen die Backups der Datenbanken MySQL und Postgres, deren Prinzip ebenfalls bereits beschrieben wurde. Neu ist lediglich, dass die entsprechenden Kommandos jetzt auf der entfernten Maschine v37829.1blu.de ausgeführt werden müssen und deshalb in entsprechende ssh ${SOURCE} "..."-Sequenzen eingesetzt werden müssen.

Dieses Prinzip funktioniert nur dann, wenn man es sich leisten kann, ein lokales Schattenverzeichnis zu bevorraten. Ist der Plattenplatz knapp, so muss man die elegantere Lösung wählen, einen externen Katalog des Voll-Backups durch dar erstellen zu lassen, an Hand dessen man dann gleich auf der entfernten Maschine ein differentielles Backup anstoßen kann, denn den Katalog kann man ja auf der entfernten Maschine bekannt machen. Diese Möglichkeit habe ich aber noch nicht ausprobiert.

Synchronisation mit einer entfernten Maschine

Wie es der Teufel so will, stirbt der Server zu Hause in der Wohnung, während man gerade in Urlaub weilt oder aus sonstigen Gründen nicht gleich Hand an die betroffene Maschine anlegen kann. Dagegen hilft nur, einen zweiten oder gar dritten Server in Betrieb zu halten, auf den regelmäßig gesichert wird und auf dem die gleichen Applikationen installiert sind. Dann kann man selbst im Urlaub noch schnell auf die andere Maschine “umschalten”. Eine solche Synchronisation wird beispielsweise zwischen den Maschinen caipirinha.homelinux.org, caipiroska.homelinux.org und rueeck.name durchgeführt, und zwar ebenfalls einmal pro Nacht. Das entsprechende Skript ist:

/root/bin/server_sync.sh:

#!/bin/bash
# Dieses Skript dient der Synchronisation des schwarzen Caipirinha-Servers mit dem blauen Caipirinha-Server und mit dem 1blu-Server
# Gabriel Rüeck, 10.08.2010

readonly BLU='v37829.1blu.de'
readonly CAIPIROSKA='caipiroska.homelinux.org'
readonly USER='Gabriel'
readonly RSYNC_PWD_FILE='/root/rsync_caipiroska.txt'

# Öffentliche Verzeichnisse synchronisieren
rsync -aq  --rsh=ssh --delete   /home/public/Bilder                  ${CAIPIROSKA}:/home/public/
rsync -azq --rsh=ssh --delete   /home/public/Dokumente               ${CAIPIROSKA}:/home/public/
rsync -azq --rsh=ssh --delete   ${BLU}:/home/public/GroupOffice      /home/public/
rsync -azq --rsh=ssh --delete   ${BLU}:/home/public/Mail             /home/public/
rsync -aq  --rsh=ssh --delete   /home/public/Mail-Indizierung        ${CAIPIROSKA}:/home/public/
rsync -aq  --rsh=ssh --delete   /home/public/Software                ${CAIPIROSKA}:/home/public/
rsync -aq  --rsh=ssh --delete   /home/public/Unterhaltung            ${CAIPIROSKA}:/home/public/

# Webserver-Verzeichnisse synchronisieren
rsync -azq --rsh=ssh --delete   ${BLU}:/srv/www/htdocs/coppermine    /srv/www/htdocs/
#rsync -azv --rsh=ssh --delete   ${BLU}:/srv/www/htdocs/groupoffice   /srv/www/htdocs/
rsync -azq --rsh=ssh --delete   ${BLU}:/srv/www/htdocs/mediawiki     /srv/www/htdocs/

# Persönliche Verzeichnisse synchronisieren
rsync -azv --rsh=ssh --delete --exclude="nohup.out" --exclude=".*"   /home/gabriel/       ${CAIPIROSKA}:/home/gabriel/
rsync -azv --rsh=ssh --delete --exclude="nohup.out" --exclude=".*"   /home/joselia/       ${CAIPIROSKA}:/home/joselia/

# tmp- und dav-Ordner synchronisieren
unison caipiroska.prf
unison 1blu.prf

# Verzeichnisse über den rsyncd auf caipiroska synchronisieren
ssh ${CAIPIROSKA} '/etc/init.d/rsyncd start' >/dev/null
rsync -aq  --delete --password-file=${RSYNC_PWD_FILE}     /home/public/6502/         ${USER}@${CAIPIROSKA}::6502
rsync -aq  --delete --password-file=${RSYNC_PWD_FILE}     /home/public/Audio/        ${USER}@${CAIPIROSKA}::Audio
rsync -azq --delete --password-file=${RSYNC_PWD_FILE}     /home/public/Bücher/       ${USER}@${CAIPIROSKA}::Bücher
rsync -azq --delete --password-file=${RSYNC_PWD_FILE}     /home/public/Datenblätter/ ${USER}@${CAIPIROSKA}::Datenblätterrsync -aq  --delete --password-file=${RSYNC_PWD_FILE}     /home/public/Linux/        ${USER}@${CAIPIROSKA}::Linux
rsync -aq  --delete --password-file=${RSYNC_PWD_FILE}     /home/public/Spiele/       ${USER}@${CAIPIROSKA}::Spiele
ssh ${CAIPIROSKA} '/etc/init.d/rsyncd stop' >/dev/null

In diesem Skript werden Verzeichnisse zwischen den genannten drei Maschinen synchronisiert. Dabei kommen die Kommandos rsync für eine unidirektionale und unison für eine bidirektionale Synchronisation zum Einsatz. Des Weiteren wird rsync in zwei Betriebsarten eingesetzt:

  • Remote Shell: In dieser Betriebsart (erster Block an rsync-Anweisungen) werden auf dem Quell- und auf dem Zielrechner je eine rsync-Instanz gestartet. Die Datenübertragung erfolgt durch eine gesicherte Verbindung (ssh). Dieser Modus ist geeignet für Daten, die auch auf ihrem Übertragungsweg nicht abgefangen werden dürfen, weil sie vertraulich sind.
  • Rsync-Dämon: In dieser Betriebsart hat entweder der Quell- oder der Zielrechner einen rsync-Dienst (üblicherweise auf Port 873) aktiv. Dieser Dienst beinhaltet eine Konfigurationsdatei, in der Verzeichnisse aufgeführt sind. Mit diesen Verzeichnissen kann synchronisiert werden. Die Anmeldung ist zwar verschlüsselt, der Datentransfer aber nicht mehr. Diese Art der Synchronisation eignet sich also nur für Daten, die auch mitgelesen werden können, weil sie keine vertraulichen Informationen enthalten. Der Vorteil ist, dass wegen der fehlenden Verschlüsselung weniger Maschinenlast erzeugt wird.

Die hier zum Einsatz kommende Konfigurationsdatei des rsync-Dienstes sieht so aus:

/etc/rsyncd.conf:

log file = /var/log/rsyncd.log
log format = %h %o %f %l %b
max connections = 4
pid file = /var/run/rsyncd.pid
read only = false
slp refresh = 300
timeout = 600
transfer logging = true
uid = gabriel
use chroot = true
use slp = false

[6502]
path = /home/public/6502
auth users = Gabriel
secrets file = /etc/rsyncd.secrets
gid = users

[Audio]
path = /home/public/Audio
auth users = Gabriel
secrets file = /etc/rsyncd.secrets
gid = relationship

[Bücher]
path = /home/public/Bücher
auth users = Gabriel
secrets file = /etc/rsyncd.secrets
gid = users

[Datenblätter]
path = /home/public/Datenblätter
auth users = Gabriel
secrets file = /etc/rsyncd.secrets
gid = users

[Linux]
path = /home/public/Linux
auth users = Gabriel
secrets file = /etc/rsyncd.secrets
gid = users

[Spiele]
path = /home/public/Spiele
auth users = Gabriel
secrets file = /etc/rsyncd.secrets
gid = users

In der Datei /etc/rsyncd.secrets sind alle zulässigen Benutzer des rsync-Dienstes und deren Passwort im Klartext ausgeführt. Deshalb darf diese Datei nur für root lesbar sein. Es muss auch klar sein, dass man eben deswegen auf keinen Fall das System-Passwort verwenden darf.

Auch für unison werden Konfigurationsdateien benötigt, nämlich die beiden hier im Skript angegebenen Dateien /root/.unison/caipiroska.prf und /root/.unison/1blu.prf, welche die Verzeichnisse festlegen, die zwischen den beteiligten Maschinen bidirektional synchronisiert werden sollen. Sie sehen so aus:

/root/.unison/caipiroska.prf:

# Synchronisation zwischen CAIPIRINHA und CAIPIROSKA

root = ssh://caipiroska.homelinux.org//home/
root = /home/

sshargs = -C -o ConnectTimeout=300
fastcheck = yes
maxthreads = 1
perms = -1
batch = true
silent = true
owner = true
group = true
logfile = /var/log/unison

# Prioirität festlegen
prefer = /home/

# Zu synchronisierende Pfade
path = tmp

ignore = Path tmp/Blue-Ray/*

/root/.unison/1blu.prf:

# Synchronisation zwischen CAIPIRINHA und 1BLU

root = ssh://rueeck.name//home/public/
root = /home/public/

sshargs = -C -o ConnectTimeout=300
fastcheck = yes
maxthreads = 1
perms = -1
batch = true
silent = true
owner = true
group = true
logfile = /var/log/unison

# Prioirität festlegen
prefer = ssh://rueeck.name//home/public/

# Zu synchronisierende Pfade
path = Dropbox

ignore = Name .*

Durch das Sichern auf externe Maschinen verschafft man sich einen Schutz gegen Feuer-, Wasser-, Kriegsschäden und gegen einen möglichen Diebstahl des Servers. Ein Saboteur, dem ein Einloggen als root auf einer der Maschinen gelingt, könnte wegen der automatischen Synchronisation aber weiterhin alle Daten sabotieren. Dagegen hilft wirklich nur ein Backup auf eine externe Festplatte, die nach dem Backup von der Maschine getrennt und sicher verwahrt wird. Eine solche externe Festplatte sollte natürlich (wie auch die Server-Platten) verschlüsselt sein, und idealerweise hat man mehrere davon, so dass man immer noch ein Backup zur Verfügung hat, wenn beim Sichern gerade der Blitz in den Server und die angeschlossene externe Festplatte schlägt und alles futsch ist.

Posted on: 2010-09-20Gabriel Rüeck