Ubuntu Linux on ZFS
There’s an excellent article about installting Ubuntu on ZFS.
There’s an excellent article about installting Ubuntu on ZFS.
Teil 7.1 wird eine Ausnahme der Reihe, denn dieser Artikel befasst sich mit der Thematik aus Teil 7, jedoch auf Red Hat/CentOS basierten Systemen.
Die Konfiguration ist hier mehstufig, zunächst wird der “bond” selbst konfiguriert; wichtig ist nur, daß diese Konfiguration vor allen anderen Netzwerk-Optionen steht.
/etc/modprobe.conf:
1 2 | alias bond0 bonding options bond0 mode=X miimon=100 |
Der Mode wird hier als Zahl definiert: mode=X.
Mögliche Modi für den “bond” sind:
balance-rr – ein Round Robin über die NICs, mode=0active-backup – active/standby-Setup, mode=1balance-xor – die MAC-Adresse des Ziels wird gehashed und entsprechend ein Interface für die Kommunikation ausgewählt. Damit ist grundlegend ein Load Balancing auf Netzwerk-Ebene realisiert, genauso wie eine ausreichende Fehlertolleranz, mode=2broadcast – jedes Paket wird auf allen Interfaces gesendet. Damit entsteht 100%ige Fehlertolleranz, mode=3802.3ad – dynamic link aggregation per IEEE 802.3ad – erfordert jedoch spezielle Switch-Konfiguration, mode=4balance-tlb – der Host entscheidet je nach Last welche NIC genutzt werden soll, mode=5Das Interface bond0 muß noch konfiguriert werden:
/etc/sysconfig/network-scripts/ifcfg-bond0:
1 2 3 4 5 6 7 | DEVICE=bond0 BOOTPROTO=none ONBOOT=yes NETWORK=198.18.0.0 NETMASK=255.255.255.0 IPADDR=198.18.0.1 USERCTL=no |
Jede reelle NIC die dem “bond” angehören soll, muß noch entsprechend konfiguriert werden:
/etc/sysconfig/network-scripts/ifcfg-ethX:
1 2 3 4 5 6 | DEVICE=ethX BOOTPROTO=none ONBOOT=yes MASTER=bond0 SLAVE=yes USERCTL=no |
Mit Heartbeat und Load Balancing kann der Ausfall kompletter Maschinen abgefangen werden, was noch fehlt ist die Redundanz auf Netzwerk-Ebene. Linux kann mehrere Ethernet Interfaces zu einem sogenannten “bond” verknuepfen, und so diese notwendige Redundanz schaffen. Bei Ausfall einer Switch-Verbindung, eines LAN-Kabels oder einer NIC fällt das System nicht auf die Nase, sondern arbeitet mit potentiell verbinderten Ressourcen weiter.
Auf Debian wird zunächst das Paket ifenslave-2.6 benötigt.
Die Datei /etc/network/interfaces wird anschließend um die Konfiguration für den “bond” erweitert:
/etc/network/interfaces:
1 2 3 4 5 6 7 | iface bond0 inet static address 198.18.0.1 netmask 255.255.255.0 network 198.18.0.0 gateway 198.18.0.254 slaves eth0 eth1 bond_mode active-backup |
In diesem Beispiel werden eth0 und eth1 zu einem Interface (= bond0) gebündelt. bond_mode active-backup definiert hierbei, daß ein Link als Backup für den anderen konfiguriert werden soll.
Andere mögliche Modi sind:
balance-rr – ein Round Robin über die NICsbalance-xor – die MAC-Adresse des Ziels wird gehashed und entsprechend ein Interface für die Kommunikation ausgewählt. Damit ist grundlegend ein Load Balancing auf Netzwerk-Ebene realisiert, genauso wie eine ausreichende Fehlertolleranz. broadcast – jedes Paket wird auf allen Interfaces gesendet. Damit entsteht 100%ige Fehlertolleranz.802.3ad – dynamic link aggregation per IEEE 802.3ad – erfordert jedoch spezielle Switch-Konfiguration.balance-tlb – der Host entscheidet je nach Last welche NIC genutzt werden soll.
Nach Teil 5, dem Load Balancing per NAT, geht’s heute um Load Balancing per direct routing.
Zur Erinnerung:

Die Anfragen kommen am Load Balancer an, der die Ziel MAC-Adresse in den Paketen abändert. Sehen wir uns diesen Prozess im Detail an:
eth0, jedoch mit einer IP die auf einem dummy-Interface gebunden ist, tut was ein Web-Server eben tut und schickt die Antwort an an den Client zurück. Das Paket erhält als Absender-IP die auf dummy gebundene IP.persistent=) behält der Load Balancer die Information auf welchen Web-Server er den Client geschickt hat eine Weile bei oder entfernt diesen Eintrag aus der Adress-Tabelle.Notwendig für die Funktion dieses Workflows ist, daß alle Maschinen sich im gleichen Layer 2 Segment befinden. Zwischen dem Load Balancer und den Web-Servern darf bei direct routing kein Router stehen.
Konkret mit IPv4 Adressen bestückt, sieht die Konfiguration auf dem Load Balancer folgendermaßen aus:

Die IP 198.18.0.1 muß direkt auf z.B. eth0 gebunden werden. Dies ist wichtig, damit der Router eine valide MAC-Adresse für 198.18.0.1 lernen kann und die Pakete auf den Load Balancer zustellen kann.
/etc/ha.d/conf/ldirectord.cf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | checktimeout=10
checkinterval=2
autoreload=yes
callback="/etc/ha.d/config_sync.sh"
logfile="local0"
quiescent=yes
virtual=198.18.0.1:80
real=198.18.0.11:80 gate
real=198.18.0.12:80 gate
real=198.18.0.13:80 gate
fallback=127.0.0.1:80
service=http
request="lb-test.html"
receive="shiny captain"
scheduler=wrr
protocol=tcp
checktype=negotiate |
Bisher ist die Konfiguration in /etc/ha.d/conf/ldirectord.cf von den IP-Adressen und gate anstatt masq stark identisch, allerdings muß für direct routing auch jeder einzelne Web-Server angefasst werden.
Zunächst muß das dummy-Modul geladen werden, so daß dem System das Interface dummy0 zur Verfügung steht.
root@webserver:~# modprobe dummy
root@webserver:~# echo dummy >> /etc/modules
Die Datei /etc/network/interfaces wird um die Konfiguration für dummy0 erweitert:
1 2 3 4 | auto dummy0
iface dummy0 inet static
address 198.18.0.1
netmask 255.255.255.255 |
Ein beherztes ifup dummy0, und alles ist bereit für einen ersten Test. Für diesen Test kann der ldirectord von Hand gestartet werden, also per /etc/init.d/ldirectord start, allerdings sollte später die /etc/ha.d/haresources um die entsprechende Zeile erweitert werden. Wenn alles klappt, sollte ipvsadm -Ln nach wenigen Sekunden die ersten Ausgaben liefern:
root@frasier:~# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 198.18.0.1:80 wrr persistent 1200
-> 198.18.0.11:80 Route 1 0 0
Der Load Balancer hat erkannt, daß auf 198.18.0.11 der konfigurierte Service erreichbar ist, sofern bei Weight eine “1″ steht. Bei “0″ konnte keine Verbindung aufgebaut werden, und quiescent=yes ist gesetzt. (Siehe Teil 5)
root@frasier:~# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 198.18.0.1:80 wrr persistent 1200
-> 198.18.0.11:80 Route 0 0 0
Entsprechendes mit IPv6 ist auch kein Hexenwerk.

In der Datei /etc/ha.d/conf/ldirectord.cf auf checktype und checkcommand achten, und die IPv6 Adressen ausschreiben und nicht mit :: abkürzen.
/etc/ha.d/conf/ldirectord.cf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | checktimeout=10
checkinterval=2
autoreload=yes
callback="/etc/ha.d/config_sync.sh"
logfile="local0"
quiescent=no
virtual=[2001:db8:0:1:0:0:0:a]:443
real=[2001:db8:0:1:0:0:0:1]:443 gate
real=[2001:db8:0:1:0:0:0:2]:443 gate
real=[2001:db8:0:1:0:0:0:3]:443 gate
fallback=[::1]:443
persistent=1200
scheduler=wrr
protocol=tcp
checktype=external
checkcommand="/usr/share/nagios/libexec/check_http" |
Entsprechend der Konfiguration für IPv4 muß auch die IP-Adresse 2001:db8:0:1::a auf jedem einzelnen Web-Server gebunden werden.
1 2 3 4 | auto dummy0
iface dummy0 inet6 static
address 2001:db8:0:1::a
netmask 64 |
Mit ipvsadm -Ln werden auch IPv6 Verbindungen angezeigt.
root@frasier:~# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 198.18.0.1:80 wrr persistent 1200
-> 198.18.0.11:80 Route 1 0 0
-> 198.18.0.12:80 Route 1 0 0
-> 198.18.0.13:80 Route 1 0 0
TCP [2001:db8:0:1::a]:80 wrr persistent 1200
-> [2001:db8:0:1::1]:80 Route 1 0 0
-> [2001:db8:0:1::2]:80 Route 1 0 0
-> [2001:db8:0:1::3]:80 Route 1 0 0
Nach dem Ausschnitt aus der haresources in Teil 1 der Hochverfügbarkeits-Reihe bin ich angesprochen worden, was es denn mit dem Interface eth2.82 (Zeile 3) und eth2.103 (Zeile 4) auf sich hat.
1 2 3 4 | frasier IPaddr2::198.18.0.1/32/eth0 \
IPaddr2::198.19.0.1/32/eth1 \
IPaddr2::10.111.222.1/32/eth2.82 \
IPv6addr::2001:db8:0:0:0:0:0:1/128/eth2.103 |
Es handelt sich dabei um nach IEEE 802.1q getaggte VLAN Interfaces. eth2.82 bedeutet nichts anderes als das VLAN 82 auf eth2. Es wird allerdings ein zusätzliches Modul im Kernel benötigt, namentlich 8021q (echo 8021q >> /etc/modules).
Wie baut man sowas?
Für obiges Beispiel: (in /etc/network/interfaces)
1 2 3 4 5 6 7 | auto eth2.82
iface eth2.82 inet static
address 10.111.222.1
network 10.111.222.0
netmask 255.255.255.0
broadcast 10.111.222.255
vlan_raw_device eth2 |
Die Gegenstelle, z.B. ein Cisco Switch sieht dann für das Beispiel so aus:
1 2 3 4 5 6 7 8 9 | ! interface FastEthernet0/4 switchport trunk encapsulation dot1q switchport trunk allowed vlan 1,82,103 switchport mode trunk ! interface VLAN82 ip address 10.111.222.241 255.255.255.0 ! |
Keine schwarze Magie™.
In Teil 2 habe in angefangen über den ldirectord zu schreiben. Nochmal zur Erinnerung: der ldirectord ist ein User-Space Script, welches mit Hilfe des IP Virtual Servers (kurz IPVS) im Kernel einen Load Balancer in Software abbildet. Angerissen habe ich bereits zwei Methoden zum Load Balancing,
Dieser Artikel beschäftigt sich mit Load Balancing per NAT.
Nochmal zum Konzept:

Die Anfragen kommen am Load Balancer an, der anschließend auf die Web-Farm NATet. Sehen wir uns diesen Prozess im Detail an:
und ändert das Paket an Hand seiner Konfiguration ab. Die Ziel-IP wird durch eine der Web-Server IPs im internen Netz ausgetauscht und mit der entsprechenden MAC-Adresse versehen. Danach schickt der Load Balancer das Paket auf dem internen Interface wieder auf’s Ethernet.
persistent=) behält der Load Balancer die Information auf welchen Web-Server er den Client geschickt hat eine Weile bei oder entfernt diesen Eintrag aus der Adress-Tabelle.Abgesehen von der Entscheidung auf welchen Ziel Web-Server die Verbindung geNATtet wird, unterscheidet sich dieser Prozess nicht vom NAT in z.B. einer Fritz!Box. Zum besseren Verständnis der Konfiguration, obiges Beispiel mit IPv4 Adressen:

Das externe Interface des Load Balancers steht in 198.18.0.0/24 und das interne Netz mit der Web-Farm wird durch 10.0.0.0/24 repräsentiert. Durch dieses Setup lassen sich effizient öffentliche IPv4-Adressen einsparen, denn für die Website www.example.org wird trotz mehrerer Web-Server nur eine einzige IPv4 Adresse benötigt.
Die Konfiguration des ldirectord geschieht in der Datei /etc/ha.d/conf/ldirectord.cf, wobei der Pfad zu dieser Datei in /etc/default/ldirectord überschrieben werden kann, z.B. wenn man den ldirectord nicht mit Heartbeat kombinieren möchte.
/etc/ha.d/conf/ldirectord.cf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | checktimeout=10
checkinterval=2
autoreload=yes
callback="/etc/ha.d/config_sync.sh"
logfile="local0"
quiescent=yes
virtual=198.18.0.1:80
real=10.0.0.21:80 masq
real=10.0.0.22:80 masq
real=10.0.0.23:80 masq
fallback=127.0.0.1:80
service=http
request="lb-test.html"
receive="shiny captain"
scheduler=wrr
protocol=tcp
checktype=negotiate |
Die Konfiguration von oben nach unten:
checktimeout und checkinterval definieren wie häufig die Ziel-Maschinen geprüft werden sollen, bzw. wie hoch das Timeout definiert werden soll, bis ein Server als tot definiert wird. Die Angaben sind in Sekunden.autoreload=yes definiert, daß die Konfiguration neu geladen wurde, sobald sie sich auf der Festplatte geändert hat. Damit einher gehtcallback, denn hier kann ein Binary oder Script angegeben werden, das ausgeführt wird, sobald die Konfiguration sich geändert hatte. config_sync.sh ist nur ein Beispiel, dieses Script ist nicht Teil der Distribution.logfile definiert eine Log-Datei bzw. eine Syslog Facilityquiescent=yes werde ich weiter unten noch genauer eingehen, denn dieser Punkt arbeitet eng mit persistent in der virtual-Sektion zusammen.Die virtual-Sektion definiert jeweils eine “virtuelle” IP (das, was man in den DNS für www einträgt) und kann mehrfach in der Konfiguration stehen, sofern mehr als nur eine IP gebalanced werden soll. Jede Zeile der Konfiguration die zu einer virtual-Sektion gehört muß mit einem Tabulator (\t) eingerückt werden; leider ist die Syntax-Prüfung etwas seltsam und man bekommt extrem nichts sagende Fehlermeldungen.
virtual=198.18.0.1:80 definiert 198.18.0.1 mit Port 80 als virtuelle Adresse. Diese IP muß auf dem Load Balancer lokal (z.B. an eth0, nicht an lo oder dummy0) gebunden werden, denn ldirectord bindet die IP nicht automatisch. Bei einem HA-Setup empfiehlt es sich natürlich diese IP als Cluster-Ressource zu definieren.real=10.0.0.21:80 masq definiert einen Backend-Server inklusive des Ports. Es ist durchaus möglich intern die Web-Server auf anderen Ports zu betreiben, nach außen allerdings weiter auf Port 80 verfügbar zu sein. masq schaltet den IPVS in den NAT-Modus.fallback=127.0.0.1:80 wird ein Notfall-Server definiert, der dann genutzt wird, wenn alle reellen Server als defekt definiert wurden. Hier kann z.B. ein “Wartungsarbeiten, komm später wieder” Text auf einem lokalen Web-Server definiert werden; oder man läßt die Zeile komplett weg, wenn man keinen Fallback-Server hat oder möchte. Anfragen gehen dann allerdings ins Leere.service definiert den zu balancenden Dienst, sofern checktype=negotiate gesetzt ist. Eine Liste der verfügbaren Dienste befindet sich in der man-page des ldirectord, interessant sind zwei besondere Fälle:
none: es soll keine Form von Checks stattfinden. Wenn ein Server ausfällt wird der Load Balancer das nicht bemerken und den Server weiterhin in der Verteilung nutzen.simpletcp: ein generischer Service der mit request eine TCP-Verbindung aufbaut und das empfangene Ergebnis mit receive matcht.Für “standard” Services wie http, https oder ssh enthält der ldirectord sehr brauchbare Checks, die die Verfügbarkeit des Services exzellent prüfen.
scheduler definiert die Load Balancing Methode, den Scheduler Algorithmus. Zur Verfügung stehen 10 verschiedene Algorithmen, unter anderem
rr – Round Robin: verteile alle Anfragen der Reihe nach auf die Serverwrr – Weighted Round Robin, gewichteter Round Robin: arbeitet wieder ungewichtete Round Robin, man kann den Servern aber verschieden hohe Prioritäten geben, um beispielsweise ungleich starke Hardware besser ausnutzen zu könnenlc – Least-Connection: Maschinen mit weniger offenen Verbindungen als andere bekommen bevorzugt neue Verbindungen zugeteiltwlc – Weighted Least-Connection: ebenfalls vergleichbar mit Least-Connection, allerdings wieder mit Gewichtung der Maschinendh – Destination Hashing: erstellt einen Hash über die Ziel-IP und weist darüber einen Backend-Server zush – Source Hashing: erstellt einen Hash über die Quell-IP und weist darüber einen Backend-Server zuipvsadm gibt im Detail Auskunft. checktype noch an auf welche Weise die Backend-Server geprüft werden sollen. Mehr dazu gleich mehr.Mit IPv6 ist die Konfiguration nahezu identisch, abgesehen von den Check-Methoden.

Außerdem wird diesmal https gebalanced.
/etc/ha.d/conf/ldirectord.cf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | checktimeout=10
checkinterval=2
autoreload=yes
callback="/etc/ha.d/config_sync.sh"
logfile="local0"
quiescent=no
virtual=[2001:db8:0:1:0:0:0:1]:443
real=[2001:db8:0:2:0:0:0:21]:443 masq
real=[2001:db8:0:2:0:0:0:22]:443 masq
real=[2001:db8:0:2:0:0:0:23]:443 masq
fallback=[::1]:443
persistent=1200
service=http
request="lb-test.html"
receive="shiny captain"
scheduler=wrr
protocol=tcp
checktype=negotiate |
Zwei Konfigurations-Direktiven bin ich Euch noch schuldig: quiescent und checktype.
Um zu erkennen in welchem Zustand sich die Backend-Server befinden, muß der Load Balancer regelmäßig prüfen, ob die Maschinen wie gewünscht antworten und ihren zugewiesenen Dienst erfüllen können. Wird checktype=connect gesetzt, so baut der ldirectord eine einfache TCP-Verbindung auf und prüft ob ein erwarteter String zurück gemeldet wird. Bei UDP würde es hier schon etwas schwerer werden, und bei Protokollen wie https muß sogar ein SSL-Handshake stattfinden. Mit einem TCP-Connect ist es also in manchen Fällen nicht unbedingt getan, hier wird etwas mehr Logik notwendig. checktype=negotiate in Verbindung mit service=https, um beim IPv6 Beispiel zu bleiben baut eine Verbindung auf Port 443 zu den Backend-Servern auf, macht den erwarteten SSL-Handshare und wertet dann den mit receive angegebenen String aus. Wird request und receive nicht angegeben, so gilt der Server bereits bei korrektem Verbindungsaufbau als intakt, ob aber beispielsweise Datenbank-Verbindungen funktionieren oder nicht wird der Load Balancer nicht erkennen. Hier ist Kreativität gefragt eine “OK“- bzw. “ERROR“-Seite zu basteln, die alle notwendigen Verbindungen der Web-Applikation prüft und entsprechend einen Status abliefert. Sofern notwendig, kann checktimeout nach oben hin angepasst werden, wenn z.B. alle Datenbank-Verbindungen aufzubauen länger als 10 Sekunden dauert.
Wird ein Backend-System auf Grund der Checks als defekt markiert, kommt quiescent ins Spiel. Bei yes wird der betroffene Backend-Server still gelegt, es werden also keine neuen Verbindungen zugelassen, er befindet sich aber noch in der Kernel-Tabelle des IPVS. Wird quiescent=no gesetzt, so wird er restlos aus der Kernel-Tabelle entfernt. In das IPv6/SSL Beispiel habe ich noch den Parameter persistent=1200 geschmuggelt. persistent definiert wie lange .. genau, das stand ja weiter oben schon, wie lange eine Verbindung in der Kernel-Tabelle gehalten werden soll, wieviele Sekunden also ein Client auf den gleichen Backend-Server gebalanced werden soll. Bei quiescent=no wird bei Ausfall eines Servers die Verbindung trotz persistent direkt neu verteilt, bei quiescent=yes muß erst das Timeout von hier 1200 Sekunden (= 20 Minuten) abgewartet werden.
Bleibt nur noch der erwähnte Sonderfall mit den Check-Methoden im Zusammenhang mit IPv6: checktype=negotiate funktioniert mit den meisten Protokollen für IPv6 noch nicht. (Stand März 2011, ldirectord-Version v1.186-ha aus Debian Squeeze) Dies ist allerdinge kein Show-Stopper, denn checktype kennt noch einen weiteren Parameter: external.
1 2 3 4 5 6 7 8 | virtual=[2001:db8:0:1:0:0:0:1]:80
real=[2001:db8:0:2:0:0:0:21]:80 masq
real=[2001:db8:0:2:0:0:0:22]:80 masq
real=[2001:db8:0:2:0:0:0:23]:80 masq
fallback=[::1]:80
scheduler=rr
checktype=external
checkcommand="/usr/share/nagios/libexec/check_http" |
Bei checktype=external kann mit checkcommand ein beiliebiges Binary oder Script angegeben werden, welches den Backend-Server prüft. Nagios-Plugins, /bin/true oder was dem geneigten Admin sonst noch so einfällt, wichtig ist nur, daß entsprechende Error-Codes an den ldirectord geliefert werden. Und der Gedanke bei Ausfall eines real-Servers gleich einen passive service check an Nagios weiter zu geben hat doch was, oder?
Teil 6 beschreibt direct routing im Detail.
Das Fazit des ersten Artikes: einen Heartbeat-Cluster zu bauen ist nicht sonderlich schwer, eigentlich nur Fleißarbeit. Und diese Fleißarbeit zu automatisieren ist “die hohe Kunst” der Faulheit.
Puppet hilft dabei zu automatisieren.
Um das Heartbeat-Modul nutzen zu können wird zunächst noch die Modul-Sammlung aus dem folgenden Repository benötigt:
git clone git://github.com/ripienaar/puppet-concat.git
Dann noch das eigentlich Heartbeat-Modul:
git clone git://github.com/shl/ipvs-lb.git
In den node-Definitionen der Cluster-Nodes muß das Modul noch eingebunden und damit konfiguriert werden:
1 2 3 4 5 6 | heartbeat { 'ClusterName': resources = [ '198.18.23.42', 'nfs-kernel-server' ], key = 'banane', iface = 'eth0', peer_ip = '198.19.0.1' } |
Puppet generiert nun beim nächsten run die notwendigen Konfigurationen und baut damit den HA-Cluster. Und weil man damit massig Zeit sparen kann, darf dieser Artikel auch etwas kürzer sein.
Der in Teil 1 aufgebauten Heartbeat-Cluster soll nun ein Aufgabe bekommen: hochverfügbarer NFS-Server mit lokalem Storage. Was sind die wichtigen Parameter dabei?
Hier beitet sich DRBD an, Distributed Replicated Block Device, eine Technologie zur synchronen Replikation von Block Devices über das IP-Netzwerk. Pro Blech wird ein physikalisches Volume (eine Festplatte, eine RAID-Gruppe, ein USB-Stick oder sonstwas…) definiert, welches von DRBD verwaltet und synchronisiert wird. Im User-Space taucht ein neues Device auf, z.B. /dev/drbd0 und kann auf dem aktiven Node wie ein normales Device verwendet werden. Auf dem passiven Node ist das Device nicht mountbar, so lange bis mittels Heartbeat definiert wird, daß dieser Node das Dateisystem exklusiv mounten darf.
Wie funktioniert diese Replikation?
Simpel ausgedrückt: jeder Schreib-Zugriff auf das DRBD-Device macht den Umweg über den IP-Stack und damit auf den anderen Node. Jedes Bit wird also zweimal geschrieben, und erst wenn beide Maschinen die Daten korrekt auf die Platten geschoben haben, bekommt der User das entsprechende Feedback und der Schreibvorgang wird beendet.

Setup:
Auf beiden Nodes benötigen wir zwei neue Partitionen, eine für die Nutz-Daten und eine weitere für die DRBD Meta-Daten. Die Meta-Partition muß minimal 128MB groß sein, je nach Größe der Daten-Partition auch gerne größer. An “Frasier” und “Niles” habe ich der Einfachheit halber je einen USB-Stick angeschlossen und zwei Partitionen erstellt:
/dev/sdb1 mit 512MB für die Nutz-Daten/dev/sdb2 mit 256MB für die Meta-DatenFolgendes ist auf beiden Nodes auszuführen:
root@frasier:~# apt-get install drbd8-utils
root@frasier:~# modprobe drbd
root@frasier:~# echo drbd >> /etc/modules
Die Konfiguration von DRBD wird in /etc/drbd.conf und /etc/drbd.d/* erledigt, wobei /etc/drbd.conf nur zwei include-Statements für /etc/drbd.d/* enthält und nicht weiter angepasst werden muß. Interessanter ist die Datei /etc/drbd.d/global_common.conf, wobei auch hier keine für das Beispiel notwendigen Änderungen vorzunehmen sind.
Für jede Ressource, die von DRBD verwaltet werden soll, wird eine spezielle Konfigurationsdatei in /etc/drbd.d/ angelegt und entsprechend benannt. Wegen dem include in /etc/drbd.conf muß jede Ressourcen-Datei *.res benannt werden.
/etc/drbd.d/r0.res:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | resource r0 {
protocol C;
handlers {
pri-on-incon-degr "echo o > /proc/sysrq-trigger ; halt -f";
pri-lost-after-sb "echo o > /proc/sysrq-trigger ; halt -f";
local-io-error "echo o > /proc/sysrq-trigger ; halt -f";
}
startup {
degr-wfc-timeout 120; # 2 minutes.
}
disk {
on-io-error detach;
}
net {
}
syncer {
rate 1M;
al-extents 257;
}
on frasier {
device /dev/drbd0;
disk /dev/sdb1;
address 198.18.1.10:7788;
meta-disk /dev/sdb2[0];
}
on niles {
device /dev/drbd0;
disk /dev/sdb1;
address 198.18.1.11:7788;
meta-disk /dev/sdb2[0];
}
} |
Es wird eine Ressource r0 definiert und entsprechend konfiguriert. Die relevanten Teile der Konfiguration von oben nach unten:
1 2 3 4 5 | handlers {
pri-on-incon-degr "echo o > /proc/sysrq-trigger ; halt -f";
pri-lost-after-sb "echo o > /proc/sysrq-trigger ; halt -f";
local-io-error "echo o > /proc/sysrq-trigger ; halt -f";
} |
Was soll passieren, wenn DRBD zu starke I/O-Fehler feststellt? In diesem Beispiel ist die konsistenz der Daten vorrangig, daher schalte den Node ab und schreibe gar nichts mehr. Wer ein anderes Verhalten wünscht tausche die Befehle in den " " entsprechend aus.
1 2 3 4 | syncer {
rate 1M;
al-extents 257;
} |
Mit welcher Geschwindigkeit sollen die Daten zwischen den Nodes synchronisiert werden? Im Falle der USB-Sticks an “Frasier” und “Niles” hier nur mit maximal 1MB/s, doch falls entsprechend schnelle Links vorhanden sind (z.B. ein dedizierter Gigabit- oder 10-Gigabit-Link) wird hier ein Wert in Megabyte pro Sekunde angegeben.
1 2 3 4 5 6 | on frasier {
device /dev/drbd0;
disk /dev/sdb1;
address 198.18.1.10:7788;
meta-disk /dev/sdb2[0];
} |
Einen solchen Block gibt es pro Node einmal, hier werden die Devices und die IP-Adresse/DNS-Name des Nodes definiert. Wie oben beschrieben ist /dev/sdb1 die Partition für die Nutz-Daten und /dev/sdb2 für die Meta-Daten des DRBD reserviert.
Die Konfigurationen müssen auf allen Nodes identisch sein. Es bietet sich also an z.B. mit rsync das komplette /etc/drbd.d/ zu übertragen. Jetzt wird es Zeit das DRBD-Device zu initialisieren:
root@frasier:~# drbdadm create-md r0
root@niles:~# drbdadm create-md r0
root@frasier:~# drbdadm up all
root@niles:~# drbdadm up all
root@frasier:~# drbdsetup /dev/drbd0 primary -o
root@frasier:~# mkfs.ext3 /dev/drbd0
root@frasier:~# mkdir /data
root@niles:~# mkdir /data
root@frasier:~# mount -t ext3 /dev/drbd0 /data
Im Hintergrund hat das DRBD schon begonnen die Devices zu synchronisieren, was man mit cat /proc/drbd beobachten kann. Sobald der initiale Transfer abgeschlossen ist, wird es Zeit den zugehörigen Service zu starten. Damit ist die Konfiguration von DRBD beendet und es fehlt nur noch die Anpassung des Heartbeats.
root@frasier:~# /etc/init.d/drbd start
root@niles:~# /etc/init.d/drbd start
Sowohl die DRBD Ressource r0 als auch das Filesystem werden als Cluster-Ressource in der Datei /etc/ha.d/haresources auf beiden Nodes verankert.
/etc/ha.d/haresources:
1 2 3 4 | frasier IPaddr2::198.18.0.1/32/eth0 \
drbddisk::r0 \
Filesystem::/dev/drbd0::/data::ext3 \
nfs-kernel-server |
Auf “frasier” ist /data bereits gemountet, der NFS-Server wird mit entsprechenden Einträgen in /etc/exports direkt loslegen können. Auf “niles” läuft kein NFS-Server und /data ist ein leerer Mountpoint. Sobald “frasier” zur Wartung abgeschaltet wird oder ausfällt, wird Heartbeat auf “niles” erst das Dateisystem mounten und anschließend den NFS-Server starten. Richtig geraten, die Reihenfolge in /etc/ha.d/haresources spielt eine Rolle.
Der Begriff Cluster wird häufig in unterschiedlichen Zusammenhängen genutzt und ist daher leider im allgemeinem Sprachgebrauch etwas verwaschen. Von HPC-Clustern oder partitionierten Datenbanken abgesehen, sind folgende zwei Cluster-Typen in dieser Artikel-Reihe interessant:
Zur Verdeutlichung:
Ein active/passive HA-Cluster mit verschiedenen Cluster-Ressourcen, die immer nur auf dem aktiven Node ausgeführt werden. Alle weiteren Nodes dienen ausschließlich zu Standby Zwecken.
Ein active/active HA-Cluster, auf dem mehrere Cluster-Ressourcen auf mehreren Nodes ausgeführt werden. Im Ausfall-Moment werden die ausfallenden Ressourcen auf die übrigend Nodes verteilt. (Die Anzahl und Leistungskraft der Nodes sollte entsprechend gewählt werden, daß bei Ausfall oder Wartung eines Nodes die verbleibenden Nodes die Aufgaben erledigen können.)
Dieser Artikel soll sich mit Load Balancing beschäftigen.
Um beim Beispiel der Web-Farm für Load Balancing zu bleiben: Die Aufgabenstellung lautet, ein Balancing über drei Web-Server zu realisieren.
Möglichkeit 1: DNS
Load Balancing per DNS Round Robin. Im DNS wird pro Web-Server ein Eintrag für den entsprechenden DNS-Namen gemacht.
1 2 3 4 5 6 | www IN A 198.18.20.1 www IN A 198.18.20.2 www IN A 198.18.20.3 www IN AAAA 2001:db8::1 www IN AAAA 2001:db8::2 www IN AAAA 2001:db8::3 |
Ausfallsicherheit bietet diese Lösung leider keine, denn die DNS-Antwort interessiert sich genau gar nicht für den Zustand der Web-Server. Allerdings führen die Mehrfacheinträge im DNS zu einer gewissen Gleichverteilung der Anfragen. Fazit: machen wir lieber richtiges Load Balancing.
Möglichkeit 2: Load Balancing per NAT
Mit Hilfe des ldirectord und dem IP Virtual Server im Kernel können wir ein recht zuverlässiges und performantes Load Balancing erreichen. Es wird hierbei zwischen zwei “Klassen” von IP-Adressen unterschieden:
www.example.org eingetragen. Die einzelnen Web-Server kennen diese Adresse nicht.Wenn ein Client nun www.example.org aufruft, so wird die Verbindung auf dem Load Balancer terminiert. Der Client wird keine Kommunikation zu den reellen Web-Servern aufbauen. Der Load Balancer allerdings nutzt NAT um die Verbindung auf einen der Web-Server durchzustellen und das entsprechende Antwort-Paket wieder dem Client zuzuordnen.

Leider bringt diese Möglichkeit alle Nachteile die man von NAT kennt mit, bietet allerdings schon deutliche Vorteile gegenüber dem DNS Round-Robin. Ein klarer Vorteil bleibt natürlich: man spart öffentliche IP-Adressen, was bei IPv4 heute ein Argument sein sollte (!).
Um späteren Artikeln etwas vorzugreifen: eine beispielhafte Konfiguration für den ldirectord und Load Balancing per NAT:
1 2 3 4 5 6 7 | virtual=198.18.1.100:80
real=198.18.20.1:80 masq
real=198.18.20.2:80 masq
real=198.18.20.3:80 masq
service=http
persistent=1200
checktype=connect |
Möglichkeit 3: Load Balancing auf Layer 2
Bei Durchsatz und Verfügbarkeit hat das Load Balancing auf dem ISO/OSI Layer 2 klar gewonnen, allerdings ist das Setup etwas komplizierter. Eine Anfrage die auf der “virtuelle” Adresse eingeht wird vom Load Balancer entgegen genommen und verarbeitet. Die Antwort auf das Paket kommt allerdings vom Web-Server selbst, und zwar direkt an den anfragenden Client.

Die böse Magie™, die der Load Balancer an dieser Stelle treibt geschieht – wie oben bereits geschrieben – auf dem ISO/OSI Layer 2. Das Paket wird vom Balancer in so fern bei der Verarbeitung abgeändert, als das er die MAC-Adresse einer der Web-Server als Ziel-Adresse einträgt und das Paket wieder auf das Ethernet schickt. Die Web-Server sind nun so konfiguriert, daß sie die virtuelle IP auf einem nicht ARP‘enden dummy-Interface konfiguriert haben, so daß der Kernel das Paket mit gutem Gewissen annehmen und verarbeiten kann. Das Antwort-Paket bekommt als Absender die virtuelle IP und wird dem Client direkt zugestellt. Der Client selbst ist nicht in der Lage zu erkennen ob und wenn ja welche Form von Load Balancing involviert war.
Auch hier vorgegriffen: eine beispielhafte Konfiguration für den ldirectord und Load Balancing per direct routing:
1 2 3 4 5 6 7 | virtual=198.18.20.10:80
real=198.18.20.1:80 gate
real=198.18.20.2:80 gate
real=198.18.20.3:80 gate
service=http
persistent=1200
checktype=connect |
Auf das Thema Load Balancing und die praktische Umsetzung mittels ldirectord werde ich in späteren Artikel noch genauer eingehen.
Als Vorbereitung empfehle ich die Folge 41 des Heldenfunks. In knappen zwei Stunden diskutieren Constantin und Hartmut jedes notwendige Vorwissen bis ins Detail durch. Ein perfekter Einstieg in das Thema Hochverfügbarkeit. (Die zwei Stunden könnten übrigens genutzt werden um nebenher zwei Maschinen mit Debian zu deployen…)
Warum man hochverfügbare Dienste möchte ist nun also geklärt, klären wir noch die Begrifflichkeiten:
Im “normal”-Zustand würde ein beispielhaftes, sehr simpel gehaltenes Setup in etwa so aussehen:

Es gibt einen aktiven Node der die Cluster-Ressourcen gebunden hat und ein Standby Node daneben der Strom in warme Luft umwandelt.
Sobald der aktive Node ausfällt (oder zur Wartung abgeschaltet wird) übernimmt der Standby Node die Ressourcen und der oder die angebotene(n) Dienst(e) laufen ohne nennenswerte Unterbrechnung weiter.

Im Podcast gut aufgepasst? Richtig, hier gibt es keinen Quorum-Server.
Setup der Nodes:
Die beiden Cluster-Nodes in diesem Artikel heißen “Frasier” und “Niles“:
root@frasier:~# apt-get install heartbeat
root@niles:~# apt-get install heartbeat
In /etc/ha.d/ müssen drei Dateien erstellt werden, z.B. nach den Vorgaben in /usr/share/doc/heartbeat:
/etc/ha.d/authkeys/etc/ha.d/haresources/etc/ha.d/ha.cfWichtig: /etc/ha.d/authkeys und /etc/ha.d/haresources müssen auf allen Nodes identisch sein, /etc/ha.d/ha.cf kann spezifische Konfigurationen für jeden Node enthalten. Sehen wir uns die Dateien im Detail an.
/etc/ha.d/authkeys:
1 2 | auth 1 1 sha1 broccoli |
Hier sollten keine großen Erklärungen notwendig sein. Der gemeinsame Schlüssel der Nodes wird als SHA1-Hash des Wortes broccoli konfiguriert.
/etc/ha.d/haresources:
1 2 | frasier IPaddr2::198.18.0.1/32/eth0 \
IPaddr2::198.19.0.1/32/eth1 |
Wir definiere zwei Cluster-Ressourcen, eine IPv4-Adresse für eth0 und eine für eth1. Die Konfiguration für eth0 und eth1 in /etc/network/interfaces geben das korrekte Subnetz vor, daher sollten in den haresources nur /32-Masken genutzt werden (z.B. eth0 mit 198.18.0.0/16 und eth1 mit 198.19.0.0/16). Beide Adressen sollen beim initialen Cluster-Start auf “frasier” gebunden werden.
Eine etwas kompliziertere Version von haresources zur Veranschaulichung:
1 2 3 4 5 6 7 8 9 | frasier IPaddr2::198.18.0.1/32/eth0 \
IPaddr2::198.19.0.1/32/eth1 \
IPaddr2::10.111.222.1/32/eth2.82 \
IPv6addr::2001:db8:0:0:0:0:0:1/128/eth2.103 \
atftpd \
radvd \
isc-dhcp-server \
nfs-kernel-server \
ldirectord |
Für das Heartbeat-Setup stellen sowohl IP-Adressen als auch Unix-Services Ressourcen da. Jene Datei definiert diese Ressourcen. Für jede “Klassen” von Ressourcen gibt es entsprechende Scripte in /etc/ha.d/resource.d. Um einen konventionellen Unix-Dienst (z.B. den isc-dhcp-server in eine Cluster-Ressource zu verwandeln, genügt es das init-Script aus /etc/init.d/ nach /etc/ha.d/resource.d zu linken und den automatischen Start zu unterbinden.
root@frasier:/etc/ha.d/resource.d# update-rc.d isc-dhcp-server remove
root@frasier:/etc/ha.d/resource.d# ln -s /etc/init.d/isc-dhcp-server isc-dhcp-server
/etc/ha.d/ha.cf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | logfile /var/log/ha-log logfacility local0 keepalive 10 deadtime 100 warntime 60 initdead 360 auto_failback on bcast eth0 mcast eth1 225.0.0.1 694 1 0 ping 198.18.0.201 node frasier node niles |
Alle Werte die in ha.cf nicht definiert werden, werden mit entsprechenden default-Werten gesetzt.
keepalive definiert die Zeit zwischen den Heartbeat-Paketen in Sekundendeadtime definiert die Zeit in Sekunden, die verstreichen muß, bis ein Node als tot definiert wird. Im Zusammenhang mit keepalive 10 müssen 10 Heartbeat-Pakete verloren gehen, bis der Node deaktiviert wird und gegebenenfalls ein Takeover stattfindet.warntime definiert (ebenfalls in Sekunden) die Zeit bis die ersten Log-Meldungen geschrieben werden, falls Heartbeat-Pakete verloren geheninitdead wann soll beim initialen Start ein Node als tot definiert werden? (Ja, wieder in Sekunden)auto_failback soll nach einem Takeover an den Standby wieder auf den ursprünglichen Master geswitcht werden, sofern dieser wieder verfügbar ist?Die Heartbeat-Pakete können über verschiedene Methoden verschickt werden. Aus der Praxis empfiehlt sich mindestens zwei unterschiedliche Methoden und am besten noch dazu unterschiedliche Transportwege zu nutzen.
bcast verschicke die Heartbeat-Pakete per Broadcast auf dem angegebenen Interfacemcast verschicke die Heartbeat-Pakete per Multicast auf dem angegebenen Interfaceucast verschicke die Heartbeat-Pakete per Unicast über das angebene Interface an die angegebene IP des anderen Nodesserial verschicke die Heartbeat-Pakete über eine serielle Leitung am angegebenen ttyIn der ping-Direktive können Maschinen angegeben werden, die von allen Cluster-Nodes gepingt werden. Hiermit wird festgestellt ob die Nodes nicht nur sich selbst (per Heartbeat) sondern auch den Rest des Netzwerks “sehen” können. Defekte NICs und LAN-Verbindungen können hiermit sinnvoll erkannt und umgangen werden. Der letzte Teil der ha.cf definiert alle verfügbaren Nodes im Cluster.
Testen des Setups:
Wenn “frasier” und “niles” korrekt konfiguriert sind ist es Zeit den Heartbeat zu starten.
root@frasier:~# /etc/init.d/heartbeat start
root@niles:~# /etc/init.d/heartbeat start
Der initiale Start dauert ein wenig, daher empfiehlt es sich auf beiden Maschinen /var/log/ha-log zu beobachten. Fehlermeldungen werden im Normalfall recht sprechend in der Log-Datei verzeichnet. Wenn alles geklappt hat, wird “frasier” die beiden IPv4-Ressourcen innerhalb von wenigen Minuten binden.