LUG Erding

GlusterFS und redundante Daten


Dirk Geschke, LUG-Erding


Letzte Änderung: 15.6.2020


Einleitung

Es gibt viele Konzepte um hochverfügbare Daten bereitzustellen. Oft läuft es aber auf ein externes Speichersystem hinaus, auf das mehrere Systeme Zugriff haben. Damit sind dann aber auch wieder zwei Probleme verbunden:

Die erste Frage ist nicht so einfach zu beantworten, schließlich sin die meisten Systeme nicht dafür ausgelegt, damit zu rechnen, dass gleichzeitig jemand anderes eine Datei verändern kann. Oft wird das Problem dadurch umgangen, dass nur ein System schreibenden Zugriff hat, ein anderes System wird erst aktiv, wenn das erste ausfällt. Das ist natürlich nicht ideal, für manche Anforderungen aber durchaus akzeptabel.

Der zweite Punkt ist natürlich auch wichtig. Für Zugriffe über das Netzwerk sind da andere Geschwindigkeiten akzeptabel als zum Beispiel für einen lokalen Speicher für schnelle Berechnungen die einen hohen I/O-Bedarf haben.
Erfolgt aber der Zugriff nur über das Netzwerk und soll möglichst immer möglich sein, so ist in aller Regel der Durchsatz des Netzwerkes entscheidend. Dieser ist oft deutlich geringer als der von Festplatten oder RAID-Systemen.

Der letzte Punkt ist dabei oft besonders wichtig: Was passiert, wenn ein zentrales System ausfällt? Wenn ein Server ausfällt, dann ein zweiter in Betrieb geht und auf das zentrale Speichermedium zugreift, dann gibt es einen kurzen Aussetzer, das lässt sich kaum vermeiden.
Aber was ist, wenn das zentrale Speichersystem ausfällt?

Die Lösung bedeutet letztendlich, dass das zentrale Speichersystem ebenfalls redundant vorhanden sein muss. Wenn aber die Daten auf zwei Systemen parallel geschrieben und von da auch gelesen werden können, was passiert dann, wenn die Synchronisation zwischen diesen Servern gestört ist?
Oder anders formuliert: Woher weiß ein System, welches noch aktiv ist und welches nicht? Vielleicht glaubt jedes System, es wäre das einzig aktive Speichermedium und lässt jeweils Änderungen an den Daten zu. Wer soll diese dann wieder korrekt zusammenführen können?
Diese Konstallation wird split-brain genannt, beide Systeme glauben das einzig aktive System zu sein.

Eine Lösung, es gibt hier durchaus zahlreiche, kann durch GlusterFS realisiert werden. Dabei sind zwei Server involviert, die jeweils alle Daten vorrätig haben. Um die split-brain Problematik zu umgehen, gibt es noch die Option einen dritten Server einzubinden. Dieser hält dann nur die Metadaten der Dateien vor und ist obendrein ein Garant für ein Quorum:

Der letzte Punkt klingt seltsam, aber es kann durchaus sein, dass lokal alles läuft, dennoch die Netzwerkverbindung unterbrochen ist. Das System ist also isoliert. Dieser Fall wäre für sich kein Problem, wenn kein Netzwerk funktioniert, kann auch kein Prozess über das Netzwerk zugreifen.
Allerdings kann es durchaus sein, dass das Netzwerk nur eingeschränkt funktioniert, es könnte auch ein Router ausgefallen sein. Oder die Replikation der Daten läuft über ein anderes Netzwerk als die Clientzugriffe. In diesem Fall kann es also durchaus sein, dass die Clients mal auf den einen und mal auf den anderen Server zugreifen, diese Server sich aber gegenseitig dennoch nicht mehr sehen.

So gesehen, werden drei Server benötigt. Da der dritte aber nur die Metadaten vorhält, braucht dieser zum einen deutlich weniger Festplattenplatz, zum anderen spielt hier dadurch die Performance keine wichtige Rolle mehr. Die bleibt für die zwei eigentlichen Datenserver relevant.

Replikation

Mein Ziel war es hier, die Daten auf zwei Servern liegen zu haben, also doppelt gespeichert. Fällt dann ein Server aus, kann der andere weiterarbeiten und umgekehrt. Das GlusterFS sorgt auch dafür, dass die Server sich wieder synchronisieren, wenn sie zum Beispiel nach einem Ausfall wieder verfügbar sind.

Ein Problem gibt es aber:

Was passiert, wenn beide Server laufen, sich aber nicht mehr gegenseitig sehen?

Das ist dann ein großes Problem, wie bereits oben erläutert, die könnten beide veränderte Daten haben und es ist nicht mehr möglich diese korrekt zu synchronisieren. Daher wären drei Server besser, hier kann mit einem Quorum gearbeitet werden. Fällt einer aus, so sehen die anderen sich noch gegenseitig. Damit ist klar, welcher isoliert ist. Sieht ein Server keine andere Gegenstelle, dann ist er der inaktive und stellt den Dateidienst ein.

Das ist eine feine Sache, jedoch wären die Daten dann nicht doppelt sondern dreifach vorhanden. Das ist dann schon eine ordentliche Platzverschwendung.

GlusterFS bietet hier eine Alternative: den Arbiter, also einen Schiedsrichter. Dieser ist zum einen ein sichtbarer Server um das Quorum zu ermöglichen. Zum anderen speichert dieser aber auch die Metadaten der Dateien, also so etwas wie Zugriffsrechte, Eigentümer, Zeitstempel und noch ein wenig mehr. Damit werden pro Datei rund 4kB an Daten benötigt, das ist wohl vertretbar.

Über diese Metadaten kann dann ein Server nach z.B. einem Neustart feststellen, ob seine lokale Datei aktuell ist oder ob sie neu abgeglichen werden muss. Es könnte ja sein, dass der eine Server wieder verfügbar ist und dann der andere ausfällt...

Auch bei diesem Konstrukt sind Probleme durchaus möglich, es ist nur unwahrscheinlich, dass sie eintreten. Das wäre zum Beispiel der Fall, wenn ein Server nach einem Ausfall wieder verfügbar wird und zeitgleich der andere Server ausfällt.

IP Adressen

Ideal ist eine Namensauflösung per DNS, man kann aber auch der Einfachheit wegen erst einmal mit Einträgen in der Datei /etc/hosts arbeiten.

Ideal ist auch beides: Auf den Servern kann man dann für die Systeme andere IP-Adressen setzen, als für die Clients. Dadurch kann dann ein anderes Netzwerk für die Synchronisation der Daten verwendet werden.

Wichtig ist auch, dass ein DNS-Name verwendet wird, der auf die zwei geplanten Dateiserver auflöst. Sollte just einer der beiden gerade nicht erreichbar sein, wird der andere Server verwendet.

So kann auch per round robin schon vorab eine Art von load balancing erfolgen. Allerdings erledigt das auch das GlusterFS im Protokoll selber. Nach der ersten Verbindung wird einem Client der zu verwendende Server zugewiesen.

Für meinen Testaufbau verwende ich nun folgende Adressen:

10.0.0.1 gl-server1 gl-server
10.0.0.2 gl-server2 gl-server
10.0.0.3 gl-arbiter

Theoretisch könnte dabei der gl-arbiter auch noch als gl-server verwendet werden, ich habe hier aber darauf verzichtet. Sollten die beiden anderen nicht erreichbar sein, nützt dieser alleine auch nichts.

Brick

Eine Brick ist bei GlusterFS das physikalische Medium, auf dem die Daten dann jeweils auf dem Server liegen. Im Idealfall ist das ein logical volume, es geht auch eine Partition. Es sollte aber nicht einfach nur ein Verzeichnis sein, obwohl das auch möglich jedoch nicht empfohlen ist.

Hat man eine volume group zum Beispiel vg-gluster, kann so ein logical volume erstellt werden:

   # lvcreate -n gl-brick -L10G vg-gluster

Danach muss diese formatiert werden, ich nehme da eigentlich immer XFS, damit habe ich bislang nur sehr gute Erfahrungen gemacht:

# mkfs -t xfs /dev/vg-gluster/gl-brick

Zum Schluss wird das Verzeichnis noch gemountet, zum Beispiel:

# mkdir -p /data/gl-brick
# mount /dev/vg-cluster/gl-brick /data/gl-brick
# chmod 0700 /data/gl-brick

Der letzte Schritt soll verhindern, dass da andere Benutzer einen direkten Zugriff haben. Die Daten, welche hier landen, gehören dann eventuell dem User und er könnte sie hier verändern wollen. Das ist definitiv keine gute Idee...

Das muss auf allen drei Servern erfolgen, lediglich der geplante Arbiter kann ein deutlich kleineres logical volume verwenden, hier liegen schließlich nur die Metadaten. Zur Not könnte hier also auch einfach nur ein lokales Verzeichnis dafür verwendet werden.

Peering

Das klingt kompliziert, es müssen die drei Server gekoppelt werden. Es ist aber erstaunlich einfach, alles was man tun muss ist auf einem der Server die anderen zu proben. Das ist hier exemplarisch von gl-server-1 aus erfolgt:

# gluster peer probe gl-server-2
peer probe: success.
# gluster peer probe gl-arbiter
peer probe: success.

Das war es schon, die Peers können sogar gesehen und angezeigt werden:

# gluster peer status
Number of Peers: 2

Hostname: gl-server-2
Uuid: 3b9d41ff-c8d5-47f5-b5ac-641c08115552
State: Peer in Cluster (Connected)

Hostname: gl-arbiter
Uuid: 743db9f2-2f48-4fde-a12b-b5e910050395
State: Peer in Cluster (Connected)

Einfacher geht das nun wirklich nicht mehr!

GlusterFS Volume

Auch dieser Schritt ist völlig einfach. Zuerst muss dafür in der obigen Brick ein Verzeichnis für das Volume erstellt werden. Ideal ist hier der gleiche Name, ich dafür einmal vm gewählt:

# mkdir /data/gl-brick/vm

Das muss natürlich auf allen drei Servern erfolgen. Da können die Daten natürlich an unterschiedlichen Orten liegen und die Namen können anders sein. Ratsam ist es jedoch, überall das gleiche Namensschema zu verwenden, sonst wird man später im Laufe des Betriebsn noch wahnsinnig:

Da nun alle Bricks vorhanden sind, kann jetzt der letzte Schritt erfolgen. Das Anlegen geht ebenfalls einfach und muss nur auf einem der drei Server ausgeführt werden:

# gluster volume create vm replica 2 arbiter 1 gl-server-1:/data/gl-brick/vm
gl-server-2:/data/gl-brick/vm gl-arbiter:/data/gl-brick/vm force

cas force am Ende wird dann notwendig, wenn eine Brick nicht durch eine eigene Partition oder logical volume bereitgestellt wird. Das sollte vermieden werden, in meinem Fall hatte ich hier aber keine andere Möglichkeit und es betrifft nur den arbiter: Das ist bei mir ein Raspberry Pi mit lediglich einer SD-Karte.

Das war es dann schon, das Ergebnis kann angezeigt werden:

# gluster volume status vm
Status of volume: vm
Gluster process                             TCP Port  RDMA Port  Online  Pid
------------------------------------------------------------------------------
Brick gl-server-1:/data/gl-brick/vm         49152     0          Y       16781
Brick gl-server-2:/data/gl-brick/vm         49152     0          Y       24829
Brick gl-arbiter:/data/gl-brick/vm          49152     0          Y       14280
Self-heal Daemon on localhost               N/A       N/A        Y       16804
Self-heal Daemon on gl-arbiter              N/A       N/A        Y       14303
Self-heal Daemon on gl-server-2             N/A       N/A        Y       24852

Task Status of Volume vm
------------------------------------------------------------------------------
There are no active volume tasks

Zu beachten ist, dass die Dienste per TCP bereitgestellt werden. Zudem ist ersichtlich, dass es einene Self-heal daemon gibt. Wichtig ist vor allem auch, dass die Dienste alle in der Spalte Online mit Y aufgelistet werden.

Mounten des GlusterFS Volumes

Das ist der letzte Schritt, man arbeitet nicht mit den Bricks sondern natürlich mit dem Gluster Volume. Daher wird dieses auch auf den Servern selber per GlusterFS gemountet. Hier kommt dann auch der DNS-Name zum Einsatz, der auf beide Server auflöst. Ich hänge diese direkt in das Root-Dateisystem unter /vm:

# mkdir /vm
# mount -t glusterfs gl-server:/vm /vm

Und schon hat man ein repliziertes, hochverfügbares Verzeichnis. Die Daten die hier abgelegt werden, landen dann auf den Servern in der jeweiligen Brick, wobei beim Arbiter nur leere Dateien mit den Metadaten liegen werden.

Hier habe ich gl-server verwendet, der testet dann die zwei Server aus der /etc/hosts (oder aus dem DNS) mit dem Namen durch. Das GlusterFS-Protokoll macht per se ein Lastverteilung, das heißt der Server mit dem man sich hier verbindet, teilt dann mit, von wo die Daten wirklich zu holen sind.

Das mount-Kommando zeigt nun auch, dass es per FUSE mit dem glusterfs-Protokoll eingebunden wurde:

# mount|grep /vm  
gl-server:/vm on /vm type fuse.glusterfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other,max_read=131072)

Theoretisch können die auch per NFS gemountet werden. Das ist aber in der Voreinstellung deaktiviert und bietet keine Vorteile, hier ist dann nur ein Server jeweils explizit nutzbar. Fällt der dann aus, muss neu gemountet werden, das ist unschön.

Wir können jetzt natürlich einfach eine Datei darin anlegen, zum Beispiel auf gl-server1:

gl-server1# echo "GlusterFS ist cool" >/vm/gluster.txt
Die finden wir nun auf allen drei Servern wieder:
gl-server1# ls -l /vm/gluster.txt 
-rw-r--r-- 1 root root 19 Jun 15 19:11 /vm/gluster.txt
gl-server2# ls -l /vm/gluster.txt
-rw-r--r-- 1 root root 19 Jun 15 19:11 /vm/gluster.txt
gl-arbiter# ls -l /vm/gluster.txt
-rw-r--r-- 1 root root 19 Jun 15 18:11 /vm/gluster.txt

Das ist nun nicht wirklich überraschend, spannender ist hingegen, was in dem jeweiligen Brick steht:

gl-server1# ls -l /data/gl-brick/vm/gluster.txt
-rw-r--r-- 2 root root 19 Jun 15 19:11 /data/gl-brick/vm/gluster.txt
gl-server2# ls -l /vm/gluster.txt
-rw-r--r-- 2 root root 19 Jun 15 19:11 /data/gl-brick/vm/gluster.txt
gl-arbiter# ls -l /vm/gluster.txt
-rw-r--r-- 2 root root 0 Jun 15 18:11 /data/gl-brick/vm/gluster.txt

Hier ist wenig überraschend die komplette Datei auf den beiden GlusterFS Servern zu sehen, jedoch nur eine leere Datei auf dem arbiter. So soll es eigentlich auch sein.

Linux User Namespaces

Mein ursprüngliches Ziel war es einmal, hier unprivilegierte LinuX Container laufen zu lassen. Details zu diesen können übrigens hier nachgelesen werden:

Unprivilegierte LinuX-Container

Die Idee war, wenn ich die zwei Server mit den gleichen Daten habe, sollten die sich recht leicht von einem Server auf einen anderen migrieren lassen. Das würde dann über einen Neustart funktionieren, aber das hätte gepasst.

Das ist jedoch ein Problem mit GlusterFS, dieser kann mit den Linux User Namespaces für unprivilegierte User nicht umgehen, das Konzept scheint hier noch unbekannt zu sein. Dabei wäre ein root-User im LXC von außen betrachtet ein ganz normaler User. Der hat keinen Zugriff auf Dateien, die ihm nicht gehören (außer er hat natürlich Leserechte). Das ist aber für einen root-User im LXC eine wichtige Eigenschaft, der kann somit nicht korrekt arbeiten.

Als Lösung habe ich dann einfach die LXCs in eine Virtuelle Maschine per QEMU gepackt, die kann damit umgehen und die Imagedatei hat kein Problem mit den Linux User Namespaces, die gehört schließlich nur einem User, der muss noch nicht einmal root heißen, alles was er benötigt, ist ein Zugriff auf /dev/kvm. Somit kann dieses VM-Image auf einem GlusterFS liegen und von dort problemlos gestartet werden. Damit laufen dann auch die LXCs darin völlig problemlos.

Das hat dann noch den schönen Nebeneffekt, dass dieses VM-Image auf allen Systemen identisch verfügbar ist. Dadurch sind dann QEMU-Live-Migrationen problemlos möglich. Nur will man diese in aller Regel automatisiert durchgeführt haben und zwar kurz bevor ein System abstürzt.

Aber immerhin, so können die VMs auch im laufenden Betrieb verschoben werden, je nachdem wie die Auslastung ist oder ob ein System einmal neu gestartet werden muss. Aber das ist eine andere Geschichte...


Dirk Geschke, dirk@lug-erding.de