Qual è il collegamento tra LVM e la matrioska?

Buona giornata.
Voglio condividere con la comunità un'esperienza pratica nella creazione di un sistema di archiviazione dati per KVM utilizzando md RAID + LVM.

Il programma includerà:

  • Assemblaggio di md RAID 1 con NVMe SSD.
  • Assemblaggio di md RAID 6 con SATA SSD e dischi standard.
  • Caratteristiche del funzionamento di TRIM/DISCARD su SSD RAID 1/6.
  • Creazione di un array md RAID 1/6 avviabile con un insieme comune di dischi.
  • Installazione del sistema su NVMe RAID 1 in assenza di supporto NVMe nel BIOS.
  • Utilizzo di LVM cache e LVM thin.
  • Utilizzo di snapshot BTRFS e send/receive per il backup.
  • Utilizzo di snapshot LVM thin e thin_delta per il backup in stile BTRFS.

Se interessa, vi prego di continuare.

Dichiarazione

L'autore non si assume alcuna responsabilità per le conseguenze dell'uso o del non uso dei materiali/esempi/codici/consigli/dati di questo articolo. Leggendo o utilizzando in qualche modo questo materiale, vi assumete la responsabilità per tutte le conseguenze di tali azioni. Le possibili conseguenze includono:

  • NVMe SSD fritti fino a diventare croccanti.
  • Risorsa di scrittura completamente esaurita e guasto dei dispositivi SSD.
  • Perdita totale di tutti i dati su tutti i dischi, comprese le copie di sicurezza.
  • Hardware informatico difettoso.
  • Tempo, nervi e denaro sprecati.
  • Qualsiasi altra conseguenza non elencata sopra.

Hardware

Disponibile:

Scheda madre di produzione attorno al 2013 basata sul chipset Z87, con Intel Core i7 / Haswell.

  • Processore 4 core, 8 thread
  • 32 Gigabyte di memoria RAM DDR3
  • 1 x 16 o 2 x 8 PCIe 3.0
  • 1 x 4 + 1 x 1 PCIe 2.0
  • 6 x 6 Gbps porte SATA 3

Adattatore SAS LSI SAS9211-8I ricaricato in modalità IT / HBA. Il firmware con supporto RAID è stato volutamente sostituito con il firmware HBA per:

  1. Consentire di rimuovere facilmente questo adattatore e sostituirlo con qualsiasi altro disponibile.
  2. TRIM/Discard funzionava correttamente sui dischi, poiché nel firmware RAID questi comandi non sono affatto supportati, e HBA in generale non si preoccupa di quali comandi trasmettere tramite il bus.

Dischi rigidi, — 8 unità HGST Travelstar 7K1000 da 1 TB in formato da 2.5, adatti per laptop. Questi dischi erano precedentemente in un array RAID 6. Nel nuovo sistema troveranno comunque un impiego. Per la conservazione di backup locali.

Inoltre è stato aggiunto:

6 unità di SSD SATA modello Samsung 860 QVO da 2TB. Questi SSD richiedevano una grande capacità, cache SLC, affidabilità e un prezzo contenuto. Era obbligatoria la supporto per discard/zero, che viene verificato dalla riga nel dmesg:

kernel: ata1.00: Abilitazione discard_zeroes_data

2 unità di SSD NVMe modello Samsung SSD 970 EVO da 500GB.

Per questi SSD la velocità di lettura/scrittura casuale e la durata sono essenziali. Un dissipatore è imprescindibile. Assolutamente imprescindibile. Altrimenti, li cuocerete a puntino alla prima sincronizzazione del RAID.

Adapter StarTech PEX8M2E2 per 2 x SSD NVMe da installare in uno slot PCIe 3.0 8x. Anche questo è un semplice HBA, ma per NVMe. Si differenzia dagli adattatori economici per l'assenza della necessità di supporto per la biforcazione PCIe da parte della scheda madre, grazie alla presenza di uno switch PCIe integrato. Funzionerà anche nel sistema più datato dove sia presente un PCIe, anche se si tratta di uno slot x1 PCIe 1.0. Ovviamente, con la velocità adeguata. Non ci sono RAID. Non è presente un BIOS integrato. Quindi, il vostro sistema non imparerà magicamente a avviarsi con NVMe e tanto meno a creare un RAID NVMe grazie a questo dispositivo.

Questo componente è stato scelto esclusivamente per la presenza di un solo slot libero 8x PCIe 3.0 nel sistema, e nel caso ci siano due slot disponibili, può facilmente essere sostituito con due economici PEX4M2E1 o simili, acquistabili ovunque a partire da 600 rubli.

La rinuncia a qualsiasi tipo di RAID hardware o integrato nel chipset/BIOS è stata fatta consapevolmente, al fine di avere la possibilità di sostituire completamente l'intero sistema, tranne gli SSD/HDD stessi, mantenendo tutti i dati. Idealmente, per consentire il salvataggio anche del sistema operativo installato durante il passaggio a nuovo/altro hardware. È importante che ci siano porte SATA e PCIe. È come un live CD o una chiavetta USB avviabile, solo molto più veloce e leggermente più ingombrante.

UmorismoE, sapete come va, a volte è necessario portare tutto l’array con sé. Non si vogliono perdere i dati. A tal fine, tutti i dispositivi menzionati sono comodamente sistemati su slitte negli alloggi standard da 5.25.

Beh, e ovviamente per esperimenti con diverse modalità di caching SSD in Linux.

I RAID hardware sono noiosi. Si accendono e o funzionano o non funzionano. Con mdadm ci sono sempre opzioni.

Software

In precedenza, su l'hardware era installato Debian 8 Jessie che è vicino al termine del supporto. È stato creato un RAID 6 con i dischi rigidi sopra menzionati insieme a LVM. Su di esso giravano macchine virtuali in kvm/libvirt.

Poiché l'autore ha esperienza nella creazione di chiavette USB SATA/NVMe avviabili e per non interrompere il consueto schema apt, come sistema di destinazione è stata scelta Ubuntu 18.04, che si è già stabilizzata abbastanza, ma ha ancora 3 anni di supporto in prospettiva.

Nel sistema menzionato sono presenti tutti i driver hardware necessari pronti all'uso. Non ci saranno necessità di software o driver di terze parti.

Preparazione all'installazione

Per installare il sistema abbiamo bisogno dell'immagine di Ubuntu Desktop. Il sistema server ha un installer un po' complesso, che mostra un'autonomia eccessiva, inserendo obbligatoriamente la partizione UEFI di sistema su uno dei dischi e rovinando tutta l'estetica. Di conseguenza, viene installato solo in modalità UEFI. Non offre alternative.

Questo non ci soddisfa.

Perché?Sfortunatamente, il caricamento UEFI è estremamente poco compatibile con il RAID avviabile software, poiché nessuno ci offre un'opzione di riserva per la partizione UEFI ESP. In rete ci sono ricette che suggeriscono di posizionare la partizione ESP su una chiavetta USB, ma questa è un punto di guasto. Ci sono ricette che utilizzano mdadm RAID 1 con metadata di versione 0.9 che non ostacolano il BIOS UEFI nel vedere questa partizione, ma questa situazione rimane valida fino al momento in cui il BIOS o un altro sistema operativo sull'hardware scrive qualcosa nell'ESP dimenticando di sincronizzarlo con i другими specchi.

Inoltre, il caricamento UEFI dipende dalla NVRAM, che non verrà trasferita insieme ai dischi su un nuovo sistema, poiché è parte della scheda madre.

Pertanto, non ci inventeremo una nuova bicicletta. Abbiamo già una bicicletta pronta, collaudata da anni, una bicicletta tradizionale ora chiamata avvio Legacy/BIOS, che porta con orgoglio il nome CSM sui sistemi compatibili con UEFI. La tireremo semplicemente fuori dallo scaffale, la oleeremo, gonfieremo le ruote e la puliremo con un panno umido.

La versione desktop di Ubuntu non riesce nemmeno a installarsi correttamente con il boot Legacy, ma qui, come si suol dire, almeno ci sono delle opzioni.

Dunque, raccogliamo l'hardware e carichiamo il sistema dalla chiavetta USB di Ubuntu Live. Dobbiamo scaricare i pacchetti, quindi impostiamo la rete che è stata attivata. Se non funziona, i pacchetti necessari possono essere caricati sulla chiavetta in anticipo.

Accediamo all'ambiente Desktop, avviamo un emulator di terminale, e via:

#sudo bash

Come…?La riga sopra è un classico trigger per dispute su sudo. Conunmaggiori possibilità arriva ancheununa maggiore responsabilità. La domanda è se sarai in grado di assumertela. Molti considerano che usare sudo in questo modo sia, almeno, poco prudente. Tuttavia:

Riproduci video

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

Perché non ZFS…?Quando installiamo software sul nostro computer, in sostanza, stiamo prestando il nostro hardware ai creatori di questo software.
Quando ci fidiamo di questo software per la sicurezza dei nostri dati, prendiamo un prestito pari al costo del recupero di questi dati, che prima o poi dovremo rimborsare.

Da questo punto di vista, ZFS è una Ferrari, mentre mdadm+lvm è più simile a una bicicletta.

Soggettivamente, l'autore preferisce prestare una bicicletta acquistata in prestito a persone sconosciute piuttosto che una Ferrari. Il costo non è alto. Non ci sono regole da seguire. È più semplice rispetto al codice stradale. I parcheggi sono gratuiti. La manovrabilità è migliore. A una bicicletta si possono sempre aggiungere delle gambe, e può anche essere riparata con le proprie mani.

Perché allora BTRFS…?Per poter caricare un sistema operativo, abbiamo bisogno di un file system supportato nativamente da Legacy/BIOS GRUB e che supporti i snapshot in tempo reale. Useremo questo per la partizione /boot. Inoltre, l'autore preferisce utilizzare questo FS per / (root), ricordando che per qualsiasi altro software possono essere creati partizioni separate su LVM e montati nelle directory necessarie.

Né le immagini di macchine virtuali, né i database verranno archiviati su questo FS.
Questo FS verrà utilizzato solo per creare snapshot istantanei del sistema senza spegnerlo, con successivo trasferimento di questi snapshot su un disco di backup utilizzando send/receive.

Inoltre, l'autore preferisce mantenere al minimo il software direttamente sull'hardware ed eseguire tutto il resto in macchine virtuali utilizzando strumenti come il passthrough GPU e i controllori PCI-USB in KVM tramite IOMMU.

Sull'hardware rimangono solo — memorizzazione dei dati, virtualizzazione e backup.

Se ti fidi di più di ZFS, in linea di massima sono intercambiabili per l'uso indicato.

Tuttavia, l'autore ignora deliberatamente le funzionalità integrate di mirroring / RAID e ridondanza presenti in ZFS, BTRFS e LVM.

Come argomento aggiuntivo, BTRFS ha la proprietà di trasformare le scritture casuali in sequenziali, il che influisce positivamente sulla velocità di sincronizzazione degli snapshot / backup su HDD.

Scansione di tutti i dispositivi:

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

Facciamo un giro d'ispezione:

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

Partizionamento dei «dischi»

NVMe SSD

Tuttavia, non procederemo con la partizione. Il nostro BIOS non riconosce questi dispositivi. Pertanto, andranno completamente in RAID software. Non creeremo nemmeno partizioni. Se vuoi seguire il «canone» o per motivi «principali» — crea una grande partizione, come per gli HDD.

SATA HDD

Non c'è molto da inventare qui. Creeremo una partizione unica per tutti. Creeremo questa partizione perché il BIOS vede questi dischi e può persino provare a avviarsi da essi. Inoltre, installeremo successivamente GRUB su questi dischi in modo che la systema possa avviarsi improvvisamente.

#cat >hdd.part << EOF
etichetta: dos
etichetta-id: 0x00000000
dispositivo: /dev/sdg
unità: settori

/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

SSD SATA

Qui abbiamo qualcosa di più interessante.

Innanzitutto, i nostri dischi hanno una capacità di 2 TB. Questo è all'interno del limite consentito per MBR, che utilizzeremo. Se necessario, possiamo passare a GPT. I dischi GPT hanno un livello di compatibilità che consente ai sistemi compatibili con MBR di vedere le prime 4 partizioni, se si trovano all'interno dei primi 2 terabyte. È fondamentale che la partizione di avvio e la partizione bios_grub si trovino all'inizio di questi dischi. Questo consente persino di avviare con Legacy/BIOS da dischi GPT.

Ma non è questo il nostro caso.

Qui creeremo due partizioni. La prima avrà una dimensione di 1 GB e sarà utilizzata per RAID 1 /boot.

La seconda sarà utilizzata per RAID 6 e occuperà tutto lo spazio libero rimanente, ad eccezione di una piccola area non allocata alla fine del disco.

Cos'è quest'area non allocata?Secondo fonti online, i nostri SSD SATA sono dotati di una cache SLC espandibile dinamicamente da 6 a 78 gigabyte. 6 gigabyte sono forniti "gratuitamente" grazie alla differenza tra "gigabyte" e "gibibyte" nel certificato del dispositivo. Gli altri 72 gigabyte vengono riservati grazie allo spazio non utilizzato.

Va notato che la nostra cache è SLC, mentre lo spazio viene utilizzato in modalità 4 bit MLC. Questo significa che per ogni 4 gigabyte di spazio libero, otteniamo solo 1 gigabyte di cache SLC.

Moltiplichiamo 72 gigabyte per 4 e otteniamo 288 gigabyte. Questo è lo spazio libero che non contrassegneremo, per consentire ai dispositivi di utilizzare completamente la cache SLC.

In questo modo, otterremo efficacemente fino a 312 gigabyte di cache SLC cumulativamente da sei dispositivi. Di tutti i dispositivi, 2 saranno utilizzati in RAID per ridondanza.

Questa quantità di cache ci permetterà di non trovarci quasi mai nella situazione in cui la scrittura non avviene nella cache. Questo compensa in modo eccellente il più triste difetto della memoria QLC: la bassissima velocità di scrittura quando i dati vengono scritti bypassando la cache. Se i tuoi carichi di lavoro non rispondono a queste esigenze, ti consiglio di riflettere seriamente su quanto a lungo i tuoi SSD resisteranno a tale carico, considerando il TBW della scheda tecnica.

#cat >ssd.part << EOF
etichetta: dos
etichetta-id: 0x00000000
dispositivo: /dev/sda
unità: settori

/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

Creazione di array

Per iniziare, dobbiamo rinominare la macchina. È necessario perché il nome host è parte del nome dell'array all'interno di mdadm e influisce su vari aspetti. Gli array possono essere rinominati in un secondo momento, ma avrebbe solo come risultato ulteriori passaggi.

#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

Perché —assume-clean…?Per non inizializzare gli array. Per entrambi i livelli RAID 1 e 6, ciò è consentito. Tutto può funzionare anche senza inizializzazione se si tratta di un nuovo array. Inoltre, l'inizializzazione di un array SSD durante la creazione è una spesa inutile delle risorse TBW. Utilizziamo TRIM/DISCARD dove possibile sugli array SSD assemblati per 'inizializzarli'.

Negli array SSD RAID 1, il DISCARD è supportato di default.

Negli array SSD RAID 6, il DISCARD deve essere abilitato nelle opzioni del modulo del kernel.

Questo è necessario farlo solo se tutti gli SSD utilizzati negli array di livelli 4/5/6 in questo sistema hanno supporto operativo per discard_zeroes_data. Talvolta si trovano supporti strani che comunicano al kernel il supporto per questa funzionalità, ma in realtà non ce l'hanno, oppure la funzione non è sempre attiva. Attualmente, il supporto è praticamente ovunque, tuttavia, gli SSD più vecchi e i firmware difettosi sono ancora presenti. Per questo motivo, il supporto per DISCARD è disattivato di default per RAID 6.

Attenzione, il comando seguente distruggerà tutti i dati sui dispositivi NVMe "inizializzando" l'array "a zero".

#blkdiscard /dev/md0

Se qualcosa va storto, prova a specificare un passo.

#blkdiscard --step 65536 /dev/md0

SSD SATA

#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

Perché così grande…?Aumentare la dimensione del chunk-size influisce positivamente sulla velocità di lettura casuale dei blocchi fino a chunk-size inclusi. Questo avviene perché un'operazione di dimensioni corrispondenti o inferiore può essere completata interamente su un singolo dispositivo. Pertanto, gli IOPS di tutti i dispositivi si sommano. Statistiche indicano che il 99% delle operazioni di input/output non supera i 512K.

I IOPS in scrittura per RAID 6 sempre minore o uguale a IOPS su un singolo disco. Tuttavia, per la lettura casuale, gli IOPS possono superare quelli di un singolo disco di diversi fattori, e qui la dimensione del blocco è cruciale.
L'autore non vede senso nel cercare di ottimizzare un parametro che è intrinsecamente scarso in RAID 6 e invece cerca di ottimizzare ciò in cui RAID 6 si comporta bene.
Compensiamo la scarsa scrittura casuale in RAID 6 con una cache su NVMe e trucchi di thin-provisioning.

Non abbiamo ancora abilitato DISCARD per RAID 6. Quindi non inizializziamo ancora questo array. Lo faremo più tardi, dopo l'installazione del sistema operativo.

SATA HDD

#mdadm --create --verbose --assume-clean /dev/md3 --chunk-size=512 --level=6 --raid-devices=8 /dev/sd[g-n]1

LVM su RAID NVMe

Per velocità, vogliamo posizionare il filesystem radice su RAID NVMe 1 che è /dev/md0.
Tuttavia, questo array veloce ci servirà anche per altre esigenze, come lo swap, i metadati e la cache LVM e i metadati LVM-thin, quindi su questo array creeremo un VG LVM.

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

Creeremo una partizione per il filesystem radice.

#lvcreate -L 128G --name root root

Creeremo una partizione per lo swap delle dimensioni della RAM.

#lvcreate -L 32G --name swap root

Installazione del sistema operativo

In sintesi, abbiamo tutto il necessario per installare il sistema.

Avviamo il master installer del sistema dall'ambiente Ubuntu Live. Installazione standard. Solo durante la fase di selezione dei dischi per l'installazione è necessario specificare quanto segue:

  • /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), — использовать как раздел подкачки
  • Installare il bootloader su /dev/sda

Scegliendo BTRFS come filesystem radice, l'installatore creerà automaticamente due volumi BTRFS denominati "@" per / (radice) e "@home" per /home.

Avviamo l'installazione...

L'installazione si concluderà con una finestra di dialogo modale che segnala un errore nell'installazione del bootloader. Sfortunatamente, non sarà possibile uscire da questo dialogo tramite i mezzi standard e continuare l'installazione. Effettuare il logout dal sistema e fare nuovamente il login, accedendo al desktop pulito di Ubuntu Live. Aprire il terminale e di nuovo:

#sudo bash

Creiamo un ambiente chroot per continuare l'installazione:

#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

Configuriamo rete e hostname nel chroot:

#cat /etc/hostname >/mnt/chroot/etc/hostname
#cat /etc/hosts >/mnt/chroot/etc/hosts
#cat /etc/resolv.conf >/mnt/chroot/etc/resolv.conf

Accediamo all'ambiente chroot:

#chroot /mnt/chroot

Per prima cosa, installeremo i pacchetti:

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

Controlliamo e ripararemo tutti i pacchetti che sono stati installati in modo errato a causa di un'installazione incompleta del sistema:

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

Se qualcosa non è andato a buon fine, potrebbe essere necessario modificare prima /etc/apt/sources.list

Correggiamo i parametri per il modulo RAID 6 per abilitare TRIM/DISCARD:

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

Accingiamo un po' le nostre array:

#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

Che cos'era..?Abbiamo creato un insieme di regole udev che faranno quanto segue:

  • Impostare una dimensione della cache dei blocchi adeguata per il 2020 per RAID 6. Il valore predefinito sembra non essere stato modificato dai tempi della creazione di Linux e non è più adeguato da tempo.
  • Riservare un minimo di IO durante i controlli/sincronizzazioni degli array. Questo è necessario affinché i vostri array non rimangano bloccati in uno stato di sincronizzazione eterna sotto carico.
  • Limitare un massimo di IO durante i controlli/sincronizzazioni degli array. Questo è fondamentale affinché la sincronizzazione/verifica degli SSD RAID non surriscaldi i vostri dispositivi fino a bruciarli. Particolarmente importante per NVMe. (Ricordate il radiatore? Non stavo scherzando.)
  • Impedire ai dischi di fermare la rotazione del disco tramite APM (HDD) e impostare un timeout per la sospensione dei controller dei dischi a 7 ore. È possibile disabilitare completamente l'APM se i dischi lo supportano (-B 255). Con il valore predefinito, i dischi si fermeranno dopo cinque secondi. Successivamente, il sistema operativo proverà a ripristinare la cache del disco, i dischi si riavvieranno e il ciclo ricomincerà. I dischi hanno un numero massimo limitato di avvii del disco. Questo semplice ciclo predefinito può facilmente danneggiare i dischi in pochi anni. Non tutti i dischi ne risentono, ma i nostri 'dischi da laptop', con le impostazioni predefinite appropriate, lo fanno rendendo un RAID una sorta di mini-MAID.
  • Impostare il readahead sui dischi (rotanti) a 1 megabyte - due blocchi sequenziali/chunk RAID 6
  • Impedire il readahead sugli array stessi.

Modifichiamo /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

Perché così..?Cercheremo la partizione /boot tramite UUID poiché la denominazione degli array teoricamente può cambiare.

Cercheremo le altre partizioni tramite i nomi LVM nella notazione /dev/mapper/vg-lv, poiché identificano le partizioni in modo abbastanza unico.

Non utilizziamo UUID per LVM poiché gli UUID dei volumi LVM e dei loro snapshot possono coincidere.Montiamo due volte /dev/mapper/root-root..?Sì. Esattamente. Questa è la caratteristica di BTRFS. Questo file system può essere montato più volte con diversi subvol.

A causa di questa stessa caratteristica, consiglio di non creare mai snapshot LVM di volumi BTRFS attivi. Potresti avere una sorpresa al riavvio.

Rigeneriamo la configurazione di mdadm:

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

Correggiamo le impostazioni di LVM:

#cat >>/etc/lvm/lvmlocal.conf << EOF

attivazione {
thin_pool_autoextend_threshold=90
thin_pool_autoextend_percent=5
}
allocazione {
cache_pool_max_chunks=2097152
}
dispositivi {
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

Che cos'era..?Abbiamo abilitato l'espansione automatica dei pool LVM thin al raggiungimento del 90% di spazio occupato, aumentandolo del 5% del volume.

Abbiamo aumentato il numero massimo di blocchi cache per LVM cache.

Abbiamo vietato a LVM di cercare volumi LVM (PV) su:

  • dispositivi contenenti LVM cache (cdata)
  • dispositivi memorizzati nella cache tramite LVM cache bypassando la cache (<lv_name>_corig). Tuttavia, il dispositivo memorizzato nella cache sarà comunque scansionato attraverso la cache (semplicemente <lv_name>).
  • dispositivi contenenti metadati di LVM cache (cmeta)
  • tutti i dispositivi nel VG con il nome images. Qui avremo le immagini dei dischi delle macchine virtuali, e non vogliamo che LVM sull'host attivi i volumi appartenenti al sistema operativo guest.
  • su tutti i dispositivi nel VG con il nome backup. Qui avremo copie di sicurezza delle immagini delle macchine virtuali.
  • su tutti i dispositivi il cui nome termina con «gpv» (guest physical volume)

Abbiamo abilitato il supporto DISCARD durante il rilascio dello spazio libero su LVM VG. Fate attenzione. Questo renderà la rimozione di LV su SSD piuttosto lunga. Questo è particolarmente vero per SSD RAID 6. Tuttavia, secondo il piano, utilizzeremo il thin provisioning, quindi ciò non ci darà fastidio.

Aggiorniamo l'immagine initramfs:

#update-initramfs -u -k all

Installeremo e configureremo grub:

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

Quali dischi dovremmo scegliere?Tutti quelli sd*. Il sistema deve essere in grado di avviarsi da qualsiasi disco SATA o SSD funzionante.

Perché abbiamo bloccato os-prober..?Per eccessiva indipendenza e manine birichine.

Non funziona correttamente se uno dei RAID è in stato degradato. Cerca di trovare il sistema operativo su partizioni che sono utilizzate in macchine virtuali che funzionano su quell'hardware.

Se ti serve, puoi lasciarlo, ma tieni presente tutto quanto sopra. Ti consiglio di cercare ricette per liberarti delle manine birichine online.

Abbiamo completato l'installazione iniziale. È tempo di riavviarsi nel nuovo sistema operativo appena installato. Non dimenticare di rimuovere il Live CD/USB di avvio.

#exit
#reboot

Per il dispositivo di avvio, scegliamo uno qualsiasi degli SSD SATA.

LVM su SSD SATA

A questo punto, siamo già entrati nel nuovo sistema operativo, abbiamo configurato la rete, apt, aperto un emulator di terminale e avviato:

#sudo bash

Continuiamo.

«Inizializziamo» l'array degli SSD SATA:

#blkdiscard /dev/md2

Se non funziona, proviamo:

#blkdiscard --step 65536 /dev/md2
Creiamo un VG LVM sugli SSD SATA:

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

Perché un altro VG..?In effetti, abbiamo già un VG chiamato root. Perché non aggiungere tutto a un unico VG?

Se ci sono più PV in VG, tutti i PV devono essere presenti (online) per una corretta attivazione del VG. Un'eccezione è l'LVM RAID, che non utilizziamo intenzionalmente.

Vogliamo davvero che in caso di guasto (ossia perdita di dati) su uno qualsiasi degli array RAID 6, il sistema operativo si avvii normalmente e ci permetta di risolvere il problema.

A questo fine, a un primo livello di astrazione isoleremo ogni tipo di «supporto» fisico in un VG separato.

In termini scientifici, i diversi array RAID appartengono a diversi «domini di affidabilità». Non è consigliabile creare per loro un ulteriore punto di guasto comune, unendoli in un unico VG.

La disponibilità di LVM a livello 'hardware' ci permetterà di suddividere liberamente pezzi di diversi array RAID combinandoli in vari modi. Ad esempio, avviare simultaneamente bcache + LVM thin, bcache + BTRFS, LVM cache + LVM thin, una configurazione complessa di ZFS con cache o qualsiasi altra miscela infernale, per toccare e confrontare tutto ciò.

A livello 'hardware' non utilizzeremo altro che i vecchi e buoni volumi LVM 'spessi'. L'eccezione a questa regola sarà forse la partizione per il backup.

Penso che a questo punto molti lettori abbiano già iniziato a sospettare qualcosa riguardo alla matrushka.

LVM su SATA HDD

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

Di nuovo una nuova VG..?Vogliamo davvero che, in caso di guasto dell'array di dischi che utilizzeremo per il backup dei dati, il nostro sistema operativo continui a funzionare correttamente, mantenendo comunque l'accesso ai dati non riservati. Pertanto, per evitare problemi di attivazione della VG, creiamo una VG separata.

Configurazione di LVM cache

Creeremo un LV su NVMe RAID 1 da utilizzare come dispositivo di caching.

#lvcreate -L 70871154688B --name cache root

Perché così poco...?Il fatto è che anche i nostri SSD NVMe dispongono di cache SLC. 4 gigabyte di "gratuito" e 18 gigabyte dinamici grazie allo spazio libero occupato nel 3-bit MLC. Una volta esaurita questa cache, gli SSD NVMe non saranno molto più veloci dei nostri SSD SATA con cache. Proprio per questo motivo non ha senso creare una partizione di LVM cache significativamente più grande del doppio della capacità della cache SLC dell'unità NVMe. Per le unità NVMe utilizzate, l'autore considera ragionevole creare 32-64 gigabyte di cache.

La dimensione della partizione è necessaria per organizzare 64 gigabyte di cache, collocare i metadati della cache e creare un backup dei metadati.

In aggiunta, segnalo che dopo uno spegnimento non pulito del sistema, LVM contrassegnerà tutta la cache come sporca e la sincronizzerà nuovamente. Inoltre, questo si ripeterà ad ogni utilizzo di lvchange su questo dispositivo fino al riavvio del sistema. Pertanto, raccomando di ricreare immediatamente la cache con il relativo script.

Creeremo un LV su SATA RAID 6 per utilizzarlo come dispositivo cache.

#lvcreate -L 3298543271936B --name cache data

Perché solo tre terabyte..?In modo da poter utilizzare, se necessario, un RAID 6 SATA SSD per altre esigenze. La dimensione dello spazio cache può essere aumentata dinamicamente, al volo, senza interrompere il funzionamento del sistema. Per fare questo è necessario fermare temporaneamente e riattivare la cache, ma un vantaggio distintivo di LVM-cache rispetto, ad esempio, a bcache, è che questo può essere fatto al volo.

Creeremo un nuovo VG per la cache.

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

Creeremo un LV sul dispositivo da cache.

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

Qui abbiamo immediatamente occupato tutto lo spazio libero su /dev/data/cache in modo che tutte le altre partizioni necessarie vengano create subito su /dev/root/cache. Se qualcosa è stato creato in un altro posto, può essere spostato usando pvmove.

Creiamo e attiviamo la 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

Perché questa chunksize..?Attraverso esperimenti pratici, l'autore è riuscito a scoprire che il miglior risultato si ottiene se la dimensione del blocco della cache LVM coincide con la dimensione del blocco LVM thin. Tuttavia, più piccola è la dimensione, migliore è la configurazione in caso di scritture casuali.

64k è la dimensione minima del blocco consentita per LVM thin.

Attenzione writeback..!Sì. Questo tipo di cache ritarda la sincronizzazione dei dati sul dispositivo di caching. Ciò comporta che, in caso di perdita della cache, i dati sul dispositivo di caching potrebbero andare persi. Più avanti, l'autore spiega quali misure, oltre a NVMe RAID 1, è possibile adottare per compensare questo rischio.

Questo tipo di cache è stato scelto intenzionalmente per compensare le prestazioni inferiori del RAID 6 in caso di scrittura casuale.

Controlliamo cosa abbiamo ottenuto:

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

Su /dev/data/cache dovrebbe trovarsi solo [cachedata_corig]. Se qualcosa non va, utilizzare pvmove.

È possibile disattivare la cache se necessario con un solo comando:

#lvconvert -y --uncache cache/cachedata

Questo avviene in online. LVM sincronizza semplicemente la cache su disco, la rimuove e rinomina cachedata_corig di nuovo in cachedata.

Configurazione LVM thin

Stimiamo approssimativamente quanto spazio ci servirà per i metadati LVM thin:

#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - 3385794560 byte è la dimensione stimata dell'area dei metadati per "--block-size=64kibibytes --pool-size=6terabytes --max-thins=100000"

Arrotondiamo a 4 gigabyte: 4294967296B

Moltiplichiamo per due e aggiungiamo 4194304B per i metadati LVM PV: 8594128896B
Creiamo una partizione separata su NVMe RAID 1 per allocare i metadati LVM thin e il loro backup:

#lvcreate -L 8594128896B --name images root

Perché..?Qui potrebbe sorgere la domanda sul perché allocare i metadati LVM thin separatamente, se tanto verranno comunque memorizzati nella cache su NVMe e funzioneranno rapidamente.

La velocità è importante, ma non è la ragione principale. Il punto è che la cache è un punto di guasto. Qualcosa potrebbe succedere con essa e, se i metadati di LVM thin vengono memorizzati nella cache, questo porterà alla completa perdita di tutto. Senza metadati integri, ricostruire i volumi thin sarà praticamente impossibile.

Spostando i metadati su un volume separato, non in cache ma veloce, garantiamo la sicurezza dei metadati in caso di perdita o danneggiamento della cache. In questo caso, tutti i danni causati dalla perdita della cache saranno localizzati all'interno dei volumi thin, semplificando notevolmente la procedura di recupero. Con grande probabilità, questi danni saranno recuperabili attraverso i log del file system.

Inoltre, se è stata precedentemente eseguita un'istantanea del volume thin e, dopo di essa, la cache è stata completamente sincronizzata almeno una volta, allora, data la specificità dell'architettura interna di LVM thin, l'integrità dell'istantanea sarà garantita in caso di perdita della cache.

Creiamo un nuovo VG che sarà responsabile del thin-provisioning:

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

Creiamo un pool:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
Perché -Z yOltre a ciò per cui è progettata questa modalità, ovvero impedire che i dati di una macchina virtuale fuoriescano in un'altra durante la riallocazione dello spazio, il zeroing viene utilizzato anche per aumentare la velocità delle scritture casuali con blocchi di dimensioni inferiori a 64k. Qualsiasi scrittura inferiore a 64k in un'area non precedentemente allocata di un volume sottile verrà convertita in blocchi da 64K allineati ai confini della cache. Questo permetterà di completare l'operazione interamente attraverso la cache, bypassando il dispositivo memorizzabile.

Spostiamo il LV sui PV corrispondenti:

#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

Controlliamo:

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

Creiamo un volume sottile per i test:

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

Installeremo i pacchetti per i test e il monitoraggio:

#apt-get install sysstat fio

Ecco come si può monitorare il comportamento della nostra configurazione dello storage in tempo reale:

#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)'

Ecco come possiamo testare la nostra configurazione:

#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

Attenzione! Risorsa!Questo codice eseguirà 36 test diversi, ognuno dei quali verrà eseguito per 4 secondi. La metà dei test riguardano le scritture. In 4 secondi su NVMe si possono scrivere molte informazioni. Fino a 3 gigabyte al secondo. Quindi, ogni esecuzione dei test di scrittura potrebbe consumare fino a 216 gigabyte di risorse SSD.

Lettura e scrittura alternate?Sì. È consigliabile eseguire i test di lettura e scrittura separatamente. Inoltre, è utile assicurarsi che tutte le cache siano sincronizzate, in modo che una scrittura precedente non influisca sulla lettura.

I risultati varieranno notevolmente al primo avvio e nei successivi, man mano che la cache si riempie e il volume sottile viene configurato. Inoltre, ciò dipende dal fatto che il sistema abbia sincronizzato le cache riempite durante l'ultimo avvio.

Tra l'altro, consiglio di misurare la velocità su un volume sottile già riempito, da cui è stato appena creato uno snapshot. L'autore ha notato come una scrittura casuale acceleri improvvisamente subito dopo la creazione del primo snapshot, specialmente quando la cache non è ancora completamente riempita. Questo avviene grazie alla semantica copy-on-write della scrittura, all'allineamento dei blocchi della cache e del volume sottile, e al fatto che una scrittura casuale su RAID 6 si trasforma in una lettura casuale con RAID 6 seguita da una scrittura nella cache. Nella nostra configurazione, la lettura casuale su RAID 6 è fino a 6 volte (il numero di SSD SATA nell'array) più veloce della scrittura. Dato che i blocchi per CoW vengono allocati sequenzialmente dal pool sottile, la scrittura si trasforma per lo più anche in sequenziale.

Entrambe queste caratteristiche possono essere sfruttate vantaggiosamente.

Snapshot "coerenti" nella cache

Per ridurre il rischio di perdita di dati in caso di danneggiamento/perdita della cache, l'autore propone di introdurre una pratica di rotazione degli snapshot che garantisca la loro integrità in questo caso.

Innanzitutto, grazie al fatto che i metadati dei volumi snelli si trovano su un dispositivo non in cache, i metadati rimarranno integri e le eventuali perdite saranno isolate all'interno dei blocchi di dati.

Il successivo ciclo di rotazione degli snapshot offre garanzia di integrità dei dati all'interno degli snapshot in caso di perdita della cache:

  1. Per ogni volume sottile con nome , creiamo uno snapshot con il nome .cached
  2. Imposteremo la soglia di migrazione su un valore ragionevolmente alto: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. Nel ciclo verifichiamo il numero di blocchi sporchi nella cache: #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' fino a quando non otteniamo zero. Se lo zero tarda troppo, può essere temporaneamente creato passando la cache in modalità writethrough. Tuttavia, considerando le prestazioni dei nostri array SATA e NVMe SSD, nonché il loro resource TBW, sarà possibile cogliere abbastanza rapidamente il momento senza modificare la modalità della cache, oppure l'hardware consumerà completamente la propria risorsa in pochi giorni. A causa delle limitazioni delle risorse, il sistema non è in grado di rimanere costantemente al 100% di carico di scrittura. I nostri NVMe SSD esauriranno completamente la loro risorsa con un carico di scrittura del 100% in 3-4 giorni. Gli SSD SATA dureranno solo il doppio. Perciò, considereremo che gran parte del carico sia in lettura, mentre la scrittura presenta picchi di attività estremamente elevati in combinazione con un carico medio basso.
  4. Non appena abbiamo raggiunto (o creato) lo zero — rinominiamo <nome>.cached in <nome>.committed. Il vecchio <nome>.committed verrà quindi eliminato.
  5. Facoltativamente, se la cache è piena al 100%, può essere ricreata tramite uno script, in questo modo liberandola. Con una cache semivuota, il sistema funziona molto più rapidamente in scrittura.
  6. Imposteremo la soglia di migrazione a zero: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata Questo impedirà temporaneamente la sincronizzazione della cache sul dispositivo principale.
  7. Stiamo aspettando che si accumulino abbastanza modifiche nella cache. #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' o che scatti il timer.
  8. Ripetiamo da capo.

A cosa servono le complicazioni con la soglia di migrazione…?Il fatto è che, nella pratica reale, la scrittura 'casuale' non è propriamente casuale. Se abbiamo scritto qualcosa in un settore di 4 kilobyte, è probabile che nei prossimi minuti venga effettuata una scrittura in questo o in uno dei settori vicini (+- 32K).

Impostando la soglia di migrazione a zero, rimandiamo la sincronizzazione della scrittura su SATA SSD e aggregiamo diverse modifiche di un blocco di 64K nella cache. In questo modo si risparmia notevolmente le risorse del SATA SSD.

E il codice dov'è..?Sfortunatamente, l'autore si considera non sufficientemente competente nello sviluppo di script bash, essendo un autodidatta al 100% e praticando quello che chiama 'sviluppo guidato da Google', quindi ritiene che quel codice spaventoso che esce dalle sue mani sia meglio non utilizzarlo da parte di altri.

Credo che i professionisti del settore possano rappresentare autonomamente tutta la logica descritta sopra, se necessario, e forse persino realizzarla in modo elegante sotto forma di servizio systemd, come ha provato a fare l'autore.

Uno schema semplice per la rotazione degli snapshot ci permetterà non solo di avere sempre uno snapshot completamente sincronizzato su un SSD SATA, ma anche, utilizzando l'utilità thin_delta, di scoprire quali blocchi sono stati modificati dopo la sua creazione, localizzando così i danni sui volumi principali e semplificando notevolmente il ripristino.

TRIM/DISCARD in libvirt/KVM

Poiché lo storage verrà utilizzato per KVM gestito da libvirt, sarebbe utile insegnare alle nostre VM non solo ad occupare spazio libero, ma anche a liberare quello non più necessario.

Questo avviene emulando il supporto per TRIM/DISCARD sui dischi virtuali. Per farlo, è necessario cambiare il tipo di controller in virtio-scsi e modificare l'xml.

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

I DISCARDS simili dai sistemi operativi guest vengono gestiti correttamente da LVM, e i blocchi vengono liberati correttamente sia nella cache che nel pool thin. Nel nostro caso, ciò avviene principalmente in modo ritardato, durante l'eliminazione dell'ultimo snapshot.

Backup BTRFS

Utilizzare script già pronti con la massima attenzione e a vostro rischio e pericolo. L'autore ha scritto questo codice da solo e esclusivamente per sé stesso. Sono sicuro che molti utenti esperti di Linux abbiano soluzioni simili, quindi non sarà necessario copiare quelle degli altri.

Creiamo un volume sul dispositivo di backup:

#lvcreate -L 256G --name backup backup

Formattiamo in BTRFS:

#mkfs.btrfs /dev/backup/backup

Creiamo mount point e montiamo le sezioni principali del 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

Creiamo directory per i backup:

#mkdir /backup/btrfs/back/remote
#mkdir /backup/btrfs/back/remote/root
#mkdir /backup/btrfs/back/remote/boot

Creiamo una directory per gli script di backup:

#mkdir /root/btrfs-backup

Copiare lo script:

Molto codice bash spaventoso. Usare a proprio rischio e pericolo. Non scrivere lettere arrabbiate all'autore...#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 "In attesa del blocco..."
wait_lock || terminate "Impossibile ottenere il blocco. Uscita..."
echo "Blocco ottenuto..."
}

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 trovato"
else
echo "$SOURCE_BASE_PATH File non trovato. Creando snapshot di $SOURCE_PATH a $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
if [ -d "$TARGET_BASE_PATH" ]
then
echo "$TARGET_BASE_PATH trovato, non sincronizzato con la sorgente... rimozione..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
if [ -d "$TARGET_BASE_PATH" ]
then
echo "$TARGET_BASE_PATH trovato"
else
echo "$TARGET_BASE_PATH non trovato. Sincronizzazione a $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 trovato, rimozione..."
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 trovato, rimozione..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Invio di $SOURCE_PEND_PATH a $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)"
trova "$LIST_TARGET_BASE_DIR" -maxdepth 1 -mindepth 1 -type d -printf "%fn" | grep "${LIST_TARGET_BASE_NAME/$BASE_SUFFIX/$SNAP_SUFFIX}.$DATE_REGEX"
}

funzione rimuovi()
{
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
}

funzione rimuovi_tutto()
{
DATE_OFFSET="$2"
FILTER="$(filter "$DATE_OFFSET")"
mentre leggi -r SNAPSHOT ; fai
rimuovi "$1" "$SNAPSHOT"
fatto < <(elenca "$1" | grep "$FILTER")

}

(
COMANDO="$1"
shift

caso "$COMANDO" in
"--help")
echo "Aiuto"
;;
"suffisso")
suffisso
;;
"filtro")
filtra "$1"
;;
"backup")
aspetta_lock_o_terminale
backup "$1"
;;
"elenca")
elenca "$1"
;;
"rimuovi")
aspetta_lock_o_terminale
rimuovi "$1" "$2"
;;
"rimuovi_tutto")
aspetta_lock_o_terminale
rimuovi_tutto "$1" "$2"
;;
*)
echo "Nessuno.."
;;
esac
) 98>$LOCK_FILE

EOF

Cosa fa esattamente..?Contiene un insieme di comandi semplici per creare snapshot BTRFS e copiarli su un'altra FS tramite BTRFS send/receive.

Il primo avvio può richiedere un po' di tempo, poiché all'inizio verranno copiati tutti i dati. I successivi avvii saranno molto rapidi, poiché verranno copiati solo i cambiamenti.

Un altro script che metteremo in cron:

Ancora un po' di codice bash#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 giorni"
$BACKUP_SCRIPT backup root/@
$BACKUP_SCRIPT rimuovi_tutto root/@ "$RETENTION"
$BACKUP_SCRIPT backup root/@home
$BACKUP_SCRIPT rimuovi_tutto root/@home "$RETENTION"
$BACKUP_SCRIPT backup boot/
$BACKUP_SCRIPT rimuovi_tutto boot/ "$RETENTION"
EOF

Cosa fa..?Crea e sincronizza su backup il filesystem in snapshot incrementali dei volumi BTRFS elencati. Successivamente, elimina tutti gli snapshot creati 60 giorni fa. Dopo l'esecuzione, nella sottocartella /backup/btrfs/back/remote/ appariranno snapshot datati dei volumi elencati.

Diamo al codice i diritti di esecuzione:

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

Controlliamo e aggiungiamo al cron:

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

Backup di LVM thin

Creiamo un pool thin sul dispositivo di backup:

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

Installa ddrescue, poiché gli script utilizzeranno questo strumento:

#apt-get install gddrescue

Creiamo una cartella per gli script:

#mkdir /root/lvm-thin-backup

Copia gli script:

Tanto bash dentro…#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 "In attesa del blocco..."
wait_lock || terminate "Impossibile ottenere il blocco. Uscita..."
echo "Blocco ottenuto..."
}

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 "Il LV sorgente non è thin.")
exit 1
fi

if [ "$DIFF_TARGET_POOL" == "" ]
then
(>&2 echo "Il LV target non è thin.")
exit 1
fi

if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ]
then
(>&2 echo "I LV sorgente e target appartengono a pool thin diversi.")
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 "Lo snapshot dei metadati del thin pool esiste già. Assumendo il valore obsoleto. Rilascio dello snapshot dei metadati in 5 secondi.")
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 "Creazione dello snapshot dei metadati del thin pool non riuscita.")
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 non è sincronizzato con la sorgente... rimozione in corso..."
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 trovato"
else
echo "$TARGET_VG/$TARGET_LV non trovato. Creando volume vuoto."
lvcreate --thin-pool "$BACKUPS_POOL" -V "$SOURCE_BASE_SIZE"B --name "$TARGET_BASE_LV" "$TARGET_VG" || exit 1
echo "Devo riavviare. Scartando la sorgente a $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 "Scartando il target a $TARGET_BASE_LV_PATH"
discard_volume "$TARGET_VG" "$TARGET_BASE_LV"
sync
fi
if [ "$SOURCE_PEND_LV_PATH" != "" ]
then
echo "$SOURCE_PEND_LV_PATH trovato, rimozione in corso..."
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 trovato, rimozione in corso..."
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 "Sincronizzazione di $SOURCE_PEND_LV_PATH a $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 non trovato"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH trovato"
else
echo "$TARGET_BASE_LV_PATH non trovato"
exit 1
fi
attiva_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo Confrontando "$SOURCE_BASE_LV_PATH" con "$TARGET_BASE_LV_PATH"
cmp "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
echo Fatto...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

funzione risincronizza()
{
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 non trovato"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH trovato"
else
echo "$TARGET_BASE_LV_PATH non trovato"
exit 1
fi
attiva_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 Sincronizzando "$SOURCE_BASE_LV_PATH" a "$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 "Sincronizzando $SYNC_LENGTH_BYTES byte a $SYNC_OFFSET_BYTES da $SOURCE_BASE_LV_PATH a $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
fatto
echo Fatto...
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"
}

funzione rimuovi()
{
REMOVE_TARGET_VG="$BACKUPS"
REMOVE_TARGET_LV="$1"
lvremove -y "$REMOVE_TARGET_VG/$REMOVE_TARGET_LV"
sync
}

funzione rimuovi_tutto()
{
DATE_OFFSET="$3"
FILTER="$(filter "$DATE_OFFSET")"
mentre leggi -r SNAPSHOT ; fai
rimuovi "$SNAPSHOT"
fatto < <(list "$1" "$2" | grep "$FILTER")

}

(
COMANDO="$1"
shift

caso "$COMANDO" in
"--help")
echo "Aiuto"
;;
"suffisso")
suffisso
;;
"filtro")
filtra "$1"
;;
"backup")
aspetta_lock_o_terminale
backup "$1" "$2"
;;
"elenca")
list "$1" "$2"
;;
"thindiff")
thindiff "$1" "$2" "$3"
;;
"thinsync")
thinsync "$1" "$2" "$3" "$4"
;;
"verifica")
aspetta_lock_o_terminale
verifica "$1" "$2"
;;
"risincronizza")
aspetta_lock_o_terminale
risincronizza "$1" "$2"
;;
"rimuovi")
aspetta_lock_o_terminale
rimuovi "$1"
;;
"rimuovi_tutto")
aspetta_lock_o_terminale
rimuovi_tutto "$1" "$2" "$3"
;;
*)
echo "Nessuno.."
;;
esac
) 98>$LOCK_FILE

EOF

Cosa fa…?Contiene un insieme di comandi per manipolare gli snapshot sottili e sincronizzare le differenze tra due snapshot sottili, ottenute tramite thin_delta, su un altro dispositivo a blocchi utilizzando ddrescue e blkdiscard.

Un altro script che metteremo in cron:

Un po' di bash in più#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 giorni"

$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

Cosa fa…?Utilizza lo script precedente per creare e sincronizzare i backup dei volumi sottili elencati. Lo script lascerà snapshot inattivi dei volumi elencati, necessari per tracciare le modifiche dall'ultima sincronizzazione.

Questo script deve essere modificato, specificando l'elenco dei volumi sottili per cui è necessario eseguire i backup. I nomi forniti sono solo a titolo di esempio. Se lo si desidera, è possibile scrivere uno script che sincronizzi tutti i volumi.

Diamo i permessi:

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

Controlliamo e aggiungiamo al cron:

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

Il primo avvio sarà lungo, poiché i volumi sottili saranno completamente sincronizzati copiando tutto lo spazio utilizzato. Grazie ai metadati LVM thin sappiamo quali blocchi sono realmente utilizzati, quindi verranno copiati solo i blocchi effettivamente utilizzati dei volumi sottili.

Le avvi successive copieranno i dati in modo incrementale grazie al tracciamento delle modifiche tramite i metadati LVM thin.

Vediamo cosa è uscito:

#time /root/btrfs-backup/cron-daily.sh
reale 0m2,967s
utente 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 mar 26 09:11 .
drwxr-xr-x 1 root root 16 mar 6 09:30 ..
drwxr-xr-x 1 root root 322 mar 26 02:00 .@base
drwxr-xr-x 1 root root 516 mar 6 09:39 .@snap.2020-03-06-09-39-37
drwxr-xr-x 1 root root 516 mar 6 09:39 .@snap.2020-03-06-09-39-57
...
/backup/btrfs/back/remote/root:
total 0
drwxr-xr-x 1 root root 2820 mar 26 09:11 .
drwxr-xr-x 1 root root 16 mar 6 09:30 ..
drwxr-xr-x 1 root root 240 mar 26 09:11 @.@base
drwxr-xr-x 1 root root 22 mar 26 09:11 @home.@base
drwxr-xr-x 1 root root 22 mar 6 09:39 @home.@snap.2020-03-06-09-39-35
drwxr-xr-x 1 root root 22 mar 6 09:39 @home.@snap.2020-03-06-09-39-57
...
drwxr-xr-x 1 root root 240 mar 6 09:39 @.@snap.2020-03-06-09-39-26
drwxr-xr-x 1 root root 240 mar 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

Cosa c'entrano le matrioske?

Probabilmente perché i volumi logici LVM LV possono essere volumi fisici LVM PV per altri VG. LVM può essere ricorsivo, come le matrioske. Questo conferisce a LVM un'incredibile flessibilità.

P.S.

Nell'articolo seguente, proveremo a utilizzare alcuni sistemi di storage KVM simili come base per creare un cluster di storage/vm geo-distribuito con backup su diversi continenti tramite desktop domestici, internet domestico e reti P2P.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster