Guten Tag.
Ich möchte meine praktischen Erfahrungen beim Aufbau eines Speichersystems für KVM mit md RAID + LVM mit der Gemeinschaft teilen.
Das Programm umfasst:
- Aufbau von md RAID 1 aus NVMe SSD.
- Aufbau von md RAID 6 aus SATA SSD und herkömmlichen Festplatten.
- Besonderheiten der TRIM/DISCARD-Funktion bei SSD RAID 1/6.
- Erstellung eines bootfähigen md RAID 1/6 Arrays aus einem allgemeinen Plattenmix.
- Installation des Systems auf NVMe RAID 1 bei fehlender NVMe-Unterstützung im BIOS.
- Verwendung von LVM-Cache und LVM-thin.
- Verwendung von BTRFS-Snapshots und Send/Receive für Backups.
- Verwendung von LVM-thin-Snapshots und thin_delta für BTRFS-ähnliche Backups.
Wenn Sie interessiert sind, klicken Sie bitte hier.
Haftungsausschluss
Der Autor übernimmt keine Verantwortung für die Folgen der Verwendung oder Nicht-Verwendung von Materialien/Beispielen/Codes/Ratschlägen/Daten aus diesem Artikel. Durch das Lesen oder die Verwendung dieses Materials übernehmen Sie die Verantwortung für alle Folgen dieser Handlungen. Zu den möglichen Konsequenzen gehören:
- Bis zur Knusprigkeit gebratene NVMe SSD.
- Völlig erschöpfter Schreibressourcen und Ausfall von SSDs.
- Vollständiger Datenverlust auf allen Speichermedien, einschließlich der Backups.
- Defekte Computerhardware.
- Verlorene Zeit, Nerven und Geld.
- Alle anderen Folgen, die oben nicht aufgeführt sind.
Hardware
Verfügbar war:
Ein Motherboard aus dem Jahr 2013 mit Z87-Chipsatz, ausgestattet mit einem Intel Core i7 / Haswell.
- Prozessor mit 4 Kernen und 8 Threads
- 32 Gigabyte DDR3-Arbeitsspeicher
- 1 x 16 oder 2 x 8 PCIe 3.0
- 1 x 4 + 1 x 1 PCIe 2.0
- 6 x 6 GBps SATA 3 Anschlüsse
SAS-Adapter LSI SAS9211-8I, geflasht in den IT / HBA-Modus. Die RAID-fähige Firmware wurde absichtlich gegen HBA-Firmware ausgetauscht, um:
- Den Adapter jederzeit ersetzen zu können, ohne auf ein bestimmtes Modell angewiesen zu sein.
- TRIM/Discard funktionierte normal auf den Laufwerken, da diese Befehle in der RAID-Firmware überhaupt nicht unterstützt werden, während HBA im Allgemeinen egal ist, welche Befehle über den Bus gesendet werden.
Festplatten — 8 Stück HGST Travelstar 7K1000 mit einem Volumen von 1 TB im 2,5-Zoll-Format, wie sie für Laptops verwendet werden. Diese Platten waren zuvor in einem RAID 6-Verbund. In dem neuen System finden sie ebenfalls Verwendung für die Speicherung lokaler Backups.
Zusätzlich wurde hinzugefügt:
6 Stück SATA SSDs vom Modell Samsung 860 QVO 2TB. Diese SSDs benötigten großen Speicherplatz, SLC-Cache, eine gewisse Zuverlässigkeit und einen günstigen Preis. Die Unterstützung von Discard/Zero war unbedingt erforderlich, was durch eine Zeile im dmesg überprüft wird:
kernel: ata1.00: Discard_zeroes_data aktivieren
2 Stück NVMe SSDs vom Modell Samsung SSD 970 EVO 500GB.
Für diese SSDs ist die Geschwindigkeit beim zufälligen Lesen/Schreiben und die Lebensdauer für Ihre Bedürfnisse wichtig. Ein Kühler ist ein Muss. Absolut nötig. Andernfalls werden sie bei der ersten RAID-Synchronisation knusprig gebraten.
StarTech PEX8M2E2 Adapter für 2 x NVMe SSDs zur Installation in einen PCIe 3.0 8x Slot. Dies ist wiederum einfach ein HBA, aber für NVMe. Er unterscheidet sich von preiswerten Adaptern durch das Fehlen der Anforderung einer PCIe-Bifurkation vom Motherboard, dank eines integrierten PCIe-Switches. Er funktioniert sogar in den ältesten Systemen mit PCIe, selbst wenn es sich um einen x1 PCIe 1.0 Slot handelt. Natürlich mit der entsprechenden Geschwindigkeit. Es gibt keine RAIDs dort. Einen integrierten BIOS gibt es nicht. Ihre Systeme werden also nicht magisch lernen, von NVMe zu booten, geschweige denn NVMe-RAIDs zu erstellen, dank dieses Geräts.
Dieses Komponente wurde ausschließlich durch die Verfügbarkeit eines einzigen freien 8x PCIe 3.0 in der Systemstruktur beeinflusst. Bei Vorhandensein von zwei freien Steckplätzen lässt es sich problemlos durch zwei günstige PEX4M2E1 oder ähnliche Komponenten ersetzen, die überall ab einem Preis von 600 Rubel erhältlich sind.
Die bewusste Entscheidung, auf jegliche Hardware- oder im Chipsatz/BIOS integrierte RAID-Lösungen zu verzichten, wurde getroffen, um die vollständige Ersetzbarkeit des gesamten Systems zu gewährleisten, mit Ausnahme der SSDs/HDDs, wobei alle Daten erhalten bleiben. Idealerweise sollte sogar das installierte Betriebssystem beim Umstieg auf völlig neue/andere Hardware beibehalten werden können. Wichtig sind nur SATA- und PCIe-Ports. Es ist wie ein Live-CD oder ein bootfähiger USB-Stick, jedoch sehr schnell und etwas volumetrisch.
HumorSie wissen ja, wie das manchmal ist – manchmal muss man den gesamten Speicher dringend mitnehmen. Und Daten möchte man nicht verlieren. Deshalb sind alle genannten Speicher bequem in Schienen in den 5.25-Slots eines Standardgehäuses untergebracht.
Nun, und natürlich für Experimente mit verschiedenen SSD-Caching-Methoden in Linux.
Hardware-RAIDs sind langweilig. Man schaltet sie ein. Sie funktionieren oder funktionieren nicht. Mit mdadm gibt es immer Optionen.
Software
Früher wurde auf der Hardware Debian 8 Jessie installiert, die kurz vor dem EOL steht. Ein RAID 6 wurde aus den oben genannten HDDs in Kombination mit LVM erstellt. Darauf liefen virtuelle Maschinen in kvm/libvirt.
Da der Autor über entsprechende Erfahrungen im Erstellen tragbarer bootfähiger SATA/NVMe-Flashlaufwerke verfügt und um das gewohnte apt-Muster nicht zu stören, wurde als Zielsystem Ubuntu 18.04 gewählt, das bereits ausreichend stabilisiert ist, aber immer noch drei Jahre Unterstützung in Aussicht hat.
In dem genannten System sind alle erforderlichen Hardwaretreiber bereits standardmäßig enthalten. Es sind keine zusätzlichen Software- oder Treiberlösungen erforderlich.
Vorbereitung zur Installation
Für die Installation des Systems benötigen wir das Ubuntu Desktop-Image. Das Installationsprogramm des Server-Systems ist übermäßig unabhängig und fügt immer einen UEFI-Systembereich auf einer der Festplatten hinzu, was das gesamte Setup stört. Daher kann es nur im UEFI-Modus installiert werden. Es werden keine Alternativen angeboten.
Das ist für uns inakzeptabel.
Warum?Leider ist UEFI-Boot extrem schlecht mit softwarebasiertem RAID kompatibel, da niemand Backup-Optionen für die UEFI ESP-Partition anbietet. Im Internet finden sich Lösungen, die vorschlagen, die ESP-Partition auf einem USB-Stick abzulegen, aber das ist ein Fehlerpunkt. Es gibt Lösungen, die ein softwarebasiertes mdadm RAID 1 mit Metadaten der Version 0.9 nutzen, was das UEFI-BIOS nicht daran hindert, diese Partition zu erkennen, jedoch funktioniert dies nur bis zu dem Moment, an dem das BIOS oder ein anderes Betriebssystem etwas in die ESP-Partition schreibt, ohne sie mit den anderen Spiegeln zu synchronisieren.
Darüber hinaus hängt der UEFI-Boot von NVRAM ab, das nicht mit den Festplatten in ein neues System umgezogen werden kann, da es Teil des Motherboards ist.
Wir werden also kein neues Rad erfinden. Wir haben bereits ein bewährtes, altbewährtes Rad, das mittlerweile als Legacy/BIOS-Boot bekannt ist und den stolzen Namen CSM auf UEFI-kompatiblen Systemen trägt. Wir nehmen es einfach vom Regal, schmieren es, pumpen die Reifen auf und wischen es mit einem feuchten Tuch ab.
Die Desktop-Version von Ubuntu kann ebenfalls nicht ordentlich mit dem Legacy-Boot installiert werden, aber hier gibt es, wie man so schön sagt, immerhin Optionen.
Also, wir stellen die Hardware zusammen und laden das System von einem bootfähigen Ubuntu Live-USB. Wir müssen Pakete herunterladen, daher richten wir das Netzwerk ein, das bei Ihnen funktioniert. Falls es nicht funktioniert, können die benötigten Pakete im Voraus auf den USB-Stick geladen werden.
Wir gehen in die Desktop-Umgebung, starten den Terminal-Emulator, und los geht's:
#sudo bash
Wie…?Die oben stehende Zeile ist ein kanonischer Trigger für die Debatten über sudo. Mitgrößtenden größeren Möglichkeiten kommt auch einegrößtengrößere Verantwortung. Die Frage ist, ob Sie bereit sind, diese zu übernehmen. Viele glauben, dass die Verwendung von sudo auf diese Weise zumindest nicht vorsichtig ist. Doch:

#apt-get install mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc
Warum nicht ZFS…?Wenn wir Software auf unseren Computer installieren, leihen wir uns im Grunde die Hardware, um diese Software auszuführen.
Wenn wir dieser Software die Sicherheit unserer Daten anvertrauen, nehmen wir einen Kredit in Höhe der Kosten für die Wiederherstellung dieser Daten auf, den wir irgendwann zurückzahlen müssen.
Aus dieser Perspektive ist ZFS wie ein Ferrari, während mdadm+lvm mehr wie ein Fahrrad ist.
Subjektiv zieht der Autor es vor, ein geleastes Fahrrad unbekannten Personen zu leihen, anstatt einen Ferrari zu fahren. Der Preis ist dabei nicht hoch. Es sind keine Führerscheine erforderlich. Die Verkehrsregeln sind einfacher. Parkplätze sind kostenlos. Die Zugänglichkeit ist besser. Man kann dem Fahrrad immer Füße anbringen, und es lässt sich problemlos selbst reparieren.
Warum also BTRFS…?Um ein Betriebssystem zu booten, benötigen wir ein Dateisystem, das von Legacy/BIOS GRUB standardmäßig unterstützt wird und gleichzeitig die Möglichkeit bietet, Snapshots in Echtzeit zu erstellen. Wir werden es für die /boot-Partition verwenden. Darüber hinaus zieht der Autor vor, dieses FS für / (Root) zu nutzen und weist darauf hin, dass für jede andere Software separate Partitionen auf LVM erstellt und in die benötigten Verzeichnisse eingebunden werden können.
Weder Images virtuellen Maschinen, noch Datenbanken werden wir auf diesem FS speichern.
Dieses FS wird ausschließlich zur Erstellung von sofortigen System-Snapshots verwendet, ohne dass das System heruntergefahren wird, gefolgt von der Übertragung dieser Snapshots auf ein Backup-Laufwerk mittels send/receive.
Darüber hinaus bevorzugt der Autor, die Software auf der Hardware auf ein Minimum zu beschränken und die gesamte andere Software in virtuellen Maschinen auszuführen, wobei er Techniken wie die GPU- und PCI-USB-Host-Controller-Passthrough in KVM über IOMMU verwendet.
Auf der Hardware verbleiben lediglich - die Datenspeicherung, Virtualisierung und Datensicherung.
Wenn Sie ZFS mehr vertrauen, sind sie für den angegebenen Anwendungsfall grundsätzlich austauschbar.
Dennoch ignoriert der Autor bewusst die eingebauten Funktionen zur Spiegelung / RAID und Redundanz, die in ZFS, BRTFS und LVM vorhanden sind.
Als zusätzliches Argument hat BTRFS die Fähigkeit, zufällige Schreibvorgänge in sequenzielle umzuwandeln, was sich positiv auf die Geschwindigkeit der Synchronisierung von Snapshots / Backups auf HDDs auswirkt.
Scannen wir alle Geräte erneut:
#udevadm control --reload-rules && udevadm trigger
Lassen Sie uns umsehen:
#lsscsi && nvme list
[0:0:0:0] Laufwerk ATA Samsung SSD 860 2B6Q /dev/sda
[1:0:0:0] Laufwerk ATA Samsung SSD 860 2B6Q /dev/sdb
[2:0:0:0] Laufwerk ATA Samsung SSD 860 2B6Q /dev/sdc
[3:0:0:0] Laufwerk ATA Samsung SSD 860 2B6Q /dev/sdd
[4:0:0:0] Laufwerk ATA Samsung SSD 860 2B6Q /dev/sde
[5:0:0:0] Laufwerk ATA Samsung SSD 860 2B6Q /dev/sdf
[6:0:0:0] Laufwerk ATA HGST HTS721010A9 A3J0 /dev/sdg
[6:0:1:0] Laufwerk ATA HGST HTS721010A9 A3J0 /dev/sdh
[6:0:2:0] Laufwerk ATA HGST HTS721010A9 A3J0 /dev/sdi
[6:0:3:0] Laufwerk ATA HGST HTS721010A9 A3B0 /dev/sdj
[6:0:4:0] Laufwerk ATA HGST HTS721010A9 A3B0 /dev/sdk
[6:0:5:0] Laufwerk ATA HGST HTS721010A9 A3B0 /dev/sdl
[6:0:6:0] Laufwerk ATA HGST HTS721010A9 A3J0 /dev/sdm
[6:0:7:0] Laufwerk ATA HGST HTS721010A9 A3J0 /dev/sdn
Knoten SN Modell Namespace Nutzung Format FW Rev
---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1 S466NXXXXXXX15L Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7
/dev/nvme1n1 S5H7NXXXXXXX48N Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7
Partitionierung der „Festplatten“
NVMe SSD
Wir werden sie jedoch nicht partitionieren. Unser BIOS erkennt diese Speichergeräte ohnehin nicht. Daher werden sie komplett in ein Software-RAID integriert. Wir werden dort nicht einmal Partitionen erstellen. Wenn man es nach dem „Kanon“ oder „grundsätzlich“ möchte – erstellen Sie eine große Partition, wie bei HDDs.
SATA HDD
Hier muss nichts Besonders erfunden werden. Wir werden einen Abschnitt für alles erstellen. Wir erstellen diesen Abschnitt, weil das BIOS diese Laufwerke erkennt und sogar versuchen kann, davon zu booten. Später werden wir sogar GRUB auf diesen Laufwerken installieren, damit das System unerwartet funktioniert.
#cat >hdd.part << EOF
Bezeichnung: dos
Bezeichnungs-ID: 0x00000000
Gerät: /dev/sdg
Einheit: Sektoren
/dev/sdg1 : start= 2048, size= 1953523120, type=fd, bootable
EOF
#sfdisk /dev/sdg < hdd.part
#sfdisk /dev/sdh < hdd.part
#sfdisk /dev/sdi < hdd.part
#sfdisk /dev/sdj < hdd.part
#sfdisk /dev/sdk < hdd.part
#sfdisk /dev/sdl < hdd.part
#sfdisk /dev/sdm < hdd.part
#sfdisk /dev/sdn < hdd.part
SATA SSD
Hier ist es am interessantesten.
Zunächst haben unsere Laufwerke eine Größe von 2 TB. Dies liegt im zulässigen Bereich für MBR, was wir nutzen werden. Bei Bedarf kann auf GPT umgestiegen werden. GPT-Laufwerke haben eine Kompatibilitätsschicht, die es MBR-kompatiblen Systemen ermöglicht, die ersten 4 Partitionen zu sehen, wenn sie innerhalb der ersten 2 Terabyte angeordnet sind. Wichtig ist, dass die Bootpartition und die Partition bios_grub an den Anfang dieser Laufwerke gesetzt werden. Dies ermöglicht sogar das Booten von GPT-Laufwerken im Legacy/Bios-Modus.
Das ist jedoch nicht unser Fall.
Hier werden wir zwei Partitionen erstellen. Die erste wird 1 GB groß sein und für RAID 1 /boot verwendet werden.
Die zweite wird für RAID 6 verwendet und den ganzen restlichen Speicherplatz einnehmen, mit Ausnahme eines kleinen nicht zugewiesenen Bereichs am Ende des Laufwerks.
Was ist dieser nicht zugewiesene Bereich?Laut online Quellen verfügen unsere SATA SSDs über einen dynamisch erweiterbaren SLC-Cache von 6 bis 78 Gigabyte. Die ersten 6 Gigabyte erhalten wir "kostenlos" durch die Differenz zwischen "Gigabyte" und "Gibibyte" im technischen Datenblatt des Speichers. Die restlichen 72 Gigabyte werden durch ungenutzten Speicherplatz bereitgestellt.
Hier sollte angemerkt werden, dass unser Cache SLC ist, während der Speicher im 4-Bit-MLC-Modus belegt ist. Für uns bedeutet das effektiv, dass wir für jede 4 Gigabyte freien Speicher nur 1 Gigabyte SLC-Cache erhalten.
Wir multiplizieren 72 Gigabyte mit 4 und erhalten 288 Gigabyte. Das ist der freie Speicherplatz, den wir nicht einteilen werden, um den Speichern die volle Nutzung des SLC-Caches zu ermöglichen.
Somit erhalten wir insgesamt bis zu 312 Gigabyte SLC-Cache aus sechs Speicherlaufwerken. Von allen Laufwerken werden 2 im RAID für Redundanz eingesetzt.
Eine derartige Cache-Größe sorgt dafür, dass wir in der Praxis selten mit der Situation konfrontiert werden, in der die Daten nicht im Cache geschrieben werden. Das kompensiert hervorragend den größten Nachteil von QLC-Speicher, nämlich die extrem niedrige Schreibgeschwindigkeit, wenn Daten um den Cache herum geschrieben werden. Wenn Ihre Lasten dies nicht unterstützen, empfehle ich Ihnen, ernsthaft darüber nachzudenken, wie lange Ihre SSDs unter solch einer Belastung angesichts des TBW aus dem technischen Datenblatt halten werden.
#cat >ssd.part << EOF
Bezeichnung: dos
Bezeichnungs-ID: 0x00000000
Gerät: /dev/sda
Einheit: Sektoren
/dev/sda1 : start= 2048, size= 2097152, type=fd, bootable
/dev/sda2 : start= 2099200, size= 3300950016, type=fd
EOF
#sfdisk /dev/sda < ssd.part
#sfdisk /dev/sdb < ssd.part
#sfdisk /dev/sdc < ssd.part
#sfdisk /dev/sdd < ssd.part
#sfdisk /dev/sde < ssd.part
#sfdisk /dev/sdf < ssd.part
Arrays erstellen
Zunächst müssen wir den Rechner umbenennen. Das ist notwendig, weil der Hostname Teil des Namens des Arrays ist, irgendwo in mdadm, und auf etwas Einfluss hat. Arrays können zwar später umbenannt werden, aber das ist zusätzlicher Aufwand.
#mcedit /etc/hostname
#mcedit /etc/hosts
#hostname
vdesk0
NVMe SSD
#mdadm --create --verbose --assume-clean /dev/md0 --level=1 --raid-devices=2 /dev/nvme[0-1]n1
Warum -assume-clean…?Um die Arrays nicht zu initialisieren. Für beide RAID-Stufen, 1 und 6, ist das zulässig. Alles kann auch ohne Initialisierung funktionieren, wenn es sich um ein neues Array handelt. Darüber hinaus ist die Initialisierung eines SSD-Arrays bei der Erstellung eine reine Verschwendung von TBW-Ressourcen. Wir verwenden TRIM/DISCARD, wo immer möglich, auf den zusammengebauten SSD-Arrays, um sie „zu initialisieren“.
Bei SSD RAID 1 wird DISCARD standardmäßig unterstützt.
Bei SSD RAID 6 muss DISCARD in den Modulen der Kernel-Parameter aktiviert werden.
Dies sollte nur geschehen, wenn alle SSDs, die in den Level-4/5/6-Arrays dieses Systems verwendet werden, eine funktionierende Unterstützung für discard_zeroes_data haben. Manchmal gibt es seltsame Speicher, die dem Kernel die Unterstützung dieser Funktion melden, aber tatsächlich ist sie nicht vorhanden oder funktioniert nicht immer. Derzeit ist die Unterstützung nahezu überall verfügbar, jedoch treten bei älteren Speichern und fehlerhaften Firmware-Versionen Probleme auf. Aus diesem Grund ist die DISCARD-Unterstützung standardmäßig für RAID 6 deaktiviert.
Achtung, der folgende Befehl wird alle Daten auf NVMe-Laufwerken „initialisieren“ und sie mit „Nullen“ überschreiben.
#blkdiscard /dev/md0
Wenn etwas schiefgeht, versuchen Sie, den Schritt anzugeben.
#blkdiscard --step 65536 /dev/md0
SATA SSD
#mdadm --create --verbose --assume-clean /dev/md1 --level=1 --raid-devices=6 /dev/sd[a-f]1
#blkdiscard /dev/md1
#mdadm --create --verbose --assume-clean /dev/md2 --chunk-size=512 --level=6 --raid-devices=6 /dev/sd[a-f]2
Warum so groß…?Die Erhöhung der chunk-size wirkt sich positiv auf die Geschwindigkeit des zufälligen Lesens von Blöcken bis zur chunk-size aus. Dies geschieht, weil eine Operation der entsprechenden Größe oder kleiner vollständig auf einem Gerät abgeschlossen werden kann. Daher werden die IOPS aller Geräte summiert. Statistisch gesehen überschreiten 99 % der IOs nicht 512K.
Bei RAID 6 sind die IOPS beim Schreiben immer weniger oder gleich IOPS eines einzelnen Speichermediums. Umgekehrt kann die IOPS bei zufälligem Lesen mehrere Male höher sein als bei einem einzelnen Medium, wobei die Blockgröße entscheidend ist.
Der Autor sieht keinen Sinn darin, einen Parameter zu optimieren, der bei RAID 6 von Grund auf schlecht ist, und optimiert stattdessen das, was RAID 6 gut kann.
Die schlechten zufälligen Schreibvorgänge von RAID 6 werden wir mit NVMe-Cache und Tricks mit Thin-Provisioning ausgleichen.
Wir haben DISCARD für RAID 6 vorerst nicht aktiviert. Daher werden wir dieses Array vorerst nicht "initialisieren". Das holen wir später nach, nach der Betriebssysteminstallation.
SATA HDD
#mdadm --create --verbose --assume-clean /dev/md3 --chunk-size=512 --level=6 --raid-devices=8 /dev/sd[g-n]1
LVM auf NVMe RAID
Für die Geschwindigkeit möchten wir das Root-Dateisystem auf NVMe RAID 1 platzieren, das /dev/md0 ist.
Dennoch brauchen wir dieses schnelle Array auch für andere Zwecke, wie Swap, Metadaten und LVM-Cache sowie LVM-thin-Metadaten. Daher werden wir auf diesem Array ein LVM VG erstellen.
#pvcreate /dev/md0
#vgcreate root /dev/md0
Erstellen wir eine Partition für das Root-Dateisystem.
#lvcreate -L 128G --name root root
Erstellen wir eine Partition für den Swap in der Größe des Arbeitsspeichers.
#lvcreate -L 32G --name swap root
Betriebssysteminstallation
Zusammenfassend haben wir alles Notwendige, um das System zu installieren.
Wir starten den Installationsassistenten aus der Ubuntu Live-Umgebung. Standardinstallation. Nur im Schritt der Auswahl der Installationslaufwerke muss Folgendes angegeben werden:
- /dev/md1, — точка монтирования /boot, ФС — BTRFS
- /dev/root/root (a.k.a /dev/mapper/root-root), — точка монтирования / (корень), ФС — BTRFS
- /dev/root/swap (a.k.a /dev/mapper/root-swap), — использовать как раздел подкачки
- Bootloader auf /dev/sda installieren
Wenn Sie BTRFS als Root-Dateisystem wählen, erstellt der Installer automatisch zwei BTRFS-Volumes mit den Namen "@" für / (Root) und "@home" für /home.
Wir starten die Installation…
Die Installation wird mit einem modalen Dialogfenster abgeschlossen, das über einen Fehler im Bootloader informiert. Leider können Sie diesen Dialog nicht mit den Standardmitteln verlassen und die Installation fortsetzen. Melden Sie sich aus dem System ab und wieder an, um auf den frischen Desktop von Ubuntu Live zu gelangen. Öffnen Sie das Terminal und erneut:
#sudo bash
Wir erstellen eine chroot-Umgebung, um die Installation fortzusetzen:
#mkdir /mnt/chroot
#mount -o defaults,space_cache,noatime,nodiratime,discard,subvol=@ /dev/mapper/root-root /mnt/chroot
#mount -o defaults,space_cache,noatime,nodiratime,discard,subvol=@home /dev/mapper/root-root /mnt/chroot/home
#mount -o defaults,space_cache,noatime,nodiratime,discard /dev/md1 /mnt/chroot/boot
#mount --bind /proc /mnt/chroot/proc
#mount --bind /sys /mnt/chroot/sys
#mount --bind /dev /mnt/chroot/dev
Lassen Sie uns das Netzwerk und den Hostnamen im chroot konfigurieren:
#cat /etc/hostname >/mnt/chroot/etc/hostname
#cat /etc/hosts >/mnt/chroot/etc/hosts
#cat /etc/resolv.conf >/mnt/chroot/etc/resolv.conf
Zugriff auf die chroot-Umgebung:
#chroot /mnt/chroot
Als Erstes installieren wir die Pakete:
apt-get install --reinstall mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc debsums hdparm
Wir überprüfen und beheben alle Pakete, die aufgrund einer unvollständigen Systeminstallation fehlerhaft installiert wurden:
#CORRUPTED_PACKAGES=$(debsums -s 2>&1 | awk '{print $6}' | uniq)
#apt-get install --reinstall $CORRUPTED_PACKAGES
Wenn etwas nicht geklappt hat, müssen Sie möglicherweise vorher /etc/apt/sources.list bearbeiten.
Wir passen die Parameter für das RAID 6-Modul an, um TRIM/DISCARD zu aktivieren:
#cat >/etc/modprobe.d/raid456.conf << EOF
Optionen raid456 Geräte_sicher_entfernen=1
EOF
Lassen Sie uns unsere Arrays etwas optimieren:
#cat >/etc/udev/rules.d/60-md.rules << EOF
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/stripe_cache_size", ATTR{md/stripe_cache_size}="32768"
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/sync_speed_min", ATTR{md/sync_speed_min}="48000"
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/sync_speed_max", ATTR{md/sync_speed_max}="300000"
EOF
#cat >/etc/udev/rules.d/62-hdparm.rules << EOF
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", RUN+="/sbin/hdparm -B 254 /dev/%k"
EOF
#cat >/etc/udev/rules.d/63-blockdev.rules << EOF
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", RUN+="/sbin/blockdev --setra 1024 /dev/%k"
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="md*", RUN+="/sbin/blockdev --setra 0 /dev/%k"
EOF
Was war das..?Wir haben eine Reihe von udev-Regeln erstellt, die Folgendes bewirken werden:
- Einen angemessenen Cache-Größenwert für RAID 6 im Jahr 2020 festlegen. Der Standardwert scheint seit der Einführung von Linux unverändert zu sein und ist mittlerweile nicht mehr zeitgemäß.
- Mindestens I/O während der Überprüfungen/Synchronisierungen der Arrays reservieren. Dies ist notwendig, damit Ihre Arrays nicht in einem Zustand ewiger Synchronisierung unter Last stecken bleiben.
- Maximal I/O während der Überprüfungen/Synchronisierungen der Arrays begrenzen. Dies ist wichtig, damit die Synchronisation/Überprüfung von SSD-RAIDs Ihre Speichergeräte nicht überhitzt. Besonders relevant für NVMe. (Erinnern Sie sich an den Kühler? Ich habe nicht gewitzelt.)
- Verhindern Sie durch APM, dass Festplatten den Spindelbetrieb (HDD) einstellen, und setzen Sie einen Timeout von 7 Stunden für die Festplattenspeichercontroller. APM kann vollständig deaktiviert werden, wenn Ihre Festplatten dies unterstützen (-B 255). Mit dem Standardwert stoppen die Festplatten nach fünf Sekunden. Dann möchte das Betriebssystem den Festplattencache zurücksetzen, die Festplatten drehen sich wieder und das Ganze beginnt von vorne. Festplatten haben eine begrenzte Anzahl von Spindeldrehungen. Dieser einfache Zyklus kann im Standardmodus Ihre Festplatten innerhalb von ein paar Jahren leicht schädigen. Nicht alle Festplatten sind davon betroffen, aber unsere „Laptop-Modelle“ mit den entsprechenden Standardeinstellungen, die RAID in eine krumme Mini-MAID-ähnliche Konfiguration verwandeln, schon.
- Setzen Sie das Readahead auf festplattenbasierten (rotierenden) Festplatten auf 1 Megabyte – zwei aufeinander folgende Blöcke/Chunks RAID 6.
- Verhindern Sie Readahead auf den Arrays selbst.
Lassen Sie uns /etc/fstab bearbeiten:
#cat >/etc/fstab << EOF
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
# file-system mount-point type options dump pass
/dev/mapper/root-root / btrfs defaults,space_cache,noatime,nodiratime,discard,subvol=@ 0 1
UUID=$(blkid -o value -s UUID /dev/md1) /boot btrfs defaults,space_cache,noatime,nodiratime,discard 0 2
/dev/mapper/root-root /home btrfs defaults,space_cache,noatime,nodiratime,discard,subvol=@home 0 2
/dev/mapper/root-swap none swap sw 0 0
EOF
Warum so..?Wir werden die Partition /boot über UUID suchen, da die Benennung der Arrays theoretisch variieren kann.
Die anderen Partitionen suchen wir über die LVM-Namen in der Notation /dev/mapper/vg-lv, da sie die Partitionen ausreichend einzigartig identifizieren.
Verwenden Sie keine UUID für LVM, da UUIDs von LVM-Volumes und deren Snapshots übereinstimmen können.Mounten wir /dev/mapper/root-root doppelt..?Ja. Genau so. Das Besondere an BTRFS ist, dass dieses Dateisystem mehrfach mit unterschiedlichen Subvolumes gemountet werden kann.
Aufgrund dieses Merkmals empfehle ich dringend, niemals LVM-Snapshots von aktiven BTRFS-Volumes zu erstellen. Dies könnte beim Neustart Überraschungen bringen.
Lass uns die mdadm-Konfiguration neu generieren:
#/usr/share/mdadm/mkconf | sed 's/#DEVICE/DEVICE/g' >/etc/mdadm/mdadm.conf
Wir passen die LVM-Einstellungen an:
#cat >>/etc/lvm/lvmlocal.conf << EOF
activation {
thin_pool_autoextend_threshold=90
thin_pool_autoextend_percent=5
}
allocation {
cache_pool_max_chunks=2097152
}
devices {
global_filter=["r|^\/dev\/.*_corig$|","r|^\/dev\/.*_cdata$|","r|^\/dev\/.*_cmeta$|","r|^\/dev\/.*gpv$|","r|^\/dev\/images\/.*$|","r|^\/dev\/mapper\/images.*$|","r|^\/dev\/backup\/.*$|","r|^\/dev\/mapper\/backup.*$|"]
issue_discards=1
}
EOF
Was war das..?Wir haben die automatische Erweiterung der LVM thin-Pools aktiviert, wenn 90 % des Speicherplatzes belegt sind, um 5 % des Volumens zu erhöhen.
Wir haben die maximale Anzahl an Cache-Blöcken für den LVM-Cache erhöht.
Wir haben LVM verboten, LVM-Volumes (PV) zu suchen auf:
- Geräten, die LVM-Cache (cdata) enthalten.
- Geräten, die durch LVM-Cache umgangen werden (<lv_name>_corig). Das gecachte Gerät selbst wird dennoch durch den Cache gescannt (einfach <lv_name>).
- Geräten, die Metadaten des LVM-Caches (cmeta) enthalten.
- Allen Geräten im VG mit dem Namen images. Hier werden wir Disk-Images von virtuellen Maschinen haben und wir wollen nicht, dass LVM auf dem Host Volumes aktiviert, die der Gast-OS gehören.
- auf allen Geräten in VG mit dem Namen Backup. Hier haben wir die Sicherungskopien der Abbildungen virtueller Maschinen.
- auf allen Geräten, deren Namen auf „gpv“ (guest physical volume) enden.
Wir haben die Unterstützung von DISCARD beim Freigeben von Speicherplatz auf LVM VG aktiviert. Seien Sie vorsichtig. Dies kann die Löschung von LV auf SSD erheblich verlängern. Besonders gilt dies für SSD RAID 6. Nach dem Plan werden wir jedoch Thin Provisioning verwenden, sodass es uns nicht weiter stören wird.
Aktualisieren wir das initramfs-Image:
#update-initramfs -u -k all
Installieren und konfigurieren wir grub:
#apt-get install grub-pc
#apt-get purge os-prober
#dpkg-reconfigure grub-pc
Welche Festplatten sollen gewählt werden?Alle, die sd* sind. Das System sollte in der Lage sein, von jeder funktionierenden SATA-Festplatte oder SSD zu booten.
Warum haben wir os-prober entfernt?Wegen übermäßiger Selbstständigkeit und unruhigen Händen.
Es funktioniert nicht korrekt, wenn einer der RAID-Arrays im degradierenden Zustand ist. Es versucht, OS auf Partitionen zu suchen, die von virtuellen Maschinen verwendet werden, die auf dieser Hardware laufen.
Wenn Sie es benötigen, können Sie es behalten, aber beachten Sie alles, was oben erwähnt wurde. Ich empfehle, im Internet nach Rezepten zur Beseitigung unruhiger Hände zu suchen.
Damit haben wir die grundlegende Installation abgeschlossen. Es ist Zeit, in das neu installierte Betriebssystem neu zu starten. Vergessen Sie nicht, die bootfähige Live-CD/USB herauszunehmen.
#exit
#reboot
Wählen Sie als Boot-Gerät eine der SATA-SSDs aus.
LVM auf SATA SSD
Zu diesem Zeitpunkt sind wir bereits im neuen Betriebssystem gestartet, haben das Netzwerk, apt konfiguriert, das Terminal-Emulator geöffnet und gestartet:
#sudo bash
Lassen Sie uns fortfahren.
„Initialisieren“ Sie das Array aus SATA SSDs:
#blkdiscard /dev/md2
Wenn es nicht funktioniert hat, versuchen wir es mit:
#blkdiscard --step 65536 /dev/md2
Erstellen Sie eine LVM VG auf der SATA SSD:
#pvcreate /dev/md2
#vgcreate data /dev/md2
Warum noch eine VG..?Tatsächlich haben wir bereits eine VG mit dem Namen root. Warum alles nicht in eine VG packen?
Wenn es in der VG mehrere PV gibt, müssen alle PV für eine korrekte Aktivierung der VG vorhanden sein (online). Eine Ausnahme bildet das LVM RAID, das wir absichtlich nicht verwenden.
Wir möchten sehr, dass das Betriebssystem beim Ausfall (d.h. Datenverlust) eines der RAID 6-Arrays normal startet und uns die Möglichkeit gibt, das Problem zu lösen.
Dafür werden wir auf der ersten Abstraktionsebene jeden Typ des physischen „Speichers“ in eine separate VG isolieren.
Wissenschaftlich betrachtet gehören verschiedene RAID-Arrays zu unterschiedlichen „Zuverlässigkeitsdomänen“. Es ist nicht ratsam, einen zusätzlichen gemeinsamen Ausfallpunkt zu schaffen, indem man alles in eine VG packt.
Die Verfügbarkeit von LVM auf der „Hardware“-Ebene ermöglicht es uns, verschiedene Raid-Gruppen in beliebig kleine Stücke zu schneiden und sie unterschiedlich zu kombinieren. Zum Beispiel können wir gleichzeitig bcache + LVM thin, bcache + BTRFS, LVM cache + LVM thin, eine komplexe ZFS-Konfiguration mit Caches oder jede andere teuflische Mischung starten, um alles anzufassen und zu vergleichen.
Auf der „Hardware“-Ebene werden wir nichts anderes als die guten alten „dicken“ LVM-Volumes verwenden. Eine Ausnahme von dieser Regel könnte der Bereich für Backups sein.
Ich denke, zu diesem Zeitpunkt haben viele Leser bereits einige Verdachtsschlüsse bezüglich der Matroschka gezogen.
LVM auf SATA HDD
#pvcreate /dev/md3
#vgcreate backup /dev/md3
Schon wieder eine neue VG..?Wir wollen auf jeden Fall, dass, wenn das Disk-Array, das wir für die Datensicherung verwenden, ausfällt, unser Betriebssystem weiterhin normal funktioniert und zugleich den Zugriff auf nicht gesicherte Daten aufrechterhält. Daher, um Probleme bei der Aktivierung der VG zu vermeiden, erstellen wir eine separate VG.
Einrichtung des LVM-Cache
Wir erstellen LV auf NVMe RAID 1, um es als Cache-Gerät zu verwenden.
#lvcreate -L 70871154688B --name cache root
Warum so wenig…?Unsere NVMe SSDs verfügen ebenfalls über einen SLC-Cache, der 4 Gigabyte "kostenlos" und 18 Gigabyte dynamisch durch den freien Platz im 3-Bit MLC bereitstellt. Nach Erschöpfung dieses Caches werden unsere NVMe SSDs nicht viel schneller sein als unsere SATA SSDs mit Cache. Aus diesem Grund macht es keinen Sinn, das LVM-Cache-Volume wesentlich größer als das doppelte Volumen des SLC-Caches des NVMe-Laufwerks zu gestalten. Für die verwendeten NVMe-Laufwerke hält der Autor es für vernünftig, einen Cache von 32-64 Gigabyte zu erstellen.
Die angegebene Partitionsgröße ist erforderlich, um einen Cache von 64 Gigabyte zu organisieren, Metadaten des Caches zu speichern und eine Sicherung der Metadaten bereitzustellen.
Zusätzlich möchte ich darauf hinweisen, dass nach einem unsauberen Herunterfahren des Systems LVM den gesamten Cache als unsauber kennzeichnet und ihn erneut synchronisieren wird. Darüber hinaus wird dies bei jedem Einsatz von lvchange auf diesem Gerät bis zum nächsten Neustart des Systems wiederholt. Daher empfehle ich, den Cache sofort mit dem entsprechenden Skript neu zu erstellen.
Wir erstellen ein LV auf SATA RAID 6, um es als cachendes Gerät zu nutzen.
#lvcreate -L 3298543271936B --name cache data
Warum nur drei Terabyte..?Damit SATA SSD RAID 6 für andere Anforderungen genutzt werden kann, lässt sich der zu cachebare Speicher dynamisch und ohne Unterbrechung des Systems erhöhen. Dafür muss der Cache vorübergehend gestoppt und dann wieder aktiviert werden. Ein herausragender Vorteil von LVM-cache gegenüber bcache ist, dass dies im laufenden Betrieb möglich ist.
Wir erstellen eine neue VG für das Caching.
#pvcreate /dev/root/cache
#pvcreate /dev/data/cache
#vgcreate cache /dev/root/cache /dev/data/cache
Wir erstellen ein LV auf dem cachebaren Gerät.
#lvcreate -L 3298539077632B --name cachedata cache /dev/data/cache
Hier haben wir den gesamten verfügbaren Speicher auf /dev/data/cache belegt, damit alle anderen benötigten Partitionen sofort auf /dev/root/cache erstellt werden. Falls etwas nicht dort erstellt wurde, kann es mit pvmove verschoben werden.
Wir erstellen und aktivieren den Cache:
#lvcreate -y -L 64G -n cache cache /dev/root/cache
#lvcreate -y -L 1G -n cachemeta cache /dev/root/cache
#lvconvert -y --type cache-pool --cachemode writeback --chunksize 64k --poolmetadata cache/cachemeta cache/cache
#lvconvert -y --type cache --cachepool cache/cache cache/cachedata
Warum eine solche chunksize..?Durch praktische Experimente hat der Autor herausgefunden, dass die besten Ergebnisse erzielt werden, wenn die Blockgröße des LVM-Caches mit der Blockgröße von LVM thin übereinstimmt. Dabei gilt: Je kleiner die Blockgröße, desto besser funktioniert die Konfiguration bei zufälligen Schreibvorgängen.
64k ist die minimale Blockgröße, die für LVM thin zulässig ist.
Vorsicht bei writeback..!Ja. Dieser Cachetyp verzögert die Synchronisation von Einträgen auf das cachebare Gerät. Dies kann dazu führen, dass bei Verlust des Caches Daten auf dem cachebaren Gerät verloren gehen. Später wird der Autor erläutern, welche Maßnahmen neben NVMe RAID 1 ergriffen werden können, um dieses Risiko zu kompensieren.
Dieser Cachetyp wurde absichtlich gewählt, um die niedrige Leistung von RAID 6 bei zufälligen Schreibvorgängen auszugleichen.
Lassen Sie uns prüfen, was wir erreicht haben:
#lvs -a -o lv_name,lv_size,devices --units B cache
LV LSize Geräte
[cache] 68719476736B cache_cdata(0)
[cache_cdata] 68719476736B /dev/root/cache(0)
[cache_cmeta] 1073741824B /dev/root/cache(16384)
cachedata 3298539077632B cachedata_corig(0)
[cachedata_corig] 3298539077632B /dev/data/cache(0)
[lvol0_pmspare] 1073741824B /dev/root/cache(16640)
Auf /dev/data/cache sollte sich nur [cachedata_corig] befinden. Wenn etwas nicht stimmt, verwenden Sie pvmove.
Den Cache kann man bei Bedarf mit einem Befehl deaktivieren:
#lvconvert -y --uncache cache/cachedata
Das geschieht online. LVM synchronisiert einfach den Cache auf die Festplatte, entfernt ihn und benennt cachedata_corig wieder in cachedata um.
LVM Thin-Konfiguration
Schätzen wir grob, wie viel Platz wir für die Metadaten von LVM Thin benötigen:
#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - geschätzte Metadatenbereichsgröße von 3.385.794.560 Bytes für "--block-size=64kibibytes --pool-size=6terabytes --max-thins=100000"
Runden wir auf 4 Gigabyte: 4294967296B
Multiplizieren wir mit zwei und addieren 4194304B für die Metadaten von LVM PV: 8594128896B
Lassen Sie uns eine separate Partition auf NVMe RAID 1 erstellen, um die Metadaten von LVM Thin und deren Backup darauf zu speichern:
#lvcreate -L 8594128896B --name images root
Warum..?Hier könnte die Frage aufkommen, warum die Metadaten von LVM Thin separat gespeichert werden sollten, wenn sie ohnehin auf NVMe zwischengespeichert werden und schnell arbeiten.
Die Geschwindigkeit ist zwar wichtig, doch nicht der Hauptgrund. Es geht darum, dass der Cache einen Schwachpunkt darstellt. Wenn etwas mit ihm passiert und die LVM thin-Metadaten im Cache gespeichert sind, kann dies zu einem vollständigen Datenverlust führen. Ohne intakte Metadaten wird es nahezu unmöglich sein, die Thin-Volumes wiederherzustellen.
Indem wir die Metadaten auf ein separates, nicht gecached, aber schnelles Volume verschieben, gewährleisten wir den Schutz der Metadaten im Falle eines Verlusts oder einer Beschädigung des Caches. In diesem Fall werden alle Schäden, die durch den Verlust des Caches verursacht werden, innerhalb der Thin-Volumes lokalisiert, was den Wiederherstellungsprozess erheblich vereinfacht. Mit großer Wahrscheinlichkeit können diese Schäden mit Hilfe der FS-Logs wiederhergestellt werden.
Darüber hinaus wird die Integrität des Snapshots gewährleistet, wenn zuvor ein Snapshot des Thin-Volumes erstellt wurde und der Cache nach diesem einmal vollständig synchronisiert wurde, aufgrund der spezifischen Eigenschaften von LVM thin.
Legen wir eine neue VG an, die für das Thin-Provisioning verantwortlich ist:
#pvcreate /dev/root/images
#pvcreate /dev/cache/cachedata
#vgcreate images /dev/root/images /dev/cache/cachedata
Erstellen wir einen Pool:
#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
Warum -Z yNeben dem ursprünglichen Zweck dieses Modus, der darin besteht, das Auslaufen von Daten zwischen virtuellen Maschinen beim Umverteilen von Speicher zu verhindern, wird Zeroing auch verwendet, um die Geschwindigkeit von zufälligen Schreibvorgängen mit Blöcken unter 64k zu erhöhen. Jeder Schreibvorgang unter 64k in einem zuvor nicht zugeordneten Bereich des Thin-Volumes wird in 64K blockausgerichtete Cache-grenzwerte umgewandelt. Dadurch kann der Vorgang vollständig über den Cache durchgeführt werden, ohne dass das zwischengespeicherte Gerät betroffen ist.
Lassen Sie uns das LV auf die entsprechenden PV verschieben:
#pvmove -n images/thin-pool_tdata /dev/root/images /dev/cache/cachedata
#pvmove -n images/lvol0_pmspare /dev/cache/cachedata /dev/root/images
#pvmove -n images/thin-pool_tmeta /dev/cache/cachedata /dev/root/images
Überprüfen Sie den Status:
#lvs -a -o lv_name,lv_size,devices --units B images
LV LSize Geräte
[lvol0_pmspare] 4 GiB /dev/root/images(0)
thin-pool 256 GiB thin-pool_tdata(0)
[thin-pool_tdata] 256 GiB /dev/cache/cachedata(0)
[thin-pool_tmeta] 4 GiB /dev/root/images(1024)
Erstellen wir ein Thin-Volume für Tests:
#lvcreate -V 64G --thin-pool thin-pool --name test images
Installieren wir die Pakete für Tests und Überwachung:
#apt-get install sysstat fio
So können Sie das Verhalten unserer Speicher-Konfiguration in Echtzeit beobachten:
#watch 'lvs --rows --reportformat basic --quiet -ocache_dirty_blocks,cache_settings cache/cachedata && (lvdisplay cache/cachedata | grep Cache) && (sar -p -d 2 1 | grep -E "sd|nvme|DEV|md1|md2|md3|md0" | grep -v Average | sort)'
So können Sie unsere Konfiguration testen:
#fio --loops=1 --size=64G --runtime=4 --filename=/dev/images/test --stonewall --ioengine=libaio --direct=1
--name=4kQD32read --bs=4k --iodepth=32 --rw=randread
--name=8kQD32read --bs=8k --iodepth=32 --rw=randread
--name=16kQD32read --bs=16k --iodepth=32 --rw=randread
--name=32KQD32read --bs=32k --iodepth=32 --rw=randread
--name=64KQD32read --bs=64k --iodepth=32 --rw=randread
--name=128KQD32read --bs=128k --iodepth=32 --rw=randread
--name=256KQD32read --bs=256k --iodepth=32 --rw=randread
--name=512KQD32read --bs=512k --iodepth=32 --rw=randread
--name=4Kread --bs=4k --rw=read
--name=8Kread --bs=8k --rw=read
--name=16Kread --bs=16k --rw=read
--name=32Kread --bs=32k --rw=read
--name=64Kread --bs=64k --rw=read
--name=128Kread --bs=128k --rw=read
--name=256Kread --bs=256k --rw=read
--name=512Kread --bs=512k --rw=read
--name=Seqread --bs=1m --rw=read
--name=Longread --bs=8m --rw=read
--name=Longwrite --bs=8m --rw=write
--name=Seqwrite --bs=1m --rw=write
--name=512Kwrite --bs=512k --rw=write
--name=256write --bs=256k --rw=write
--name=128write --bs=128k --rw=write
--name=64write --bs=64k --rw=write
--name=32write --bs=32k --rw=write
--name=16write --bs=16k --rw=write
--name=8write --bs=8k --rw=write
--name=4write --bs=4k --rw=write
--name=512KQD32write --bs=512k --iodepth=32 --rw=randwrite
--name=256KQD32write --bs=256k --iodepth=32 --rw=randwrite
--name=128KQD32write --bs=128k --iodepth=32 --rw=randwrite
--name=64KQD32write --bs=64k --iodepth=32 --rw=randwrite
--name=32KQD32write --bs=32k --iodepth=32 --rw=randwrite
--name=16KQD32write --bs=16k --iodepth=32 --rw=randwrite
--name=8KQD32write --bs=8k --iodepth=32 --rw=randwrite
--name=4kQD32write --bs=4k --iodepth=32 --rw=randwrite
| grep -E 'read|write|test' | grep -v ioengine
Achtung! Ressource!Dieser Code führt 36 verschiedene Tests aus, von denen jeder 4 Sekunden dauert. Die Hälfte der Tests sind Schreibvorgänge. In 4 Sekunden kann die NVMe eine enorme Menge an Daten schreiben – bis zu 3 Gigabyte pro Sekunde. Daher kann jeder Start eines Schreibtests bis zu 216 Gigabyte an SSD-Ressourcen verbrauchen.
Lesen und Schreiben gemischt?Ja. Es macht Sinn, Lese- und Schreibtests separat durchzuführen. Darüber hinaus ist es wichtig, sicherzustellen, dass alle Caches synchronisiert sind, damit frühere Schreibvorgänge die Leseergebnisse nicht beeinflussen.
Die Ergebnisse werden beim ersten Start und bei den folgenden stark variieren, während der Cache und das Thin Volume gefüllt werden. Dies hängt auch davon ab, ob das System die beim vorherigen Start gefüllten Caches synchronisieren konnte.
Ich empfehle außerdem, die Geschwindigkeit auf einem bereits gefüllten Thin Volume zu messen, von dem gerade ein Snapshot erstellt wurde. Der Autor hat beobachtet, dass sich zufällige Schreibvorgänge nach der Erstellung des ersten Snapshots deutlich beschleunigen, insbesondere wenn der Cache noch nicht vollständig gefüllt ist. Dies geschieht aufgrund der Copy-on-Write-Semantik, die das Blockausrichten zwischen Cache und Thin Volume und die Tatsache, dass zufällige Schreibvorgänge auf RAID 6 in zufällige Lesevorgänge mit nachfolgendem Schreiben in den Cache umgewandelt werden. In unserer Konfiguration ist zufälliges Lesen auf RAID 6 bis zu 6-mal (Anzahl der SATA SSDs im Array) schneller als das Schreiben. Da die Blöcke für CoW sequenziell aus dem Thin Pool zugewiesen werden, verwandelt sich das Schreiben größtenteils ebenfalls in sequenzielle Schreibvorgänge.
Beide diese Funktionen können gewinnbringend genutzt werden.
Cache-„kohärente“ Snapshots
Um das Risiko eines Datenverlusts bei Beschädigung/Verlust des Caches zu reduzieren, empfiehlt der Autor die Einführung einer Praxis zur Rotation von Snapshots, die die Integrität in diesem Fall gewährleistet.
Erstens, da die Metadaten der dünnen Volumes auf einem nicht-cachierten Gerät gespeichert sind, bleiben die Metadaten intakt, und mögliche Verluste werden innerhalb der Datenblöcke isoliert.
Der nächste Zyklus zur Rotation der Snapshots garantiert die Integrität der Daten innerhalb der Snapshots im Falle eines Cacheverlustes:
- Für jedes dünne Volume mit dem Namen <имя> erstellen wir einen Snapshot mit dem Namen <имя>.cached
- Setzen wir den Migration Threshold auf einen angemessen hohen Wert:
#lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata - Im Zyklus überprüfen wir die Anzahl der schmutzigen Blöcke im Cache:
#lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}'Wenn wir keine Null erhalten, kann diese vorübergehend erstellt werden, indem der Cache in den Writethrough-Modus versetzt wird. Allerdings werden, angesichts der Geschwindigkeitseigenschaften unserer SATA- und NVMe-SSDs sowie deren TBW-Ressource, Sie entweder den Moment schnell genug erfassen können, ohne den Cache-Modus zu ändern, oder Ihre Hardware wird ihre gesamte Ressource innerhalb weniger Tage verbrauchen. Aufgrund der Ressourcenschränkungen kann das System grundsätzlich nicht ständig unter 100 % Schreiblast stehen. Unsere NVMe-SSDs werden bei 100 % Schreiblast ihre Ressource vollständig aufbrauchen in 3-4 Tagen. SATA-SSDs halten etwa doppelt so lange. Daher gehen wir davon aus, dass der Großteil der Last auf Lesevorgänge entfällt, während das Schreiben - relativ kurzfristige Spitzen extrem hoher Aktivität in Kombination mit niedrigerer durchschnittlicher Last - umfasst. - Sobald Sie die Null erfasst (oder erstellt) haben, benennen Sie .cached in .committed um. Die alte .committed wird dabei gelöscht.
- Optional, wenn der Cache zu 100 % gefüllt ist, kann er mit einem Skript neu erstellt werden, um ihn zu bereinigen. Mit einem halb leeren Cache arbeitet das System beim Schreiben wesentlich schneller.
- Setzen wir den Migration Threshold auf Null:
#lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedataDies wird vorübergehend die Synchronisation des Caches auf das Hauptlaufwerk verhindern. - Wir warten, bis im Cache ausreichend viele Änderungen gesammelt wurden.
#lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}'oder der Timer abläuft. - Wir wiederholen von vorn.
Warum die Schwierigkeiten mit dem Migrationstreshold…?Es liegt daran, dass in der Praxis eine "zufällige" Aufzeichnung in Wirklichkeit nicht ganz zufällig ist. Wenn wir etwas in einen 4-Kilobyte-Sektor schreiben, ist die Wahrscheinlichkeit groß, dass in den nächsten paar Minuten in diesen oder einen der benachbarten (+- 32K) Sektoren geschrieben wird.
Wenn wir den Migrationstreshold auf null setzen, verzögern wir die Synchronisation der Aufzeichnung auf dem SATA SSD und aggregieren mehrere Änderungen eines 64K-Blocks im Cache. Dadurch wird die Ressource des SATA SSD erheblich geschont.
Und wo ist der Code…?Leider hält der Autor sich für nicht kompetent genug in der Entwicklung von Bash-Skripten, da er zu 100 % Autodidakt ist und „google”-gesteuerte Entwicklung praktiziert. Daher glaubt er, dass der schreckliche Code, der aus seinen Händen kommt, von niemandem anders verwendet werden sollte.
Ich bin sicher, dass die Fachleute in der Lage sind, die zuvor beschriebene Logik bei Bedarf selbstständig umzusetzen und vielleicht sogar in Form eines systemd-Dienstes ansprechend zu gestalten, wie der Autor es versucht hat.
Ein solches einfaches Schema zur Rotation von Snapshots ermöglicht es uns nicht nur, ständig einen vollständig syncronisierten Snapshot auf einem SATA-SSD zu haben, sondern auch mithilfe des Tools thin_delta herauszufinden, welche Blöcke nach seiner Erstellung geändert wurden, wodurch wir Beschädigungen auf den Hauptvolumen lokalisieren und die Wiederherstellung erheblich vereinfachen können.
TRIM/DISCARD in libvirt/KVM
Da der Speicher für KVM unter libvirt verwendet wird, wäre es sinnvoll, unsere VMs nicht nur Platz zu beanspruchen, sondern auch nicht mehr benötigten Platz freizugeben.
Dies erfolgt durch die Emulation der Unterstützung von TRIM/DISCARD auf virtuellen Laufwerken. Dazu muss der Controller-Typ auf virtio-scsi geändert und die XML bearbeitet werden.
#virsh edit vmname
<disk type='block' device='disk'>
<driver name='qemu' type='raw' cache='writethrough' io='threads' discard='unmap'/>
<source dev='/dev/images/vmname'/>
<backingStore/>
<target dev='sda' bus='scsi'/>
<alias name='scsi0-0-0-0'/>
</disk>
<controller type='scsi' index='0' model='virtio-scsi'>
<alias name='scsi0'/>
</controller>
Ähnliche DISCARDs aus Guest-OS werden korrekt vom LVM verarbeitet, und die Blöcke werden sowohl im Cache als auch im Thin-Pool korrekt freigegeben. In unserem Fall geschieht dies hauptsächlich verzögert, beim Entfernen des nächsten Snapshots.
BTRFS-Backup
Verwenden Sie fertige Skripte mit äußerster Vorsicht und auf eigenes Risiko.. Der Autor hat diesen Code selbst geschrieben und ausschließlich für sich selbst. Ich bin mir sicher, dass viele erfahrene Linux-Nutzer ähnliche Hacks haben, und es nicht nötig sein wird, чужие zu kopieren.
Legen wir ein Volume auf dem Backup-Gerät an:
#lvcreate -L 256G --name backup backup
Formatieren wir in BTRFS:
#mkfs.btrfs /dev/backup/backup
Erstellen wir Mount-Punkte und mounten wir die root-Subsysteme des FS:
#mkdir /backup
#mkdir /backup/btrfs
#mkdir /backup/btrfs/root
#mkdir /backup/btrfs/back
#ln -s /boot /backup/btrfs
# cat >>/etc/fstab << EOF
/dev/mapper/root-root /backup/btrfs/root btrfs defaults,space_cache,noatime,nodiratime 0 2
/dev/mapper/backup-backup /backup/btrfs/back btrfs defaults,space_cache,noatime,nodiratime 0 2
EOF
#mount -a
#update-initramfs -u
#update-grub
Erstellen wir Verzeichnisse für Backups:
#mkdir /backup/btrfs/back/remote
#mkdir /backup/btrfs/back/remote/root
#mkdir /backup/btrfs/back/remote/boot
Erstellen wir ein Verzeichnis für Backup-Skripte:
#mkdir /root/btrfs-backup
Kopieren wir das Skript:
Viel erschreckender Bash-Code. Nutzen Sie auf eigenes Risiko. Wenden Sie sich nicht mit wütenden E-Mails an den Autor...#cat >/root/btrfs-backup/btrfs-backup.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"
LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
DATE_PREFIX='%Y-%m-%d'
DATE_FORMAT=$DATE_PREFIX'-%H-%M-%S'
DATE_REGEX='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
BASE_SUFFIX=".@base"
PEND_SUFFIX=".@pend"
SNAP_SUFFIX=".@snap"
MOUNTS="/backup/btrfs/"
BACKUPS="/backup/btrfs/back/remote/"
function terminate ()
{
echo "$1" >&2
exit 1
}
function wait_lock()
{
flock 98
}
function wait_lock_or_terminate()
{
echo "Warte auf Sperre..."
wait_lock || terminate "Sperre konnte nicht erhalten werden. Beende..."
echo "Sperre erhalten..."
}
function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}
function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}
function backup()
{
SOURCE_PATH="$MOUNTS$1"
TARGET_PATH="$BACKUPS$1"
SOURCE_BASE_PATH="$MOUNTS$1$BASE_SUFFIX"
TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
TARGET_BASE_DIR="$(dirname $TARGET_BASE_PATH)"
SOURCE_PEND_PATH="$MOUNTS$1$PEND_SUFFIX"
TARGET_PEND_PATH="$BACKUPS$1$PEND_SUFFIX"
if [ -d "$SOURCE_BASE_PATH" ]
then
echo "$SOURCE_BASE_PATH gefunden"
else
echo "$SOURCE_BASE_PATH Datei nicht gefunden, erstelle Snapshot von $SOURCE_PATH zu $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
if [ -d "$TARGET_BASE_PATH" ]
then
echo "$TARGET_BASE_PATH gefunden, nicht synchron mit Quelle... wird entfernt..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
if [ -d "$TARGET_BASE_PATH" ]
then
echo "$TARGET_BASE_PATH gefunden"
else
echo "$TARGET_BASE_PATH nicht gefunden. Synchronisiere zu $TARGET_BASE_DIR"
btrfs send $SOURCE_BASE_PATH | btrfs receive $TARGET_BASE_DIR
sync
fi
if [ -d "$SOURCE_PEND_PATH" ]
then
echo "$SOURCE_PEND_PATH gefunden, wird entfernt..."
btrfs subvolume delete -c $SOURCE_PEND_PATH
sync
fi
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_PEND_PATH
sync
if [ -d "$TARGET_PEND_PATH" ]
then
echo "$TARGET_PEND_PATH gefunden, wird entfernt..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Sende $SOURCE_PEND_PATH zu $TARGET_PEND_PATH"
btrfs send -p $SOURCE_BASE_PATH $SOURCE_PEND_PATH | btrfs receive $TARGET_BASE_DIR
sync
TARGET_DATE_SUFFIX=$(suffix)
btrfs subvolume snapshot -r $TARGET_PEND_PATH "$TARGET_PATH$TARGET_DATE_SUFFIX"
sync
btrfs subvolume delete -c $SOURCE_BASE_PATH
sync
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
mv $SOURCE_PEND_PATH $SOURCE_BASE_PATH
mv $TARGET_PEND_PATH $TARGET_BASE_PATH
sync
}
function list()
{
LIST_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
LIST_TARGET_BASE_DIR="$(dirname $LIST_TARGET_BASE_PATH)"
LIST_TARGET_BASE_NAME="$(basename -s .$BASE_SUFFIX $LIST_TARGET_BASE_PATH)"
find "$LIST_TARGET_BASE_DIR" -maxdepth 1 -mindepth 1 -type d -printf "%fn" | grep "${LIST_TARGET_BASE_NAME/$BASE_SUFFIX/$SNAP_SUFFIX}.$DATE_REGEX"
}
function remove()
{
REMOVE_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
REMOVE_TARGET_BASE_DIR="$(dirname $REMOVE_TARGET_BASE_PATH)"
btrfs subvolume delete -c $REMOVE_TARGET_BASE_DIR/$2
sync
}
function removeall()
{
DATE_OFFSET="$2"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$1" "$SNAPSHOT"
done < <(list "$1" | grep "$FILTER")
}
(
COMMAND="$1"
Shift
case "$COMMAND" in
"--help")
echo "Hilfe"
;;
"suffix")
Suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1"
;;
"list")
list "$1"
;;
"remove")
wait_lock_or_terminate
remove "$1" "$2"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2"
;;
*)
echo "Keine vorhanden.."
;;
esac
) 98>$LOCK_FILE
EOF
Was macht es überhaupt..?Es enthält eine Reihe einfacher Befehle zur Erstellung von BTRFS-Snapshots und deren Übertragung auf ein anderes Dateisystem mithilfe von BTRFS send/receive.
Der erste Lauf kann relativ lange dauern, da zu Beginn alle Daten kopiert werden. Weitere Läufe werden sehr schnell sein, da nur die Änderungen kopiert werden.
Ein weiterer Skript, den wir in cron einfügen werden:
Ein wenig mehr Bash-Code#cat >/root/btrfs-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"
BACKUP_SCRIPT="$SCRIPT_DIR/btrfs-backup.sh"
RETENTION="-60 Tage"
$BACKUP_SCRIPT backup root/@
$BACKUP_SCRIPT removeall root/@ "$RETENTION"
$BACKUP_SCRIPT backup root/@home
$BACKUP_SCRIPT removeall root/@home "$RETENTION"
$BACKUP_SCRIPT backup boot/
$BACKUP_SCRIPT removeall boot/ "$RETENTION"
EOF
Was macht es..?Erstellt und synchronisiert inkrementelle Snapshots der angegebenen BTRFS-Volumes auf dem Backup-Dateisystem. Anschließend werden alle Snapshots, die älter als 60 Tage sind, gelöscht. Nach dem Start werden in den Unterverzeichnissen /backup/btrfs/back/remote/ datierte Snapshots der angegebenen Volumes erstellt.
Geben wir dem Skript Ausführungsrechte:
#chmod +x /root/btrfs-backup/cron-daily.sh
#chmod +x /root/btrfs-backup/btrfs-backup.sh
Überprüfen wir es und füge es zu Cron hinzu:
#/usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/btrfs-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t btrfs-backup
#cat /var/log/syslog | grep btrfs-backup
#crontab -e
0 2 * * * /usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/btrfs-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t btrfs-backup
LVM thin Backup
Erstellen wir einen dünnen Pool auf dem Backup-Gerät:
#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T backup/thin-pool
Installieren wir ddrescue, da die Skripte dieses Tool verwenden werden:
#apt-get install gddrescue
Erstellen wir ein Verzeichnis für die Skripte:
#mkdir /root/lvm-thin-backup
Kopieren wir die Skripte:
Sehr viel Bash im Einsatz...#cat >/root/lvm-thin-backup/lvm-thin-backup.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"
LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
DATE_PREFIX='%Y-%m-%d'
DATE_FORMAT=$DATE_PREFIX'-%H-%M-%S'
DATE_REGEX='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
BASE_SUFFIX=".base"
PEND_SUFFIX=".pend"
SNAP_SUFFIX=".snap"
BACKUPS="backup"
BACKUPS_POOL="thin-pool"
export LVM_SUPPRESS_FD_WARNINGS=1
function terminate ()
{
echo "$1" >&2
exit 1
}
function wait_lock()
{
flock 98
}
function wait_lock_or_terminate()
{
echo "Warte auf Sperre..."
wait_lock || terminate "Sperre konnte nicht erhalten werden. Beende..."
echo "Sperre erhalten..."
}
function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}
function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}
function read_thin_id {
lvs --rows --reportformat basic --quiet -othin_id "$1/$2" | awk '{print $2}'
}
function read_pool_lv {
lvs --rows --reportformat basic --quiet -opool_lv "$1/$2" | awk '{print $2}'
}
function read_lv_dm_path {
lvs --rows --reportformat basic --quiet -olv_dm_path "$1/$2" | awk '{print $2}'
}
function read_lv_active {
lvs --rows --reportformat basic --quiet -olv_active "$1/$2" | awk '{print $2}'
}
function read_lv_chunk_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -ochunk_size "$1/$2" | awk '{print $2}'
}
function read_lv_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -olv_size "$1/$2" | awk '{print $2}'
}
function activate_volume {
lvchange -ay -Ky "$1/$2"
}
function deactivate_volume {
lvchange -an "$1/$2"
}
function read_thin_metadata_snap {
dmsetup status "$1" | awk '{print $7}'
}
function thindiff()
{
DIFF_VG="$1"
DIFF_SOURCE="$2"
DIFF_TARGET="$3"
DIFF_SOURCE_POOL=$(read_pool_lv $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_POOL=$(read_pool_lv $DIFF_VG $DIFF_TARGET)
if [ "$DIFF_SOURCE_POOL" == "" ]
then
(>&2 echo "Der Quell-LV ist nicht thin.")
exit 1
fi
if [ "$DIFF_TARGET_POOL" == "" ]
then
(>&2 echo "Der Ziel-LV ist nicht thin.")
exit 1
fi
if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ]
then
(>&2 echo "Quell- und Ziel-LVs gehören zu unterschiedlichen Thin-Pools.")
exit 1
fi
DIFF_POOL_PATH=$(read_lv_dm_path $DIFF_VG $DIFF_SOURCE_POOL)
DIFF_SOURCE_ID=$(read_thin_id $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_ID=$(read_thin_id $DIFF_VG $DIFF_TARGET)
DIFF_POOL_PATH_TPOOL="$DIFF_POOL_PATH-tpool"
DIFF_POOL_PATH_TMETA="$DIFF_POOL_PATH"_tmeta
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)
if [ "$DIFF_POOL_METADATA_SNAP" != "-" ]
then
(>&2 echo "Thin-Pool-Metadatensnapshot existiert bereits. Angenommen wird, dass dieser veraltet ist. Snapshot wird in 5 Sekunden freigegeben.")
sleep 5
dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap
fi
dmsetup message $DIFF_POOL_PATH_TPOOL 0 reserve_metadata_snap
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)
if [ "$DIFF_POOL_METADATA_SNAP" == "-" ]
then
(>&2 echo "Fehler beim Erstellen des Thin-Pool-Metadatensnapshots.")
exit 1
fi
#We keep output in variable because metadata snapshot need to be released early.
DIFF_DATA=$(thin_delta -m$DIFF_POOL_METADATA_SNAP --snap1 $DIFF_SOURCE_ID --snap2 $DIFF_TARGET_ID $DIFF_POOL_PATH_TMETA)
dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap
echo $"$DIFF_DATA" | grep -E 'different|left_only|right_only' | sed 's/>/g'>/g' | awk -F'>' '{print $6 "t" $8 "t" $11}' | sed 's/different/copy/g' | sed 's/left_only/copy/g' | sed 's/right_only/discard/g'
}
function thinsync()
{
SYNC_VG="$1"
SYNC_PEND="$2"
SYNC_BASE="$3"
SYNC_TARGET="$4"
SYNC_PEND_POOL=$(read_pool_lv $SYNC_VG $SYNC_PEND)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SYNC_VG $SYNC_PEND_POOL)
SYNC_PEND_PATH=$(read_lv_dm_path $SYNC_VG $SYNC_PEND)
activate_volume $SYNC_VG $SYNC_PEND
while read -r SYNC_ACTION SYNC_OFFSET SYNC_LENGTH ; do
SYNC_OFFSET_BYTES=$((SYNC_OFFSET * SYNC_BLOCK_SIZE))
SYNC_LENGTH_BYTES=$((SYNC_LENGTH * SYNC_BLOCK_SIZE))
if [ "$SYNC_ACTION" == "copy" ]
then
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SYNC_PEND_PATH" "$SYNC_TARGET"
fi
if [ "$SYNC_ACTION" == "discard" ]
then
blkdiscard -o $SYNC_OFFSET_BYTES -l $SYNC_LENGTH_BYTES "$SYNC_TARGET"
fi
done < <(thindiff "$SYNC_VG" "$SYNC_PEND" "$SYNC_BASE")
}
function discard_volume()
{
DISCARD_VG="$1"
DISCARD_LV="$2"
DISCARD_LV_PATH=$(read_lv_dm_path "$DISCARD_VG" "$DISCARD_LV")
if [ "$DISCARD_LV_PATH" != "" ]
then
echo "$DISCARD_LV_PATH gefunden"
else
echo "$DISCARD_LV nicht in $DISCARD_VG gefunden"
exit 1
fi
DISCARD_LV_POOL=$(read_pool_lv $DISCARD_VG $DISCARD_LV)
DISCARD_LV_SIZE=$(read_lv_size "$DISCARD_VG" "$DISCARD_LV")
lvremove -y --quiet "$DISCARD_LV_PATH" || exit 1
lvcreate --thin-pool "$DISCARD_LV_POOL" -V "$DISCARD_LV_SIZE"B --name "$DISCARD_LV" "$DISCARD_VG" || exit 1
}
function backup()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
SOURCE_PEND_LV="$SOURCE_LV$PEND_SUFFIX"
TARGET_PEND_LV="$TARGET_LV$PEND_SUFFIX"
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")
if [ "$SOURCE_BASE_LV_PATH" != "" ]
then
echo "$SOURCE_BASE_LV_PATH gefunden"
else
echo "Quellbasis nicht gefunden, erstelle Snapshot von $SOURCE_VG/$SOURCE_LV nach $SOURCE_VG/$SOURCE_BASE_LV"
lvcreate --quiet --snapshot --name "$SOURCE_BASE_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo "Entferne $SOURCE_BASE_LV_PATH, da wir bootstrappen müssen."
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
sync
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH mit der Quelle nicht synchron. Entferne..."
lvremove -y --quiet $TARGET_BASE_LV_PATH || exit 1
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
sync
fi
fi
SOURCE_BASE_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_BASE_LV")
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH gefunden"
else
echo "$TARGET_VG/$TARGET_LV nicht gefunden. Leeres Volume erstellen."
lvcreate --thin-pool "$BACKUPS_POOL" -V "$SOURCE_BASE_SIZE"B --name "$TARGET_BASE_LV" "$TARGET_VG" || exit 1
echo "Muss neu gebootstrapped werden. Quelle bei $SOURCE_BASE_LV_PATH verwerfen"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
TARGET_BASE_POOL=$(read_pool_lv $TARGET_VG $TARGET_BASE_LV)
TARGET_BASE_CHUNK_SIZE=$(read_lv_chunk_size $TARGET_VG $TARGET_BASE_POOL)
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
echo "Ziel bei $TARGET_BASE_LV_PATH verwerfen"
discard_volume "$TARGET_VG" "$TARGET_BASE_LV"
sync
fi
if [ "$SOURCE_PEND_LV_PATH" != "" ]
then
echo "$SOURCE_PEND_LV_PATH gefunden, entferne..."
lvremove -y --quiet "$SOURCE_PEND_LV_PATH" || exit 1
sync
fi
lvcreate --quiet --snapshot --name "$SOURCE_PEND_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
sync
if [ "$TARGET_PEND_LV_PATH" != "" ]
then
echo "$TARGET_PEND_LV_PATH gefunden, entferne..."
lvremove -y --quiet $TARGET_PEND_LV_PATH
sync
fi
lvcreate --quiet --snapshot --name "$TARGET_PEND_LV" "$TARGET_VG/$TARGET_BASE_LV" || exit 1
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")
SOURCE_PEND_LV_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_PEND_LV")
lvresize -L "$SOURCE_PEND_LV_SIZE"B "$TARGET_PEND_LV_PATH"
activate_volume "$TARGET_VG" "$TARGET_PEND_LV"
echo "Synchronisiere $SOURCE_PEND_LV_PATH mit $TARGET_PEND_LV_PATH"
thinsync "$SOURCE_VG" "$SOURCE_PEND_LV" "$SOURCE_BASE_LV" "$TARGET_PEND_LV_PATH" || exit 1
sync
TARGET_DATE_SUFFIX=$(suffix)
lvcreate --quiet --snapshot --name "$TARGET_LV$TARGET_DATE_SUFFIX" "$TARGET_VG/$TARGET_PEND_LV" || exit 1
sync
lvremove --quiet -y "$SOURCE_BASE_LV_PATH" || exit 1
sync
lvremove --quiet -y "$TARGET_BASE_LV_PATH" || exit 1
sync
lvrename -y "$SOURCE_VG/$SOURCE_PEND_LV" "$SOURCE_BASE_LV" || exit 1
lvrename -y "$TARGET_VG/$TARGET_PEND_LV" "$TARGET_BASE_LV" || exit 1
sync
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}
function verify()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
if [ "$SOURCE_BASE_LV_PATH" != "" ]
then
echo "$SOURCE_BASE_LV_PATH gefunden"
else
echo "$SOURCE_BASE_LV_PATH nicht gefunden"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH gefunden"
else
echo "$TARGET_BASE_LV_PATH nicht gefunden"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo Vergleiche "$SOURCE_BASE_LV_PATH" mit "$TARGET_BASE_LV_PATH"
cmp "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
echo Fertig...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}
function resync()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
if [ "$SOURCE_BASE_LV_PATH" != "" ]
then
echo "$SOURCE_BASE_LV_PATH gefunden"
else
echo "$SOURCE_BASE_LV_PATH nicht gefunden"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH gefunden"
else
echo "$TARGET_BASE_LV_PATH nicht gefunden"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
echo Synchronisiere "$SOURCE_BASE_LV_PATH" nach "$TARGET_BASE_LV_PATH"
CMP_OFFSET=0
while [[ "$CMP_OFFSET" != "" ]] ; do
CMP_MISMATCH=$(cmp -i "$CMP_OFFSET" "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH" | grep differ | awk '{print $5}' | sed 's/,//g' )
if [[ "$CMP_MISMATCH" != "" ]] ; then
CMP_OFFSET=$(( CMP_MISMATCH + CMP_OFFSET ))
SYNC_OFFSET_BYTES=$(( ( CMP_OFFSET / SYNC_BLOCK_SIZE ) * SYNC_BLOCK_SIZE ))
SYNC_LENGTH_BYTES=$(( SYNC_BLOCK_SIZE ))
echo "Synchronisiere $SYNC_LENGTH_BYTES Bytes bei $SYNC_OFFSET_BYTES von $SOURCE_BASE_LV_PATH nach $TARGET_BASE_LV_PATH"
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
else
CMP_OFFSET=""
fi
fertig
echo Fertig...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}
function list()
{
LIST_SOURCE_VG="$1"
LIST_SOURCE_LV="$2"
LIST_TARGET_VG="$BACKUPS"
LIST_TARGET_LV="$LIST_SOURCE_VG-$LIST_SOURCE_LV"
LIST_TARGET_BASE_LV="$LIST_TARGET_LV$SNAP_SUFFIX"
lvs -olv_name | grep "$LIST_TARGET_BASE_LV.$DATE_REGEX"
}
function remove()
{
REMOVE_TARGET_VG="$BACKUPS"
REMOVE_TARGET_LV="$1"
lvremove -y "$REMOVE_TARGET_VG/$REMOVE_TARGET_LV"
sync
}
function removeall()
{
DATE_OFFSET="$3"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$SNAPSHOT"
done < <(list "$1" "$2" | grep "$FILTER")
}
(
COMMAND="$1"
Shift
case "$COMMAND" in
"--help")
echo "Hilfe"
;;
"suffix")
Suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1" "$2"
;;
"list")
list "$1" "$2"
;;
"thindiff")
thindiff "$1" "$2" "$3"
;;
"thinsync")
thinsync "$1" "$2" "$3" "$4"
;;
"verify")
wait_lock_or_terminate
verify "$1" "$2"
;;
"resync")
wait_lock_or_terminate
resync "$1" "$2"
;;
"remove")
wait_lock_or_terminate
remove "$1"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2" "$3"
;;
*)
echo "Keine vorhanden.."
;;
esac
) 98>$LOCK_FILE
EOF
Was macht es…?Enthält eine Reihe von Befehlen zur Manipulation von Thin-Snapshots und zur Synchronisierung der Unterschiede zwischen zwei Thin-Snapshots, die über thin_delta auf ein anderes Blockgerät unter Verwendung von ddrescue und blkdiscard übertragen werden.
Ein weiteres Skript, das wir in Cron einfügen werden:
Noch etwas Bash#cat >/root/lvm-thin-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"
BACKUP_SCRIPT="$SCRIPT_DIR/lvm-thin-backup.sh"
RETENTION="-60 Tage"
$BACKUP_SCRIPT backup images linux-dev
$BACKUP_SCRIPT backup images win8
$BACKUP_SCRIPT backup images win8-data
#etc
$BACKUP_SCRIPT removeall images linux-dev "$RETENTION"
$BACKUP_SCRIPT removeall images win8 "$RETENTION"
$BACKUP_SCRIPT removeall images win8-data "$RETENTION"
#etc
EOF
Was macht es…?Verwendet das vorherige Skript, um Backups der angegebenen Thin-Volumes zu erstellen und zu synchronisieren. Das Skript lässt inaktive Snapshots der genannten Volumes zurück, die zum Verfolgen von Änderungen seit der letzten Synchronisierung erforderlich sind.
Dieses Skript muss bearbeitet werden, indem die Liste der Thin-Volumes angegeben wird, für die Backups erstellt werden sollen. Die genannten Bezeichner dienen nur als Beispiel. Es ist möglich, ein Skript zu schreiben, das alle Volumes synchronisiert.
Rechte vergeben:
#chmod +x /root/lvm-thin-backup/cron-daily.sh
#chmod +x /root/lvm-thin-backup/lvm-thin-backup.sh
Überprüfen wir es und füge es zu Cron hinzu:
#/usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/lvm-thin-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t lvm-thin-backup
#cat /var/log/syslog | grep lvm-thin-backup
#crontab -e
0 3 * * * /usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/lvm-thin-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t lvm-thin-backup
Der erste Start wird längere Zeit in Anspruch nehmen, da die dünnen Volumes durch das vollständige Synchronisieren des gesamten genutzten Speicherplatzes erstellt werden. Dank der LVM thin Metadaten wissen wir, welche Blöcke tatsächlich verwendet werden, sodass nur die tatsächlich genutzten Blöcke der dünnen Volumes kopiert werden.
Bei den nachfolgenden Starts werden die Daten inkrementell kopiert, da die Änderungen über die LVM thin Metadaten verfolgt werden.
Schauen wir uns an, was passiert ist:
#time /root/btrfs-backup/cron-daily.sh
reale 0m2,967s
Benutzer 0m0,225s
System 0m0,353s
#time /root/lvm-thin-backup/cron-daily.sh
real 1m2,710s
user 0m12,721s
sys 0m6,671s
#ls -al /backup/btrfs/back/remote/*
/backup/btrfs/back/remote/boot:
total 0
drwxr-xr-x 1 root root 1260 Mär 26 09:11 .
drwxr-xr-x 1 root root 16 Mär 6 09:30 ..
drwxr-xr-x 1 root root 322 Mär 26 02:00 .@base
drwxr-xr-x 1 root root 516 Mär 6 09:39 .@snap.2020-03-06-09-39-37
drwxr-xr-x 1 root root 516 Mär 6 09:39 .@snap.2020-03-06-09-39-57
...
/backup/btrfs/back/remote/root:
total 0
drwxr-xr-x 1 root root 2820 Mär 26 09:11 .
drwxr-xr-x 1 root root 16 Mär 6 09:30 ..
drwxr-xr-x 1 root root 240 Mär 26 09:11 @.@base
drwxr-xr-x 1 root root 22 Mär 26 09:11 @home.@base
drwxr-xr-x 1 root root 22 Mär 6 09:39 @home.@snap.2020-03-06-09-39-35
drwxr-xr-x 1 root root 22 Mär 6 09:39 @home.@snap.2020-03-06-09-39-57
...
drwxr-xr-x 1 root root 240 Mär 6 09:39 @.@snap.2020-03-06-09-39-26
drwxr-xr-x 1 root root 240 Mär 6 09:39 @.@snap.2020-03-06-09-39-56
...
#lvs -olv_name,lv_size images && lvs -olv_name,lv_size backup
LV LSize
linux-dev 128,00g
linux-dev.base 128,00g
thin-pool 1,38t
win8 128,00g
win8-data 2,00t
win8-data.base 2,00t
win8.base 128,00g
LV LSize
backup 256,00g
images-linux-dev.base 128,00g
images-linux-dev.snap.2020-03-08-10-09-11 128,00g
images-linux-dev.snap.2020-03-08-10-09-25 128,00g
...
images-win8-data.base 2,00t
images-win8-data.snap.2020-03-16-14-11-55 2,00t
images-win8-data.snap.2020-03-16-14-19-50 2,00t
...
images-win8.base 128,00g
images-win8.snap.2020-03-17-04-51-46 128,00g
images-win8.snap.2020-03-18-03-02-49 128,00g
...
thin-pool <2,09t
Was haben Matroschkas damit zu tun?
Wahrscheinlich damit, dass logische LVM-Volumes (LV) physische LVM-Volumes (PV) für andere Volume-Gruppen (VG) sein können. LVM kann rekursiv sein, ähnlich wie Matroschkas. Dies verleiht LVM eine außergewöhnliche Flexibilität.
P.S.
Im nächsten Artikel werden wir versuchen, mehrere ähnliche mobile SAN/KVM als Grundlage für den Aufbau eines geo-verteilten Storage/VM-Clusters mit Redundanz auf mehreren Kontinenten unter Verwendung von Heim-Desktops, heimischem Internet und P2P-Netzwerken zu verwenden.
Quelle: habr.com
