Common
selfmade HA für Webanwendungen
20. November 2012
0

Basics

Es geht hier um die praktische Umsetzung einer einfachen Desaster-Recovery Strategie für Webanwendungen.
Auch wenn Backups pflicht sind, kann ein Serverausfall zu unangenehm langen Downtimes führen. Ein möglicher Ansatz soll hier diskutiert werden.

Der Grundgedanke besteht darin, die benötigten Daten auf ein Backup-System zu replizieren und im Fehlerfall einen möglichst schnellen Failover durchzuführen.

Rahmenbedingungen

  • Debian Linux als Betriebssystem
  • MySQL als DBMS
  • Apache2 als Webserver

Replikation

Die zu replizierenden Daten bestehen zum einen aus einer Datenbank und zum anderen aus der eigentlichen Webanwendung in Form von Dateien. Für den Export der Datenbank wird mysqldump verwendet. Die exportierte Datenbank und die übrigen Dateien werden mit rsync auf das Backup-System übertragen.

Dafür sollten im Vorfeld SSH-Keys ausgetauscht werden, damit sich das Produktiv-System ohne Passworteingabe gegenüber dem Backup-System authentifizieren kann.

Datenbankexport

Alle Datenbanken bis auf die in “IGNORE” genannten werden nach “REPL_TEMP” exportiert als einzelne .sql Dateien. Dabei wird der Account “debian-sys-maint” verwendet, welcher automatisch bei der Installation von mysql aus den Paketquellen angelegt wird in “/etc/mysql/debian.cnf”.

db_export.sh:

#!/bin/bash
# script for one-way replication of web-apps
# Marco Hoyer | [email protected]
# version 0.1

# REPL_TEMP: temp directory for exported db's
# IGNORE: list of db's to be ignored
# CONF: mysql sysmaintainer config file
# SYSNAME: hostname of this system
REPL_TEMP=/var/db_replication_temp
IGNORE="phpmyadmin|mysql|information_schema|performance_schema|icinga"
CONF=/etc/mysql/debian.cnf
SYSNAME=`hostname`

# parse available db's
DBS="$(/usr/bin/mysql --defaults-extra-file=$CONF -Bse 'show databases' | /bin/grep -Ev $IGNORE)"
if [ $? -ne 0 ]; then
        echo "[ERROR] - could not parse db's on $SYSNAME"
        exit 1
fi

# export db's to REPL_TEMP
for DB in $DBS; do
        /usr/bin/mysqldump --defaults-extra-file=$CONF --databases --add-drop-database $DB > $REPL_TEMP/$DB.sql
        if [ $? -ne 0 ]; then
                echo "[ERROR] - could not dump databases on $SYSNAME"
                exit 1
        fi
done

echo "[OK] - successfully exported db's on $SYSNAME"
exit 0

Replikation der Daten

Das Skript ruft db_export.sh auf um die Datenbanken zu exportieren, anschließend werden die Datenbankexport, die Konfigurationsdaten für Apache2 und die eigentlichen Webseitendateien per rsync auf das Backup-System übertragen.

Rsync wird dabei über Parameter angewiesen, Rechte und Besitzer der Dateien zu übernehmen.

replicate.sh:

#!/bin/sh
# script replicating web-apps to a remote-site using rsync and mysqldump
# Marco Hoyer | [email protected]
# version 0.1

# LOG: path to log file
# REMOTE_LOCATION: ip or dns-name of the replication target system
# REMOTE_USER: user to authenticate with on remote system (should be made by ssh-keys)
# SYSNAME: hostname of this system
# MODULE: name of this artifact for log identification
LOG=/var/log/replication.log
REMOTE_LOCATION=<target system>
REMOTE_USER=<usermane>
SYSNAME=`hostname`
MODULE=REPLICATE

echo "$MODULE [INFO] - replication started on $SYSNAME at `date`" >> $LOG

# db export
/scripts/db_export.sh
if [ $? -ne 0 ]; then
        echo "$MODULE [ERROR] - db_export.sh failed on $SYSNAME" >> $LOG
        exit 1
fi

# apache2 site-configuration replication
rsync --verbose --progress --stats --compress --recursive --perms --owner --group --times --links --delete /etc/apache2/sites-enabled/ ${REMOTE_USER}@${REMOTE_LOCATION}:/etc/apache2/sites-enabled/
if [ $? -ne 0 ]; then
        echo "$MODULE [ERROR] - apache2 sited-enabled replication failed on $SYSNAME" >> $LOG
        exit 1
fi

# www-data replication
rsync --verbose --progress --stats --compress --recursive --perms --owner --group --times --links --delete /var/www/ ${REMOTE_USER}@${REMOTE_LOCATION}:/var/www/
if [ $? -ne 0 ]; then
        echo "$MODULE [ERROR] - www-data replication failed on $SYSNAME" >> $LOG
        exit 1
fi

# mysql dump replication
rsync --verbose --progress --stats --compress --recursive --perms --owner --group --times --links --delete /var/db_replication_temp/ ${REMOTE_USER}@${REMOTE_LOCATION}:/var/db_replication_temp/
if [ $? -ne 0 ]; then
        echo "$MODULE [ERROR] - db-dump replication failed on $SYSNAME" >> $LOG
        exit 1
fi

# trigger remote db restore
ssh ${REMOTE_USER}@${REMOTE_LOCATION} /scripts/db_restore.sh
if [ $? -ne 0 ]; then
        echo "$MODULE [ERROR] - remote db-restore failed on $REMOTE_LOCATION" >> $LOG
        exit 1
fi

echo "$MODULE [OK] - replication successful on $SYSNAME at `date`" >> $LOG
exit 0

Wiederherstellung der DB auf dem Backup-System

Nachdem alle Daten auf das Backup-System übertragen wurden, startet replicate.sh auf dem Zielsystem db_restore.sh, um die Datenbanksicherungen in das DBMS zurückzusichern.

Achtung: Die Benutzer und Rechte werden nicht automatisch übertragen und müssen initial manuell angelegt werden.

#!/bin/sh
# script restoring databases found in RESTORE_PATH
# counterpart for remote-replication
# author: Marco Hoyer | [email protected]
# version 0.1

# REPL_TEMP: temp directory for exported db's (beware of the *)
# CONF: mysql sysmaintainer config file
# SYSNAME: hostname of this system
# MODULE: name of this artifact for log identification
REPL_TEMP=/var/db_replication_temp/*
CONF=/etc/mysql/debian.cnf
SYSNAME=`hostname`
MODULE=DB-RESTORE

for FILE in $REPL_TEMP; do
        sudo mysql --defaults-extra-file=$CONF < $FILE
        if [ $? -ne 0 ]; then
                echo "$MODULE [ERROR] - could not restore db: $FILE on $SYSNAME"
                exit 1
        fi
done

echo "$MODULE [OK] - db restore successful on $SYSNAME"
exit 0

Failover

Nachdem nun die Daten hoffentlich erfolgreich auf das Backup-System übertragen wurden, muss im Worst-Case der Server auch einspringen können und von den Clients erreicht werden.

Die Verwendung eines hochverfügbaren Load-Balancers (z.B. als Managed Service) scheidet auf Grund der hohen Kosten aus. Die einfachste denkbare Lösung besteht darin, Clients direkt auf den Backup-Server zu leiten.

Dies wird mit Hilfe von DNS realisiert. Da eine Webseite idealerweise über eine Domäne angesprochen wird und das Hosting der DNS-Zone recht günstig und hochverfügbar von einem Dienstleister erledigt werden kann, muss im Fehlerfall nur die IP-Adresse des DNS-Records für die Webseite geändert werden.

Damit dies möglichst schnell von statten geht, sollte die TTL (idR. 1 Tag) im Vorfeld auf einen sehr niedrigen Wert geändert werden. Dabei haben sich Werte im Bereich von wenigen Minuten bewährt. Zu niedrig und der Name muss zu häufig abgefragt werden, was verhältnismäßig lange dauert und zu Performance-Problemen auf Clientseite führen kann.

Test

Schlussbemerkungen

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.