Einrichtung eines hochverfügbaren Speichers mit DRBD und OCFS2

08.06.2015 Server Archiv
 
Bitte beachten Sie, dass es hierbei sich um einen von meiner alten Seite migrierten Artikel handelt.

Einleitung

Da sich in der Vergangenheit der Einsatz von GlusterFS zum synchronisieren der Webverzeichnisse aus ungeeignet herausgestellt hat, musste ich eine Alternative für die Server finden. In meinen Tests hatte sich herausgestellt, dass Seitenaufrufe teilweise bis zu 15 Sekunden dauerten, während dieselben Aufrufe weniger als eine Sekunde auf einem ext4-Dateisystem dauerten. Dazu kam, dass teilweise Fehler wegen File Locks auftraten.

Erfahrungsgemäß nutzen viele Firmen einen zentralen Speicher, um mit vielen virtuellen Nodes auf die Daten zuzugreifen. Mir war es jedoch wichtig, dass es keinen Single Point of Failure gibt. Die simpelste Lösung das Problem zu umgehen wäre also die Daten auf dem gleichen Server zu speichern, der auf diese zugreifen muss.

Im Internet finden sich unzählige Anleitungen dazu, wie man Webdaten zwischen Servern mittels Rsync oder Unison synchronisiert, allerdings sind diese Verfahren asynchron. Das heißt, Daten werden zeitverzögert übertragen. Stürzt der Knoten ab, auf dem diese Daten geschrieben wurden, dann sind die Daten erst verfügbar, sobald dieser Knoten wieder aktiv wird. Aus Sicherheitsgründen wäre also eine synchrone Replikation sinnvoll.

Nach längerer Recherche haben sich drei Softwarelösungen als die bekanntesten herausgestellt:
  • GlusterFS
  • DRBD (Protocol C)
  • Ceph
Schließlich habe ich mich für DRBD entschieden. Der Grund dafür war, dass sich für Ceph nur sehr unvollständige Anleitungen finden ließen. Außerdem synchronisiert DRBD auf Blockgerät-Level und stellt mir somit die Wahl des Dateisystems frei.

Hardware

Damit die Synchronisation zwischen den zwei neuen (bzw. alten) Servern reibungslos funktioniert, entschloss ich mich die Hardware aufzurüsten. Auf eBay habe ich also folgende Hardware gekauft:
  • 2x Mellanox MT25408A0-FCC-QI ConnectX, Dual Port 40Gb/s InfiniBand / 10GigE Adapter IC (zusammen 60€ gebraucht)
  • Gore 5m QSFP Infiniband Kabel (10€ gebraucht)
Neben einer direkten 40G-InfiniBand-Verbindung hatte ich nun auch die Möglichkeit meine Server via 10Gbe anzubinden.

Vorbereitungen

Nach dem Einbau der Netzwerkkarten wurden diese auch direkt auf beiden Servern erkannt:
# lspci | grep Mellanox
02:00.0 InfiniBand: Mellanox Technologies MT25408A0-FCC-QI ConnectX, Dual Port 40Gb/s InfiniBand / 10GigE Adapter IC with PCIe 2.0 x8 5.0GT/s In... (rev b0)

Da DRBD in der freien Version kein Infiniband/RDMA unterstützt, habe ich IP-over-Infiniband eingerichtet. Die Latenz soll laut einigen Blogs deutlich höher sein. Jedoch entschloss ich mich, mir RDMA später anzusehen. Um RDMA oder IP-over-Infiniband zu aktivieren musste ich rdma-core installieren:
# apt install rdma-core

Nun musste ich lediglich das Netzwerk im System anlegen.:
# --- 40g interface
auto ib0
iface ib0 inet static
address 10.0.22.1
netmask 255.255.255.0
post-up echo connected > /sys/class/net/ib0/mode
post-up ifconfig ib0 mtu 65520
Datei: /etc/network/interfaces

Auf dem zweiten Server musste ich das selbe noch einmal machen. Hier habe ich allerdings die IP-Adresse 10.0.22.2 eingetragen. Anschließend mussten auf beiden Servern die Interfaces aktiviert werden:
# ifup ib0

Nach einem Durchlauf mit iPerf konnte ich sehen, dass nicht die volle Bandbreite verfügbar war:
# iperf -c 10.0.22.1
------------------------------------------------------------
Client connecting to 10.0.22.1, TCP port 5001
TCP window size: 2.50 MByte (default)
------------------------------------------------------------
[  3] local 10.0.22.2 port 54348 connected with 10.0.22.1 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  22.8 GBytes  19.6 Gbits/sec
Ein kurzer Blick in htop zeigte, dass der Falschenhals der Messung die CPU-Leistung war. Einer der Kerne lief während der Tests auf 100% Last. Auch im Multithreaded-Mode konnte ich aufgrund der CPU lediglich 26 Gbit/s erreichen. Da die Netzwerkbandbreite in diesem Setup nicht der Flaschenhals ist habe ich das nicht weiter untersucht. Aus Interesse habe ich noch die Latenz gemessen.

Einfacher Ping:
# ping 10.0.22.22
PING 10.0.22.2 (10.0.22.2) 56(84) bytes of data.
...
64 bytes from 10.0.22.2: icmp_seq=8 ttl=64 time=0.179 ms
^C
--- 10.0.22.2 ping statistics ---
8 packets transmitted, 8 received, 0% packet loss, time 7003ms
rtt min/avg/max/mdev = 0.163/0.183/0.197/0.011 ms
Mit ca. 180µs nur für die Übertragung an den anderen Server wäre ein schreibender Zugriff vergleichsweise langsam.

Deshalb habe ich zum Vergleich das Ganze via RDMA getestet:
# apt install -y perftest

Server 2:
s02 # ib_send_lat
************************************
* Waiting for client to connect... *
************************************

Server 1:
s01 # ib_send_lat 10.0.22.2
---------------------------------------------------------------------------------------
        Send Latency Test
Dual-port       : OFF          Device         : mlx4_0
Number of qps   : 1            Transport type : IB
Connection type : RC           Using SRQ      : OFF
TX depth        : 1
Mtu             : 4096[B]
Link type       : IB
Max inline data : 236[B]
rdma_cm QPs     : OFF
Data ex. method : Ethernet
---------------------------------------------------------------------------------------
#bytes #iterations    t_min[usec]    t_max[usec]  t_typical[usec]    t_avg[usec]    t_stdev[usec]   99% percentile[usec]   99.9% percentile[usec]
2       1000          0.73           4.69         0.79               0.82           0.26            1.64                   4.69
---------------------------------------------------------------------------------------
Mit lediglich 0,82µs war die Latenz via RDMA also deutlich geringer. Da die Latenz nur den schreibenden Zugriff beeinflusst gehe ich nicht weiter darauf ein.

In den nachfolgenden Kapiteln gehe ich darauf ein, wie Sie selbst DRBD mit OCFS2 auf Ihren Servern installieren können.

DRBD

DRBD installieren:
# apt install drbd-utils

Modul laden und prüfen, ob es geladen wurde:
# modprobe drbd
# lsmod | grep drbd
drbd 548864 0

Folgende Konfiguration anlegen:
resource ssd1 {
protocol    C;
device      /dev/drbd0;
meta-disk   internal;

startup {
degr-wfc-timeout 60;
become-primary-on both;
}

disk {
on-io-error   detach;
c-plan-ahead  10;
c-fill-target 100K;
c-min-rate    500M;
c-max-rate    1000M;

no-disk-flushes;
no-disk-barrier;
}

net {
transport   tcp;
max-buffers 36k;
sndbuf-size 1024k;
rcvbuf-size 2048k;

allow-two-primaries;
}

on s01 {
disk    /dev/md3;
address 10.0.22.1:7789;
node-id 1;
}
on s02 {
disk    /dev/md3;
address 10.0.22.2:7789;
node-id 2;
}
}
Datei: /etc/drbd.d/ssd1.res

Anschließend muss die Ressource auf beiden Servern angelegt und initialisiert werden:
# systemctl stop drbd
# drbdadm create-md ssd1
# drbdadm up ssd1
# systemctl start drbd

Folgende Kommandos müssen auf einem der beiden Server ausgeführt werden:
s01 # drbdadm primary ssd1 --force
s01 # drbdadm -- --overwrite-data-of-peer primary all

Jetzt kann man via drbd status den aktuellen Status der Synchronisation anzeigen lassen:
# drbdadm status
ssd1 role:Primary
disk:UpToDate
s02 role:Secondary
replication:SyncSource peer-disk:Inconsistent done:9.92

Da ich DRBD für den Multi-Primary-Modus konfiguriert habe, musste ich auch ein Dateisystem verwenden, welches ein gleichzeitiges Einbinden erlaubte. Hier habe ich OCFS2 verwendet, da es exakt für diesen Anwendungsfall entwickelt wurde.

OCFS2

Da nun das geteiltes Gerät /dev/drbd0 verfügbar ist, kann OCFS2 installiert werden.
# apt install ocfs2-tools

Folgende Datei für das OCFS2-Cluster auf beiden Servern anlegen:
cluster:
node_count = 2
name = ssd1

node:
ip_port = 7777
ip_address = 10.0.22.1
number = 1
name = s01
cluster = ssd1

node:
ip_port = 7777
ip_address = 10.0.22.2
number = 2
name = s02
cluster = ssd1
Datei: /etc/ocfs2/cluster.conf

Dateisystem auf dem Gerät erstellen:
# mkfs.ocfs2 /dev/drbd0
Achtung: Falls zu diesem Zeitpunkt drbd0 zwischen den Servern noch nicht synchronisiert sind muss das Kommando auf dem aktuellen Primärknoten ausgeführt werden.

Anschließend kann das Gerät gemountet werden. Dazu folgende Zeile in die fstab eintragen:
/dev/drbd0 /mnt/ssd1 ocfs2 defaults,noauto,_netdev 0 0
Datei: /etc/fstab

Nun muss nur noch der OCFS2-Dienst aktiviert und neu gestartet, sowie das Verzeichnis angelegt und gemountet werden:
# systemctl enable ocfs2
# systemctl start ocfs2
# mkdir /mnt/ssd1
# mount /mnt/ssd1

Legt man nun eine Datei auf dem einen Server an, ist diese auch auf dem anderen Server vorhanden:
s01 # touch /mnt/ssd1/test.txt
s02 # ls /mnt/ssd1
lost+found  test.txt

Tests

Um die Geschwindigkeit des Ganzen zu messen habe ich fio installiert:
# apt install fio

Random Read/Write Performance:
# fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=test --bs=4k --iodepth=64 --size=4G --readwrite=randrw --rwmixread=75
test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.1
Starting 1 process
Jobs: 1 (f=1): [m(1)][100.0%][r=191MiB/s,w=63.0MiB/s][r=49.0k,w=16.1k IOPS][eta 00m:00s]
test: (groupid=0, jobs=1): err= 0: pid=5511: Mon May 18 13:04:57 2020
read: IOPS=45.6k, BW=178MiB/s (187MB/s)(3070MiB/17242msec)
bw (  KiB/s): min=109651, max=196664, per=99.91%, avg=182169.65, stdev=16683.36, samples=34
iops        : min=27412, max=49166, avg=45542.29, stdev=4170.95, samples=34
write: IOPS=15.2k, BW=59.5MiB/s (62.4MB/s)(1026MiB/17242msec)
bw (  KiB/s): min=36384, max=66120, per=99.93%, avg=60890.12, stdev=5638.36, samples=34
iops        : min= 9096, max=16530, avg=15222.50, stdev=1409.58, samples=34
cpu          : usr=13.58%, sys=84.77%, ctx=19261, majf=0, minf=9
IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwt: total=785920,262656,0, short=0,0,0, dropped=0,0,0
latency   : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=178MiB/s (187MB/s), 178MiB/s-178MiB/s (187MB/s-187MB/s), io=3070MiB (3219MB), run=17242-17242msec
WRITE: bw=59.5MiB/s (62.4MB/s), 59.5MiB/s-59.5MiB/s (62.4MB/s-62.4MB/s), io=1026MiB (1076MB), run=17242-17242msec

Ein Test mit einem single-node (lokal) OCFS2 sieht wie folgt aus:
# fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=test --bs=4k --iodepth=64 --size=4G --readwrite=randrw --rwmixread=75
test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.1
Starting 1 process
Jobs: 1 (f=1): [m(1)][100.0%][r=225MiB/s,w=74.6MiB/s][r=57.6k,w=19.1k IOPS][eta 00m:00s]
test: (groupid=0, jobs=1): err= 0: pid=11794: Mon May 18 13:27:34 2020
read: IOPS=51.6k, BW=202MiB/s (211MB/s)(3070MiB/15231msec)
bw (  KiB/s): min=124768, max=254040, per=99.72%, avg=205821.00, stdev=37717.36, samples=30
iops        : min=31192, max=63510, avg=51455.23, stdev=9429.37, samples=30
write: IOPS=17.2k, BW=67.4MiB/s (70.6MB/s)(1026MiB/15231msec)
bw (  KiB/s): min=41648, max=86296, per=99.71%, avg=68777.97, stdev=12824.76, samples=30
iops        : min=10412, max=21574, avg=17194.47, stdev=3206.19, samples=30
cpu          : usr=13.07%, sys=85.17%, ctx=22426, majf=0, minf=9
IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwt: total=785920,262656,0, short=0,0,0, dropped=0,0,0
latency   : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=202MiB/s (211MB/s), 202MiB/s-202MiB/s (211MB/s-211MB/s), io=3070MiB (3219MB), run=15231-15231msec
WRITE: bw=67.4MiB/s (70.6MB/s), 67.4MiB/s-67.4MiB/s (70.6MB/s-70.6MB/s), io=1026MiB (1076MB), run=15231-15231msec

Die zuvor mit ext4 (lokal) getesteten Werte sehen wie folgt aus:
# fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=test --bs=4k --iodepth=64 --size=4G --readwrite=randrw --rwmixread=75
test: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=64
fio-3.1
Starting 1 process
test: Laying out IO file (1 file / 4096MiB)
Jobs: 1 (f=1): [m(1)][100.0%][r=263MiB/s,w=87.2MiB/s][r=67.3k,w=22.3k IOPS][eta 00m:00s]
test: (groupid=0, jobs=1): err= 0: pid=6275: Mon May 18 13:20:40 2020
read: IOPS=74.9k, BW=293MiB/s (307MB/s)(3070MiB/10493msec)
bw (  KiB/s): min=258664, max=351368, per=100.00%, avg=301115.15, stdev=39726.61, samples=20
iops        : min=64666, max=87842, avg=75278.85, stdev=9931.57, samples=20
write: IOPS=25.0k, BW=97.8MiB/s (103MB/s)(1026MiB/10493msec)
bw (  KiB/s): min=86968, max=117752, per=100.00%, avg=100736.10, stdev=13403.82, samples=20
iops        : min=21742, max=29438, avg=25184.00, stdev=3350.98, samples=20
cpu          : usr=18.81%, sys=73.26%, ctx=8416, majf=0, minf=8
IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwt: total=785920,262656,0, short=0,0,0, dropped=0,0,0
latency   : target=0, window=0, percentile=100.00%, depth=64

Run status group 0 (all jobs):
READ: bw=293MiB/s (307MB/s), 293MiB/s-293MiB/s (307MB/s-307MB/s), io=3070MiB (3219MB), run=10493-10493msec
WRITE: bw=97.8MiB/s (103MB/s), 97.8MiB/s-97.8MiB/s (103MB/s-103MB/s), io=1026MiB (1076MB), run=10493-10493msec

Was sieht man hier?
Die meisten Performanceeinbußen sind OCFS2 zuzuschreiben. Wird DRBD verwendet, wird das Dateisystem vergleichsweise nur wenig langsamer. Wem also Performance besonders wichtig ist, kann auch ein Single-Primary-System mit Heartbeat und ext4 nutzen.