Cosa hanno in comune LVM e una matryoshka?

Buon pomeriggio.
Vorrei condividere con la comunità un'esperienza pratica nella costruzione di un sistema di archiviazione dati per KVM utilizzando md RAID + LVM.

Il programma includerà:

  • Assemblaggio di md RAID 1 da SSD NVMe.
  • Assemblaggio di md RAID 6 da SSD SATA e dischi tradizionali.
  • Caratteristiche del funzionamento di TRIM/DISCARD su SSD RAID 1/6.
  • Creazione di un array di md RAID 1/6 avviabile su un set 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 backup in stile BTRFS.

Se sei interessato, ti prego di cliccare qui.

Dichiarazione

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

  • SSD NVMe fritti fino a diventare croccanti.
  • Completamente esaurito il ciclo di scrittura e guasto degli SSD.
  • Perdita totale di tutti i dati su tutti gli archivi, incluse le copie di sicurezza.
  • Hardware informatico difettoso.
  • Tempo, nervi e denaro sprecati.
  • Qualsiasi altra conseguenza non elencata sopra.

Hardware

Erano a disposizione:

Una scheda madre di circa 2013 con chipset Z87, abbinata a un Intel Core i7 / Haswell.

  • Processore 4 core, 8 thread
  • 32 Gigabyte di 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 ricompilato in modalità IT / HBA. Il firmware con supporto RAID è stato intenzionalmente sostituito con il firmware HBA per:

  1. Poterlo rimuovere in qualsiasi momento e sostituirlo con qualsiasi altro adattatore a caso.
  2. TRIM/Discard funzionava correttamente sui dischi, poiché in RAID il firmware non supporta affatto questi comandi, mentre HBA non si preoccupa dei comandi da inviare.

Dischi rigidi, 8 unità HGST Travelstar 7K1000 da 1 TB in formato 2,5, come quelli per laptop. Questi dischi erano precedentemente in un array RAID 6. Anche nel nuovo sistema troveranno un utilizzo. Per lo stoccaggio di backup locali.

È stato aggiunto:

6 pezzi di SSD SATA modello Samsung 860 QVO 2TB. Questi SSD richiedevano una grande capacità, presenza di cache SLC, desiderabile affidabilità e un prezzo contenuto. Era obbligatoria la supporto per discard/zero verificabile tramite la seguente riga in dmesg:

kernel: ata1.00: Abilitazione dei dati discard_zeroes

2 pezzi di SSD NVMe modello Samsung SSD 970 EVO 500GB.

Per questi SSD è importante la velocità di lettura/scrittura casuale e la durata secondo le vostre necessità. Un dissipatore è obbligatorio. Assolutamente obbligatorio. Altrimenti, li brucerete a una croccante doratura al primo avvio della sincronizzazione RAID.

Adattatore StarTech PEX8M2E2 per 2 x SSD NVMe da installare in uno slot PCIe 3.0 8x. Questo, di nuovo, è semplicemente un HBA, ma per NVMe. Si differenzia dai adattatori economici per l'assenza della necessità di supporto della biforcazione PCIe da parte della scheda madre grazie alla presenza di uno switch PCIe integrato. Funzionerà anche nel sistema più datato in cui è presente PCIe, anche se si tratta di uno slot PCIe 1.0 x1. Naturalmente, alla velocità corrispondente. Non ci sono RAID qui. Non c'è BIOS integrato a bordo. Quindi, il vostro sistema non imparerà magicamente a partire da NVMe, tantomeno a creare RAID NVMe grazie a questo dispositivo.

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

La scelta di rinunciare a qualsiasi tipo di RAID hardware o integrato nel chipset/BIOS è stata fatta consapevolmente, al fine di poter sostituire completamente l'intero sistema, ad eccezione degli SSD/HDD, mantenendo tutti i dati. Idealmente, affinché si possa mantenere anche il sistema operativo installato durante il passaggio a un hardware completamente nuovo/differente. L'importante è che ci siano porte SATA e PCIe. È come un live CD o una chiavetta di avvio, solo che è molto più veloce e un po' ingombrante.

UmorismoE sapete come va, a volte è necessario portare via l'intero array. E non si vogliono perdere dati. Per questo tutti i supporti menzionati sono facilmente sistemabili su guide negli scomparti da 5.25 di un case standard.

Beh, e ovviamente, per esperimenti con diversi metodi di caching SSD su Linux.

I RAID hardware sono noiosi. Li accendi. O funziona, o non funziona. Con mdadm ci sono sempre alternative.

Software

In precedenza, sul hardware era installato Debian 8 Jessie, che si avvicina alla fine del supporto. È stato creato un RAID 6 con gli HDD menzionati sopra in combinazione con LVM. Su di esso giravano macchine virtuali in kvm/libvirt.

Poiché l'autore ha esperienza nel creare unità flash SATA/NVMe avviabili portatili e per non interrompere il consueto modello 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 fin da subito. Non avremo bisogno di software o driver di terze parti.

Preparazione all'installazione

Per installare il sistema avremo bisogno dell'immagine di Ubuntu Desktop. Nel sistema server c'è un installer un po' complicato, che mostra un'eccessiva autonomia non disattivabile, inserendo sempre la partizione di sistema UEFI su uno dei dischi, rovinando tutta l'estetica. Pertanto, può essere installato solo in modalità UEFI. Non offre alternative.

Questo non ci piace.

Perché?Sfortunatamente, l'avvio UEFI è estremamente incompatibile con il RAID software avviabile, poiché nessuno ci offre il backup per la partizione ESP UEFI. Sul web ci sono ricette che suggeriscono di posizionare la partizione ESP su una chiavetta USB, ma questo rappresenta un punto di guasto. Ci sono ricette per utilizzare RAID 1 software mdadm con metadati version 0.9 che non ostacolano il BIOS UEFI nel vedere questa partizione, ma questo dura fino al momento felice in cui il BIOS o un'altra OS sull'hardware scrive qualcosa nell'ESP dimenticando di sincronizzarlo con gli altri specchi.

Inoltre, l'avvio UEFI dipende dalla NVRAM, che non si trasferirà con i dischi su un nuovo sistema, poiché fa parte della scheda madre.

Quindi, non inventeremo una nuova bicicletta. Abbiamo già una bicicletta pronta, collaudata da anni, attualmente conosciuta come avvio Legacy/BIOS, con il prestigioso nome di CSM sui sistemi compatibili con UEFI. La tireremo semplicemente giù dallo scaffale, la ingrasseremo, gonfieremo le ruote e la puliremo con un panno umido.

Anche la versione Desktop di Ubuntu non si installa bene con il boot Legacy, ma qui, come si suol dire, ci sono almeno delle opzioni.

E quindi, assemblando l'hardware, carichiamo il sistema dalla chiavetta USB di Ubuntu Live. Dovremo scaricare i pacchetti, quindi configuriamo la rete, che sia funzionante. Se non funziona, i pacchetti necessari possono essere caricati sulla chiavetta in anticipo.

Entriamo nell'ambiente Desktop, apriamo un emulatore di terminale e iniziamo:

#sudo bash

Come…?La riga sopra è un trigger canonico per i dibattiti su sudo. Con BunCon le maggiori possibilità arriva anche unaunmaggiore responsabilità. La domanda è se sei in grado di assumertela. Molti ritengono che utilizzare sudo in questo modo sia, perlomeno, poco prudente. Tuttavia:

Guarda il 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 agli sviluppatori di quel software.
Quando ci fidiamo di questo software per la sicurezza dei nostri dati, stiamo prendendo un prestito pari al valore del recupero di quei dati, che un giorno dovremo ripagare.

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

Soggettivamente, l'autore preferisce prestare a sconosciuti una bicicletta presa a prestito piuttosto che una Ferrari. Il costo è contenuto. Non servono permessi. È più semplice da gestire. I parcheggi sono gratuiti. L'attraversamento è migliore. Si possono sempre aggiungere pedali e riparare la bicicletta con le proprie mani.

Allora perché BTRFS…?Per avviare il sistema operativo avremo bisogno di un file system supportato in Legacy/BIOS GRUB out of the box e, al tempo stesso, che supporti snapshot in tempo reale. Lo utilizzeremo per la partizione /boot. Inoltre, l'autore preferisce usare questo FS per / (la root), non dimenticando di notare che per qualsiasi altro software possono essere create partizioni separate su LVM e montate nelle directory necessarie.

Né immagini macchine virtuali, né database conserveremo su questo FS.
Questo FS verrà utilizzato solo per creare istantanee del sistema senza spegnerlo, con successivo trasferimento di queste istantanee su un disco di backup tramite send/receive.

Inoltre, l'autore predilige avere il minimo indispensabile di software direttamente sull'hardware e far girare tutto il resto in macchine virtuali utilizzando tecnologie come il passaggio di GPU e controller host 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 principio per l'uso indicato sono intercambiabili.

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

Come argomento aggiuntivo, BTRFS ha la capacità di trasformare una scrittura casuale in sequenziale, il che influisce positivamente sulla velocità di sincronizzazione delle istantanee / backup su HDD.

Riscanneremo tutti i dispositivi:

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

Diamo un'occhiata:

#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 li partizioneremo in alcun modo. Del resto, il nostro BIOS non vede questi dischi. Quindi, andranno interamente in RAID software. Non creeremo nemmeno partizioni. Se si desidera seguire il «canone» o per «principio» — create una grande partizione come per HDD.

SATA HDD

Qui non dobbiamo inventare nulla di speciale. Creeremo una partizione per tutto. Creiamo una partizione perché questi dischi sono visibili al BIOS e può persino provare a avviarsi da essi. Installeremo anche GRUB su questi dischi in seguito, affinché il sistema riesca a farlo improvvisamente.

#cat >hdd.part << EOF
etichetta: dos
ID-etichetta: 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

SATA SSD

Qui abbiamo le cose più interessanti.

Innanzitutto, i dischi hanno una dimensione di 2 TB. Questo è all'interno dei limiti consentiti per MBR, e ne faremo uso. Se necessario, può essere sostituito da GPT. I dischi GPT hanno uno strato di compatibilità che consente ai sistemi compatibili con MBR di vedere le prime 4 partizioni se sono collocate nei primi 2 terabyte. L'importante è che la partizione di avvio e la partizione bios_grub su questi dischi siano all'inizio. Questo consente anche di avviare in modalità Legacy / BIOS da dischi GPT.

Ma questo non è 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 partizionata alla fine del dispositivo.

Che cos'è l'area non partizionata?Secondo le fonti online, i nostri SATA SSD hanno a bordo una cache SLC a espansione dinamica con dimensioni che vanno da 6 a 78 gigabyte. Otteniamo 6 gigabyte «gratuiti» grazie alla differenza tra «gigabyte» e «gibibyte» nel certificato tecnico del dispositivo. I restanti 72 gigabyte sono assegnati dallo spazio inutilizzato.

Va notato che la cache è SLC, mentre lo spazio è occupato in modalità 4 bit MLC. Ciò significa in modo efficace che per ogni 4 gigabyte di spazio libero otteniamo solo 1 gigabyte di cache SLC.

Moltiplicando 72 gigabyte per 4, otteniamo 288 gigabyte. Questo è lo spazio libero che non partizioneremo, per consentire ai dischi di utilizzare appieno la cache SLC.

In questo modo, otterremo efficacemente fino a 312 gigabyte di cache SLC complessivamente da sei unità. Di tutte le unità, 2 saranno utilizzate in RAID per la ridondanza.

Questa quantità di cache ci permetterà di raramente, nella pratica reale, trovarci nella situazione in cui la scrittura non avviene nella cache. Questo compensa estremamente bene il difetto più deludente della memoria QLC: la velocità di scrittura estremamente bassa quando i dati vengono scritti bypassando la cache. Se il tuo carico di lavoro non si allinea a questo, ti consiglio di riflettere seriamente su quanto dureranno i tuoi SSD sotto tale carico, considerando il TBW della scheda tecnica.

#cat >ssd.part << EOF
etichetta: dos
ID-etichetta: 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 da qualche parte all'interno di mdadm e influisce su qualcosa. Gli array possono certamente essere rinominati in un secondo momento, ma si tratta di azioni superflue.

#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, è consentito. Tutto può funzionare anche senza inizializzazione, se si tratta di un nuovo array. Inoltre, l'inizializzazione di un array SSD durante la creazione è uno spreco di risorse TBW. Utilizziamo TRIM/DISCARD dove possibile sui array SSD assemblati per 'inizializzarli'.

Negli array SSD RAID 1 DISCARD è supportato di default.

Negli array SSD RAID 6, è necessario abilitare DISCARD nelle opzioni del modulo del kernel.

È opportuno farlo solo se tutti gli SSD utilizzati negli array di livelli 4/5/6 in questo sistema hanno un supporto funzionante per discard_zeroes_data. A volte si trovano unità strane che segnalano al kernel il supporto per questa funzione, ma in realtà non lo hanno, o la funzione non funziona sempre. Attualmente, il supporto è praticamente universale, tuttavia, si incontrano ancora vecchie unità e firmware con bug. Per questa ragione, il supporto DISCARD è disabilitato per impostazione predefinita per RAID 6.

Attenzione, il comando seguente distruggerà tutti i dati sulle unità NVMe 'inizializzando' l'array con 'zeri'.

#blkdiscard /dev/md0

Se qualcosa va storto, prova a specificare il passo.

#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

Perché così grande…?Aumentare il chunk-size influisce positivamente sulla velocità di lettura casuale a blocchi fino a chunk-size incluso. Questo avviene perché un'operazione di dimensioni corrispondenti o inferiore può essere completata interamente su un'unica unità. Pertanto, gli IOPS di tutte le unità vengono sommati. Secondo le statistiche, il 99% delle operazioni I/O non supera i 512K.

In RAID 6, le IOPS in scrittura sempre sono minori o uguali agli IOPS di un singolo disco. D'altra parte, per la lettura casuale, gli IOPS possono superare quelli di un singolo disco di diversi fattori, e qui la dimensione del blocco gioca un ruolo cruciale.
L'autore non vede alcun senso nei tentativi di ottimizzare un parametro che è intrinsecamente scarso nel RAID 6 e, invece, ottimizza ciò in cui il RAID 6 si comporta bene.
Compensiamo le scarse prestazioni di scrittura casuale del RAID 6 con la cache NVMe e trucchi di thin-provisioning.

Non abbiamo ancora abilitato il DISCARD per il RAID 6. Quindi non inizializzeremo 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 root su RAID NVMe 1 che è /dev/md0.
Tuttavia, avremo ancora bisogno di questo array veloce per altre necessità, come swap, metadati e cache LVM-cache e metadati LVM-thin, per cui su questo array creeremo un VG LVM.

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

Creiamo una partizione per il filesystem root.

#lvcreate -L 128G --name root root

Creiamo una partizione per lo swap della dimensione della RAM.

#lvcreate -L 32G --name swap root

Installazione del sistema operativo

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

Avviamo l'installer del sistema dall'ambiente Ubuntu Live. Installazione normale. Solo nella fase di selezione dei dischi per l'installazione dobbiamo 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

Quando si sceglie BTRFS come filesystem root, l'installer creerà automaticamente due volumi BTRFS chiamati "@" per / (root) e "@home" per /home.

Avviamo l'installazione...

L'installazione terminerà con una finestra di dialogo modale che segnala un errore dell'installer del bootloader. Sfortunatamente, non sarà possibile uscire da questo dialogo con mezzi normali e continuare l'installazione. Effettuiamo il logout dal sistema e accediamo di nuovo, entrando nel desktop pulito di Ubuntu Live. Apriamo il terminale e nuovamente:

#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 la rete e il hostname in chroot:

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

Entro nell'ambiente chroot:

#chroot /mnt/chroot

Per prima cosa, installiamo i pacchetti:

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

Verifichiamo e ripariamo tutti i pacchetti che sono stati installati male a causa dell'installazione incompleta del sistema:

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

Se qualcosa non ha funzionato, potrebbe essere necessario modificare prima /etc/apt/sources.list

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

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

Configuriamo meglio i nostri array:

#cat >/etc/udev/rules.d/60-md.rules << EOF
SOTTORETE=="block", KERNEL=="md*", AZIONE=="cambia", TEST=="md/stripe_cache_size", ATTR{md/stripe_cache_size}="32768"
SOTTORETE=="block", KERNEL=="md*", AZIONE=="cambia", TEST=="md/sync_speed_min", ATTR{md/sync_speed_min}="48000"
SOTTORETE=="block", KERNEL=="md*", AZIONE=="cambia", TEST=="md/sync_speed_max", ATTR{md/sync_speed_max}="300000"
EOF
#cat >/etc/udev/rules.d/62-hdparm.rules << EOF
SOTTORETE=="block", AZIONE=="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
SOTTORETE=="block", AZIONE=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", RUN+="/sbin/blockdev --setra 1024 /dev/%k"
SOTTORETE=="block", AZIONE=="add|change", KERNEL=="md*", RUN+="/sbin/blockdev --setra 0 /dev/%k"
EOF

Cos'era…?Abbiamo creato un insieme di regole udev che faranno quanto segue:

  • Impostare una dimensione della cache dei blocchi adeguata per l'anno 2020 per RAID 6. Il valore predefinito, sembra, non è cambiato dai tempi della creazione di Linux ed è da tempo inadeguato.
  • Riservare un minimo di IO durante le verifiche/sincronizzazioni dei gruppi. Questo è necessario affinché i tuoi gruppi non si blocchino in uno stato di sincronizzazione eterna sotto carico.
  • Limitare un massimo di IO durante le verifiche/sincronizzazioni dei gruppi. Questo è necessario affinché la sincronizzazione/verifica dei RAID SSD non surriscaldi i tuoi dispositivi a tal punto da diventare croccanti. Questo è particolarmente rilevante per NVMe. (Ricorda il radiatore? Non stavo scherzando.)
  • Impedire ai dischi di fermare la rotazione del disco (HDD) tramite APM e impostare un timeout di sonno per i controller dei dischi di 7 ore. Puoi disattivare completamente APM se i tuoi dischi lo supportano (-B 255). Con il valore predefinito, i dischi si fermeranno dopo cinque secondi. Poi il sistema operativo vorrà azzerare la cache del disco, i dischi ripartiranno, e il tutto ricomincerà. I dischi hanno un numero massimo limitato di avvii. Questo ciclo semplice per impostazione predefinita può facilmente danneggiare i tuoi dischi in pochi anni. Non tutti i dischi soffrono di questo, ma i nostri
  • Impostare readahead sui dischi (rotanti) a 1 megabyte — due blocchi/pezzetti consecutivi per RAID 6
  • Impedire readahead sui gruppi 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ì..?La partizione /boot la cercheremo tramite UUID poiché la denominazione dei gruppi può teoricamente cambiare.

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

Non utilizziamo UUID per LVM poiché gli UUID delle partizioni LVM e dei loro snapshot possono coincidere.Montiamo due volte /dev/mapper/root-root..?Sì. Proprio così. È una caratteristica di BTRFS. Questo filesystem può essere montato più volte con subvol differenti.

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

Rigeneriamo la configurazione mdadm:

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

Correggiamo le impostazioni 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

Cos'era…?Abbiamo abilitato l'estensione automatica dei pool LVM thin quando si raggiunge il 90% dello spazio occupato, aumentando del 5% il volume.

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

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

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

Abbiamo abilitato il supporto DISCARD quando si libera 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, come pianificato, utilizzeremo thin provisioning, quindi non ci darà alcun 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 scegliere?Tutti quelli che sd*. Il sistema dovrebbe essere in grado di avviarsi da qualsiasi disco SATA o SSD funzionante.

Perché abbiamo disabilitato os-prober..?Per eccessiva autonomia e mani birichine.

Non funziona correttamente se uno dei RAID è in stato degradato. Cerca il sistema operativo sulle partizioni utilizzate in macchine virtuali che girano su questa hardware.

Se ne hai bisogno, puoi tenerlo, ma sii consapevole di tutto quanto sopra. Ti consiglio di cercare ricette per liberarti delle mani birichine online.

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

#exit
#reboot

Come dispositivo di avvio scegliamo uno qualsiasi dei SATA SSD.

LVM su SATA SSD

A questo punto siamo già avviati nel nuovo sistema operativo, abbiamo configurato la rete, apt, aperto un terminale, e lanciato:

#sudo bash

Continuiamo.

«Inizializziamo» l'array da SATA SSD:

#blkdiscard /dev/md2

Se non ha funzionato, proviamo:

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

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

Perché un'altra VG..?In effetti, abbiamo già un VG con il nome root. Perché non aggiungere tutto a un unico VG?

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

Desideriamo davvero che, in caso di interruzione (cioè perdita di dati) in uno qualsiasi dei gruppi RAID 6, il sistema operativo si avvii normalmente e ci consenta di risolvere il problema.

A questo scopo, a un primo livello di astrazione isoleremo ogni tipo di "supporto" fisico in un VG separato.

Se vogliamo essere scientifici, i diversi array RAID appartengono a diversi "domini di affidabilità". Non dovremmo creare un ulteriore punto di guasto unendoli in un unico VG.

La presenza di LVM a livello "hardware" ci consentirà di tagliare su richiesta pezzi di diversi array RAID combinandoli in modi diversi. Ad esempio, - avviare contemporaneamente bcache + LVM thin, bcache + BTRFS, LVM cache + LVM thin, una configurazione complessa di ZFS con cache o qualsiasi altra miscela infernale, per esplorare e confrontare tutto questo.

A livello "hardware" non utilizzeremo nient'altro che i vecchi e buoni volumi LVM "spessi". Probabilmente, l'unica eccezione a questa regola sarà la partizione per il backup.

Credo che a questo punto molti lettori abbiano già cominciato a sospettare qualcosa riguardo alla matrjoska.

LVM su SATA HDD

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

Di nuovo una nuova VG..?Desideriamo davvero che, in caso di interruzione del gruppo di dischi che utilizzeremo per il backup dei dati, il nostro sistema operativo continui a funzionare normalmente, mantenendo accesso normale ai dati non riservati. Pertanto, per evitare problemi di attivazione di VG, creiamo un VG separato.

Configurazione di LVM cache

Creeremo un LV su NVMe RAID 1 per utilizzarlo come dispositivo di cache.

#lvcreate -L 70871154688B --name cache root

Perché così pochi…?Il fatto è che anche i nostri SSD NVMe hanno una cache SLC. 4 gigabyte di "gratuito" e 18 gigabyte dinamici grazie allo spazio libero occupato in 3-bit MLC. Una volta esaurita questa cache, gli SSD NVMe non saranno molto più veloci dei nostri SSD SATA con cache. Infatti, per questa ragione non ha senso creare una partizione LVM cache di dimensioni molto superiori al doppio del volume della cache SLC dell'unità NVMe. Per le unità NVMe utilizzate, l'autore considera ragionevole realizzare una cache di 32-64 gigabyte.

La dimensione della partizione indicata è necessaria per organizzare 64 gigabyte di cache, per la memorizzazione dei metadati della cache e per il backup dei metadati.

In aggiunta, vorrei notare che dopo uno spegnimento anomalo, il 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 prossimo riavvio del sistema. Pertanto, consiglio di ricreare immediatamente la cache utilizzando lo script appropriato.

Creeremo un LV su SATA RAID 6 da usare come dispositivo di cache.

#lvcreate -L 3298543271936B --name cache data

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

Creiamo un nuovo VG per la caching.

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

Creiamo un LV sul dispositivo di 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 nel posto sbagliato, puoi spostarlo 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é un chunksize così..?Grazie ad esperimenti pratici, l'autore ha scoperto che il miglior risultato si ottiene quando la dimensione del blocco della cache LVM corrisponde alla dimensione del blocco LVM thin. Inoltre, minore è la dimensione, migliore è la configurazione in caso di scrittura casuale.

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

Attenzione writeback..!Sì. Questo tipo di cache rinvia la sincronizzazione della scrittura sul dispositivo di cache. Ciò significa che, in caso di perdita della cache, si potrebbero perdere dati sul dispositivo di cache. In seguito, l'autore spiegherà quali misure, oltre a NVMe RAID 1, possono essere adottate per compensare questo rischio.

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

Verifichiamo 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, utilizza pvmove.

Puoi disattivare la cache, se necessario, con un unico comando:

#lvconvert -y --uncache cache/cachedata

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

Configurazione di LVM thin

Eseguiamo una stima di quanto spazio ci occorrerà per i metadati di LVM thin:

#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - 3385794560 byte dimensione stimata dell'area metadata 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 può sorgere la domanda sul perché allocare i metadati LVM thin separatamente, se saranno comunque memorizzati nella cache su NVMe e funzioneranno rapidamente.

La velocità, pur essendo importante, non è di certo la ragione principale. Il fatto è che la cache è un punto di guasto. Può succedere qualcosa e, se i metadati LVM thin sono memorizzati nella cache, questo porterà a una perdita totale di tutto. Senza metadati integri, sarà praticamente impossibile assemblare i volumi thin.

Spostando i metadati su un volume separato non memorizzato nella cache, ma veloce, garantiamo la conservazione 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 alta probabilità, questi danni possono essere riparati usando i registri del FS.

Inoltre, se in precedenza è stata eseguita una istantanea del volume thin e, dopo, la cache è stata sincronizzata almeno una volta completamente, allora, a causa delle peculiarità della struttura 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 questo è propriamente destinato — non permettere ai dati di una macchina virtuale di trapelare in un'altra macchina virtuale durante la redistribuzione dello spazio — zeroing viene utilizzato anche per aumentare la velocità di scrittura casuale di blocchi inferiori a 64k. Qualsiasi scrittura inferiore a 64k in un’area precedentemente non allocata del volume thin verrà trasformata in 64K allineati al confine della cache. Questo permetterà di eseguire l'operazione interamente attraverso la cache, bypassando il dispositivo cachato.

Spostiamo il LV sui PV appropriati:

#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 thin 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 è possibile osservare il comportamento della nostra configurazione di 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 è possibile 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 è sulla scrittura. In 4 secondi su NVMe si possono fare molte scritture. 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 mescolati?Sì. È sensato 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 alla prima esecuzione e successivamente man mano che la cache e il thin volume si riempiono, e a seconda che il sistema sia riuscito o meno a sincronizzare le cache riempite durante l'esecuzione precedente.

Inoltre, consiglio di misurare la velocità su un thin volume già riempito, da cui è stato appena creato un snapshot. L'autore ha avuto modo di osservare come la scrittura casuale aumenti bruscamente subito dopo la creazione del primo snapshot, soprattutto quando la cache non è ancora completamente riempita. Questo avviene grazie alla semantica di copy-on-write, all'allineamento dei blocchi della cache e del thin volume, e al fatto che la scrittura casuale su RAID 6 diventa lettura casuale su RAID 6 con successiva scrittura nella cache. Nella nostra configurazione, la lettura casuale su RAID 6 è fino a 6 volte (numero di SSD SATA nel pool) più veloce della scrittura. Poiché i blocchi per CoW vengono allocati sequenzialmente dal pool sottile, la scrittura, per la maggior parte, diventa anche sequenziale.

Entrambe queste caratteristiche possono essere sfruttate a proprio vantaggio.

Snapshot «coerenti» nella cache

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

Innanzitutto, grazie al fatto che i metadati dei thin volume sono collocati su un dispositivo non in cache, i metadati saranno integri e le eventuali perdite saranno isolate all'interno dei blocchi dati.

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

  1. Per ogni thin volume con nome <nome>, creiamo uno snapshot con nome <nome>.cached
  2. Impostiamo il migration threshold su un valore ragionevolmente alto: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. Nel ciclo controlliamo 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 zero non è disponibile per troppo tempo, può essere creato temporaneamente impostando la cache in modalità writethrough. Tuttavia, considerando le caratteristiche di velocità dei nostri array SATA e NVMe SSD, così come la loro durata TBW, riuscirete o a cogliere il momento abbastanza rapidamente senza modificare la modalità della cache, oppure il vostro hardware consumerà completamente la sua risorsa in pochi giorni. A causa delle limitazioni delle risorse, il sistema non può essere costantemente sotto carico di scrittura al 100%. I nostri NVMe SSD sotto carico di scrittura al 100% esauriranno completamente la risorsa in 3-4 giorni. Gli SSD SATA dureranno solo due volte di più. Pertanto, considereremo che la maggior parte del carico riguarda la lettura, mentre per la scrittura abbiamo picchi di attività estremamente elevata, relativamente brevi, mescolati a un carico medio basso.
  4. Appena abbiamo ottenuto (o creato) zero, rinominiamo <nome>.cached in <nome>.committed. Il vecchio <nome>.committed verrà eliminato.
  5. Facoltativamente, se la cache è riempita al 100%, può essere ricreata tramite uno script, liberando così la cache. Con una cache parzialmente vuota, il sistema lavora molto più velocemente 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 supporto principale.
  7. Aspettiamo finché non accumuliamo un numero sufficiente di modifiche nella cache #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' oppure finché non scatta il timer.
  8. Ripetiamo da capo.

Perché complicarsi la vita con la soglia di migrazione…?Il fatto è che, nella pratica reale, la scrittura "casuale" non è poi così casuale. Se abbiamo scritto qualcosa in un settore di 4 kilobyte, c'è una grande probabilità che nei minuti successivi venga effettuata una scrittura nello stesso o in uno dei settori adiacenti (+- 32K).

Impostando la soglia di migrazione a zero, ritardiamo la sincronizzazione della scrittura sugli SSD SATA e accorpiamo più modifiche di un blocco 64K nella cache. In questo modo si risparmia notevolmente la risorsa degli SSD SATA.

E dove è il codice..?Sfortunatamente, l'autore si considera non abbastanza competente nella scrittura di script bash poiché è un autodidatta al 100% e pratica lo sviluppo guidato da "Google", pertanto ritiene che quel codice terribile che esce dalle sue mani sia meglio non utilizzarlo da altri.

Penso che i professionisti del settore possano rappresentare da soli tutta la logica descritta sopra, se necessario, e, forse, anche realizzarla in modo elegante sotto forma di servizio systemd, come ha cercato di fare l'autore.

Uno schema semplice di rotazione degli snapshot ci permetterà non solo di avere costantemente uno snapshot completamente sincronizzato su SSD SATA, ma anche di utilizzare l'utilità thin_delta per capire quali blocchi siano stati modificati dopo la sua creazione, consentendo così di localizzare i danneggiamenti sui volumi principali, semplificando notevolmente il ripristino.

TRIM/DISCARD in libvirt/KVM

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

Questo si realizza attraverso l'emulazione del supporto TRIM/DISCARD sui dischi virtuali. Per fare ciò, bisogna 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>

Discarichi simili dalle OS guest vengono correttamente gestiti da LVM, e i blocchi vengono liberati in modo appropriato sia nella cache che nel pool thin. Nel nostro caso, questo avviene principalmente in modo ritardato, durante l'eliminazione di uno snapshot.

Backup BTRFS

Utilizzare script già pronti con estrema cautela e a proprio rischio e pericolo. L'autore ha scritto questo codice per uso personale. Sono sicuro che molti utenti Linux esperti abbiano simili soluzioni, quindi non sarà necessario copiare le altre.

Creiamo un volume sul dispositivo di backup:

#lvcreate -L 256G --name backup backup

Formattiamo in BTRFS:

#mkfs.btrfs /dev/backup/backup

Creiamo punti di montaggio e montiamo le sottodirectory del file system:

#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 inviare lettere minatorie 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 di lock..."
wait_lock || terminate "Impossibile ottenere il lock. Uscita..."
echo "Lock 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"
se [ -d "$SOURCE_BASE_PATH" ]
allora
echo "$SOURCE_BASE_PATH trovato"
else
echo "$SOURCE_BASE_PATH File non trovato, creando snapshot di $SOURCE_PATH in $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
se [ -d "$TARGET_BASE_PATH" ]
allora
echo "$TARGET_BASE_PATH trovato non sincronizzato con la sorgente... rimuovendo..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
se [ -d "$TARGET_BASE_PATH" ]
allora
echo "$TARGET_BASE_PATH trovato"
else
echo "$TARGET_BASE_PATH non trovato. Sincronizzando in $TARGET_BASE_DIR"
btrfs send $SOURCE_BASE_PATH | btrfs receive $TARGET_BASE_DIR
sync
fi
se [ -d "$SOURCE_PEND_PATH" ]
allora
echo "$SOURCE_PEND_PATH trovato rimuovendo..."
btrfs subvolume delete -c $SOURCE_PEND_PATH
sync
fi
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_PEND_PATH
sync
se [ -d "$TARGET_PEND_PATH" ]
allora
echo "$TARGET_PEND_PATH trovato rimuovendo..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Invio $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)"
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 "Aiuto"
;;
"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 "Nessuno.."
;;
esac
) 98>$LOCK_FILE

EOF

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

Il primo avvio può essere relativamente lungo, poiché all'inizio verranno copiati tutti i dati. Le esecuzioni successive saranno molto veloci, poiché verranno copiati solo i cambiamenti.

Un altro script che inseriremo in cron:

Ancora un po' di 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 giorni"
$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

Cosa fa..?Crea e sincronizza su FS di backup snapshot incrementali dei BTRFS mount listati. Successivamente elimina tutti gli snapshot creati 60 giorni fa. Dopo l'esecuzione, nelle sottocartelle /backup/btrfs/back/remote/ appariranno snapshot datati dei volumi specificati.

Diamo i permessi di esecuzione al codice:

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

Controlliamo e mettiamo in 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 LVM thin

Creiamo un pool thin nel dispositivo di backup:

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

Installiamo ddrescue, poiché gli script utilizzeranno questo strumento:

#apt-get install gddrescue

Creiamo un catalogo per gli script:

#mkdir /root/lvm-thin-backup

Copiare gli script:

Molto bash all'interno...#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 di lock..."
wait_lock || terminate "Impossibile ottenere il lock. Uscita..."
echo "Lock 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" == "" ]
allora
(>&2 echo "Il LV di origine non è thin.")
exit 1
fi

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

if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ]
allora
(>&2 echo "I LV di origine e destinazione 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" != "-" ]
allora
(>&2 echo "Lo snapshot dei metadati del thin pool esiste già. Si presuppone che sia obsoleto. Rilascerò lo snapshot dei metadati tra 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" == "-" ]
allora
(>&2 echo "Impossibile creare lo snapshot dei metadati del thin pool.")
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/copia/g' | sed 's/left_only/copia/g' | sed 's/right_only/scarta/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" ]
allora
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" ]
allora
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" != "" ]
allora
echo "$DISCARD_LV_PATH trovato"
else
echo "$DISCARD_LV non trovato 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" != "" ]
allora
echo "$SOURCE_BASE_LV_PATH trovato"
else
echo "Base sorgente non trovata, creando snapshot di $SOURCE_VG/$SOURCE_LV a $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 "Scartando $SOURCE_BASE_LV_PATH poiché dobbiamo effettuare un 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" != "" ]
allora
echo "$TARGET_BASE_LV_PATH trovato fuori sincronizzazione con la sorgente... rimuovendo..."
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" != "" ]
allora
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 il bootstrap. Scartando 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 target a $TARGET_BASE_LV_PATH"
discard_volume "$TARGET_VG" "$TARGET_BASE_LV"
sync
fi
if [ "$SOURCE_PEND_LV_PATH" != "" ]
allora
echo "$SOURCE_PEND_LV_PATH trovato, rimuovendo..."
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" != "" ]
allora
echo "$TARGET_PEND_LV_PATH trovato, rimuovendo..."
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 "Sincronizzando $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" != "" ]
allora
echo "$SOURCE_BASE_LV_PATH trovato"
else
echo "$SOURCE_BASE_LV_PATH non trovato"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
allora
echo "$TARGET_BASE_LV_PATH trovato"
else
echo "$TARGET_BASE_LV_PATH non trovato"
exit 1
fi
activate_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"
}

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" != "" ]
allora
echo "$SOURCE_BASE_LV_PATH trovato"
else
echo "$SOURCE_BASE_LV_PATH non trovato"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
allora
echo "$TARGET_BASE_LV_PATH trovato"
else
echo "$TARGET_BASE_LV_PATH non trovato"
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 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"
}

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
rimuovi "$SNAPSHOT"
fatto < <(list "$1" "$2" | grep "$FILTER")

}

(
COMMAND="$1"
shift

case "$COMMAND" in
"--help")
echo "Aiuto"
;;
"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
verifica "$1" "$2"
;;
"resync")
wait_lock_or_terminate
resync "$1" "$2"
;;
"remove")
wait_lock_or_terminate
rimuovi "$1"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2" "$3"
;;
*)
echo "Nessuno.."
;;
esac
) 98>$LOCK_FILE

EOF

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

Un altro script da inserire nel cron:

Ancora un po' di 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 giorni"

$BACKUP_SCRIPT backup immagini linux-dev
$BACKUP_SCRIPT backup immagini win8
$BACKUP_SCRIPT backup immagini win8-data
#etc

$BACKUP_SCRIPT removeall immagini linux-dev "$RETENTION"
$BACKUP_SCRIPT removeall immagini win8 "$RETENTION"
$BACKUP_SCRIPT removeall immagini win8-data "$RETENTION"
#etc

EOF

Cosa fa…?Utilizza lo script precedente per creare e sincronizzare backup dei volumi sottili elencati. Lo script lascerà le istantanee non attive dei volumi elencati, necessarie per tracciare le modifiche dall'ultima sincronizzazione.

Questo script deve essere modificato specificando l'elenco dei volumi sottili per cui è necessario effettuare il backup. I nomi forniti sono solo a titolo di esempio. Se desideri, puoi 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 mettiamo in 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

La prima esecuzione richiederà tempo, poiché i volumi sottili saranno completamente sincronizzati copiando tutto lo spazio utilizzato. Grazie ai metadati LVM thin sappiamo quali blocchi sono effettivamente utilizzati, quindi verranno copiati solo i blocchi realmente utilizzati dei volumi sottili.

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

Vediamo cosa è stato ottenuto:

#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:
totale 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:
totale 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'elevata flessibilità.

P.S.

Nell'articolo successivo proveremo a utilizzare alcuni di questi sistemi di storage mobili / KVM come base per creare un cluster di storage / VM geo-distribuito con backup su più continenti utilizzando 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