MySQL/MariaDB: Ausfallsicheren Galera-Cluster erstellen

Eine eigene MySQL-Datenbank zu erstellen ist kein Problem. Aber der Stress beginnt dann, wenn Aliens auf das Rechenzentrum ballern und der Server ausfällt.

Während sich also die Welt darum kümmert, nicht von grünköpfigen Glubschaugen unterjocht zu werden, kämpft der Administrator an seiner eigenen Front: Den Datenbankserver wieder in die Gänge zu bekommen, damit die interstellare Abwehrarmee der Menschen ihre Dosenrationen online überwachen kann.

Das sind die wahren Helden!

Besser wäre es aber im vorhinein gewesen, wenn der Server ein Cluster gewesen wäre, also ein Netzwerk bestehend aus mindestens drei Servern. Fällt einer aus, sind mindestens zwei noch im Einsatz.

Doch Stop! MySQL kann das nicht von Haus aus. Stattdessen kann man hier das Pendant MariaDB einsetzen. Und das geht unter Ubuntu 18.0 so…

Zunächst mein Dank an howtoforge, deren Artikel die Grundlage für diesen hier bildet.

Los geht’s! Erstmal in der Linux-Shell das Paket installieren:

Ubuntu 18

sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
sudo add-apt-repository 'deb [arch=amd64,i386,ppc64el] http://ftp.utexas.edu/mariadb/repo/10.4/ubuntu xenial main'
sudo apt update
sudo apt install mariadb-server rsync -y

Ubuntu 20

Achtung: Es ist wichtig, dass du MariaDB Version 10.5 auf Ubuntu 20 installierst; Galera startet sonst nicht.

sudo apt update && sudo apt upgrade
sudo apt -y install software-properties-common
sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc'
sudo add-apt-repository 'deb [arch=amd64] http://mariadb.mirror.globo.tech/repo/10.5/ubuntu focal main'
sudo apt update
sudo apt install mariadb-server mariadb-client rsync -y

Als nächstes: Den Server konfigurieren und installieren. Das geht am Besten über das mitgelieferte Tool.

sudo mysql_secure_installation

Easy Peasy! Nächster Schritt: Die Kommunikation zwischen den Servern im Cluster absichern. Je nachdem, wie diese verteilt sind, sollte die interne Absprache verschlüsselt sein. Das geht natürlich über SSL – die Abkürzung für SuperSichereVerschLüsselung.

Also als nächstes ein paar Verzeichnisse anlegen, in denen die Zertifikate abgelegt werden. Die werden später an die richtige Stelle kopiert.

cd ~
mkdir ssl
cd ssl

Dann werden die Zertifikate generiert; ich höre jetzt schon einen Aufschrei. Seit Anbeginn der Menschheit versuchen Administratoren, die Generierung von SSL-Zertifikaten zu deuten und zu verstehen. Viele sind beim Versuch gestorben. Insofern: Einfach die Schritte ausführen und hoffen, das es schon so passt.

Achtung: Unbedingt bei der Abfrage auf den angegebenen “Common Name” achten! Die anderen Felder müssen nicht ausgefüllt werden.

# "Common Name" = 'db'
openssl genrsa 2048 > ca-key.pem
openssl req -new -x509 -nodes -days 21900 -key ca-key.pem > ca-cert.pem

# "Common Name" = 'server'
openssl req -newkey rsa:2048 -days 21900 -nodes -keyout server-key-pkcs8.pem > server-req.pem
openssl rsa -in server-key-pkcs8.pem -out server-key.pem
openssl x509 -req -in server-req.pem -days 21900 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 > server-cert.pem

# "Common Name" = "client"
openssl req -newkey rsa:2048 -days 10000 -nodes -keyout client-key-pkcs8.pem > client-req.pem
openssl rsa -in client-key-pkcs8.pem -out client-key.pem
openssl x509 -req -in client-req.pem -days 21900 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 > client-cert.pem

Die Zertifikatsdateien sind jetzt verfügbar. Aber nicht lesbar und für den falschen Nutzer – das müssen wir ändern.

sudo mkdir /var/ssl
sudo mkdir /var/ssl/mysql
sudo cp *.pem /var/ssl/mysql
# Benutzer außerhalb MySQL können es lesen (damit du dich lokal verbinden kannst)
sudo chmod 664 /var/ssl/mysql/*
sudo chmod 775 /var/ssl/mysql/
sudo chown -R mysql:mysql /var/ssl/mysql

Fertig. Hinweis: Du kannst die Zertifikatsdateien auch mit 500 für das Verzeichnis und 400 für Dateien schützen. Dann hat nur der mysql-User Zugriff, aber du kannst dich nicht lokal verbinden. In dem Fall setze einfach andere Rechte für die Client-Zertifikate. Jetzt müssen wir noch der Konfiguration von MariaDB mitteilen, dass wir SSL nutzen wollen.

Dafür wird die Datei /etc/mysql/conf.d/galera.cnf (Ubuntu 18, MariaDB 10.3) oder /etc/mysql/mariadb.conf.d/60-galera.cnf (Ubuntu 20, MariaDB 10.5) editiert. Achtung: Hier sollten später die IPs der anderen Server eingetragen werden, die im Cluster aktiv sind. Momentan reicht die des ersten Servers.

Achtung: Unter bind-address wird eingestellt, dass der Server von außen erreichbar ist. Wenn du das nicht möchtest, kannst du die IP anpassen. Generell kann man den Zugriff aber dann auch über eine Firewall regeln.

[mysql]
# Using mysql in case this is used
ssl=true
ssl-ca=/var/ssl/mysql/ca-cert.pem
ssl-cert=/var/ssl/mysql/client-cert.pem
ssl-key=/var/ssl/mysql/client-key.pem

[client]
# Also client to be sure
ssl=true
ssl-ca=/var/ssl/mysql/ca-cert.pem
ssl-cert=/var/ssl/mysql/client-cert.pem
ssl-key=/var/ssl/mysql/client-key.pem

[mysqld]
ssl=true
ssl-ca=/var/ssl/mysql/ca-cert.pem
ssl-cert=/var/ssl/mysql/server-cert.pem
ssl-key=/var/ssl/mysql/server-key.pem

[galera]
binlog_format=ROW
default-storage-engine=innodb
innodb_autoinc_lock_mode=2
bind-address=0.0.0.0

# Galera Provider Configuration
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name="galera_cluster"

# Achtung: Hier IP ersetzen!
wsrep_cluster_address="gcomm://<SERVERIP 1>"

wsrep_sst_method=rsync
wsrep_provider_options="socket.ssl_key=/var/ssl/mysql/server-key.pem;socket.ssl_cert=/var/ssl/mysql/server-cert.pem;socket.ssl_ca=/var/ssl/mysql/ca-cert.pem"

# Galera Node Configuration

# Achtung: Hier IP ersetzen!
wsrep_node_address="<SERVERIP 1>"

wsrep_node_name="MariaDB1"

Jetzt wieder in die Shell wechseln und den neuen Galera Cluster starten. Zunächst muss aber der Service gestoppt werden.

sudo systemctl stop mysqld
sudo galera_new_cluster

Gespannt ob es geklappt hat? Mal schnell über die Shell in die Datenbank schauen. Du wirst übrigens automatisch mit den SSL-Zertifikaten verbunden, weil das in der Galera-Config schon im [client] Bereich aktiviert ist.

mysql -u root -p -e "show status like 'wsrep_cluster_size'"

Als Antwort sollte jetzt “1” erscheinen.

Das war’s auch schon. Du hast jetzt einen Server-Cluster mit einem Server. Das ungefähr so sinnvoll wie eine 1-Mann-Armee. Aber dazu gleich mehr – erstmal versuchen wir, auf den Server zu connecten. Dazu musst du sicherstellen, dass die Config auf bind-address = 0.0.0.0 gesetzt ist.

Wenn du einen Client wie Workbench oder Navicat benutzt, achte darauf, dass “SSL” aktiviert ist. Die Zertifikate musst du nicht einfügen.

Solltes du komische Fehlermeldungen wie mariadb system error: 0 "Internal error/check" erhalten, bedeutet das, dass dein MySQL root User falsch konfiguriert ist. Um das zu beheben, wird diesem Zugriff von außen ermöglicht:

# Auf MySQL Client wechseln
mysql -u root -p

# Rechte für Root vergeben
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '<PASSWORT>' WITH GRANT OPTION;
FLUSH PRIVILEGES;

Wenn du jetzt den Server auf Reboot-Sicherheit testen möchtest, muss ich dich enttäuschen: Da dies der einzige Server im Cluster ist, muss er wieder mit galera_new_cluster gestartet werden. Das ist natürlich nicht nötig, wenn es mehrere gibt!

Um einen Cluster zu starten, sollte der Server noch zweimal geklont werden. Dann nicht vergessen, bei allen Servern die Datei /etc/mysql/conf.d/galera.cnf anzupassen. Dadurch, dass die Server geklont werden, sind die SSL-Keys bereits an Ort und Stelle.

# Achtung: Hier IPs aller Server ersetzen!
wsrep_cluster_address="gcomm://<SERVERIP 1>,<SERVERIP 2>,<SERVERIP 3>"

# Achtung: Hier IP ersetzen!
wsrep_node_address="<SERVERIP>"

wsrep_node_name="MariaDB<ServerNummer>"

Anschließend die MySQL-Services auf allen Servern ausschalten:

sudo systemctl stop mysqld

Einmal ein Stoßgebet zum Servergott senden und auf dem ersten Server den Cluster starten:

sudo galera_new_cluster

Wenn das geklappt hat, die Services auf den anderen Servern starten:

sudo systemctl start mysqld

Fertig!

MariaDB wieder komplett entfernen

Manchmal kommt es vor, dass du z.B. die falsche Version installierst, oder dich irgendwas anderes nervt. Um ALLES zu löschen, kannst du folgende Befehle ausführen. Es müssen nicht unbedingt alle Pakete vorhanden sein!

sudo apt-get purge mariadb-* -y
sudo apt-get remove --purge mysql* -y
sudo apt-get purge mysql* -y
sudo apt-get autoremove -y
sudo apt-get autoclean
sudo apt-get remove dbconfig-mysql

# Anschließend noch vorhandene Dateien löschen
rm -r /etc/mysql/*

Bonus: Datenbank mit User erstellen

Da man ja eigentlich den Root-User nicht remote aktivieren sollte, hier mal schnell die Befehle, um eine Datenbank inkl. User zu erstellen:

# Lokal einloggen
mysql -u root -p

# Datenbank anlegen
CREATE DATABASE testdb;

# Admin-User für diese DB anlegen (kann auch von außen erreicht werden)
CREATE USER 'testdb-admin'@'%' IDENTIFIED BY '<password>';
GRANT ALL PRIVILEGES ON testdb.* TO 'testdb-admin'@'%';
GRANT GRANT OPTION ON testdb.* TO 'testdb-admin'@'%';
FLUSH PRIVILEGES;

Wenn du einen globalen User anlegen möchtest, ändere das testdb.* in *.* um.

Data-Verzeichnis verschlüsseln

Gemäß der neuen DSGVO (aber auch logisch schon vorher) solltest du die Daten deiner Datenbank so verschlüsseln, dass ein einfacher Diebstahl des Servers nicht den Zugriff auf alle Daten erlaubt. Ein Weg ist, das Data-Verzeichnis per ecryptfs zu verschlüsseln.

Dazu installieren wir ecryptfs, legen ein neues Verzeichnis an, verschlüsseln dieses und kopieren die MySQL-Daten.

# Galera/DB-Service stoppen
sudo systemctl stop mysqld

# Verhindern, dass der Dienst beim Booten gestartet wird, da wir zunächst manuell das Verzeichnis mounten müssen.
sudo systemctl disable mysql

# ecryptfs installieren
sudo apt -y install ecryptfs-utils

# Daten-Verzeichnis beim Galera-Cluster anpassen
sudo mcedit /etc/mysql/mariadb.conf.d/60-galera.cnf

Anpassung der Config:

[mysqld]
datadir = /var/lib/mysql-enc

Zurück in der Shell:

# Verzeichnis anlegen, das verschlüsselt wird
sudo mkdir /var/lib/mysql-enc

# MySQL-User zugreifen lassen
sudo chown mysql:mysql /var/lib/mysql-enc

# Verzeichnis verschlüsseln
sudo mount -t ecryptfs /var/lib/mysql-enc /var/lib/mysql-enc -o ecryptfs_cipher=aes,ecryptfs_key_bytes=32,ecryptfs_passthrough=no,ecryptfs_enable_filename_crypto=no

# Vorhandene MySQL-Daten kopieren und User anpassen
sudo cp /var/lib/mysql/* /var/lib/mysql-enc -R
sudo chown mysql:mysql /var/lib/mysql-enc/* -R

# Cluster starten
sudo galera_new_cluster

Das Verzeichnis sollte jetzt verschlüsselt sein. So kannst du es unmounten:

sudo umount /var/lib/mysql-enc

Wird der Server neu gestartet, musst du zunächst das Verzeichnis mounten, da es sonst Fehler gibt. ecryptfs verschlüsselt in dieser Einstellung nicht die Dateinamen, so dass Galera meckert!

sudo mount -t ecryptfs /var/lib/mysql-enc /var/lib/mysql-enc -o ecryptfs_cipher=aes,ecryptfs_key_bytes=32,ecryptfs_passthrough=no,ecryptfs_enable_filename_crypto=no

# Beim Neustarten des Clusters (oder nur ein Server)
sudo galera_new_cluster

# Beim Neustarten einer Node eines Clusters
sudo systemctl start mysql

Wie performt das Ganze?

Zeit für ein paar Tests! In meinem Fall habe ich drei MariaDB-Server zu einem Galera-Cluster zusammengefasst. Die Server stehen in unterschiedlichen Rechenzentrum (aber am gleichen Ort) und sind per SSL-Verschlüsselung miteinander verbunden.

Als nächstes habe ich eine SQL-Datei erstellt, die superduper viele Inserts enthält:

Die Idee ist, diese Datei per mysql-Shell Befehl an alle drei Server gleichzeitig zu senden. Fangen wir an mit Server 1:

Ca. 160 Writes pro Sekunde. Was passiert, wenn wir das gleiche parallel auf Server 2 machen?

Uh! Fast doppelt soviel! Und jetzt das ganze noch auf Server 3:

Ca. 390 Writes – das scheint das Maximum zu sein. Und wie hoch ist die Query-Time eines einfachen SQL-Statements, wenn alle Server ihren Import gleichzeitig durchführen?

Das ändert sich übrigens nicht, wenn der Import nicht aktiv ist.

Optimierungen, die man vornehmen kann

  • Setze innodb_buffer_pool_size auf 80% des verfügbaren RAMs oder berechne es genauer.
  • Setze innodb_buffer_pool_instances auf die Anzahl deiner CPU-Cores. Dabei solltest du aber innodb_buffer_pool_chunk_size so einstellen, dass instances * chunk_size = pool_size ist, denn sonst ist die Größe des Buffer-Pools irrelevant.
  • Den MySQLTuner laufen lassen. Der gibt viele wichtige Hinweise!
Björn Falszewski
31. Januar 2020
Disclaimer
Alle meine Artikel entstehen mit bestem Wissen und Gewissen, sind aber nicht perfekt und sollten immer nur als Ausgangspunkt für deine eigenen Recherchen bilden.

Sollte dir etwas Fehlerhaftes auffallen, freue ich mich über deine Nachricht!