Es kann immer mal wieder vorkommen, dass Daten durch einen Systemfehler unwiderruflich verloren gehen oder Schafsoftware ihr Unwesen treibt. Daher sind regelmäßige Sicherungen Pflicht und das auch im kleinsten Homelab. Doch das Aufsetzen eines Backup-Servers kostet leider etwas Geld und noch dazu viel Zeit. Viele sträuben sich daher vor der Thematik.
Doch so muss das nicht laufen. Wer mit wenig Rechenleistung und Plattenspeicher auskommt, kann bereits mit einem ausgemusterten PC starten. Ich selbst habe hingegen einen alten HP Proliant Microserver mit dem vielsagenden Namen N40l ersteigert. Viel Power hat die kleine Kiste nicht, aber dafür sind 4 Festplatteneinschübe für 3,5 Zoll große Festplatten verbaut.
Mehr HDDs würde die schwachbrüstige Hardware beim Einsatz von ZFS aber auch nicht abkönnen. Im Großen und Ganzen war der kleine Rechner für deutlich unter 100 Euro ein solider Kauf. Mir gefällt vor allem der kleine Formfaktor und die sehr gute Verarbeitung. Sogar eine ganze Menge Festplatten-Schrauben samt einem passenden Inbus verstecken sich im Inneren der Tür.
Doch genug über meine neue Hardware geredet. Zur Synchronisation mit meinen Geräten greife ich auf das altbewährte Tool RSync zurück. Aus Sicherheitsgründen kommt dabei das Pull-Verfahren zum Einsatz. Dabei holt der Backup-Server die Dateien von einem entfernten System ab. Netter Nebeneffekt ist dabei noch, dass man die ganzen Konfigurationen zentral am Backup-Server tätigen kann.
Doch wie funktioniert RSync genau, wie habe ich meinen Backup-Server im Detail konfiguriert und wieso setze ich auf ZFS im Daten-Pool? Das sind nur einige der Fragen, welche ich im Folgenden beantworten möchte. Selbstredend werde ich zum Abschluss auch noch alle Skripte teilen, die ich zum Anfertigen der Backups nutze und auch zum Überwachen der vorhandenen Software-Raids.
Wie funktioniert RSync genau?
Bei RSync handelt es sich um ein plattformübergreifendes Tool für die Dateisynchronisation und -übertragung in Computernetzwerken. Ursprünglich wurde RSync für Unix-Systeme entwickelt, ist jedoch heute auf einer Vielzahl von Betriebssystemen verfügbar, darunter Linux, macOS, Windows und BSD. Die Implementierung ist dabei unter Windows nicht wirklich gut gelungen.
RSync bietet eine effiziente Möglichkeit, große Datenmengen zwischen verschiedenen Rechnern oder Dateisystemen zu synchronisieren und zu kopieren, indem es nur die Änderungen zwischen den Quell- und Zielordnern überträgt. Dadurch wird die Übertragung von großen Dateien beschleunigt und die Belastung des Netzwerks und der beteiligten Systeme reduziert.
Obwohl es auch grafische Tools für RSync gibt, ist ein Shell-Skript oft die ressourcenschonendere Variante für automatisierte Backups. Trotzdem möchte ich grafische Umsetzungen wie Grsync oder BackupPC nicht unerwähnt lassen. Letzteres ist dabei besonders interessant, da unter anderem inkrementelle Backups erstellt werden können. Außerdem werden redundante Daten eliminiert.
Ich selbst brauche diese Features allerdings allesamt nicht. Abgesehen davon habe ich auch keinen Bedarf an einer GUI. Diese ist besonders dann wichtig, wenn man dutzende Hosts und deren Sicherungen verwalten möchte. Sollte ich in Zukunft aber noch Plattenplatz sparen wollen, bringt das von mir eingesetzte ZFS Deduplikation sowie Kompression von Haus aus mit.
Wie ist mein Server konfiguriert?
Ich nutze auf meinem Backup-Server ein Mdraid für das Betriebssystem sowie einen ZFS-Mirror für das Datengrab. Als OS kommt dabei Debian zum Einsatz. Einmal wöchentlich fahre ich den Server mittels Wake on Lan hoch und lasse über einen Cronjob das Backup-Skript ausführen. Um sicherzustellen, dass meine Daten immer geschützt sind, überwache ich auch die beiden Raids mit einem Skript.
Dieses startet ebenfalls direkt nach dem Booten. Dank dieser automatisierten Prozesse kann ich mich entspannt zurücklehnen und habe stets eine zuverlässige Sicherungskopie in der Hinterhand. Seitdem ich weiß, dass die Daten meiner NAS sowie die Backups meiner virtuellen Maschinen doppelt vorhanden sind, kann ich nachts auch wieder ruhig schlafen.
Nicht zuletzt, da bauartbedingt in meinem Hypervisor nur eine einzelne Festplatte als Sicherungsziel für Backups genutzt werden kann. Um die genaue Ausstattung meines Proxmox-Servers oder mein Wohlbefinden in Bezug auf Sicherungen soll es hier aber nicht gehen. Die meisten meiner Leser interessieren sich natürlich für meine Skripte.
Bevor ich aber auf diese recht langen Codeberge eingehen werde, möchte ich noch ein paar Learnings von dem Projekt teilen. Ich bin mir nämlich relativ sicher, dass nicht jeder diese Punkte auf dem Schirm hat:
- SSDs sind inzwischen wirklich günstig geworden. Wer nicht allzu viel Speicherbedarf hat, sollte lieber zu ihnen greifen. SSDs sind einfach deutlich leistungsfähiger und das macht sich bemerkbar. Mein ZFS-Mirror mit SMR-HDDs schafft gerade einmal Schreibarten von 50 bis 60 MB/s. Im Vergleich dazu erreicht mein SSD-Mirror im Proxmox-Host bis zu 2,4 GB/s.
- ZFS-Features wie Deduplikation und Komprimierung brauchen viel schnellen RAM und noch dazu eine flotte CPU. Ansonsten reduziert sich die Geschwindigkeit der Datenübertragung von Minute zu Minute.
- Micro-Server sind deutlich leiser als ihre Pendants im 19-Zoll-Format. Im Vergleich zu einer NAS oder einem PC sind sie aber noch sehr gut wahrnehmbar. Von Wohnzimmer tauglich würde ich also nicht sprechen.
Wie funktionieren meine Skripte?
Das Herzstück meines Backup-Servers ist natürlich das Kommandozeilenprogramm RSync und der von mir dafür geschriebene Bash-Code. Kurz gesagt enthält das Skript verschiedene Einstellungen, Variablen und Funktionen, um Backups zu erstellen und passende Benachrichtigungen an den Serveradministrator zu versenden.
Wichtig war mir dabei, dass jeder User selbst entscheiden kann, ob er via Mail oder Gotify eine Nachricht erhalten möchte. Selbstverständlich lassen sich die Benachrichtigungen auch vollends abschalten und es wird dann nur die Logdatei mit Inhalt befüllt. Außerdem überprüft das Skript noch, ob RSync installiert ist und holt dies für Debian-Systeme bei Bedarf nach.
Damit der eigentliche Schreib-Prozess so performant wie möglich vonstattengeht, wird noch das ZFS-Kernelmodul aktiviert. In meinen Tests hat sich die Leistung des Raid 1 damit von 36 auf 56 MB/s gesteigert. Und da Shell-Skripte selten eine Augenweide sind, habe ich noch ein bisschen mit Farben gespielt. Aber jetzt genug geredet, hier noch das kleine Skript:
#!/bin/bash
##########################
# NOTIFICATION SETTINGS #
##########################
# For email notifications to work, a mail transfer agent must be installed and configured correctly
send_mail="no" # yes or no
recipientmail="your_mail@here.com"
# For gotify notifications to work, a gotify server must be must be present and set up correctly
send_gotify="no" # yes or no
gotify_token="Your_Token_here"
#################################
# RSYNC PARAMETER FOR PROXNATOR #
#################################
remote_user_proxnator="root"
remote_host_proxnator="1.2.3.4"
remote_dir_proxnator="/mnt/backupplatte"
local_backup_dir_proxnator="/mnt/zfs-mirror/hypervisor/proxmox-01"
###########################
# RSYNC PARAMETER FOR NAS #
###########################
remote_user_terramaster="admin"
remote_host_terramaster="1.1.1.2"
remote_dir_terramaster="/mnt/md0"
local_backup_dir_terramaster="/mnt/zfs-mirror/nas/nas-01"
###########
# LOGFILE #
###########
log_dir="/var/log/sync"
log_file="$(date +'%Y-%m-%d').log"
log_path="${log_dir}/${log_file}"
###############################
# CHECK IF RSYNC IS INSTALLED #
###############################
if ! command -v rsync &> /dev/null; then
# IF NOT, INSTALL RSYNC
echo "rsync is not installed. Installing it now..."
sudo apt update
sudo apt install rsync -y
fi
############################################
# CREATE LOG DIRECTORY IF IT DOESN'T EXIST #
############################################
if [ ! -d "$log_dir" ]; then
mkdir -p "$log_dir"
fi
############################################################
# CREATE LOCAL DIRECTORY FOR PROXNATOR IF IT DOESN'T EXIST #
############################################################
if [ ! -d "$local_dir_proxnator" ]; then
mkdir -p "$local_backup_dir_proxnator"
fi
######################################################
# CREATE LOCAL DIRECTORY FOR NAS IF IT DOESN'T EXIST #
######################################################
if [ ! -d "$local_dir_nas" ]; then
mkdir -p "$local_backup_dir_terramaster"
fi
##############################################
# LOGGER FUNCTION TO LOG TO CONSOLE AND FILE #
##############################################
function logger {
echo "$(date +"%d.%m.%Y %H:%M:%S") $1"
echo "$(date +"%d.%m.%Y %H:%M:%S") $1" >> "$log_path"
}
###########################################################################
# FUNCTION TO SEND EMAIL IF VARIABLE send_mail IS SET TO "yes" or "true " #
###########################################################################
send_email() {
if [[ "$send_mail" =~ ^(yes|true)$ ]]; then
local subject="$1"
local log_content=$(cat "$log_path")
echo "$log_content" | mail -s "$subject" "$recipientmail"
fi
}
############################################################################################
# FUNCTION TO SEND GOTIFY-NOTIFICATIONS IF VARIABLE send_gotify IS SET TO "yes" or "true " #
############################################################################################
send_gotify() {
if [[ "$send_gotify" =~ ^(yes|true)$ ]]; then
local subject="$1"
local log_content=$(cat "$log_path")
statuscode=$(curl --write-out '%{http_code}' --silent --output /dev/null -X POST "https://gotify.fabianscloud.de/message?token=${gotify_token}" -F "title=${subject}" -F "message=${log_content}" -F "priority=8")
if [ ${statuscode} -ne 200 ]; then
logger "The Gotify API could not be reached."
exit 1;
fi
fi
}
##############################################################################
# Enabling the kernel module increased the ZFS write rate from 36 to 56 MB/s #
##############################################################################
modprobe zfs
if [ $? -eq 0 ]
then
echo "" | tee -a "$log_path"
echo "+++ $(date +"%d.%m.%Y %H:%M:%S") +++" | tee -a "$log_path"
echo -e "\033[1mStarting with Rsync pull backups...\033[0m" | tee -a "$log_path"
echo "" | tee -a "$log_path"
echo -e "\033[32mThe ZFS kernel module was successfully loaded. Because of that the write rate is as fast as possible.\033[0m" | tee -a "$log_path"
echo "" | tee -a "$log_path"
else
echo "" | tee -a "$log_path"
echo "+++ $(date +"%d.%m.%Y %H:%M:%S") +++" | tee -a "$log_path"
echo -e "\033[1mStarting with Rsync pull backups...\033[0m" | tee -a "$log_path"
echo -e "\033[31mThe ZFS kernel module could not be loaded. Because of that the write rate is not as fast as possible.\033[0m" | tee -a "$log_path"
echo ""
fi
##########################
# SYNC FILES - PROXNATOR #
##########################
logger "Starting sync from Proxnator server..."
rsync -a --delete --stats "$remote_user_proxnator@$remote_host_proxnator:$remote_dir_proxnator/*" "$local_backup_dir_proxnator" 2>&1 | tee -a "$log_path"
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "" | tee -a "$log_path"
logger "Error occurred while syncing files from Proxnator server. Please check the log file at $log_path for more details."
send_email "Rsync backup failed for Proxnator"
send_gotify "Rsync backup failed for Proxnator"
exit 1
fi
####################
# SYNC FILES - NAS #
####################
echo "" | tee -a "$log_path"
logger "Starting sync from NAS server..."
rsync -r --delete --stats --ignore-errors --exclude='application' --exclude='lost+found' --exclude='@system' --exclude='@zlog' --exclude='User' --exclude='appdata' --exclude='quota.group' --exclude='quota.user' "$remote_user_terramaster@$remote_host_terramaster:$remote_dir_terramaster/*" "$local_backup_dir_terramaster" 2>&1 | tee -a "$log_path"
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "" | tee -a "$log_path"
logger "Error occurred while syncing files from NAS server. Please check the log file at $log_path for more details."
send_email "Rsync backup failed for NAS"
send_gotify "Rsync backup failed for NAS"
exit 1
fi
#######################
# PRUNE OLD LOG FILES #
#######################
if [ $(find "$log_dir" -name "*.log" -mtime +30 -type f | wc -l) -gt 0 ]; then
find "$log_dir" -name "*.log" -mtime +30 -type f -delete
echo "" | tee -a "$log_path"
logger "Old log files have been deleted."
fi
###################
# SUCCESS MESSAGE #
###################
echo "" | tee -a "$log_path"
echo -e "\033[32mSync process completed successfully.\033[0m" | tee -a "$log_path"
echo ""
########################################
# SEND NOTIFICATIONS WITH INFORMATIONS #
########################################
send_email "Summary of rsync backup"
send_gotify "Summary of rsync backup"
exit 0
Der beste Backup-Server bringt einem natürlich nichts, wenn die Festplatten im Laufe der Zeit unbemerkt ausfallen. Deshalb überwache ich beide Software-Raids mit einem kleinen Shell-Skript. Im Detail betrachtet, wird dabei wie folgt vorgegangen. Zunächst wird ein Log-Verzeichnis erstellt und der Hostname des Systems ermittelt.
Das Skript überprüft dann, ob die RAID-Systeme Fehler aufweisen und gibt eine entsprechende Meldung aus. Falls ein Fehler auftritt, wird eine Benachrichtigung über Gotify gesendet. Zum Schluss wird eine Meldung ausgegeben, dass alle RAID-Systeme in Ordnung sind. Wer das Ganze lieber per Mail bekommen möchte, kann das über die crontab und Mail-Direktiven erledigen lassen.
#!/bin/bash
#############################
# Set the log file location #
#############################
LOGDIR="/var/log/raid-checks"
LOGFILE="/var/log/raid-checks/raid_check.log"
##################################
# Get the hostname of the system #
#################################
HOSTNAME=$(hostname -f)
############################################
# Create log directory if it doesn't exist #
############################################
if [ ! -d "$LOGDIR" ]; then
mkdir -p "$LOGDIR"
fi
#######################################
# Create log file if it doesn't exist #
#######################################
if [ ! -f "$LOGFILE" ]; then
touch "$LOGFILE"
fi
########################################################################
# Print a greeting message with the hostname and devices being checked #
########################################################################
echo ""
echo -e "\033[1m$(date +%Y-%m-%d-%H:%M:%S) Starting RAID and ZFS pool check on $HOSTNAME...\033[0m" | tee -a $LOGFILE
echo ""
echo "Checking the following devices:"
echo ""
echo "- Md-Raid /dev/md0"
echo "- ZFS pool zfs-mirror"
echo ""
#################################################################
# Check if any of the disks in the md raid are faulty or failed #
#################################################################
mdadm --detail /dev/md0 | egrep 'faulty|failed|error' > /dev/null
if [ $? -eq 0 ]; then
# If a faulty or failed disk is found, log the error and send a gotify notificationi
echo -e "\033[31mERROR: The mdraid for the operating system has errors. Probably one disk is faulty or failed.\033[0m" | tee -a $LOGFILE
# Send notficiation via Gotify if one disk is faulty
statuscode=$(curl --write-out '%{http_code}' --silent --output /dev/null -X POST "https://gotify.fabianscloud.de/message?token=YOUR_TOKEN_HERE" -F "title=Storinator - MDRaid degraded" -F "message=ERROR: A disk in the md raid for the OS is faulty or failed." -F "priority=8")
if [ ${statuscode} -ne 200 ]; then
echo -e "\033[31mThe Gotify notification could not be sent.\033[0m" | tee -a $LOGFILE
fi
else
# If no faulty or failed disk is found, then log the success message
echo -e "\033[32mSUCCESS: \033[0mNo faulty or failed disk found in the md raid for the operating system." | tee -a $LOGFILE
fi
#################################################
# Check if there are any errors in the ZFS pool #
#################################################
zpool status -x zfs-mirror > /dev/null
zpool_status=$?
if [ "$zpool_status" -ne 0 ]; then
##################################################################################
# If there are errors in the ZFS pool, log the error and exit with an error code #
##################################################################################
echo -e "\033[31mERROR: The zfs-mirror pool has errors. Probably one disk is faulty or failed.\033[0m" | tee -a $LOGFILE
######################################################
# Send notification via Gotify if one disk is faulty #
######################################################
statuscode=$(curl --write-out '%{http_code}' --silent --output /dev/null -X POST "https://gotify.fabianscloud.de/message?token=YOUR_TOKEN_HERE" -F "title=Storinator - ZFS Pool degraded" -F "message=ERROR: The zfs-mirror pool has errors. Probably one disk is faulty or failed" -F "priority=8")
if [ ${statuscode} -ne 200 ]; then
echo -e "\033[31mThe Gotify notification could not be sent.\033[0m" | tee -a $LOGFILE
fi
exit 1
elif [ "$zpool_status" -eq 0 ]; then
################################################################################################
# If no errors are found in the ZFS pool, log the success message and exit with a success code #
################################################################################################
echo -e "\033[32mSUCCESS: \033[0mThe zfs-mirror pool for the backupgs has no errors." | tee -a $LOGFILE
fi
#########################################
# Final message that all RAIDs are fine #
#########################################
echo ""
echo -e "\033[32m>>> All disks are fine. <<<\033[0m" | tee -a "$LOGFILE"
echo ""