Was haben LVM und Matroschka gemeinsam?

Guten Tag.
Ich möchte mit der Community meine praktischen Erfahrungen beim Aufbau eines Datenspeichersystems für KVM unter Verwendung von md RAID + LVM teilen.

Das Programm umfasst:

  • Erstellen von MD RAID 1 aus NVMe SSD.
  • Zusammenbau von MD RAID 6 aus SATA SSD und regulären Laufwerken.
  • Funktionen des TRIM/DISCARD-Vorgangs auf SSD RAID 1/6.
  • Erstellen eines bootfähigen MD-RAID 1/6-Arrays auf einem gemeinsamen Festplattensatz.
  • Installation des Systems auf NVMe RAID 1, wenn im BIOS keine NVMe-Unterstützung vorhanden ist.
  • Verwendung von LVM-Cache und LVM Thin.
  • Verwendung von BTRFS-Snapshots und Senden/Empfangen zur Sicherung.
  • Verwendung von LVM-Thin-Snapshots und Thin_delta für Sicherungen im BTRFS-Stil.

Bei Interesse schauen Sie sich bitte die Kat.-Nr. an.

Aussage

Der Autor übernimmt keine Verantwortung für die Folgen der Verwendung oder Nichtverwendung von Materialien/Beispielen/Code/Tipps/Daten aus diesem Artikel. Durch das Lesen oder Verwenden dieses Materials übernehmen Sie die Verantwortung für alle Folgen dieser Handlungen. Mögliche Folgen sind:

  • Knusprig gebratene NVMe-SSDs.
  • Völlig verbrauchte Aufzeichnungsressource und Ausfall von SSD-Laufwerken.
  • Vollständiger Verlust aller Daten auf allen Laufwerken, einschließlich Sicherungskopien.
  • Fehlerhafte Computerhardware.
  • Verschwendete Zeit, Nerven und Geld.
  • Alle anderen Konsequenzen, die oben nicht aufgeführt sind.

Eisen

Verfügbar waren:

Motherboard von etwa 2013 mit Z87-Chipsatz, komplett mit Intel Core i7 / Haswell.

  • Prozessor 4 Kerne, 8 Threads
  • 32 GB DDR3-RAM
  • 1 x 16 oder 2 x 8 PCIe 3.0
  • 1 x 4 + 1 x 1 PCIe 2.0
  • 6 x 6 Gbit/s SATA 3-Anschlüsse

Der SAS-Adapter LSI SAS9211-8I wurde in den IT-/HBA-Modus geflasht. RAID-fähige Firmware wurde absichtlich durch HBA-Firmware ersetzt, um:

  1. Sie können diesen Adapter jederzeit wegwerfen und durch einen anderen ersetzen, den Sie gefunden haben.
  2. TRIM/Discard funktionierte auf Datenträgern normal, weil... In der RAID-Firmware werden diese Befehle überhaupt nicht unterstützt, und dem HBA ist es im Allgemeinen egal, welche Befehle über den Bus übertragen werden.

Festplatten – 8 Stück HGST Travelstar 7K1000 mit einer Kapazität von 1 TB im 2.5-Formfaktor, wie bei Laptops. Diese Laufwerke befanden sich zuvor in einem RAID 6-Array. Sie werden auch im neuen System Verwendung finden. Zum Speichern lokaler Backups.

Zusätzlich hinzugefügt:

6 Stück SATA SSD Modell Samsung 860 QVO 2TB. Diese SSDs benötigten ein großes Volumen, das Vorhandensein eines SLC-Cache, Zuverlässigkeit und ein niedriger Preis waren erwünscht. Es war Unterstützung für Discard/Zero erforderlich, was durch die Zeile in dmesg überprüft wird:

kernel: ata1.00: Enabling discard_zeroes_data

2 Stück NVMe SSD Modell Samsung SSD 970 EVO 500GB.

Bei diesen SSDs sind die zufällige Lese-/Schreibgeschwindigkeit und die Ressourcenkapazität für Ihre Anforderungen wichtig. Kühler für sie. Notwendig. Absolut. Andernfalls braten Sie sie bei der ersten RAID-Synchronisierung knusprig an.

StarTech PEX8M2E2-Adapter für 2 x NVMe SSD installiert im PCIe 3.0 8x-Steckplatz. Dies ist wiederum nur ein HBA, aber für NVMe. Er unterscheidet sich von billigen Adaptern dadurch, dass er aufgrund des eingebauten PCIe-Switches keine PCIe-Bifurkationsunterstützung vom Motherboard benötigt. Es funktioniert auch im ältesten System mit PCIe, selbst wenn es sich um einen x1 PCIe 1.0-Steckplatz handelt. Natürlich mit der entsprechenden Geschwindigkeit. Da gibt es keine RAIDs. Es gibt kein integriertes BIOS an Bord. Ihr System lernt also nicht auf magische Weise, mit NVMe zu booten, geschweige denn NVMe-RAID dank dieses Geräts.

Diese Komponente war ausschließlich auf das Vorhandensein von nur einem freien 8x PCIe 3.0 im System zurückzuführen und kann bei zwei freien Steckplätzen problemlos durch zwei Penny-PEX2M4E2 oder Analoga ersetzt werden, die überall zum Preis von 1 erhältlich sind Rubel.

Der Verzicht auf jegliche Art von Hardware oder eingebauten Chipsatz-/BIOS-RAIDs erfolgte bewusst, um das gesamte System, mit Ausnahme der SSD/HDD selbst, komplett austauschen zu können und dabei alle Daten zu erhalten. Im Idealfall, damit Sie bei einem Umstieg auf komplett neue/andere Hardware sogar das installierte Betriebssystem sparen können. Hauptsache, es gibt SATA- und PCIe-Anschlüsse. Es ist wie eine Live-CD oder ein bootfähiges Flash-Laufwerk, nur sehr schnell und etwas sperrig.

HumorAnsonsten wissen Sie, was passiert – manchmal muss man dringend das gesamte Array zum Mitnehmen mitnehmen. Aber ich möchte keine Daten verlieren. Dazu sind alle genannten Medien bequem auf den Auszügen in den 5.25-Schächten des Standardgehäuses untergebracht.

Nun, und natürlich zum Experimentieren mit verschiedenen Methoden des SSD-Cachings unter Linux.

Hardware-Raids sind langweilig. Mach es an. Entweder es funktioniert oder nicht. Und mit mdadm gibt es immer Optionen.

Weich

Zuvor war auf der Hardware Debian 8 Jessie installiert, was kurz vor EOL steht. RAID 6 wurde aus den oben genannten Festplatten gepaart mit LVM zusammengestellt. Es lief virtuelle Maschinen in kvm/libvirt.

Weil Der Autor verfügt über entsprechende Erfahrung in der Erstellung tragbarer bootfähiger SATA/NVMe-Flash-Laufwerke. Um die übliche Apt-Vorlage nicht zu durchbrechen, wurde Ubuntu 18.04 als Zielsystem ausgewählt, das bereits ausreichend stabilisiert ist, aber noch über 3 Jahre verfügt Unterstützung in der Zukunft.

Das genannte System enthält alle benötigten Hardwaretreiber sofort einsatzbereit. Wir benötigen keine Software oder Treiber von Drittanbietern.

Vorbereitung für die Installation

Zur Installation des Systems benötigen wir das Ubuntu Desktop Image. Das Serversystem verfügt über eine Art leistungsstarkes Installationsprogramm, das eine übermäßige Unabhängigkeit aufweist und nicht durch Verschieben der UEFI-Systempartition auf eine der Festplatten deaktiviert werden kann, was die ganze Schönheit ruiniert. Dementsprechend wird es nur im UEFI-Modus installiert. Bietet keine Optionen.

Damit sind wir nicht zufrieden.

Warum?Leider ist UEFI-Boot äußerst schlecht mit Boot-Software-RAID kompatibel, weil ... Niemand bietet uns Reservierungen für die UEFI ESP-Partition an. Es gibt Rezepte im Internet, die vorschlagen, die ESP-Partition auf einem Flash-Laufwerk an einem USB-Anschluss zu platzieren, aber das ist eine Fehlerquelle. Es gibt Rezepte, die Software mdadm RAID 1 mit Metadatenversion 0.9 verwenden, die das UEFI-BIOS nicht daran hindern, diese Partition zu sehen, aber das bleibt so lange bestehen, bis das BIOS oder ein anderes Hardware-Betriebssystem etwas in den ESP schreibt und vergisst, es mit anderen zu synchronisieren Spiegel.

Darüber hinaus ist der UEFI-Boot vom NVRAM abhängig, der nicht zusammen mit den Festplatten auf das neue System verschoben wird, weil ist Teil des Motherboards.

Wir werden also kein neues Rad neu erfinden. Wir haben bereits ein fertiges, bewährtes Großvater-Bike, das jetzt Legacy/BIOS-Boot heißt und auf UEFI-kompatiblen Systemen den stolzen Namen CSM trägt. Wir nehmen es einfach aus dem Regal, schmieren es, pumpen die Reifen auf und wischen es mit einem feuchten Tuch ab.

Auch die Desktop-Version von Ubuntu lässt sich mit dem Legacy-Bootloader nicht richtig installieren, aber hier gibt es, wie man so schön sagt, zumindest Optionen.

Und so sammeln wir die Hardware und laden das System vom bootfähigen Ubuntu Live-Flash-Laufwerk. Wir müssen Pakete herunterladen, damit wir das für Sie geeignete Netzwerk einrichten. Sollte es nicht funktionieren, können Sie die benötigten Pakete vorab auf einen USB-Stick laden.

Wir gehen in die Desktop-Umgebung, starten den Terminal-Emulator und los geht's:

#sudo bash

Wie…?Die obige Zeile ist der kanonische Auslöser für Holiwars über Sudo. C bоEs kommen größere Chancen undоgrößere Verantwortung. Die Frage ist, ob man es auf sich nehmen kann. Viele Leute denken, dass die Verwendung von sudo auf diese Weise zumindest nicht vorsichtig ist. Jedoch:

#apt-get install mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc

Warum nicht ZFS...?Wenn wir Software auf unserem Computer installieren, leihen wir im Wesentlichen unsere Hardware den Entwicklern dieser Software zum Fahren.
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 eines Tages abbezahlen müssen.

Aus dieser Sicht ist ZFS ein Ferrari und mdadm+lvm eher ein Fahrrad.

Subjektiv leiht der Autor Unbekannten lieber ein Fahrrad auf Kredit als einen Ferrari. Dort ist der Preis der Emission nicht hoch. Keine Notwendigkeit für Rechte. Einfacher als Verkehrsregeln. Das Parken ist kostenlos. Die Geländegängigkeit ist besser. Sie können jederzeit Beine an einem Fahrrad befestigen und ein Fahrrad mit Ihren eigenen Händen reparieren.

Warum dann BTRFS...?Um das Betriebssystem zu booten, benötigen wir ein Dateisystem, das in Legacy/BIOS GRUB standardmäßig unterstützt wird und gleichzeitig Live-Snapshots unterstützt. Wir werden es für die /boot-Partition verwenden. Darüber hinaus bevorzugt der Autor die Verwendung dieses FS für / (root), wobei nicht zu vergessen ist, dass Sie für jede andere Software separate Partitionen auf LVM erstellen und diese in den erforderlichen Verzeichnissen bereitstellen können.

Wir werden auf diesem FS keine Abbilder von virtuellen Maschinen oder Datenbanken speichern.
Dieser FS wird nur dazu verwendet, Snapshots des Systems zu erstellen, ohne es auszuschalten, und diese Snapshots dann per Senden/Empfangen auf eine Sicherungsfestplatte zu übertragen.

Darüber hinaus bevorzugt der Autor im Allgemeinen, ein Minimum an Software direkt auf der Hardware zu belassen und die gesamte andere Software in virtuellen Maschinen auszuführen, indem er beispielsweise GPUs und PCI-USB-Host-Controller über IOMMU an KVM weiterleitet.

Auf der Hardware verbleiben nur noch Datenspeicherung, Virtualisierung und Backup.

Wenn Sie ZFS mehr vertrauen, sind sie grundsätzlich für die angegebene Anwendung austauschbar.

Der Autor ignoriert jedoch bewusst die integrierten Spiegelungs-/RAID- und Redundanzfunktionen von ZFS, BRTFS und LVM.

Als zusätzliches Argument verfügt BTRFS über die Fähigkeit, zufällige Schreibvorgänge in sequentielle umzuwandeln, was sich äußerst positiv auf die Geschwindigkeit der Synchronisierung von Snapshots/Backups auf der Festplatte auswirkt.

Lassen Sie uns alle Geräte erneut scannen:

#udevadm control --reload-rules && udevadm trigger

Schauen wir uns um:

#lsscsi && nvme list
[0:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sda
[1:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdb
[2:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdc
[3:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdd
[4:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sde
[5:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdf
[6:0:0:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdg
[6:0:1:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdh
[6:0:2:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdi
[6:0:3:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdj
[6:0:4:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdk
[6:0:5:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdl
[6:0:6:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdm
[6:0:7:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdn
Node SN Model Namespace Usage 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

Festplattenlayout

NVMe SSD

Aber wir werden sie in keiner Weise markieren. Trotzdem erkennt unser BIOS diese Laufwerke nicht. Daher werden sie vollständig auf Software-RAID umsteigen. Wir werden dort nicht einmal Abschnitte erstellen. Wenn Sie dem „Kanon“ oder „prinzipiell“ folgen möchten, erstellen Sie eine große Partition, z. B. eine Festplatte.

SATA HDD

Hier muss man nichts Besonderes erfinden. Wir erstellen für alles einen Abschnitt. Wir werden eine Partition erstellen, da das BIOS diese Festplatten erkennt und möglicherweise sogar versucht, von ihnen zu booten. Wir werden später sogar GRUB auf diesen Festplatten installieren, damit das System dies plötzlich tun kann.

#cat >hdd.part << EOF
label: dos
label-id: 0x00000000
device: /dev/sdg
unit: sectors

/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 wird es für uns interessant.

Erstens sind unsere Laufwerke 2 TB groß. Dies liegt im akzeptablen Bereich für MBR, den wir verwenden werden. Bei Bedarf kann es durch GPT ersetzt werden. GPT-Festplatten verfügen über eine Kompatibilitätsschicht, die es MBR-kompatiblen Systemen ermöglicht, die ersten vier Partitionen zu sehen, wenn sie sich innerhalb der ersten zwei Terabyte befinden. Die Hauptsache ist, dass die Boot-Partition und die bios_grub-Partition auf diesen Festplatten am Anfang stehen sollten. Dies ermöglicht Ihnen sogar das Booten von GPT-Legacy-/BIOS-Laufwerken.

Aber das ist nicht unser Fall.

Hier erstellen wir zwei Abschnitte. Der erste wird 1 GB groß sein und für RAID 1/Boot verwendet werden.

Der zweite wird für RAID 6 verwendet und nimmt den gesamten verbleibenden freien Speicherplatz ein, mit Ausnahme eines kleinen nicht zugewiesenen Bereichs am Ende des Laufwerks.

Was ist dieser unmarkierte Bereich?Quellen im Netzwerk zufolge verfügen unsere SATA-SSDs über einen dynamisch erweiterbaren SLC-Cache mit einer Größe von 6 bis 78 Gigabyte. Aufgrund der Differenz zwischen „Gigabyte“ und „Gibibyte“ im Datenblatt des Laufwerks erhalten wir 6 Gigabyte „kostenlos“. Die restlichen 72 Gigabyte werden aus ungenutztem Speicherplatz zugewiesen.

Hierbei ist zu beachten, dass wir über einen SLC-Cache verfügen und der Speicherplatz im 4-Bit-MLC-Modus belegt ist. Was für uns effektiv bedeutet, dass wir pro 4 Gigabyte freiem Speicherplatz nur 1 Gigabyte SLC-Cache erhalten.

Multiplizieren Sie 72 Gigabyte mit 4 und erhalten Sie 288 Gigabyte. Dies ist der freie Speicherplatz, den wir nicht markieren, damit die Laufwerke den SLC-Cache vollständig nutzen können.

Somit erhalten wir effektiv bis zu 312 Gigabyte SLC-Cache aus insgesamt sechs Laufwerken. Von allen Laufwerken werden 2 aus Redundanzgründen im RAID verwendet.

Diese Menge an Cache ermöglicht es uns, im wirklichen Leben äußerst selten auf eine Situation zu stoßen, in der ein Schreibvorgang nicht in den Cache gelangt. Dadurch wird der traurigste Nachteil des QLC-Speichers hervorragend kompensiert – die extrem niedrige Schreibgeschwindigkeit, wenn Daten unter Umgehung des Caches geschrieben werden. Sollten Ihre Belastungen dem nicht entsprechen, dann empfehle ich Ihnen, unter Berücksichtigung des TBW aus dem Datenblatt genau darüber nachzudenken, wie lange Ihre SSD bei einer solchen Belastung durchhält.

#cat >ssd.part << EOF
label: dos
label-id: 0x00000000
device: /dev/sda
unit: sectors

/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

Zuerst müssen wir die Maschine umbenennen. Dies ist notwendig, da der Hostname Teil des Array-Namens irgendwo in mdadm ist und irgendwo etwas beeinflusst. Natürlich können Arrays später umbenannt werden, aber das ist ein unnötiger Schritt.

#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 Initialisierung von Arrays zu vermeiden. Dies gilt für beide RAID-Level 1 und 6. Alles kann ohne Initialisierung funktionieren, wenn es sich um ein neues Array handelt. Darüber hinaus ist die Initialisierung des SSD-Arrays bei der Erstellung eine Verschwendung von TBW-Ressourcen. Wir verwenden TRIM/DISCARD, wo möglich, bei zusammengebauten SSD-Arrays, um sie zu „initialisieren“.

Für SSD-Arrays wird RAID 1 DISCARD standardmäßig unterstützt.

Für SSD RAID 6 DISCARD-Arrays müssen Sie es in den Kernelmodulparametern aktivieren.

Dies sollte nur durchgeführt werden, wenn alle SSDs, die in Level 4/5/6-Arrays in diesem System verwendet werden, über funktionierende Unterstützung für Discard_zeroes_data verfügen. Manchmal stößt man auf seltsame Laufwerke, die dem Kernel mitteilen, dass diese Funktion unterstützt wird, sie aber tatsächlich nicht vorhanden ist oder die Funktion nicht immer funktioniert. Im Moment ist der Support fast überall verfügbar, allerdings gibt es alte Laufwerke und Firmware mit Fehlern. Aus diesem Grund ist die DISCARD-Unterstützung für RAID 6 standardmäßig deaktiviert.

Achtung, der folgende Befehl zerstört alle Daten auf NVMe-Laufwerken, indem er das Array mit „Nullen“ „initialisiert“.

#blkdiscard /dev/md0

Wenn etwas schief geht, versuchen Sie, einen 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ß...?Eine Erhöhung der Chunk-Größe wirkt sich positiv auf die Geschwindigkeit des zufälligen Lesens in Blöcken bis einschließlich Chunk-Größe aus. Dies liegt daran, dass ein Vorgang der entsprechenden Größe oder kleiner vollständig auf einem einzigen Gerät ausgeführt werden kann. Daher werden die IOPS aller Geräte zusammengefasst. Laut Statistik überschreiten 99 % der E/A 512 KB nicht.

RAID 6 IOPS pro Schreibvorgang immer kleiner oder gleich den IOPS eines Laufwerks. Bei einem zufälligen Lesevorgang können die IOPS um ein Vielfaches höher sein als die eines Laufwerks, und hier ist die Blockgröße von entscheidender Bedeutung.
Der Autor sieht keinen Sinn darin, einen Parameter zu optimieren, der in RAID 6 von Natur aus schlecht ist, und optimiert stattdessen das, was RAID 6 gut kann.
Das schlechte Random Write von RAID 6 kompensieren wir mit einem NVMe-Cache und Thin-Provisioning-Tricks.

Wir haben DISCARD für RAID 6 noch nicht aktiviert. Daher werden wir dieses Array vorerst nicht „initialisieren“. Wir werden dies später nach der Installation des Betriebssystems tun.

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

Aus Geschwindigkeitsgründen möchten wir das Root-Dateisystem auf NVMe RAID 1 platzieren, das /dev/md0 ist.
Allerdings benötigen wir dieses schnelle Array weiterhin für andere Anforderungen, wie z. B. Swap, Metadaten und LVM-Cache und LVM-Thin-Metadaten, daher werden wir auf diesem Array ein LVM-VG erstellen.

#pvcreate /dev/md0
#vgcreate root /dev/md0

Lassen Sie uns eine Partition für das Root-Dateisystem erstellen.

#lvcreate -L 128G --name root root

Erstellen wir eine Partition zum Austauschen entsprechend der Größe des RAM.

#lvcreate -L 32G --name swap root

Betriebssysteminstallation

Insgesamt verfügen wir über alles Notwendige zur Installation des Systems.

Starten Sie den Systeminstallationsassistenten in der Ubuntu Live-Umgebung. Normale Installation. Erst bei der Auswahl der Festplatten für die Installation müssen Sie Folgendes angeben:

  • /dev/md1, – Einhängepunkt /boot, FS – BTRFS
  • /dev/root/root (auch bekannt als /dev/mapper/root-root), – Mountpunkt / (root), FS – BTRFS
  • /dev/root/swap (auch bekannt als /dev/mapper/root-swap), – als Swap-Partition verwenden
  • Installieren Sie den Bootloader unter /dev/sda

Wenn Sie BTRFS als Root-Dateisystem auswählen, erstellt das Installationsprogramm automatisch zwei BTRFS-Volumes mit den Namen „@“ für / (Root) und „@home“ für /home.

Beginnen wir mit der Installation...

Die Installation endet mit einem modalen Dialogfeld, das auf einen Fehler bei der Installation des Bootloaders hinweist. Leider können Sie diesen Dialog nicht mit den üblichen Mitteln verlassen und mit der Installation fortfahren. Wir melden uns vom System ab und erneut an und landen auf einem sauberen Ubuntu Live-Desktop. Öffnen Sie das Terminal und noch einmal:

#sudo bash

Erstellen Sie 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 in 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

Gehen wir in die Chroot-Umgebung:

#chroot /mnt/chroot

Zunächst liefern wir die Pakete:

apt-get install --reinstall mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc debsums hdparm

Lassen Sie uns alle Pakete überprüfen und reparieren, die aufgrund einer unvollständigen Systeminstallation schief installiert wurden:

#CORRUPTED_PACKAGES=$(debsums -s 2>&1 | awk '{print $6}' | uniq)
#apt-get install --reinstall $CORRUPTED_PACKAGES

Wenn etwas nicht funktioniert, müssen Sie möglicherweise zuerst /etc/apt/sources.list bearbeiten

Passen wir die Parameter für das RAID 6-Modul an, um TRIM/DISCARD zu aktivieren:

#cat >/etc/modprobe.d/raid456.conf << EOF
options raid456 devices_handle_discard_safely=1
EOF

Lassen Sie uns unsere Arrays ein wenig 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:

  • Stellen Sie die Block-Cache-Größe für RAID 2020 so ein, dass sie für 6 angemessen ist. Der Standardwert scheint sich seit der Entwicklung von Linux nicht geändert zu haben und war schon lange nicht mehr ausreichend.
  • Reservieren Sie ein Minimum an E/A für die Dauer der Array-Prüfungen/Synchronisierungen. Dies soll verhindern, dass Ihre Arrays unter Last in einem Zustand ewiger Synchronisierung stecken bleiben.
  • Begrenzen Sie die maximale E/A während der Überprüfung/Synchronisierung von Arrays. Dies ist notwendig, damit die Synchronisierung/Überprüfung von SSD-RAIDs Ihre Laufwerke nicht kaputt macht. Dies gilt insbesondere für NVMe. (Erinnern Sie sich an den Kühler? Ich habe nicht gescherzt.)
  • Verhindern Sie, dass Festplatten die Spindelrotation (HDD) über APM stoppen, und legen Sie das Ruhezeitlimit für Festplattencontroller auf 7 Stunden fest. Sie können APM vollständig deaktivieren, wenn Ihre Laufwerke dies können (-B 255). Mit dem Standardwert stoppen die Antriebe nach fünf Sekunden. Dann möchte das Betriebssystem den Festplatten-Cache zurücksetzen, die Festplatten werden wieder hochgefahren und alles beginnt von vorne. Discs haben eine begrenzte maximale Anzahl von Spindelumdrehungen. Ein solch einfacher Standardzyklus kann Ihre Festplatten in ein paar Jahren leicht zerstören. Darunter leiden nicht alle Festplatten, aber bei uns handelt es sich um „Laptop“-Festplatten mit den entsprechenden Standardeinstellungen, die das RAID wie ein Mini-MAID aussehen lassen.
  • Installieren Sie Readahead auf Festplatten (rotierend) 1 Megabyte – zwei aufeinanderfolgende Blöcke/Chunk RAID 6
  • Deaktivieren Sie Readahead für die Arrays selbst.

Bearbeiten wir /etc/fstab:

#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 nach der /boot-Partition anhand der UUID suchen. Die Array-Benennung könnte sich theoretisch ändern.

Wir werden die verbleibenden Abschnitte nach LVM-Namen in der /dev/mapper/vg-lv-Notation suchen, weil Sie identifizieren Partitionen ziemlich eindeutig.

Wir verwenden keine UUID für LVM, weil Die UUID von LVM-Volumes und deren Snapshots können identisch sein./dev/mapper/root-root.. zweimal mounten?Ja. Genau so. Funktion von BTRFS. Dieses Dateisystem kann mehrmals mit unterschiedlichen Subvolumes gemountet werden.

Aufgrund derselben Funktion empfehle ich, niemals LVM-Snapshots aktiver BTRFS-Volumes zu erstellen. Möglicherweise erleben Sie beim Neustart eine Überraschung.

Lassen Sie uns die mdadm-Konfiguration neu generieren:

#/usr/share/mdadm/mkconf | sed 's/#DEVICE/DEVICE/g' >/etc/mdadm/mdadm.conf

Passen wir 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 von LVM-Thin-Pools aktiviert, sobald 90 % des belegten Speicherplatzes um 5 % des Volumens erreicht sind.

Wir haben die maximale Anzahl von Cache-Blöcken für den LVM-Cache erhöht.

Wir haben verhindert, dass LVM nach LVM-Volumes (PV) sucht auf:

  • Geräte mit LVM-Cache (cdata)
  • Geräte werden mithilfe des LVM-Cache zwischengespeichert, wobei der Cache umgangen wird ( _corig). In diesem Fall wird das zwischengespeicherte Gerät selbst weiterhin über den Cache gescannt (nur). ).
  • Geräte mit LVM-Cache-Metadaten (cmeta)
  • alle Geräte in VG mit den Namensbildern. Hier haben wir Disk-Images von virtuellen Maschinen und möchten nicht, dass LVM auf dem Host Volumes aktiviert, die zum Gastbetriebssystem gehören.
  • alle Geräte in VG mit dem Namen Backup. Hier haben wir Sicherungskopien der Images virtueller Maschinen.
  • alle Geräte, deren Name mit „gpv“ (Guest Physical Volume) endet

Wir haben die DISCARD-Unterstützung beim Freigeben von freiem Speicherplatz auf LVM VG aktiviert. Seid vorsichtig. Dadurch wird das Löschen von LVs auf der SSD recht zeitaufwändig. Dies gilt insbesondere für SSD RAID 6. Allerdings werden wir laut Plan Thin Provisioning nutzen, sodass uns dies überhaupt nicht behindert.

Aktualisieren wir das initramfs-Image:

#update-initramfs -u -k all

Grub installieren und konfigurieren:

#apt-get install grub-pc
#apt-get purge os-prober
#dpkg-reconfigure grub-pc

Welche Festplatten soll ich wählen?Alle, die SD* sind. Das System muss von jedem funktionierenden SATA-Laufwerk oder SSD booten können.

Warum haben sie OS-Prober hinzugefügt?Für übermäßige Unabhängigkeit und verspielte Hände.

Es funktioniert nicht richtig, wenn sich eines der RAIDs in einem herabgesetzten Zustand befindet. Es versucht, auf Partitionen, die in virtuellen Maschinen verwendet werden, die auf dieser Hardware ausgeführt werden, nach dem Betriebssystem zu suchen.

Wenn Sie es brauchen, können Sie es lassen, aber beachten Sie alle oben genannten Punkte. Ich empfehle, online nach Rezepten zu suchen, um unartige Hände loszuwerden.

Damit haben wir die Erstinstallation abgeschlossen. Es ist Zeit, das neu installierte Betriebssystem neu zu starten. Vergessen Sie nicht, die bootfähige Live-CD/USB zu entfernen.

#exit
#reboot

Wählen Sie eine der SATA-SSDs als Startgerät aus.

LVM auf SATA SSD

Zu diesem Zeitpunkt haben wir bereits das neue Betriebssystem gebootet, das Netzwerk und apt konfiguriert, den Terminalemulator geöffnet und Folgendes gestartet:

#sudo bash

Lass uns weitermachen.

„Initialisieren“ Sie das Array von SATA SSD:

#blkdiscard /dev/md2

Wenn es nicht funktioniert, versuchen Sie Folgendes:

#blkdiscard --step 65536 /dev/md2
LVM VG auf SATA SSD erstellen:

#pvcreate /dev/md2
#vgcreate data /dev/md2

Warum noch ein VG...?Tatsächlich haben wir bereits eine VG namens root. Warum nicht alles in einer VG zusammenfassen?

Wenn in einer VG mehrere PVs vorhanden sind, müssen für eine korrekte Aktivierung der VG alle PVs vorhanden (online) sein. Ausnahme ist LVM RAID, auf das wir bewusst verzichten.

Wir möchten unbedingt, dass bei einem Fehler (Lesedatenverlust) auf einem der RAID-6-Arrays das Betriebssystem normal startet und wir die Möglichkeit haben, das Problem zu lösen.

Dazu isolieren wir auf der ersten Abstraktionsebene jede Art von physischen „Medien“ in einem separaten VG.

Wissenschaftlich gesehen gehören verschiedene RAID-Arrays zu unterschiedlichen „Zuverlässigkeitsdomänen“. Sie sollten für sie keine zusätzliche gemeinsame Fehlerquelle schaffen, indem Sie sie in einer VG zusammenfassen.

Das Vorhandensein von LVM auf der „Hardware“-Ebene wird es uns ermöglichen, Teile verschiedener RAID-Arrays willkürlich zu zerschneiden, indem wir sie auf unterschiedliche Weise kombinieren. Zum Beispiel - laufen gleichzeitig bcache + LVM Thin, bcache + BTRFS, LVM Cache + LVM Thin, eine komplexe ZFS-Konfiguration mit Caches oder jede andere höllische Mischung, um alles zu vergleichen.

Auf der „Hardware“-Ebene werden wir nichts anderes als die guten alten „dicken“ LVM-Volumes verwenden. Die Ausnahme von dieser Regel kann die Backup-Partition sein.

Ich glaube, zu diesem Zeitpunkt hatten viele Leser bereits eine Ahnung von der Nistpuppe.

LVM auf SATA-Festplatte

#pvcreate /dev/md3
#vgcreate backup /dev/md3

Schon wieder neue VG..?Wir möchten wirklich, dass unser Betriebssystem normal weiterarbeitet, wenn das Festplatten-Array, das wir für die Datensicherung verwenden, ausfällt, während der Zugriff auf nicht gesicherte Daten wie gewohnt aufrechterhalten wird. Um Probleme bei der VG-Aktivierung zu vermeiden, erstellen wir daher eine separate VG.

LVM-Cache einrichten

Erstellen wir ein LV auf NVMe RAID 1, um es als Caching-Gerät zu verwenden.

#lvcreate -L 70871154688B --name cache root

Warum gibt es so wenig...?Fakt ist, dass unsere NVMe-SSDs auch über einen SLC-Cache verfügen. 4 Gigabyte „frei“ und 18 Gigabyte dynamisch aufgrund des im 3-Bit-MLC belegten freien Speicherplatzes. Sobald dieser Cache erschöpft ist, sind NVMe-SSDs nicht mehr viel schneller als unsere SATA-SSD mit Cache. Aus diesem Grund macht es für uns eigentlich keinen Sinn, die LVM-Cache-Partition viel größer als doppelt so groß zu machen wie der SLC-Cache des NVMe-Laufwerks. Für die verwendeten NVMe-Laufwerke hält der Autor es für sinnvoll, 32-64 Gigabyte Cache vorzusehen.

Die angegebene Partitionsgröße ist erforderlich, um 64 Gigabyte Cache, Cache-Metadaten und Metadatensicherung zu organisieren.

Darüber hinaus stelle ich fest, dass LVM nach einem fehlerhaften Herunterfahren des Systems den gesamten Cache als fehlerhaft markiert und erneut synchronisiert. Darüber hinaus wird dies jedes Mal wiederholt, wenn lvchange auf diesem Gerät verwendet wird, bis das System erneut gestartet wird. Daher empfehle ich, den Cache sofort mit dem entsprechenden Skript neu zu erstellen.

Erstellen wir ein LV auf SATA RAID 6, um es als zwischengespeichertes Gerät zu verwenden.

#lvcreate -L 3298543271936B --name cache data

Warum nur drei Terabyte...?Damit Sie SATA SSD RAID 6 bei Bedarf auch für andere Zwecke nutzen können. Die Größe des zwischengespeicherten Speicherplatzes kann dynamisch und im laufenden Betrieb erhöht werden, ohne dass das System angehalten werden muss. Dazu müssen Sie den Cache vorübergehend anhalten und wieder aktivieren. Der entscheidende Vorteil von LVM-Cache gegenüber beispielsweise Bcache besteht jedoch darin, dass dies im laufenden Betrieb erfolgen kann.

Lassen Sie uns eine neue VG für das Caching erstellen.

#pvcreate /dev/root/cache
#pvcreate /dev/data/cache
#vgcreate cache /dev/root/cache /dev/data/cache

Lassen Sie uns ein LV auf dem zwischengespeicherten Gerät erstellen.

#lvcreate -L 3298539077632B --name cachedata cache /dev/data/cache

Hier haben wir sofort den gesamten freien Platz auf /dev/data/cache belegt, sodass alle weiteren notwendigen Partitionen sofort auf /dev/root/cache angelegt wurden. Wenn Sie etwas am falschen Ort erstellt haben, können Sie es mit pvmove verschieben.

Lassen Sie uns den Cache erstellen und aktivieren:

#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 so groß?Durch praktische Experimente konnte der Autor herausfinden, dass das beste Ergebnis erzielt wird, wenn die Größe des LVM-Cache-Blocks mit der Größe des LVM-Thin-Blocks übereinstimmt. Darüber hinaus gilt: Je kleiner die Größe, desto besser ist die Leistung der Konfiguration bei einer Zufallsaufzeichnung.

64 KB ist die minimal zulässige Blockgröße für LVM Thin.

Seien Sie vorsichtig beim Zurückschreiben..!Ja. Diese Art von Cache verschiebt die Schreibsynchronisierung auf das zwischengespeicherte Gerät. Das bedeutet, dass Sie bei einem Cache-Verlust möglicherweise Daten auf dem zwischengespeicherten Gerät verlieren. Welche Maßnahmen zusätzlich zu NVMe RAID 1 ergriffen werden können, um dieses Risiko zu kompensieren, verrät Ihnen der Autor später.

Dieser Cache-Typ wurde bewusst gewählt, um die schlechte zufällige Schreibleistung von RAID 6 auszugleichen.

Schauen wir uns an, was wir haben:

#lvs -a -o lv_name,lv_size,devices --units B cache
LV LSize Devices
[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)

Nur [cachedata_corig] sollte sich unter /dev/data/cache befinden. Wenn etwas nicht stimmt, verwenden Sie pvmove.

Sie können den Cache bei Bedarf mit einem Befehl deaktivieren:

#lvconvert -y --uncache cache/cachedata

Dies erfolgt online. LVM synchronisiert einfach den Cache mit der Festplatte, entfernt ihn und benennt „cachedata_corig“ wieder in „cachedata“ um.

LVM Thin einrichten

Lassen Sie uns grob abschätzen, wie viel Platz wir für LVM-Thin-Metadaten benötigen:

#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - 3385794560 bytes estimated metadata area size for "--block-size=64kibibytes --pool-size=6terabytes --max-thins=100000"

Auf 4 Gigabyte aufrunden: 4294967296B

Multiplizieren Sie mit zwei und addieren Sie 4194304B für LVM-PV-Metadaten: 8594128896B
Erstellen wir eine separate Partition auf NVMe RAID 1, um LVM-Thin-Metadaten und ihre Sicherungskopie darauf zu platzieren:

#lvcreate -L 8594128896B --name images root

Wofür..?Hier stellt sich möglicherweise die Frage: Warum sollten LVM-Thin-Metadaten separat platziert werden, wenn sie dennoch auf NVMe zwischengespeichert werden und schnell funktionieren?

Obwohl Geschwindigkeit hier wichtig ist, ist sie bei weitem nicht der Hauptgrund. Die Sache ist, dass der Cache ein Fehlerpunkt ist. Es könnte etwas passieren, und wenn die LVM-Thin-Metadaten zwischengespeichert werden, geht alles vollständig verloren. Ohne vollständige Metadaten wird es nahezu unmöglich sein, dünne Volumes zusammenzustellen.

Indem wir die Metadaten auf ein separates, nicht zwischengespeichertes, aber schnelles Volume verschieben, garantieren wir die Sicherheit der Metadaten im Falle eines Cache-Verlusts oder einer Beschädigung. In diesem Fall werden alle durch Cache-Verlust verursachten Schäden innerhalb dünner Volumes lokalisiert, was den Wiederherstellungsvorgang um Größenordnungen vereinfacht. Mit hoher Wahrscheinlichkeit werden diese Schäden mithilfe von FS-Protokollen behoben.

Wenn außerdem zuvor ein Snapshot eines Thin-Volumes erstellt wurde und der Cache danach mindestens einmal vollständig synchronisiert wurde, ist aufgrund des internen Designs von LVM Thin die Integrität des Snapshots im Falle eines Cache-Verlusts gewährleistet .

Lassen Sie uns eine neue VG erstellen, die für Thin-Provisioning verantwortlich ist:

#pvcreate /dev/root/images
#pvcreate /dev/cache/cachedata
#vgcreate images /dev/root/images /dev/cache/cachedata

Lassen Sie uns einen Pool erstellen:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
Warum -Z yZusätzlich zu dem eigentlichen Zweck dieses Modus – um zu verhindern, dass bei der Neuverteilung von Speicherplatz Daten von einer virtuellen Maschine auf eine andere virtuelle Maschine gelangen – wird Zeroing zusätzlich verwendet, um die Geschwindigkeit des zufälligen Schreibens in Blöcken kleiner als 64 KB zu erhöhen. Jeder Schreibvorgang von weniger als 64 KB in einen zuvor nicht zugewiesenen Bereich des Thin Volume wird im Cache zu 64 KB kantenausgerichtet. Dadurch kann der Vorgang vollständig über den Cache ausgeführt werden und das zwischengespeicherte Gerät umgangen werden.

Verschieben wir die LVs in die entsprechenden PVs:

#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

Lass uns das Prüfen:

#lvs -a -o lv_name,lv_size,devices --units B images
LV LSize Devices
[lvol0_pmspare] 4294967296B /dev/root/images(0)
thin-pool 274877906944B thin-pool_tdata(0)
[thin-pool_tdata] 274877906944B /dev/cache/cachedata(0)
[thin-pool_tmeta] 4294967296B /dev/root/images(1024)

Erstellen wir ein dünnes Volume für Tests:

#lvcreate -V 64G --thin-pool thin-pool --name test images

Wir installieren Pakete für Tests und Überwachung:

#apt-get install sysstat fio

So können Sie das Verhalten unserer Speicherkonfiguration 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 wir 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

Sorgfältig! Ressource!Dieser Code führt 36 verschiedene Tests aus, die jeweils 4 Sekunden lang laufen. Die Hälfte der Tests dient der Aufzeichnung. Sie können in 4 Sekunden viel auf NVMe aufzeichnen. Bis zu 3 Gigabyte pro Sekunde. Jeder Schreibtestdurchlauf kann Ihnen also bis zu 216 Gigabyte SSD-Ressourcen verbrauchen.

Lesen und Schreiben gemischt?Ja. Es ist sinnvoll, die Lese- und Schreibtests getrennt durchzuführen. Darüber hinaus ist es sinnvoll sicherzustellen, dass alle Caches synchronisiert sind, damit ein zuvor durchgeführter Schreibvorgang keinen Einfluss auf den Lesevorgang hat.

Die Ergebnisse werden beim ersten und den folgenden Starts stark variieren, da sich der Cache und das Thin Volume füllen und auch davon abhängen, ob es dem System gelungen ist, die beim letzten Start gefüllten Caches zu synchronisieren.

Ich empfehle unter anderem, die Geschwindigkeit an einem bereits vollen Thin-Volume zu messen, von dem gerade ein Snapshot erstellt wurde. Der Autor hatte die Gelegenheit zu beobachten, wie sich zufällige Schreibvorgänge direkt nach der Erstellung des ersten Snapshots stark beschleunigen, insbesondere wenn der Cache noch nicht vollständig gefüllt ist. Dies geschieht aufgrund der Copy-on-Write-Schreibsemantik, der Ausrichtung von Cache- und Thin-Volume-Blöcken und der Tatsache, dass ein zufälliger Schreibvorgang in RAID 6 zu einem zufälligen Lesevorgang von RAID 6 wird, gefolgt von einem Schreibvorgang in den Cache. In unserer Konfiguration ist das zufällige Lesen von RAID 6 bis zu sechsmal schneller (die Anzahl der SATA-SSDs im Array) als das Schreiben. Weil Blöcke für CoW werden sequentiell aus einem dünnen Pool zugewiesen, dann wird die Aufzeichnung größtenteils auch sequentiell.

Beide Funktionen können Sie zu Ihrem Vorteil nutzen.

Cachen Sie „kohärente“ Snapshots

Um das Risiko eines Datenverlusts im Falle einer Beschädigung/eines Cache-Verlusts zu verringern, schlägt der Autor vor, die Praxis rotierender Snapshots einzuführen, um in diesem Fall deren Integrität zu gewährleisten.

Da sich Thin-Volume-Metadaten auf einem nicht zwischengespeicherten Gerät befinden, sind die Metadaten erstens konsistent und mögliche Verluste werden innerhalb der Datenblöcke isoliert.

Der folgende Snapshot-Rotationszyklus garantiert die Integrität der Daten in den Snapshots im Falle eines Cache-Verlusts:

  1. Erstellen Sie für jedes Thin-Volume mit dem Namen <Name> einen Snapshot mit dem Namen <Name>.cached
  2. Stellen wir den Migrationsschwellenwert auf einen angemessen hohen Wert ein: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. In der Schleife prüfen wir die Anzahl der Dirty Blocks im Cache: #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' bis wir Null bekommen. Wenn die Null zu lange fehlt, kann sie durch vorübergehendes Umschalten des Caches in den Durchschreibmodus erstellt werden. Unter Berücksichtigung der Geschwindigkeitseigenschaften unserer SATA- und NVMe-SSD-Arrays sowie ihrer TBW-Ressource können Sie jedoch entweder schnell den Moment einfangen, ohne den Cache-Modus zu ändern, oder Ihre Hardware wird ihre gesamte Ressource vollständig auffressen ein paar Tage. Aufgrund von Ressourcenbeschränkungen ist es grundsätzlich nicht möglich, das System ständig unter 100 % Schreiblast zu halten. Unsere NVMe-SSDs erschöpfen bei einer Schreiblast von 100 % die Ressourcen vollständig 3-4 Tage. SATA-SSDs halten nur doppelt so lange. Daher gehen wir davon aus, dass die meiste Last auf das Lesen entfällt und wir relativ kurzfristige Ausbrüche extrem hoher Aktivität haben, kombiniert mit einer durchschnittlich geringen Last beim Schreiben.
  4. Sobald wir eine Null gefangen (oder erstellt) haben, benennen wir <name>.cached in <name>.committed um. Der alte <name>.committed wird gelöscht.
  5. Wenn der Cache zu 100 % voll ist, kann er optional durch ein Skript neu erstellt und somit geleert werden. Bei halb leerem Cache arbeitet das System beim Schreiben deutlich schneller.
  6. Setzen Sie den Migrationsschwellenwert auf Null: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata Dadurch wird vorübergehend verhindert, dass der Cache mit dem Hauptmedium synchronisiert wird.
  7. Wir warten, bis sich im Cache ziemlich viele Änderungen ansammeln #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' oder der Timer läuft ab.
  8. Wir wiederholen es noch einmal.

Warum Schwierigkeiten mit der Migrationsschwelle...?Tatsache ist, dass eine „zufällige“ Aufnahme in der Praxis eigentlich nicht völlig zufällig ist. Wenn wir etwas in einen Sektor mit einer Größe von 4 Kilobyte geschrieben haben, besteht eine hohe Wahrscheinlichkeit, dass in den nächsten Minuten eine Aufzeichnung in denselben oder einen der benachbarten (+- 32 KB) Sektoren erfolgt.

Indem wir den Migrationsschwellenwert auf Null setzen, verschieben wir die Schreibsynchronisierung auf der SATA-SSD und fassen mehrere Änderungen in einem 64-KB-Block im Cache zusammen. Dadurch wird die Ressource der SATA-SSD erheblich gespart.

Wo ist der Code?Leider hält sich der Autor für nicht ausreichend kompetent in der Entwicklung von Bash-Skripten, da er zu 100 % Autodidakt ist und „Google“-gesteuerte Entwicklung praktiziert. Daher ist er der Meinung, dass der schreckliche Code, der aus seinen Händen kommt, von niemandem verwendet werden sollte anders.

Ich denke, dass Fachleute auf diesem Gebiet in der Lage sein werden, die gesamte oben beschriebene Logik bei Bedarf unabhängig darzustellen und sie vielleicht sogar schön als Systemdienst zu gestalten, wie es der Autor versucht hat.

Ein solch einfaches Snapshot-Rotationsschema ermöglicht es uns nicht nur, ständig einen Snapshot vollständig auf der SATA-SSD zu synchronisieren, sondern ermöglicht uns auch, mit dem Dienstprogramm „thin_delta“ herauszufinden, welche Blöcke nach seiner Erstellung geändert wurden, und so den Schaden zu lokalisieren die Hauptvolumes, was die Wiederherstellung erheblich vereinfacht.

TRIM/DISCARD in libvirt/KVM

Weil Wenn der Datenspeicher für KVM mit libvirt verwendet wird, wäre es eine gute Idee, unseren VMs beizubringen, nicht nur freien Speicherplatz zu belegen, sondern auch das freizugeben, was nicht mehr benötigt wird.

Dies geschieht durch die Emulation der TRIM/DISCARD-Unterstützung auf virtuellen Festplatten. Dazu müssen Sie den Controller-Typ in virtio-scsi ändern und die XML bearbeiten.

#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'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>

<controller type='scsi' index='0' model='virtio-scsi'>
<alias name='scsi0'/>
<address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
</controller>

Solche DISCARDs von Gastbetriebssystemen werden von LVM korrekt verarbeitet und Blöcke werden sowohl im Cache als auch im Thin Pool korrekt freigegeben. In unserem Fall geschieht dies hauptsächlich verzögert, wenn der nächste Snapshot gelöscht wird.

BTRFS-Backup

Verwenden Sie vorgefertigte Skripte mit крайней Vorsicht und auf eigenes Risiko. Der Autor hat diesen Code selbst und ausschließlich für sich selbst geschrieben. Ich bin mir sicher, dass viele erfahrene Linux-Benutzer über ähnliche Tools verfügen und es nicht nötig ist, die von jemand anderem zu kopieren.

Erstellen wir ein Volume auf dem Backup-Gerät:

#lvcreate -L 256G --name backup backup

Formatieren wir es in BTRFS:

#mkfs.btrfs /dev/backup/backup

Lassen Sie uns Mount-Punkte erstellen und die Root-Unterabschnitte des Dateisystems mounten:

#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

Lassen Sie uns Verzeichnisse für Backups erstellen:

#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:

Jede Menge gruseliger Bash-Code. Benutzung auf eigene Gefahr. Schreiben Sie dem Autor keine wütenden Briefe ...#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 "Wating for lock..."
wait_lock || terminate "Failed to get lock. Exiting..."
echo "Got lock..."
}

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 found"
else
echo "$SOURCE_BASE_PATH File not found creating snapshot of $SOURCE_PATH to $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
if [ -d "$TARGET_BASE_PATH" ] then
echo "$TARGET_BASE_PATH found out of sync with source... removing..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
if [ -d "$TARGET_BASE_PATH" ] then
echo "$TARGET_BASE_PATH found"
else
echo "$TARGET_BASE_PATH not found. Synching to $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 found removing..."
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 found removing..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Sending $SOURCE_PEND_PATH to $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 "Help"
;;
"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 "None.."
;;
esac
) 98>$LOCK_FILE

EOF

Was macht es überhaupt?Enthält eine Reihe einfacher Befehle zum Erstellen von BTRFS-Snapshots und zum Kopieren dieser auf einen anderen FS mithilfe von BTRFS-Senden/Empfangen.

Der erste Start kann relativ lange dauern, weil... Zu Beginn werden alle Daten kopiert. Weitere Starts werden sehr schnell erfolgen, weil... Es werden nur Änderungen kopiert.

Ein weiteres Skript, das wir in Cron einfügen werden:

Etwas 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 day"
$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 tut es..?Erstellt und synchronisiert inkrementelle Snapshots der aufgelisteten BTRFS-Volumes auf dem Backup-FS. Danach werden alle Bilder gelöscht, die vor 60 Tagen erstellt wurden. Nach dem Start werden datierte Snapshots der aufgelisteten Volumes in den Unterverzeichnissen /backup/btrfs/back/remote/ angezeigt.

Geben wir dem Code Ausführungsrechte:

#chmod +x /root/btrfs-backup/cron-daily.sh
#chmod +x /root/btrfs-backup/btrfs-backup.sh

Lass es uns überprüfen und in den Cron einfügen:

#/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 Thin-Pool auf dem Backup-Gerät:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T backup/thin-pool

Lasst uns ddrescue installieren, weil... Skripte verwenden dieses Tool:

#apt-get install gddrescue

Erstellen wir ein Verzeichnis für Skripte:

#mkdir /root/lvm-thin-backup

Kopieren wir die Skripte:

Viel Bash drinnen...#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 "Wating for lock..."
wait_lock || terminate "Failed to get lock. Exiting..."
echo "Got lock..."
}

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 "Source LV is not thin.")
exit 1
fi

if [ "$DIFF_TARGET_POOL" == "" ] then
(>&2 echo "Target LV is not thin.")
exit 1
fi

if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ] then
(>&2 echo "Source and target LVs belong to different 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 metadata snapshot already exist. Assuming stale one. Will release metadata snapshot in 5 seconds.")
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 "Failed to create thin pool metadata snapshot.")
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' | sed 's/ /"/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 found"
else
echo "$DISCARD_LV not found in $DISCARD_VG"
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 found"
else
echo "Source base not found creating snapshot of $SOURCE_VG/$SOURCE_LV to $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 "Discarding $SOURCE_BASE_LV_PATH as we need to bootstrap."
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 found out of sync with source... removing..."
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 found"
else
echo "$TARGET_VG/$TARGET_LV not found. Creating empty volume."
lvcreate --thin-pool "$BACKUPS_POOL" -V "$SOURCE_BASE_SIZE"B --name "$TARGET_BASE_LV" "$TARGET_VG" || exit 1
echo "Have to rebootstrap. Discarding source at $SOURCE_BASE_LV_PATH"
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 "Discarding target at $TARGET_BASE_LV_PATH"
discard_volume "$TARGET_VG" "$TARGET_BASE_LV"
sync
fi
if [ "$SOURCE_PEND_LV_PATH" != "" ] then
echo "$SOURCE_PEND_LV_PATH found removing..."
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 found removing..."
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 "Synching $SOURCE_PEND_LV_PATH to $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 found"
else
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_BASE_LV_PATH not found"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo Comparing "$SOURCE_BASE_LV_PATH" with "$TARGET_BASE_LV_PATH"
cmp "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
echo Done...
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 found"
else
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_BASE_LV_PATH not found"
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 Syncronizing "$SOURCE_BASE_LV_PATH" to "$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 "Synching $SYNC_LENGTH_BYTES bytes at $SYNC_OFFSET_BYTES from $SOURCE_BASE_LV_PATH to $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
done
echo Done...
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 "Help"
;;
"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 "None.."
;;
esac
) 98>$LOCK_FILE

EOF

Was tut es...?Enthält eine Reihe von Befehlen zum Bearbeiten von Thin-Snapshots und zum Synchronisieren der Differenz zwischen zwei über Thin_delta empfangenen Thin-Snapshots mit einem anderen Blockgerät mithilfe von ddrescue und blkdiscard.

Ein weiteres Skript, das wir in Cron einfügen werden:

Etwas mehr 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 days"

$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 tut es...?Verwendet das vorherige Skript, um Backups der aufgelisteten Thin Volumes zu erstellen und zu synchronisieren. Das Skript hinterlässt inaktive Snapshots der aufgelisteten Volumes, die zum Nachverfolgen von Änderungen seit der letzten Synchronisierung benötigt werden.

Dieses Skript muss bearbeitet werden und die Liste der Thin Volumes angeben, für die Sicherungskopien erstellt werden sollen. Die angegebenen Namen dienen nur zur Veranschaulichung. Wenn Sie möchten, können Sie ein Skript schreiben, das alle Volumes synchronisiert.

Geben wir die Rechte:

#chmod +x /root/lvm-thin-backup/cron-daily.sh
#chmod +x /root/lvm-thin-backup/lvm-thin-backup.sh

Lass es uns überprüfen und in den Cron einfügen:

#/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 lange dauern, weil... Thin-Volumes werden vollständig synchronisiert, indem der gesamte verwendete Speicherplatz kopiert wird. Dank der LVM-Thin-Metadaten wissen wir, welche Blöcke tatsächlich verwendet werden, sodass nur tatsächlich verwendete Thin-Volume-Blöcke kopiert werden.

Nachfolgende Läufe kopieren die Daten inkrementell dank der Änderungsverfolgung über LVM-Thin-Metadaten.

Lass uns nachsehen, was passiert ist:

#time /root/btrfs-backup/cron-daily.sh
real 0m2,967s
user 0m0,225s
sys 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 мар 26 09:11 .
drwxr-xr-x 1 root root 16 мар 6 09:30 ..
drwxr-xr-x 1 root root 322 мар 26 02:00 .@base
drwxr-xr-x 1 root root 516 мар 6 09:39 [email protected]
drwxr-xr-x 1 root root 516 мар 6 09:39 [email protected]
...
/backup/btrfs/back/remote/root:
total 0
drwxr-xr-x 1 root root 2820 мар 26 09:11 .
drwxr-xr-x 1 root root 16 мар 6 09:30 ..
drwxr-xr-x 1 root root 240 мар 26 09:11 @.@base
drwxr-xr-x 1 root root 22 мар 26 09:11 @home.@base
drwxr-xr-x 1 root root 22 мар 6 09:39 @[email protected]
drwxr-xr-x 1 root root 22 мар 6 09:39 @[email protected]
...
drwxr-xr-x 1 root root 240 мар 6 09:39 @[email protected]
drwxr-xr-x 1 root root 240 мар 6 09:39 @[email protected]
...

#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 hat das mit Nistpuppen zu tun?

Höchstwahrscheinlich, da logische LVM-LV-Volumes physische LVM-PV-Volumes für andere VGs sein können. LVM kann rekursiv sein, wie Nistpuppen. Dies verleiht LVM extreme Flexibilität.

PS

Im nächsten Artikel werden wir versuchen, mehrere ähnliche mobile Speichersysteme/KVM als Grundlage für die Erstellung eines geoverteilten Speicher-/VM-Clusters mit Redundanz auf mehreren Kontinenten unter Verwendung von Heim-Desktops, dem Heim-Internet und P2P-Netzwerken zu verwenden.

Source: habr.com

Kommentar hinzufügen