Das Schöne an Proxmox VE (und generell an Open Source Software) ist, dass man nicht auf die Anwendungsmöglichkeiten beschränkt ist, die ein Hersteller für mich vorsieht.
Proxmox VE bindet FC- oder iSCSI-Storages normalerweise als Block-Storage ein. Das ist recht unkompliziert, performant und einfach zu erledigen. Allerdings lassen sich dann keine Snapshots von einzelnen Images oder VMs erzeugen. Storage-seitig können zwar Snapshots erzeugt werden (sofern die Storage das anbietet), aber dann wird eben die gesamte LUN einem Snapshot unterzogen.
Möchte man FC-/iSCSI-Storage und Proxmox-seitige Snapshots von einzelnen VMs, kann man mit Cluster-Filesystemen arbeiten, die bereits im Linux Kernel integriert sind wie beispielsweise GFS2.
Dieser Blog Artikel beschreibt die Vorgehensweise, wenn ein bestehender FC-/iSCSI-/GFS2-/Proxmox-Cluster um zusätzliche Server erweitert werden soll.
Ich setze voraus, das es bereits einen fertigen Proxmox VE Cluster mit funktionierendem Corosync gibt und iSCSI-LUNs bereits als Device auf dem Proxmox VE Server erscheinen.
Wie immer gilt: Vorher ein Backup erstellen für den Fall, dass etwas nicht nach Plan läuft oder völlig schief geht.
Schema der Erweiterung
%%{init: {'theme': 'neutral', "flowchart" : { "curve" : "basis" } } }%%
flowchart BT
subgraph Storage
SAN["FC-/iSCSI SAN (z.B. LUN0)"]
end
subgraph alt[Bestehende Server]
style alt fill:lightgreen
SAN -- FC / iSCSI --> PVE1
SAN -- FC / iSCSI --> PVE2
SAN -- FC / iSCSI --> PVE3
PVE1["Proxmox VE"]
PVE2["Proxmox VE"]
PVE3["Proxmox VE"]
end
subgraph neu[zusätzlicher Server]
style neu fill:yellow
SAN -- FC / iSCSI --> PVE4
PVE4["Proxmox VE"]
linkStyle 3 stroke-dasharray: 3 3
end
subgraph LVM
lvmlockd <==> LVM_VG1
style lvmlockd fill:orange
dlm <==> lvmlockd
style dlm fill:orange
PVE1 --> LVM_PV
PVE2 --> LVM_PV
PVE3 --> LVM_PV
PVE4 -.-> LVM_PV
LVM_PV["LVM PV (z.B. /dev/sdf)"] ---> LVM_VG1
LVM_VG1["LVM VG1 (z.B. /dev/vg1)"] --> LVM_LV1
end
LVM_LV1["LVM LV1 (z.B. /dev/vg1/lv1)"] --BlockDevice--> GFS2_FS1["GFS2 FS1 (z.B. ClusterName:fs1)"] --FileSystem--> DIR_FS1["Mountpoint für FS1 (z.B. /mnt/gfs2-fs1)"]
subgraph fs1[File Storage]
DIR_FS1["Mountpoint (z.B. /mnt/gfs2-fs1)"] --/mnt/gfs2-fs1/images/100/--> VM_100["VM-Image (z.B. vm-100-disk-0.qcow2)"]
DIR_FS1["Mountpoint (z.B. /mnt/gfs2-fs1)"] --/mnt/gfs2-fs1/images/101/--> VM_101["VM-Image (z.B. vm-101-disk-0.qcow2)"]
end
Benötigte Pakete installieren
Zusätzlich zum bereits durch Proxmox VE verwendeten corosync benötigen wir einen Lockmanager für LVM sowie für später noch die Tools für das Management des GFS2:
apt install lvm2-lockd dlm-controld gfs2-utils
Nach der Installation der Pakete werden die Services bereits automatisch gestartet. Mit dmesg können wir das Ergebnis betrachten:
[ 2284.071591] DLM installed
[ 2285.994440] dlm: Using TCP for communications
[ 2285.994869] dlm: lvm_global: joining the lockspace group...
[ 2286.013531] dlm: lvm_global: dlm_recover 1
[ 2286.013554] dlm: lvm_global: group event done 0
[ 2286.013651] dlm: lvm_global: add member 4
[ 2286.013688] dlm: lvm_global: add member 3
[ 2286.013698] dlm: lvm_global: add member 2
[ 2286.013703] dlm: lvm_global: add member 1
[ 2286.013751] dlm: connecting to 3
[ 2286.014196] dlm: got connection from 2
[ 2286.014241] dlm: connecting to 2
[ 2286.014451] dlm: connecting to 1
[ 2286.014494] dlm: successful connected to node 3
[ 2286.014729] dlm: successful connected to node 2
[ 2286.014741] dlm: got connection from 1
[ 2286.014878] dlm: successful connected to node 1
[ 2286.014951] dlm: got connection from 3
[ 2286.015169] dlm: version 0x00030002 for node 2 detected
[ 2286.015472] dlm: version 0x00030002 for node 3 detected
[ 2286.015598] dlm: version 0x00030002 for node 1 detected
[ 2286.016968] dlm: lvm_global: dlm_recover_members 4 nodes
[ 2286.016985] dlm: lvm_global: join complete
[ 2286.042853] dlm: lvm_global: generation 3 slots 4 1:1 2:2 3:3 4:4
[ 2286.042860] dlm: lvm_global: dlm_recover_directory
[ 2286.044671] dlm: lvm_global: dlm_recover_directory 0 in 0 new
[ 2286.119042] dlm: lvm_global: dlm_recover_directory 0 out 3 messages
[ 2286.271171] dlm: lvm_global: dlm_recover 1 generation 3 done: 228 ms
[ 2287.048460] dlm: lvm_vg1: joining the lockspace group...
[ 2287.065781] dlm: lvm_vg1: dlm_recover 1
[ 2287.065821] dlm: lvm_vg1: group event done 0
[ 2287.065882] dlm: lvm_vg1: add member 4
[ 2287.065900] dlm: lvm_vg1: add member 3
[ 2287.065903] dlm: lvm_vg1: add member 2
[ 2287.065905] dlm: lvm_vg1: add member 1
[ 2287.067360] dlm: lvm_vg1: dlm_recover_members 4 nodes
[ 2287.067387] dlm: lvm_vg1: join complete
[ 2287.143024] dlm: lvm_vg1: generation 3 slots 4 1:1 2:2 3:3 4:4
[ 2287.143032] dlm: lvm_vg1: dlm_recover_directory
[ 2287.145123] dlm: lvm_vg1: dlm_recover_directory 1 in 1 new
[ 2287.170910] dlm: lvm_vg1: dlm_recover_directory 0 out 3 messages
[ 2287.226828] dlm: lvm_vg1: dlm_recover 1 generation 3 done: 84 ms
Mit dlm_tool status und dlm_tool ls können wir uns das Ergebnis danach auch noch mal anschauen:
root@pve4:~# dlm_tool status
cluster nodeid 4 quorate 1 ring seq 253 253
daemon now 2505 fence_pid 0
node 1 M add 2283 rem 0 fail 0 fence 0 at 0 0
node 2 M add 2283 rem 0 fail 0 fence 0 at 0 0
node 3 M add 2283 rem 0 fail 0 fence 0 at 0 0
node 4 M add 2283 rem 0 fail 0 fence 0 at 0 0
root@pve4:~# dlm_tool ls
dlm lockspaces
name lvm_vg1
id 0x31931822
flags 0x00000000
change member 4 joined 1 remove 0 failed 0 seq 1,1
members 1 2 3 4
name lvm_global
id 0x12aabd2d
flags 0x00000000
change member 4 joined 1 remove 0 failed 0 seq 1,1
members 1 2 3 4
Das LVM Locking und DLM sind nun also betriebsbereit. Schauen wir uns die PV/VG/LV des LVM an:
root@pve4:~# pvs
PV VG Fmt Attr PSize PFree
/dev/sda3 pve lvm2 a-- <99.50g <12.38g
/dev/sdf vg1 lvm2 a-- <99.97g 992.00m
Das PV (Physical Volume) sieht aus wie erwartet. Werfen wir einen Blick auf die Volume Group:
root@pve4:~# vgs
VG #PV #LV #SN Attr VSize VFree
pve 1 3 0 wz--n- <99.50g <12.38g
vg1 1 2 0 wz--ns <99.97g 992.00m
Die Volume Group ist da und mit s = Shared gekennzeichnet. So soll das sein. Und das LV?
root@pve4:~# lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
data pve twi-a-tz-- 52.37g 0.00 1.59
root pve -wi-ao---- 24.75g
swap pve -wi-ao---- 8.00g
gfs2-lv1 vg1 -wi------- 50.00g
Das Logical Volume ist noch nicht aktiv, darum kümmern wir uns gleich. Zunächst müssen wir dafür sorgen, dass auch weitere Nodes in das GFS2 Filesystem aufgenommen werden können, in dem wir die Anzahl der GFS2-Journals prüfen und ggf. erweitern.
GFS2 Journals prüfen und ggf. erweitern
Pro Node im Cluster benötigt jedes GFS2-Filesystem ein eigenes Journal. Haben wir drei Nodes im Cluster, benötigen wir also mindestens 3 Journals. Soll eine hinzukommen wie bei uns, sollten wir die Anzahl prüfen und ggf. erhöhen. Es schadet auch nicht, die Anzahl der Journals vorsorglich höher auszulegen. Laut Dokumentation hat das keinen negativen Effekt, sorgt aber dafür dass wir später einfacher neue Nodes in den Cluster aufnehmen können.
Auf einer bereits bestehenden Node im Cluster können wir das mit gfs2_edit -p jindex <device> | grep journal herausfinden:
root@pve1:~# gfs2_edit -p jindex /dev/vg1/gfs2-lv1 | grep journal
3/3 [fc7745eb] 1/20 (0x1/0x14) +0: File journal0
4/4 [8b70757d] 2/32859 (0x2/0x805b) +0: File journal1
5/5 [127924c7] 3/65698 (0x3/0x100a2) +0: File journal2
Um eine vierte Node in den GFS2-Cluster aufnehmen zu können müssen wir also mindestens ein weiteres Journal erzeugen. Da etwas mehr Journale nicht schaden, erzeugen wir direkt zwei weitere für den Fall dass noch mal eine neue Node hinzugefügt werden soll. Dazu verwenden wir gfs2_jadd -j <anzahl zusätzliche logs> -J <log size> <device>.
Nicht ungeduldig werden - das kann einige Minuten oder länger dauern und man erhält in der Zwischenzeit keine Ausgabe.
root@pve1:~# gfs2_jadd -j 2 -J 128 /mnt/gfs2-lv1/
Filesystem: /mnt/gfs2-lv1/
Old journals: 3
New journals: 5
Diesen Vorgang müssen wir für jedes GFS2-Filesystem wiederholen, das auf die weiteren Nodes ausgerollt werden soll.
Logical Volumes aktivieren im Shared Mode
Die LVs auf denen die GFS2-Filesysteme liegen sind auf den anderen Nodes ja bereits gemounted. Wir müssen LVM nun also mitteilen, dass wir diese auf dem neuen Host ebenfalls shared mounten möchten, da sie sonst aus Sicherheitsgründen exklusiv gemounted werden.
Mit lvchange -asy können wir das Logical Volume aktivieren. Das “s” muss angegeben werden um das LV shared zu mounten. Geben wir das nicht an, wird versucht das LV im “e” = exclusive Mode zu mounten und stünde nur auf einer Node zur Verfügung. Damit wäre es nicht als Grundlage für unser GFS2 geeignet.
root@pve4:~# lvchange -asy /dev/vg1/gfs2-lv1
root@pve4:~# lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
data pve twi-a-tz-- 52.37g 0.00 1.59
root pve -wi-ao---- 24.75g
swap pve -wi-ao---- 8.00g
gfs2-lv1 vg1 -wi-a----- 50.00g
Dies müssen wir für jedes gewünschte GFS2-Filesystem wiederholen.
GFS2 mounten
Nun können wir das GFS2-Filesystem unter dem gewünschten Mountpoint einhängen:
root@pve4:~# mkdir /mnt/gfs2-lv1
root@pve4:~# mount /dev/vg1/gfs2-lv1 /mnt/gfs2-lv1/
root@pve4:~# df -h
Filesystem Size Used Avail Use% Mounted on
[...]
/dev/mapper/vg1-gfs2--lv1 50G 11G 40G 22% /mnt/gfs2-lv1
[...]
Directory Storage in Proxmox hinzufügen
Damit wir dort qcow2-Images für VMs hinlegen können, müssen wir die Storage als shared Directory-Storage in Proxmox noch anlegen. Das lässt sich entweder über die Web-GUI oder direkt über die Shell erledigen:
pvesm add dir gfs2-lv1 --path /mnt/gfs2-lv1 --content images,rootdir --shared 1
Soll das so?
Normalerweise sollte sich ein Cluster Manager wie Pacemaker um das automatische Starten, Monitoren und Stoppen der benötigten Komponenten und Dienste kümmern. Da Proxmox einen eigenen Cluster Manager verwendet, der sich allerdings ausschließlich um VMs und Container kümmert, müssen wir die einzelnen Komponenten unmanaged verwenden (Notiz an mich: Vielleicht gibt’s da noch bessere Varianten - mal darüber nachdenken).
In besagter Umgebung habe ich zwar das iSCSI-Login auf automatisch gesetzt, lvmlockd und dlm habe ich allerdings auf disabled gesetzt um sie nach dem Systemstart dann manuell hochzufahren und das GFS anschließend einzubinden.
systemctl disable lvmlockd dlm
lvmlockd und dlm starte ich also nach einem Host Neustart manuell:
systemctl start lvmlockd dlm
Anschließend mounte ich das GFS2 dann als Filesystem:
mount -t gfs2 /dev/vg1/lv1 /mnt/gfs2-lv1
Mir persönlich ist das zu viel Handarbeit für ein Cluster Filesystem. Zumal die Lösung oben nicht einfach zu supporten ist. Ich würde sie daher auch nur in Ausnahmefällen einsetzen und regulär eher darauf verzichten.