Active/Active Cluster mit haproxy, glusterfs und Galera
Ich habe hier im Blog ja schon öfter Artikel zu Clustern geschrieben, es handelte sich aber immer um Active/Passive Setups. Diesmal soll es ein Active/Active Setup werden.
Worin liegt der Unterschied? Bei einem Active/Passive Setup gibt es einen aktiven Node auf dem alle gewünschten Dienste gestartet sind. Auf dem passiven Node sind zwar dieselben Dienste und Daten vorhanden, aber nicht gestartet. Tritt auf dem aktiven Node ein Fehler auf werden die restlichen Dienste dort heruntergefahren und auf dem passiven Node gestartet. Das dauert natürlich etwas und in der Zeit sind die Dienste nicht erreichbar. In einem Active/Active Setup sind die Dienste auf allen Nodes gestartet und es gibt keine Downtime, wenn auf einem Node ein Fehler auftritt. Ein weiterer Vorteil, man kann die Last auf die Nodes verteilen.
Das bedeutet aber natürlich auch dass beide Nodes gleichzeitig auf die Daten zugreifen können müssen, in der Regel schreibend. Damit das klappt benötigt man zunächst ein Dateisystem welches dafür sorgt dass hierbei kein Chaos entsteht. Bei MySQL-Datenbanken klappt das nicht wirklich gut (selbst wenn es einem, dank external-locking
, nicht die Datenbank zerschießt ist die Performance nicht die Beste), daher sollte man hier einen Galera-Cluster nutzen.
Zur Verteilung der Last kommt dann noch haproxy
dazu.
Das Setup besteht aus insgesamt 3 Maschinen: node1
, node2
, node3
. node3
hat eine Sonderstellung, da hier keine Daten abgelegt werden, außerdem wird hier der haproxy installiert (dies wäre auch in einem redundanten Modus möglich und im Produktivbetrieb auch sinnvoll). node3 hat die Aufgabe zu entscheiden ob der Cluster noch korrekt funktionieren kann. In einem Cluster mit nur 2 Nodes gibt es, z.B. bei einem Netzwerkfehler, keine Möglichkeit für die beiden Nodes unlösbare Konflikte zu verhindern, daher müssen alle Dienste gestoppt oder in einen read-only Modus versetzt werden. Hat man einen dritten Node ist dies nicht nötig (vorausgesetzt es verbleiben noch mindestens 2 Nodes die miteinander kommunizieren können).
WICHTIG: Das folgende ist ein Minimalsetup welches so nicht als Produktivumgebung genutzt werden sollte. Als Basis für einige Tests und Einstiegspunkt zum weiteren Wissensaufbau sollte es aber gut geeignet sein. Man sollte sich unbedingt intensiver mit den Themen split-brains
, quorum
, fencing
und stonith
auseinandersetzen, wenn einem seine Daten lieb sind.
WARNING: The following is a minimalistic test setup. DO NOT USE IT FOR PRODUCTION SYSTEMS!
glusterfs
Beginnen wir mit glusterfs
. Alle Nodes verfügen über ein separates Laufwerk/eine separate Partition /dev/xvda3
), die mit xfs
formatiert wird. Auf node3 kann diese kleiner sein, da dieser nur Metadaten speichern soll. Außerdem verfügen alle Nodes über ein internes Netzwerk:
- node1: 10.0.0.1
- node2: 10.0.0.2
- node3: 10.0.0.3
Die Namen und IPs sind in den jeweiligen /etc/hosts
Dateien auf den Nodes entsprechend hinterlegt.
Zunächst binden wir auf allen Nodes das gluster-Repository ein und installieren die glusterfs Client und Serverkomponenten:
wget -O – http://download.gluster.org/pub/gluster/glusterfs/LATEST/rsa.pub | apt-key add –
echo deb http://download.gluster.org/pub/gluster/glusterfs/LATEST/Debian/jessie/apt jessie main > /etc/apt/sources.list.d/gluster.list
apt-get update
apt-get install glusterfs-server glusterfs-client
systemctl start glusterfs-server
Im ersten Einrichtungsschritt formatieren wir auf allen 3 Nodes die vorbereiteten Laufwerke/Partitionen, fügen sie /etc/fstab
hinzu und mounten sie:
mkfs.xfs -i size=512 /dev/xvda3
mkdir -p /data/brick
echo ‘/dev/xvda3 /data/brick xfs defaults 1 2’ >> /etc/fstab
mount -a && mount
Nun verbinden wir die Nodes miteinander, zunächst verbinden wir node1
mit node2
:
gluster peer probe node1
Nun wechseln wir auf node2
und verbinden diesen mit node1
:
gluster peer probe node2
Den node3
können wir nun, von einem beliebigen anderen Node (außer ihm selbst) aus, mit dem Cluster verbinden:
gluster peer probe node3
Mittels gluster peer status
kann man jetzt noch einmal prüfen ob alles korrekt funktioniert. Es fehlt jeweils der Node in der Liste, von dem aus der Befehl aufgerufen wurde, die beiden anderen sollten aber aufgeführt sein:
Number of Peers: 2
Hostname: 10.0.0.2
Uuid: 71dc428b-4d72-4873-b3e7-640308c83af2
State: Peer in Cluster (Connected)
Hostname: 10.0.0.3
Uuid: 542b41ff-fa31-4013-b3fe-2f9449976877
State: Peer in Cluster (Connected)
Auf node
richten wir nun das Glusterfs-Volume (gv0) ein:
gluster volume create gv0 replica 3 arbiter 1 node1:/data/brick/gv0 node2:/data/brick/gv0 node3:/data/brick/gv0
gluster volume start gv0
Der Befehl erzeugt ein, zwischen node1
und node2
, repliziertes Volume. node3
wird der sog. arbiter
Node, der nur Metadaten speichert und die Integrität des Clusters sicherstellt.
Mittels gluster volume info
prüfen wir auch hier nochmal ab ob alles funktioniert:
Volume Name: gv0
Type: Replicate
Volume ID: 8e44598f-7734-4e8b-a34e-8dd8821df96a
Status: Started
Number of Bricks: 1 x (2 + 1) = 3
Transport-type: tcp
Bricks:
Brick1: 10.0.0.1:/data/brick/gv0
Brick2: 10.0.0.2:/data/brick/gv0
Brick3: 10.0.0.3:/data/brick/gv0 (arbiter)
Mittels mount -t glusterfs node1:/gv0 /mnt
können wir das Laufwerk nun auf beliebigen Systemen in internen Netz mounten. Später werden wir das Volume auf beiden Nodes unter /var/www/
einbinden, damit unsere Webserverdaten auf beiden Servern synchron bleiben.
Galera / MariaDB
Weiter geht es mit Galera/mariadb, auch hier richten wir zunächst das entsprechende Repository ein und installieren die nötigen Pakete, diesmal allerdings nur auf node1
und node2
:
apt-get install software-properties-common
apt-key adv –recv-keys –keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db
add-apt-repository ‘deb http://ftp.hosteurope.de/mirror/mariadb.org/repo/10.1/debian jessie main’
apt-get update
apt-get install rsync galera-3 mariadb-server
Nun legen wir die Datei /etc/mysql/conf.d/galera.cnf
an:
cat << EOF > /etc/mysql/conf.d/galera.cnf
[mysqld]
binlog_format=ROW
default-storage-engine=innodb
innodb_autoinc_lock_mode=2
innodb_doublewrite=1
query_cache_size=0
query_cache_type=0
bind-address=0.0.0.0
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name=”galera_cluster”
wsrep_cluster_address=gcomm://node1,node2,node3
wsrep_sst_method=rsync
EOF
Der Galera-Cluster muss nun auf beiden Nodes gestoppt werden:
systemctl stop mysql
Auf node1
wird nun die Einrichtung des Clusters mit dem Befehl galera_new_cluster
gestartet. Anschließend prüfen wir ob die Einrichtung geklappt hat:
mysql -u root -p -e ‘SELECT VARIABLE_VALUE as “cluster size” FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME=”wsrep_cluster_size”‘
Es sollte folgenden Output geben:
cluster size: 1
Nun wird Galera/mariadb auf node2
gestartet:
systemctl start mysql
Der Befehl mysql -u root -p -e ‘SELECT VARIABLE_VALUE as “cluster size” FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME=”wsrep_cluster_size”‘
sollte nun folgerichtig folgendes zurückgeben:
cluster size: 2
Auf node3
gehen wir etwas anders vor, da dieser auch hier wieder nur als sog. Arbitrator
genutzt werden soll. Das Tool hierzu heißt bei Galera garb
:
apt-get install software-properties-common
apt-key adv –recv-keys –keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db
add-apt-repository ‘deb http://ftp.hosteurope.de/mirror/mariadb.org/repo/10.1/debian jessie main’
apt-get update
apt-get install galera-arbitrator-3
Nun wird garb
über die Datei /etc/default/garb
konfiguriert und anschließend gestartet:
cat << EOF > /etc/default/garb
GALERA_NODES=”node1:4567,node2:4567″
GALERA_GROUP=”galera_cluster”
GALERA_OPTIONS=”pc.wait_prim=no”
LOG_FILE=”/var/log/garbd.log”
EOF
systemctl start garb
Nun sollte mysql -u root -p -e ‘SELECT VARIABLE_VALUE as “cluster size” FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME=”wsrep_cluster_size”‘
also die 3 zurückliefern:
cluster size: 3
haproxy
Damit fehlt nur noch haproxy
. Der Einfachheit halber installieren wir ihn einfach auf node3
, für ein Produktivsystem sollte man aber lieber zwei Instanzen (wegen Ausfallsicherheit) auf separaten Maschinen nutzen. Ich gehe davon aus dass Apache auf node1
und node2
bereits installiert wurde und auf Port 80 lauscht. node3
verfügt über eine externe IP Adresse und es wurde ein SSL-Zertifikat (crt + key) in /etc/ssl/snakeoil.pem
hinterlegt. Weiter sagen wir mal node3
ist unter der Domain foobar.com
erreichbar.
Auch hier starten wir mit der Einbindung des nötigen Repositories:
echo ‘deb http://ftp.debian.org/debian jessie-backports main’ >> /etc/apt/sources.list
apt-get update
apt-get install -t jessie-backports hacluster
Die Konfiguration erfolgt über die Datei /etc/haproxy/haproxy.conf
:
cat << EOF > /etc/haproxy/haproxy.conf
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
ssl-default-bind-options no-sslv3
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend http_front
bind *:80
bind *:443 ssl crt /etc/ssl/certs/snakeoil.pem
redirect scheme https code 301 if !{ ssl_fc }
stats uri /haproxy?stats
default_backend http_back
backend http_back
mode http
balance roundrobin
option forwardfor
option httpchk HEAD / HTTP/1.1\r\nHost:localhost
server node1 10.0.0.1:80 check
server node2 10.0.0.2:80 check
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
EOF
Nun wird haproxy
noch gestartet und unser Active/Active Minimal-Setup ist fertig:
systemctl restart haproxy
Wird nun http://foobar.com
aufgerufen leitet haproxy
die Anfragen zunächst auf https um und holt sich dann die angeforderte Seite von node1
oder node2
. Fällt node1
aus gehen die Anfragen nur noch an node2
. so lange bis node1
wieder verfügbar ist. Unter http://foobar.com/haproxy?stats
kann die Statusseite von haproxy aufgerufen werden.