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
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)
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 65520Datei:
/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/secEin 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 msMit 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 = ssd1Datei:
/etc/ocfs2/cluster.conf
Dateisystem auf dem Gerät erstellen:
# mkfs.ocfs2 /dev/drbd0Achtung: 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 0Datei:
/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 ichfio 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.