MySQL Master Master Synchronisation einrichten

Wenn ein Server der Last der Web-Anfragen nicht mehr Herr wird, oder man eine erhöhte Ausfallsicherheit benötigt, kommt man um die Dopplung der Datenbank nicht mehr herum.

Als Open-Source Fan hat man dazu mit MySQL eine Datenbank an der Hand die von Haus aus dafür schon einiges mitbringt, aber ohne eine gewisse Konfigurationsorgie geht es bei OpenSource eben meist nicht. Mit dieser Anleitung versuche ich diesen nervigen Stress zu minimieren und mir selbst eine Gedächtnisstütze zu schaffen, wenn ich das Ganze nochmals machen muss oder die Konfiguration updaten muss.

Diese Anleitung ist nichts für Anfänger und Einsteiger, da man seine Datenbanken bei diesen Experimenten sehr schnell kaputt machen kann.

 

Ziel:

Zwei Server sollen sowohl von den Webservern als auch von den Datenbanken synchron laufen, damit im Falle eines Ausfalls oder einer Lastspitze zwischen beiden Servern hin und her geschaltet werden kann, ohne das korrupte Datensätze entstehen. Es muss also parallel schreibend und lesend auf beide DBs zugegriffen werden können und die Datensätze müssen dabei gleichzeitig synchronisiert werden. Die gebräuchlichen Buzz-Words hierfür sind Datenbank-Cluster, Hochverfügbarkeitslösung und Ausfallsicherheit.

Um dies zu gewährleisten ist man mit einer Master-Master Synchronistation am Besten bedient.

Aufbau

Es existieren 2 Server:

DB1 oder Master1: Der erste Server, der seit längerem läuft und produktiv ist.

DB2 oder Master2: Der zweite neu gemietete Server mit einer leeren MySQL Datenbank.

DB1 und DB2 werden in der /etc/hosts Datei mit Namen und IP hinterlegt, damit später mit Namen anstatt IPs gearbeitet werden kann. Dadurch muss bei einem Austausch der Server weniger geändert werden.

Prinzip und Hintergrundwissen

MySQL kann keine direkte Master-Master Replikation. Im folgenden werden deshalb im Folgenden zwei Master-Slave Replikationen eingerichtet. Der Master des einen ist der Slave des anderen und vis versa.

Der zentrale Punkt dabei ist es Konflikte bei Primärschlüsseln um jeden Preis zu vermeiden.

MySQL zählt Primärschlüssel beim erstellen automatisch um den Wert eins hoch. Wenn einer der Server aus dem Takt gerät, weil die Sync-Verbindung für eine gewisse Zeit getrennt wird kann es sonst zu sehr unschönen und arbeitsintensiven Split Brain Situationen kommen. Um diese Situation von vornherein zu vermeiden wird die MySQL Konfiguration so angepasst, das DB1 nur gerade Primärschlüssel vergibt und DB2 nur ungerade. Falls jemand auf die (blöde) Idee gekommen ist Rechnungsnummern, Artikelnummern o.ä. als Primärschlüssel zu verwenden ist das natürlich ein Problem, da eine fortlaufende Nummerierung nicht mehr gewährleistet ist. CMS Systeme wie Typo3 oder WordPress haben damit kein Problem.

SSH-User erstellen

Der Replikations-User muss natürlich auf beiden Systemen vorhanden sein und muss von beiden Systemen vorautorisiert auf das jeweils andere System zugreifen können.

DB1:

adduser replicator

su replicator

ssh-keygen -i rsa -b 2048

ssh-copy-id -i ~/.ssh/id_rsa.pub replicator@DB2

 

DB2:

adduser replicator

su replicator

ssh-keygen -i rsa -b 2048

ssh-copy-id -i ~/.ssh/id_rsa.pub replicator@DB1

 

DB1 MySQL Vollbackup erstellen und auf Master2 einspielen

Als erstes muss mittels mysqldump ein Vollbackup der Daten von DB1 gemacht werden.

mysqldump -p --master-data --all-databases –result-file=DB1_fulldump.sql

 

Dieses Vollbackup muss nun auf DB2 kopiert und eingelesen werden.

mysql -u root -p

source DB1_fulldump.sql;

 

Achtung! Da es sich um ein Vollbackup handelt wird auch die Benutzerdatenbank-Tabelle überschrieben.

Aus meiner Sicht hat das Verfahren den Vorteil, dass die DB direkt verwendet werden kann und ich mich nicht händisch darum kümmern muss, welcher User auf welche Tabelle mit welchen Rechten zugreifen durfte. Natürlich kann man auch jede Tabelle einzeln ex- und importieren und die Rechte anpassen. Man muss sich aber bewusst sein, dass dies dann bei jeder neuen Tabelle auf allen Cluster Servern von Hand gemacht werden muss. This is where the fun ends.

SSH Tunnel für Replikation einrichten

MySQL kommuniziert normal direkt über IPs und Port 3306 mit dem anderen Server. Diese Kommunikation findest normalerweise in eigenen Serverräumen statt und ist dadurch nach außen nicht sichtbar. Wenn das Hosting allerdings auf gemieteten Servern bei möglichst verschiedenen Providern statt findet ist die Klartext-Kommunikation von MySQL ein Problem, da alle Daten unverschlüsselt durch das Internet geschickt werden.

Um dieses Problem zu beheben gibt es im Grunde nur zwei Möglichkeiten: IPSec-VPN oder einen SSH-Tunnel (SSH-Port-Forwarding).

Ich habe dem SSH-Tunnel den Vorzug gegeben, weil diese Lösung im Vergleich zu VPN wesentlich leichter und schneller funktionsfähig ist.

Das Prinzip des SSH-Tunnels ist schnell erklärt. Man öffnet einen lokalen(!) Port auf DB1, SSH tunnelt diesen Port über das Netzwerk und gibt die Daten am anderen ende des Tunnels auf DB2 unter dem Zielport aus.

Eine normale SSH-Tunnel Syntax spiegelt auch genau das wieder:

ssh replicator@DB2 -L 3307:127.0.0.1:3306

 

Allerdings wird im aktuellen Fall auch der Rückkanal benötigt. Das kann man mit einem zweiten SSH Tunnel von DB2 nach DB1 realisieren, oder man benutzt einfach die eingebaute -R(everse) Option von SSH. Das hat den Vorteil, dass nur eine Seite überwacht und Aufgebaut werden muss.

Die komplette Syntax auf DB1:

ssh replicator@DB2 -L 3307:127.0.0.1:3306 -R 3307:127.0.0.1:3306

 

Ob der Tunnel funktionsfähig ist lässt sich sehr einfach testen. Man ruft auf einem DB-Server die MySQL-Shell mit dem Port 3307 auf.

mysql -u root -P 3307 -p

 

Im Prinzip reicht es, wenn „Enter password:“ angezeigt wird. Bekommt man diese Verbindung, steht der Tunnel und wir können (fast) mit der Replikation beginnen.

Automatierter Aufbau des SSH Tunnels mittels Crontab

Crontab vs. Inittab

Ich wurde jetzt schon mehrfach gefragt, warum Crontab und nicht Inittab.

(Für alle Linux Nutzer ist Inittab ein fester Begriff, für alle Anderen:) Inittab ist ein Systemdienst, der selbständig prüft, ob ein Dienst läuft. Falls er nicht mehr läuft startet Inittab den Dienst direkt neu. Genau da liegt das Problem: Wenn jetzt einer der Server neu gestartet wird, bricht die Verbindung und damit der Tunnel erwartungsgemäß zusammen. Inittab versucht sofort und permanent den Dienst neu zu starten. So weit, so gut, ABER inzwischen haben fast alle Rechenzentren Firewalls, Intrusion Prevention Systems (IPS) usw. etabliert. Wenn nun die Inittab im Sekundentakt versucht einen SSH-Dienst neu zu starten, vermuten IPS einen SSH-Angiff und blocken die Verbindung für einen unbekannten Zeitraum (von Minuten bis Tagen). Sehr schlecht, zumal die DBs die sofortige Wiederherstellung gar nicht benötigen (siehe oben).

Langer Rede, kurzer Sinn -> Crontab

Autossh

Damit der SSH-Tunnel nicht alle paar Minuten überprüft werden muss gibt es „autossh“

Ein kurzer Auszug aus der Feature-Liste: * Automatically restart SSH sessions and tunnels. * autossh is a program to start a copy of ssh and monitor it, restarting it as necessary should it die or stop passing traffic. The idea is from rstunnel (Reliable SSH Tunnel). Backs off on rate of connection attempts when experiencing rapid failures such as connection refused.

Autossh deckt damit genau unsere Anwendungsfälle ab.

Damit Autossh aus der Crontab sauber gestartet werden wird ein kurzes wrapper-script benötigt um sicherzustellen, dass autossh gestartet und ggf restartet wird.

autossh.sh

#!/bin/sh

tunnelsite=DB2

if ! screen -ls | grep -F .$tunnelsite >/dev/null; then

screen -d -m -S $tunnelsite autossh -n -N -T $tunnelsite -L 3307:127.0.0.1:3306 -R 3307:127.0.0.1:3306

fi

 

Dieses Script muss jetzt in der Crontab des Replikations-Users eingetragen werden.

Crontab -e

 

# m h dom mon dow command

*/15 * * * * /bin/bash /home/replicator/autossh.sh

 

DB Replikations User anlegen

Auf beiden Servern wird ein Replications User benötigt, der Daten vom jeweils anderen Server abholen darf.

Auf Master1 in der MySQL-Shell:

grant replication slave on *.* to replicator@'127.0.0.1' identified by 'a-secret-password';

 

Auf Master2 in der MySQL-Shell:

grant replication slave on *.* to replicator@'127.0.0.1' identified by 'a-secret-password';

Achtung! Immer IP-Adresse statt „localhost“ verwenden.

Anpassen der MySQL-Server-Konfigurationen

Anpassen der MySQL-Server-Konfigurationen

Achtung! Ich zeige nur die für die Anpassung wichtigen Zeilen der my.cnf!

Auf beiden Servern als root:

vi /etc/mysql/my.cnf

 

DB1

DB2

server-id = 1

server-id = 2

expire_logs_days = 21

expire_logs_days = 21

max_binlog_size = 500M

max_binlog_size = 500M

replicate-same-server-id = 0

replicate-same-server-id = 0

auto-increment-increment = 2

auto-increment-increment = 2

auto-increment-offset = 2

auto-increment-offset = 1

Danach muss man beide MySQL Server neu starten und auf beiden den Slave in der MySQL-Shell starten:

change master to master_host='127.0.0.1', master_user='replicator', master_password='a-secret-password';

 

Test der Konfiguration

Die relevanten Kommandos sind:

show master status\g

show slave status\g

start slave;

Fertig!

Security und System Hardening

Zu guter Letzt noch ein Security Tipp: Damit man DB2 bzw. DB1 nicht mittels eines vorauthentifizieren Logins von Server zu Server springen kann, empfiehlt es sich den Shell zugang abzustellen und nur ein einfaches Port-Forwarding zu ermöglichen. Die hierzu nötigen Restriktionen kann man direkt in .ssh/authorized_keys eintragen:

Als root oder replicator-User:
vi  /home/ replicator /.ssh/authorized_keys
command=”/bin/false”,no-agent-forwarding,no-X11-forwarding,no-pty ssh-rsa AAAA..
.

4 Kommentare
  1. Patrick
    Patrick sagte:

    Hier steht: “Falls jemand auf die (blöde) Idee gekommen ist Rechnungsnummern, Artikelnummern o.ä. als Primärschlüssel zu verwenden ist das natürlich ein Problem, da eine fortlaufende Nummerierung nicht mehr gewährleistet ist. CMS Systeme wie Typo3 oder WordPress haben damit kein Problem.”

    Was ist mit der Typo3-Tabelle fe_sessions und fe_session_data?

    Antworten
    • hagen
      hagen sagte:

      Das Sessionmanagement basiert soweit ich noch weiß auf generierten Session-IDs und diese sind eindeutig.
      Die oben beschriebene Lösung funktioniert bei uns seit 2014 problemlos. Daher wage ich die Aussage das es einfach funktioniert. 😉

      Antworten
  2. Klaus Scheffer
    Klaus Scheffer sagte:

    Ich hatte etwas Probleme, den Remote MySQL Server via SSH zu erreichen. Es lag daran, dass der mysql Client (mysql Ver 14.14 Distrib 5.5.49) per Default bei Localhost Verbindungen die Socket Verbindung nutzt. In der my.cnf, Abschnitt [client] zusätzlich den Parameter ‘protocol = TCP’ löste das Problem:

    [client]
    port = 3306
    socket = /var/run/mysqld/mysqld.sock
    protocol = TCP

    Gruß
    Klaus

    Antworten
  3. Amin
    Amin sagte:

    Ich habe das Problem, dass mein MariaDB-Server die my.cnf anscheinend nicht zur kentnis nimmt und keine server id setzt…

    Antworten

Hinterlasse einen Kommentar

An der Diskussion beteiligen?
Hinterlasse uns deinen Kommentar!

Schreibe einen Kommentar zu Amin Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert