glusterfs, galera, haproxy (debian jessie)

imported

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!

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.

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            |
+--------------+

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.


Comments

Blog Comments powered by Disqus.

Next Post Previous Post