Howto georedundantes Backup

Nachdem vom Homeserver die ersten SMART-Warnungen kamen, war es an der Zeit, über eine neue Backup-Lösung nachzudenken. Nachdem wir als Firma 2020 lernen durften, dass ein Raid kein Backup ist, sollte die Wahl dieses Mal auf ein echtes Backup fallen. Und da der Trend natürlich zum Zweitstandort geht, war klar, dass eine echte Georedundanz dabei nicht fehlen durfte. Denn was bringen mir schließlich zwei Homeserver mehr als einer? Maximal, dass die Panzerknacker mehr zu tragen haben, wenn sie mir die Bude ausräumen möchten.

Als Standort für einen zweiten Knoten schien mir das Haus meiner Eltern ganz brauchbar – ein paar Terabyte Nutzlast sicherten dann auch die Bereitschaft zu, selbigen zu finanzieren, also war als zweites Ziel jedoch geboren, die goldenen Platten im Laden zu lassen und eine Lösung zu finden, die auch noch halbwegs erschwinglich sein sollte. Um den Panzerknackern dann ob des geringen Warenwerts den Coup zusätzlich zu vermiesen, sollte zudem eine Vollverschlüsselung mitgedacht sein.

Nach einiger Überlegung ergaben sich damit die folgenden Anforderungen an das Backup-System:

  • georedundante Speicherung
  • relativ erschwingliche Kosten
  • Möglichkeit zur Nutzung herkömmlicher DSL-Leitungen
  • Vollverschlüsselung der Daten
  • verschlüsselte Datenübertragung
  • Trennung der Nutzerdaten für mehrere Nutzer
  • ausschließliche Nutzung von Open Source
  • keine Einbindung von Drittanbieterdiensten
  • Datentransfer für Nutzer via SFTP
  • lokaler Datenzugriff, volles Backup
  • Im Fehlerfall einfache Wiederherstellung der Dienste
  • 10TB Nutzlast + Möglichkeit zur Erweiterung
  • geringer Stromverbrauch
  • Möglichkeit zur Erweiterung durch weitere Standorte
  • Unterstützung von Gruppenordnern

Plattform

Um die Kosten und den Stromverbrauch gering zu halten, fiel die Wahl auf je einen Raspberry Pi 4B mit 8GB RAM pro Standort. Als Datenspeicher wurde je eine WD Red 10TB gewählt. Um die Wärmeentwicklung bestmöglich abführen zu können, wurde statt eines herkömmlichen Gehäuses ein Festplattendock mit USB 3.0-Anschluss gewählt. Das wiederum würde die Möglichkeit bieten, bei Bedarf auch gleich eine zweite 10TB-Platte beherbergen zu können.

Systemstruktur

Da über die Raspberry-Lösung der Nutzspeicher nur als externes Medium zur Verfügung stand, fiel die Wahl darauf, das System auf die interne MicroSD-Karte zu schreiben und nicht zu verschlüsseln. Die externe Festplatte wurde mittels LUKS verschlüsselt. Das Setup hat somit zum Vorteil, dass der Raspberry Pi selbstständig durchbooten kann und im Falle eines Defekts eine MicroSD-Karte einfach per Post verschickt werden kann. Im Fall eines Diebstahls kann zwar auf das System, jedoch nicht auf die Nutzlast der externen Festplatte zugegriffen werden. Da die Platte nicht mit einem System bespielt werden muss, kann sie im Fehlerfall lokal neu beschafft werden. Als wesentliche Nachteile sind der eher geringe Datendurchsatz von Raspberry Pi zur Festplatte und die Möglichkeit zur unentdeckten Kompromittierung des Systems auf dem Raspberry Pi zu sehen: Wer unbemerkt Zugriff hat, könnte Zusatzsoftware installieren, über die ein Mitschneiden der LUKS-Keyeingabe oder ein Kopieren der Daten von der entschlüsselten Festplatte ermöglicht. Auch war zu Projektbeginn unklar, ob der Raspberry Pi dem Umfang an Datentransfer und -verschlüsselung gerecht wird, diese Sorge stellte sich jedoch als unbegründet heraus.

Für die Verwaltung von Nutzern und Gruppen (für geteilte Ordner) sollte die Linux-Benutzerverwaltung zum Einsatz kommen, um die bewährte systemseitige Sicherheit zu haben und keinen unnötigen Overhead zu betreiben.

Software

Die Vernetzung beider Knoten sollte möglichst leichtgewichtig sein und wenig Overhead generieren. Außerdem sollte das System autark abseits der bereits bestehenden OpenVPN-Infrastrukturen funktionieren, um einerseits die Anzahl der involvierten Systeme klein zu halten und andererseits den Angriffsvektor auf mein Netzwerk zu minimieren. Daher fiel die Wahl auf Wireguard – die Konfiguration ist denkbar einfach:

# NODE 1
[Interface]
Address = 10.0.0.1/32
PrivateKey = H34dsf36LKgmrigFDttezt345/wfds=
ListenPort = 51820

[Peer]
PublicKey = MIOijs894ejk23Kmk43dtMFGr34)JIf=
AllowedIPs = 10.0.0.2/32


# NODE 2
[Interface]
Address = 10.0.0.1/32
PrivateKey = Hrewg5(4ffdsvh5JKHUr43rfdsbg0=

[Peer]
PublicKey = WDS334fvGbdft54/(4tfdsdsq34RFD=
AllowedIPs = 10.0.0.0/24
EndPoint = public-domain-of-node1.de:51820
PersistentKeepalive = 25 

Mittels wg genkey lässt sich dabei ein passendes Schlüsselpaar generieren, das auf die jeweiligen Configs zu verteilen ist und wg-quick up wg0 startet die Verbindung – fertig. Wichtig ist dabei nur, nicht zu glauben, AllowedIPs würde eine Liste an IPs führen, denen ein Verbindungsaufbau erlaubt ist. Stattdessen verbirgt sich hinter der Direktive nämlich die zu routenden Subnetze des Peers – wer hier 0.0.0.0 einträgt, kann sich damit also sehr gut selbst ausschließen (für Sie getestet!)

Bei der Art der Synchronisierung habe ich lange über verteilte Dateisysteme nachgedacht, was mir aufgrund der hohen Netzwerklatenz letztendlich jedoch zu unstabil erschien. Stattdessen sollte Syncthing zum Einsatz kommen – hier stellte sich jedoch leider heraus, dass Syncthing keinen Mehrbenutzermodus anbietet, stattdessen können nur Dateien des Users synchronisiert werden, unter dem der Dienst läuft. Neue Dateien werden dann auch grundsätzlich mit UID und GID dieses Nutzers geschrieben, was den Einsatz als root unmöglich macht (von dem ohnehin abgeraten wird). Dokumentierte Abhilfe ist, für jeden Nutzer eine Syncthing-Instanz laufen zu lassen, was mir jedoch recht aufwendig erschien und zudem auch noch für jeden gemeinsamen Ordner eine weitere Instanz bedurft hätte.

Stattdessen ist die Wahl final auf Unison gefallen – auch die recht schmucke Website im Retrodesign konnte mich nicht davon abhalten. Somit konnte ich den Wunsch, die Linux-Benutzerverwaltung auch zur Verwaltung und Trennung der einzelnen Benutzerordner zu nutzen (und nicht noch einen separaten FTP-Server o.ä. mit eigener Benutzerverwaltung on top zu verwenden). Die Konfiguration von Unison gestaltet sich dabei ähnlich einfach wie die von Wireguard:

root = /mnt/usb0/sync
root = ssh://user@10.0.0.2//mnt/usb0/sync
batch = true
auto = true
servercmd = sudo unison
group = true
owner = true
times = true
confirmbigdeletes = true
fastcheck = true
silent = true
prefer = newer
xferbycopying = false

Mittels der beiden root-Direktiven werden dabei die zu synchronisierenden Verzeichnisse angegeben. Die flags group, owner und times sorgen dafür, dass UIDs, GIDs und Erstellungszeiten mit synchronisiert werden, als kleiner Bonus sorgt confirmbigdeletes dafür, dass eine Synchronisierung nicht durchgeführt wird, wenn einer der beiden root-Ordner leer ist – praktisch für den Fall, dass nach einem Stromausfall zwar der Raspberry Pi neu bootet, die LUKS-Partition jedoch noch nicht wieder entschlüsselt wurde, der Mountpoint somit leer ist und eine Synchronisierung somit wunderbar die MicroSD-Karte vollschreiben würde!

Zu guter Letzt letzt wurde Zabbix verwendet, um den Zustand der Knoten zu überwachen und einen Ausfall mitzubekommen. Ein kleines eigenes Template ermöglicht zudem einen minimalen Einblick in den Zustand der Synchronisierung:

UserParameter=backup.filecount_local,df -i /dev/mapper/usb0 | grep mapper | awk '{split($0,a); print a[3]}'
UserParameter=backup.lastrun,stat -c %Y /var/log/unison.log
UserParameter=backup.laststate,cat /var/log/unison.log

Ursprünglich kam hier find zum Zählen der Dateien auf beiden Knoten zum Einsatz, allerdings kam das mit steigender Dateianzahl schnell an seine Grenzen, weshalb das Zählen der verwendeten Inodes über df eine deutlich bessere Alternative darstellte. Als kleines Gimmick lassen sich die Informationen aus Zabbix natürlich auch entsprechend in Grafana auswerten:

Synchronisierungs-Dashboard in Grafana

Zur kompletten Einrichtung der Knoten wurde Ansible gewählt – sowohl zur Grundeinrichtung des Systems, zur Verwaltung der Benutzer und Gruppenordnern, als auch zum Mounten und Unmounten der USB-Platten, um zu vermeiden, dass die LUKS-Keys auf den jeweiligen Raspberrys hinterlegt werden müssten.

Fazit

Die Einrichtung der Systeme gelang insbesondere durch die sehr einfache Konfiguration von Wireguard und Unison sehr einfach – die Nutzung von Ansible tat ihr übriges.

Ein erster Stresstest mit mittlerweile fast 2 Mio. Dateien und knapp 2TB Daten klappt erstaunlich gut, selbst über wackelige belgische DSL-Leitungen mit gelegentlichen Abbrüchen. Es macht geradezu Spaß, Unison beim Synchronisieren zuzuschauen – auch die Menge der Dateien hat bislang keinen erkennbaren Einfluss auf die Belastung der Systeme. Es bleibt abzuwarten, wie sich der Langzeittest entwickelt, bis dato kann ich die Lösung aber durchaus als stabil weiterempfehlen!


Schreibe einen Kommentar