Cosa hanno in comune LVM e Matrioska?

Buongiorno.
Vorrei condividere con la comunità la mia esperienza pratica nella creazione di un sistema di archiviazione dati per KVM utilizzando md RAID + LVM.

Il programma includerà:

  • Creazione di md RAID 1 da SSD NVMe.
  • Assemblaggio di md RAID 6 da SSD SATA e unità normali.
  • Caratteristiche dell'operazione TRIM/DISCARD su SSD RAID 1/6.
  • Creazione di un array md RAID 1/6 avviabile su un set comune di dischi.
  • Installazione del sistema su NVMe RAID 1 quando non è presente il supporto NVMe nel BIOS.
  • Utilizzando la cache LVM e LVM thin.
  • Utilizzo di snapshot BTRFS e invio/ricezione per il backup.
  • Utilizzo di thin snapshot LVM e thin_delta per backup in stile BTRFS.

Se sei interessato, guarda il gatto.

dichiarazione

L'autore non si assume alcuna responsabilità per le conseguenze derivanti dall'utilizzo o dal mancato utilizzo di materiali/esempi/codici/suggerimenti/dati contenuti in questo articolo. Leggendo o utilizzando questo materiale in qualsiasi modo, ti assumi la responsabilità di tutte le conseguenze di queste azioni. Le possibili conseguenze includono:

  • SSD NVMe croccanti.
  • Risorse di registrazione completamente esaurite e guasto delle unità SSD.
  • Perdita completa di tutti i dati su tutte le unità, comprese le copie di backup.
  • Hardware del computer difettoso.
  • Tempo sprecato, nervi e denaro.
  • Qualsiasi altra conseguenza non elencata sopra.

ferro

Erano disponibili:

Scheda madre del 2013 circa con chipset Z87, completa di Intel Core i7 / Haswell.

  • Processore 4 core, 8 thread
  • RAM DDR32 da 3GB
  • 1x16 o 2x8 PCIe 3.0
  • 1x4 + 1x1 PCIe 2.0
  • 6 connettori SATA 6 da 3 Gbps

L'adattatore SAS LSI SAS9211-8I è passato alla modalità IT / HBA. Il firmware abilitato per RAID è stato intenzionalmente sostituito con il firmware HBA per:

  1. Potresti buttare via questo adattatore in qualsiasi momento e sostituirlo con qualsiasi altro tu abbia incontrato.
  2. TRIM/Discard ha funzionato normalmente sui dischi, perché... nel firmware RAID questi comandi non sono affatto supportati e all'HBA, in generale, non interessa quali comandi vengono trasmessi sul bus.

Dischi rigidi: 8 pezzi di HGST Travelstar 7K1000 con una capacità di 1 TB in un fattore di forma 2.5, come per i laptop. Queste unità erano precedentemente in un array RAID 6. Avranno un utilizzo anche nel nuovo sistema. Per archiviare i backup locali.

Aggiunto inoltre:

SSD SATA da 6 pezzi modello Samsung 860 QVO 2TB. Questi SSD richiedevano un volume elevato, si desiderava la presenza di una cache SLC, affidabilità e un prezzo basso. Era richiesto il supporto per scarto/zero, che viene controllato dalla riga in dmesg:

kernel: ata1.00: Enabling discard_zeroes_data

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

Per questi SSD, la velocità di lettura/scrittura casuale e la capacità delle risorse per le tue esigenze sono importanti. Radiatore per loro. Necessariamente. Assolutamente. Altrimenti friggeteli fino a renderli croccanti durante la prima sincronizzazione RAID.

Adattatore StarTech PEX8M2E2 per 2 SSD NVMe installati nello slot PCIe 3.0 8x. Questo, ancora una volta, è solo un HBA, ma per NVMe. Si differenzia dagli adattatori economici in quanto non richiede il supporto della biforcazione PCIe dalla scheda madre a causa della presenza di uno switch PCIe integrato. Funzionerà anche nel sistema più vecchio con PCIe, anche se si tratta di uno slot x1 PCIe 1.0. Naturalmente, alla velocità appropriata. Non ci sono RAID lì. Non è presente un BIOS integrato a bordo. Quindi, il tuo sistema non imparerà magicamente ad avviarsi con NVMe, tanto meno a fare RAID NVMe grazie a questo dispositivo.

Questo componente è dovuto esclusivamente alla presenza di un solo 8x PCIe 3.0 libero nel sistema e, se ci sono 2 slot liberi, può essere facilmente sostituito con due penny PEX4M2E1 o analoghi, che possono essere acquistati ovunque al prezzo di 600 rubli.

Il rifiuto di tutti i tipi di hardware o chipset/BIOS RAID integrati è stato fatto deliberatamente, per poter sostituire completamente l'intero sistema, ad eccezione degli stessi SSD/HDD, preservando tutti i dati. Idealmente, in modo da poter salvare anche il sistema operativo installato quando si passa a un hardware completamente nuovo/diverso. La cosa principale è che ci sono porte SATA e PCIe. È come un CD live o un'unità flash avviabile, solo molto veloce e un po' ingombrante.

UmorismoAltrimenti, sai cosa succede: a volte hai urgentemente bisogno di portare con te l'intero array da portare via. Ma non voglio perdere dati. Per fare ciò, tutti i supporti menzionati sono comodamente posizionati sulle guide negli alloggiamenti da 5.25 del case standard.

Bene, e, ovviamente, per sperimentare diversi metodi di memorizzazione nella cache SSD in Linux.

I raid hardware sono noiosi. Accendilo. O funziona oppure no. E con mdadm ci sono sempre delle opzioni.

morbido

In precedenza, sull'hardware era installato Debian 8 Jessie, che è vicino all'EOL. Il RAID 6 è stato assemblato dagli HDD sopra menzionati abbinati a LVM. Eseguiva macchine virtuali in kvm/libvirt.

Perché L'autore ha un'esperienza adeguata nella creazione di unità flash SATA/NVMe avviabili portatili e inoltre, per non rompere il solito modello adatto, come sistema di destinazione è stato scelto Ubuntu 18.04, che è già sufficientemente stabilizzato, ma ha ancora 3 anni di supporto in futuro.

Il sistema menzionato contiene tutti i driver hardware di cui abbiamo bisogno e pronti all'uso. Non abbiamo bisogno di software o driver di terze parti.

Preparazione per l'installazione

Per installare il sistema abbiamo bisogno di Ubuntu Desktop Image. Il sistema server ha una sorta di vigoroso programma di installazione, che mostra un'eccessiva indipendenza che non può essere disabilitata inserendo la partizione del sistema UEFI su uno dei dischi, rovinandone tutta la bellezza. Di conseguenza, è installato solo in modalità UEFI. Non offre alcuna opzione.

Non siamo contenti di questo.

Perché?Sfortunatamente, l'avvio UEFI è estremamente poco compatibile con il software di avvio RAID, perché... Nessuno ci offre prenotazioni per la partizione UEFI ESP. Esistono ricette su Internet che suggeriscono di posizionare la partizione ESP su un'unità flash in una porta USB, ma questo è un punto di fallimento. Esistono ricette che utilizzano il software mdadm RAID 1 con metadati versione 0.9 che non impediscono al BIOS UEFI di vedere questa partizione, ma questo sopravvive fino al momento felice in cui il BIOS o un altro sistema operativo hardware scrive qualcosa sull'ESP e si dimentica di sincronizzarlo con altri specchi.

Inoltre, l'avvio UEFI dipende dalla NVRAM, che non si sposterà insieme ai dischi nel nuovo sistema, perché fa parte della scheda madre.

Quindi non reinventeremo una nuova ruota. Abbiamo già una bici del nonno già pronta e collaudata nel tempo, ora chiamata Legacy/BIOS boot, che porta l'orgoglioso nome di CSM sui sistemi compatibili con UEFI. Lo toglieremo semplicemente dallo scaffale, lo lubrificheremo, gonfieremo le gomme e lo puliremo con un panno umido.

Anche la versione desktop di Ubuntu non può essere installata correttamente con il bootloader Legacy, ma qui, come si suol dire, almeno ci sono delle opzioni.

E così, raccogliamo l'hardware e carichiamo il sistema dall'unità flash avviabile Ubuntu Live. Dovremo scaricare i pacchetti, quindi configureremo la rete che funziona per te. Se non funziona, puoi caricare in anticipo i pacchetti necessari su un'unità flash.

Entriamo nell'ambiente Desktop, lanciamo l'emulatore di terminale e partiamo:

#sudo bash

Come…?La riga sopra è l'innesco canonico per gli holiwar su sudo. C bоarrivano maggiori opportunità eоmaggiore responsabilità. La domanda è se puoi affrontarlo da solo. Molte persone pensano che usare sudo in questo modo almeno non sia prudente. Tuttavia:

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

Perché non ZFS...?Quando installiamo un software sul nostro computer, essenzialmente prestiamo il nostro hardware agli sviluppatori di questo software per guidarlo.
Quando affidiamo a questo software la sicurezza dei nostri dati, prendiamo un prestito pari al costo del ripristino di questi dati, che un giorno dovremo ripagare.

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

Soggettivamente l'autore preferisce prestare una moto a credito a sconosciuti anziché una Ferrari. Lì il prezzo della questione non è alto. Non c'è bisogno di diritti. Più semplice delle regole del traffico. Il parcheggio è gratuito. La capacità di attraversare il paese è migliore. Puoi sempre attaccare le gambe a una bicicletta e puoi ripararla con le tue mani.

Perché allora BTRFS...?Per avviare il sistema operativo, abbiamo bisogno di un file system che sia supportato in Legacy/BIOS GRUB e che allo stesso tempo supporti le istantanee live. Lo useremo per la partizione /boot. Inoltre, l'autore preferisce utilizzare questo FS per / (root), senza dimenticare di notare che per qualsiasi altro software è possibile creare partizioni separate su LVM e montarle nelle directory necessarie.

Non memorizzeremo alcuna immagine di macchine virtuali o database su questo FS.
Questo FS verrà utilizzato solo per creare istantanee del sistema senza spegnerlo e quindi trasferire queste istantanee su un disco di backup utilizzando invio/ricezione.

Inoltre, l'autore generalmente preferisce mantenere un minimo di software direttamente sull'hardware ed eseguire tutto il resto del software su macchine virtuali utilizzando cose come l'inoltro di GPU e controller host PCI-USB a KVM tramite IOMMU.

Le uniche cose rimaste sull'hardware sono l'archiviazione dei dati, la virtualizzazione e il backup.

Se ti fidi di più di ZFS, in linea di principio sono intercambiabili per l'applicazione specificata.

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

Come ulteriore argomento, BTRFS ha la capacità di trasformare le scritture casuali in sequenziali, il che ha un effetto estremamente positivo sulla velocità di sincronizzazione di istantanee/backup sull'HDD.

Eseguiamo nuovamente la scansione di tutti i dispositivi:

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

Diamo un'occhiata in giro:

#lsscsi && nvme list
[0:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sda
[1:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdb
[2:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdc
[3:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdd
[4:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sde
[5:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdf
[6:0:0:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdg
[6:0:1:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdh
[6:0:2:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdi
[6:0:3:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdj
[6:0:4:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdk
[6:0:5:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdl
[6:0:6:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdm
[6:0:7:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdn
Node SN Model Namespace Usage Format FW Rev
---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1 S466NXXXXXXX15L Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7
/dev/nvme1n1 S5H7NXXXXXXX48N Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7

Disposizione del disco

NVMe SSD

Ma non li contrassegneremo in alcun modo. Tuttavia, il nostro BIOS non vede queste unità. Quindi passeranno interamente al RAID software. Non creeremo nemmeno sezioni lì. Se vuoi seguire il "canone" o "principalmente", crea una partizione grande, come un HDD.

SATA HDD

Non è necessario inventare nulla di speciale qui. Creeremo una sezione per tutto. Creeremo una partizione perché il BIOS vede questi dischi e potrebbe anche provare ad avviarsi da essi. Successivamente installeremo GRUB su questi dischi in modo che il sistema possa farlo all'improvviso.

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

/dev/sdg1 : start= 2048, size= 1953523120, type=fd, bootable
EOF
#sfdisk /dev/sdg < hdd.part
#sfdisk /dev/sdh < hdd.part
#sfdisk /dev/sdi < hdd.part
#sfdisk /dev/sdj < hdd.part
#sfdisk /dev/sdk < hdd.part
#sfdisk /dev/sdl < hdd.part
#sfdisk /dev/sdm < hdd.part
#sfdisk /dev/sdn < hdd.part

SSD SATA

È qui che le cose si fanno interessanti per noi.

Innanzitutto, le nostre unità hanno una dimensione di 2 TB. Questo rientra nell'intervallo accettabile per l'MBR, che è quello che utilizzeremo. Se necessario, può essere sostituito con 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 entro i primi 2 terabyte. La cosa principale è che la partizione di avvio e la partizione bios_grub su questi dischi dovrebbero essere all'inizio. Ciò ti consente anche di eseguire l'avvio da unità GPT Legacy/BIOS.

Ma questo non è il nostro caso.

Qui creeremo due sezioni. Il primo avrà una dimensione di 1 GB e verrà utilizzato per RAID 1 /boot.

Il secondo verrà utilizzato per RAID 6 e occuperà tutto lo spazio libero rimanente tranne una piccola area non allocata alla fine del disco.

Cos'è quest'area non contrassegnata?Secondo fonti in rete, i nostri SSD SATA hanno a bordo una cache SLC espandibile dinamicamente di dimensioni comprese tra 6 e 78 gigabyte. Otteniamo 6 gigabyte “gratuitamente” a causa della differenza tra “gigabyte” e “gibibyte” nella scheda tecnica dell'unità. I restanti 72 gigabyte vengono allocati dallo spazio inutilizzato.

Qui va notato che abbiamo una cache SLC e lo spazio è occupato in modalità MLC a 4 bit. Ciò per noi significa effettivamente che per ogni 4 gigabyte di spazio libero otterremo solo 1 gigabyte di cache SLC.

Moltiplica 72 gigabyte per 4 e ottieni 288 gigabyte. Questo è lo spazio libero che non delegheremo per consentire alle unità di sfruttare appieno la cache SLC.

Pertanto, otterremo effettivamente fino a 312 gigabyte di cache SLC da sei unità in totale. Di tutte le unità, 2 verranno utilizzate in RAID per la ridondanza.

Questa quantità di cache ci consentirà estremamente raramente nella vita reale di incontrare una situazione in cui una scrittura non va alla cache. Ciò compensa molto bene lo svantaggio più triste della memoria QLC: la velocità di scrittura estremamente bassa quando i dati vengono scritti bypassando la cache. Se i tuoi carichi non corrispondono a questo, ti consiglio di riflettere attentamente su quanto tempo durerà il tuo SSD con un tale carico, tenendo conto del TBW dalla scheda tecnica.

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

/dev/sda1 : start= 2048, size= 2097152, type=fd, bootable
/dev/sda2 : start= 2099200, size= 3300950016, type=fd
EOF
#sfdisk /dev/sda < ssd.part
#sfdisk /dev/sdb < ssd.part
#sfdisk /dev/sdc < ssd.part
#sfdisk /dev/sdd < ssd.part
#sfdisk /dev/sde < ssd.part
#sfdisk /dev/sdf < ssd.part

Creazione di array

Per prima cosa dobbiamo rinominare la macchina. Ciò è necessario perché il nome host fa parte del nome dell'array da qualche parte all'interno di mdadm e influisce su qualcosa da qualche parte. Naturalmente, gli array possono essere rinominati in seguito, ma questo è un passaggio non necessario.

#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é -assumere-pulito...?Per evitare di inizializzare gli array. Ciò è valido per entrambi i livelli RAID 1 e 6. Tutto può funzionare senza inizializzazione se si tratta di un nuovo array. Inoltre, inizializzare l'array SSD al momento della creazione è uno spreco di risorse TBW. Utilizziamo TRIM/DISCARD ove possibile sugli array SSD assemblati per "inizializzarli".

Per gli array SSD, RAID 1 DISCARD è supportato immediatamente.

Per gli array SSD RAID 6 DISCARD, è necessario abilitarlo nei parametri del modulo kernel.

Questa operazione dovrebbe essere eseguita solo se tutti gli SSD utilizzati negli array di livello 4/5/6 in questo sistema dispongono del supporto funzionante per scarget_zeroes_data. A volte ti imbatti in strani drive che dicono al kernel che questa funzione è supportata, ma in realtà non c'è, oppure la funzione non sempre funziona. Al momento il supporto è disponibile quasi ovunque, tuttavia ci sono vecchie unità e firmware con errori. Per questo motivo, il supporto DISCARD è disabilitato per impostazione predefinita per RAID 6.

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

#blkdiscard /dev/md0

Se qualcosa va storto, prova a specificare un passaggio.

#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...?L'aumento della dimensione del blocco ha un effetto positivo sulla velocità di lettura casuale in blocchi fino alla dimensione del blocco inclusa. Ciò accade perché un'operazione di dimensioni adeguate o inferiori può essere completata interamente su un singolo dispositivo. Pertanto, vengono sommati gli IOPS di tutti i dispositivi. Secondo le statistiche, il 99% dell'IO non supera i 512K.

Il RAID ha 6 IOPS per scrittura sempre inferiore o uguale agli IOPS di un'unità. Quando, come lettura casuale, gli IOPS possono essere molte volte maggiori di quelli di un'unità, e qui la dimensione del blocco è di fondamentale importanza.
L'autore non vede il motivo di cercare di ottimizzare un parametro che non è corretto in RAID 6 in base alla progettazione e ottimizza invece ciò in cui RAID 6 è bravo.
Compenseremo la scarsa scrittura casuale di RAID 6 con una cache NVMe e trucchi di thin-provisioning.

Non abbiamo ancora abilitato DISCARD per RAID 6. Quindi per ora non "inizializzeremo" questo array. Lo faremo più tardi, dopo aver installato il 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 file system root su NVMe RAID 1 che è /dev/md0.
Tuttavia, avremo ancora bisogno di questo array veloce per altre esigenze, come swap, metadati, cache LVM e metadati LVM-thin, quindi creeremo un VG LVM su questo array.

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

Creiamo una partizione per il file system root.

#lvcreate -L 128G --name root root

Creiamo una partizione per lo swap in base alla dimensione della RAM.

#lvcreate -L 32G --name swap root

Installazione del sistema operativo

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

Avvia la procedura guidata di installazione del sistema dall'ambiente Ubuntu Live. Installazione normale. Solo nella fase di selezione dei dischi per l'installazione, è necessario specificare quanto segue:

  • /dev/md1, - punto di montaggio /boot, FS - BTRFS
  • /dev/root/root (noto anche come /dev/mapper/root-root), - punto di montaggio / (root), FS - BTRFS
  • /dev/root/swap (noto anche come /dev/mapper/root-swap), - utilizzare come partizione di swap
  • Installa il bootloader su /dev/sda

Quando selezioni BTRFS come file system root, il programma di installazione creerà automaticamente due volumi BTRFS denominati "@" per / (root) e "@home" per /home.

Iniziamo l'installazione...

L'installazione terminerà con una finestra di dialogo modale che indica un errore nell'installazione del bootloader. Sfortunatamente, non potrai uscire da questa finestra di dialogo utilizzando i mezzi standard e continuare l'installazione. Usciamo dal sistema ed effettuiamo nuovamente l'accesso, ritrovandoci in un desktop Ubuntu Live pulito. Apri il terminale e ancora:

#sudo bash

Crea 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 nome host 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

Andiamo nell'ambiente chroot:

#chroot /mnt/chroot

Innanzitutto consegneremo i pacchi:

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

Controlliamo e sistemiamo tutti i pacchetti che sono stati installati in modo storto 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 funziona, potrebbe essere necessario prima modificare /etc/apt/sources.list

Regoliamo i parametri del modulo RAID 6 per abilitare TRIM/DISCARD:

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

Modifichiamo un po' i nostri 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

Cos'era..?Abbiamo creato una serie di regole udev che faranno quanto segue:

  • Imposta la dimensione della cache dei blocchi per RAID 2020 in modo che sia adeguata per il 6. Il valore predefinito, a quanto pare, non è cambiato dalla creazione di Linux e non è stato adeguato per molto tempo.
  • Riservare un minimo di I/O per la durata dei controlli/sincronizzazione degli array. Questo per evitare che i tuoi array rimangano bloccati in uno stato di eterna sincronizzazione sotto carico.
  • Limita l'IO massimo durante i controlli/sincronizzazione degli array. Ciò è necessario affinché la sincronizzazione/controllo dei RAID SSD non rovini le tue unità. Ciò è particolarmente vero per NVMe. (Ricordi il radiatore? Non stavo scherzando.)
  • Impedisci ai dischi di interrompere la rotazione del mandrino (HDD) tramite APM e imposta il timeout di sospensione per i controller del disco su 7 ore. Puoi disabilitare completamente l'APM se le tue unità possono farlo (-B 255). Con il valore predefinito, gli azionamenti si fermeranno dopo cinque secondi. Quindi il sistema operativo vuole reimpostare la cache del disco, i dischi gireranno di nuovo e tutto ricomincerà da capo. I dischi hanno un numero massimo limitato di rotazioni del mandrino. Un ciclo predefinito così semplice può facilmente uccidere i tuoi dischi in un paio d'anni. Non tutti i dischi ne soffrono, ma i nostri sono “laptop”, con le opportune impostazioni predefinite, che fanno sembrare il RAID un mini-MAID.
  • Installa readahead su dischi (rotanti) da 1 megabyte - due blocchi/chunk consecutivi RAID 6
  • Disabilita 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é..?Cercheremo la partizione /boot in base all'UUID. La denominazione degli array potrebbe teoricamente cambiare.

Cercheremo le restanti sezioni in base ai nomi LVM nella notazione /dev/mapper/vg-lv, perché identificano le partizioni in modo abbastanza univoco.

Non utilizziamo l'UUID per LVM perché L'UUID dei volumi LVM e le relative istantanee possono essere gli stessi.Montare /dev/mapper/root-root... due volte?SÌ. Esattamente. Caratteristica di BTRFS. Questo file system può essere montato più volte con diversi sottovolti.

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

Rigeneriamo la configurazione di mdadm:

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

Regoliamo le impostazioni LVM:

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

activation {
thin_pool_autoextend_threshold=90
thin_pool_autoextend_percent=5
}
allocation {
cache_pool_max_chunks=2097152
}
devices {
global_filter=["r|^/dev/.*_corig$|","r|^/dev/.*_cdata$|","r|^/dev/.*_cmeta$|","r|^/dev/.*gpv$|","r|^/dev/images/.*$|","r|^/dev/mapper/images.*$|","r|^/dev/backup/.*$|","r|^/dev/mapper/backup.*$|"] issue_discards=1
}
EOF

Cos'era..?Abbiamo abilitato l'espansione automatica dei thin pool LVM al raggiungimento del 90% dello spazio occupato del 5% del volume.

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

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

  • dispositivi contenenti cache LVM (cdata)
  • dispositivi memorizzati nella cache utilizzando la cache LVM, ignorando la cache ( _corig). In questo caso, il dispositivo memorizzato nella cache verrà comunque scansionato attraverso la cache (solo ).
  • dispositivi contenenti metadati della cache LVM (cmeta)
  • tutti i dispositivi in ​​VG con il nome immagini. Qui avremo immagini disco di macchine virtuali e non vogliamo che LVM sull'host attivi i volumi appartenenti al sistema operativo guest.
  • tutti i dispositivi in ​​VG con il nome backup. Qui avremo copie di backup delle immagini della macchina virtuale.
  • tutti i dispositivi il cui nome termina con “gpv” (volume fisico guest)

Abbiamo abilitato il supporto DISCARD quando si libera spazio libero su LVM VG. Stai attento. Ciò renderà l'eliminazione dei LV sull'SSD piuttosto dispendiosa in termini di tempo. Ciò vale soprattutto per SSD RAID 6. Tuttavia, secondo il piano, utilizzeremo il thin provisioning, quindi questo non ci ostacolerà affatto.

Aggiorniamo l'immagine initramfs:

#update-initramfs -u -k all

Installa e configura grub:

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

Quali dischi scegliere?Tutti coloro che sono SD*. Il sistema deve essere in grado di avviarsi da qualsiasi unità SATA o SSD funzionante.

Perché hanno aggiunto os-prober...?Per un'eccessiva indipendenza e mani giocose.

Non funziona correttamente se uno dei RAID è in uno stato degradato. Tenta di cercare il sistema operativo sulle partizioni utilizzate nelle macchine virtuali in esecuzione su questo hardware.

Se ne hai bisogno, puoi lasciarlo, ma tieni presente tutto quanto sopra. Consiglio di cercare ricette online per sbarazzarsi delle mani cattive.

Con questo abbiamo completato l'installazione iniziale. È ora di riavviare il sistema operativo appena installato. Non dimenticare di rimuovere il Live CD/USB avviabile.

#exit
#reboot

Seleziona uno qualsiasi degli SSD SATA come dispositivo di avvio.

LVM su SSD SATA

A questo punto, abbiamo già avviato il nuovo sistema operativo, configurato la rete, apt, aperto l'emulatore di terminale ed eseguito:

#sudo bash

Noi continuiamo

"Inizializza" l'array da SSD SATA:

#blkdiscard /dev/md2

Se non funziona, prova:

#blkdiscard --step 65536 /dev/md2
Crea LVM VG su 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 in un VG?

Se sono presenti più PV in un VG, affinché il VG venga attivato correttamente è necessario che tutti i PV siano presenti (online). L'eccezione è LVM RAID, che deliberatamente non utilizziamo.

Vogliamo davvero che se si verifica un guasto (leggi perdita di dati) su uno qualsiasi degli array RAID 6, il sistema operativo si avvierà normalmente e ci darà l'opportunità di risolvere il problema.

Per fare ciò, al primo livello di astrazione isoleremo ciascun tipo di “media” fisico in un VG separato.

Dal punto di vista scientifico, diversi array RAID appartengono a diversi “domini di affidabilità”. Non dovresti creare un ulteriore punto comune di fallimento per loro stipandoli in un unico VG.

La presenza di LVM a livello “hardware” ci consentirà di tagliare arbitrariamente pezzi di diversi array RAID combinandoli in modi diversi. Ad esempio: corri contemporaneamente bcache + LVM thin, bcache + BTRFS, cache LVM + LVM thin, una configurazione ZFS complessa con cache o qualsiasi altra combinazione infernale per provare a confrontare il tutto.

A livello "hardware", non utilizzeremo altro che i buoni vecchi volumi LVM "spessi". L'eccezione a questa regola potrebbe essere la partizione di backup.

Penso che a questo punto molti lettori avessero già iniziato a sospettare qualcosa sulla bambola da nidificazione.

LVM su HDD SATA

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

Di nuovo nuovo VG..?Vogliamo davvero che, se l'array di dischi che utilizzeremo per il backup dei dati si guasta, il nostro sistema operativo continui a funzionare normalmente, pur mantenendo l'accesso ai dati non di backup come al solito. Pertanto, per evitare problemi di attivazione del VG, creiamo un VG separato.

Configurazione della cache LVM

Creiamo un LV su NVMe RAID 1 per utilizzarlo come dispositivo di caching.

#lvcreate -L 70871154688B --name cache root

Perché c'è così poco...?Il fatto è che i nostri SSD NVMe hanno anche una cache SLC. 4 gigabyte di “libero” e 18 gigabyte di dinamico grazie allo spazio libero occupato nell'MLC a 3 bit. Una volta esaurita questa cache, gli SSD NVMe non saranno molto più veloci dei nostri SSD SATA con cache. In realtà, per questo motivo, non ha senso per noi rendere la partizione della cache LVM molto più grande del doppio della dimensione della cache SLC dell'unità NVMe. Per le unità NVMe utilizzate, l'autore ritiene ragionevole creare 32-64 gigabyte di cache.

La dimensione della partizione specificata è necessaria per organizzare 64 gigabyte di cache, metadati della cache e backup dei metadati.

Inoltre, noto che dopo un arresto anomalo del sistema, LVM contrassegnerà l'intera cache come sporca e si sincronizzerà nuovamente. Inoltre, ciò verrà ripetuto ogni volta che lvchange viene utilizzato su questo dispositivo fino al riavvio successivo del sistema. Pertanto consiglio di ricreare immediatamente la cache utilizzando lo script appropriato.

Creiamo un LV su SATA RAID 6 per utilizzarlo come dispositivo memorizzato nella cache.

#lvcreate -L 3298543271936B --name cache data

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

Creiamo un nuovo VG per la memorizzazione nella cache.

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

Creiamo un LV sul dispositivo memorizzato nella cache.

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

Qui abbiamo subito occupato tutto lo spazio libero su /dev/data/cache in modo che tutte le altre partizioni necessarie siano state create immediatamente su /dev/root/cache. Se hai creato qualcosa nel posto sbagliato, puoi spostarlo usando pvmove.

Creiamo e abilitiamo 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é pezzi così grandi...?Attraverso esperimenti pratici, l'autore è riuscito a scoprire che il risultato migliore si ottiene se la dimensione del blocco della cache LVM coincide con la dimensione del thin block LVM. Inoltre, minore è la dimensione, migliore sarà la prestazione della configurazione in una registrazione casuale.

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

Fai attenzione alla riscrittura..!SÌ. Questo tipo di cache rinvia la sincronizzazione in scrittura al dispositivo memorizzato nella cache. Ciò significa che se la cache viene persa, potresti perdere i dati sul dispositivo memorizzato nella cache. Successivamente l'autore vi dirà quali misure si possono adottare, oltre a NVMe RAID 1, per compensare questo rischio.

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

Controlliamo cosa abbiamo ottenuto:

#lvs -a -o lv_name,lv_size,devices --units B cache
LV LSize Devices
[cache] 68719476736B cache_cdata(0)
[cache_cdata] 68719476736B /dev/root/cache(0)
[cache_cmeta] 1073741824B /dev/root/cache(16384)
cachedata 3298539077632B cachedata_corig(0)
[cachedata_corig] 3298539077632B /dev/data/cache(0)
[lvol0_pmspare] 1073741824B /dev/root/cache(16640)

Solo [cachedata_corig] dovrebbe trovarsi in /dev/data/cache. Se qualcosa non va, usa pvmove.

Puoi disabilitare la cache se necessario con un comando:

#lvconvert -y --uncache cache/cachedata

Questo viene fatto on-line. LVM sincronizzerà semplicemente la cache sul disco, la rimuoverà e rinominerà cachedata_corig in cachedata.

Configurazione di LVM sottile

Stimiamo approssimativamente la quantità di spazio necessaria per i metadati thin LVM:

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

Arrotondare per eccesso a 4 gigabyte: 4294967296B

Moltiplicare per due e aggiungere 4194304B per i metadati LVM PV: 8594128896B
Creiamo una partizione separata su NVMe RAID 1 per posizionare su di esso i metadati thin LVM e la relativa copia di backup:

#lvcreate -L 8594128896B --name images root

Per quello..?Qui potrebbe sorgere la domanda: perché posizionare i metadati thin LVM separatamente se verranno comunque memorizzati nella cache su NVMe e funzioneranno rapidamente.

Anche se qui la velocità è importante, non è la ragione principale. Il fatto è che la cache è un punto di fallimento. Potrebbe succedergli qualcosa e, se i metadati thin di LVM vengono memorizzati nella cache, ciò causerà la completa perdita di tutto. Senza metadati completi, sarà quasi impossibile assemblare piccoli volumi.

Spostando i metadati in un volume separato, non memorizzato nella 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 di volumi sottili, il che semplificherà la procedura di ripristino di diversi ordini di grandezza. Con un'alta probabilità, questi danni verranno ripristinati utilizzando i registri FS.

Inoltre, se in precedenza è stata eseguita un'istantanea del volume thin e successivamente la cache è stata completamente sincronizzata almeno una volta, a causa della progettazione 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 una piscina:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
Perché -Z yOltre allo scopo effettivo di questa modalità, ovvero impedire che i dati di una macchina virtuale si diffondano su un'altra macchina virtuale durante la ridistribuzione dello spazio, l'azzeramento viene utilizzato anche per aumentare la velocità di scrittura casuale in blocchi inferiori a 64k. Qualsiasi scrittura inferiore a 64K su un'area precedentemente non allocata del volume thin verrà allineata ai bordi a 64K nella cache. Ciò consentirà di eseguire l'operazione interamente tramite la cache, ignorando il dispositivo memorizzato nella cache.

Spostiamo i LV nei 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

Dai un'occhiata:

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

Creiamo un volume sottile per i test:

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

Installeremo i pacchetti per test e monitoraggio:

#apt-get install sysstat fio

Ecco come puoi osservare il comportamento della nostra configurazione di archiviazione 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

Accuratamente! Risorsa!Questo codice eseguirà 36 test diversi, ciascuno della durata di 4 secondi. La metà dei test sono destinati alla registrazione. Puoi registrare molto su NVMe in 4 secondi. Fino a 3 gigabyte al secondo. Pertanto, ogni esecuzione di test di scrittura può consumare fino a 216 gigabyte di risorse SSD.

Leggere e scrivere misti?SÌ. È opportuno eseguire i test di lettura e scrittura separatamente. Inoltre, è opportuno assicurarsi che tutte le cache siano sincronizzate in modo che una scrittura effettuata in precedenza non influisca sulla lettura.

I risultati varieranno notevolmente durante il primo avvio e quelli successivi man mano che la cache e il volume sottile si riempiono e anche a seconda che il sistema sia riuscito a sincronizzare le cache riempite durante l'ultimo avvio.

Tra l'altro consiglio di misurare la velocità su un volume sottile già pieno da cui è stata appena scattata un'istantanea. L'autore ha avuto l'opportunità di osservare come le scritture casuali accelerano bruscamente subito dopo aver creato il primo snapshot, soprattutto quando la cache non è ancora completamente piena. Ciò accade a causa della semantica di scrittura copy-on-write, dell'allineamento della cache e dei blocchi di volume thin e del fatto che una scrittura casuale su RAID 6 si trasforma in una lettura casuale da RAID 6 seguita da una scrittura nella cache. Nella nostra configurazione, la lettura casuale da RAID 6 è fino a 6 volte (il numero di SSD SATA nell'array) più veloce della scrittura. Perché i blocchi per CoW vengono allocati in sequenza da un pool sottile, quindi anche la registrazione, per la maggior parte, diventa sequenziale.

Entrambe queste funzionalità possono essere utilizzate a tuo vantaggio.

Memorizza nella cache istantanee “coerenti”.

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

Innanzitutto, poiché i metadati del volume sottile risiedono su un dispositivo non memorizzato nella cache, i metadati saranno coerenti e le possibili perdite verranno isolate all'interno dei blocchi di dati.

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

  1. Per ogni volume thin con il nome <nome>, creare uno snapshot con il nome <nome>.cached
  2. Impostiamo la soglia di migrazione 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}' finché non arriviamo a zero. Se lo zero manca per troppo tempo, può essere creato commutando temporaneamente la cache in modalità writethrough. Tuttavia, tenendo conto delle caratteristiche di velocità dei nostri array SSD SATA e NVMe, nonché della loro risorsa TBW, sarai in grado di cogliere rapidamente l'attimo senza modificare la modalità cache, oppure il tuo hardware consumerà completamente l'intera risorsa in pochi giorni. A causa delle limitazioni delle risorse, il sistema, in linea di principio, non può essere sempre sotto il 100% del carico di scrittura. I nostri SSD NVMe con un carico di scrittura del 100% esauriranno completamente la risorsa 3-4 giorni. Gli SSD SATA dureranno solo il doppio del tempo. Pertanto, supponiamo che la maggior parte del carico sia destinata alla lettura e che abbiamo esplosioni di attività estremamente elevata a breve termine combinate con un carico medio basso per la scrittura.
  4. Non appena catturiamo (o realizziamo) uno zero, rinominiamo <name>.cached in <name>.comtched. Il vecchio <nome>.comtched viene eliminato.
  5. Facoltativamente, se la cache è piena al 100%, può essere ricreata da uno script, cancellandola. Con una cache semivuota, il sistema funziona molto più velocemente durante la scrittura.
  6. Imposta la soglia di migrazione su zero: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata Ciò impedirà temporaneamente la sincronizzazione della cache con il supporto principale.
  7. Aspettiamo che nella cache si accumulino parecchie modifiche #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' oppure il timer si spegnerà.
  8. Ripetiamo ancora.

Perché difficoltà con la soglia di migrazione...?Il fatto è che nella pratica reale una registrazione “casuale” non è del tutto casuale. Se scriviamo qualcosa su un settore di 4 kilobyte, c'è un'alta probabilità che nei prossimi due minuti verrà effettuata una registrazione sullo stesso o su uno dei settori adiacenti (+- 32K).

Impostando la soglia di migrazione su zero, posticipiamo la sincronizzazione in scrittura sull'SSD SATA e aggreghiamo diverse modifiche a un blocco da 64 KB nella cache. Ciò consente di risparmiare in modo significativo la risorsa dell'SSD SATA.

Dov'è il codice...?Purtroppo l'autore si ritiene non sufficientemente competente nello sviluppo di script bash perché è autodidatta al 100% e pratica lo sviluppo guidato da "google", pertanto ritiene che il terribile codice che gli esce dalle mani non dovrebbe essere utilizzato da nessuno altro.

Penso che i professionisti in questo campo saranno in grado di rappresentare autonomamente tutta la logica sopra descritta, se necessario, e, forse, anche di progettarla magnificamente come un servizio systemd, come ha cercato di fare l'autore.

Uno schema di rotazione degli snapshot così semplice ci consentirà non solo di avere costantemente uno snapshot completamente sincronizzato sull'SSD SATA, ma ci consentirà anche, utilizzando l'utilità thin_delta, di scoprire quali blocchi sono stati modificati dopo la sua creazione e quindi localizzare il danno su i volumi principali, semplificando notevolmente il ripristino.

TAGLIA/SCARTA in libvirt/KVM

Perché l'archiviazione dei dati verrà utilizzata per KVM che esegue libvirt, quindi sarebbe una buona idea insegnare alle nostre VM non solo a occupare spazio libero, ma anche a liberare quello che non è più necessario.

Questo viene fatto emulando il supporto TRIM/DISCARD sui dischi virtuali. Per fare ciò, è necessario cambiare il tipo di controller in virtio-scsi e modificare il file 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'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>

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

Tali DISCARD dai sistemi operativi guest vengono elaborati correttamente da LVM e i blocchi vengono liberati correttamente sia nella cache che nel thin pool. Nel nostro caso, ciò avviene principalmente in modo ritardato, quando si elimina lo snapshot successivo.

Backup BTRFS

Utilizza script già pronti con estremo cautela e a proprio rischio. L'autore ha scritto questo codice lui stesso ed esclusivamente per se stesso. Sono sicuro che molti utenti Linux esperti abbiano strumenti simili e non sia necessario copiare quelli di qualcun altro.

Creiamo un volume sul dispositivo di backup:

#lvcreate -L 256G --name backup backup

Formattiamolo in BTRFS:

#mkfs.btrfs /dev/backup/backup

Creiamo punti di montaggio e montiamo le sottosezioni root 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 le 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

Copiamo lo script:

Un sacco di codice bash spaventoso. Utilizzare a proprio rischio. 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 "Wating for lock..."
wait_lock || terminate "Failed to get lock. Exiting..."
echo "Got lock..."
}

function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function backup()
{
SOURCE_PATH="$MOUNTS$1"
TARGET_PATH="$BACKUPS$1"
SOURCE_BASE_PATH="$MOUNTS$1$BASE_SUFFIX"
TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
TARGET_BASE_DIR="$(dirname $TARGET_BASE_PATH)"
SOURCE_PEND_PATH="$MOUNTS$1$PEND_SUFFIX"
TARGET_PEND_PATH="$BACKUPS$1$PEND_SUFFIX"
if [ -d "$SOURCE_BASE_PATH" ] then
echo "$SOURCE_BASE_PATH found"
else
echo "$SOURCE_BASE_PATH File not found creating snapshot of $SOURCE_PATH to $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
if [ -d "$TARGET_BASE_PATH" ] then
echo "$TARGET_BASE_PATH found out of sync with source... removing..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
if [ -d "$TARGET_BASE_PATH" ] then
echo "$TARGET_BASE_PATH found"
else
echo "$TARGET_BASE_PATH not found. Synching to $TARGET_BASE_DIR"
btrfs send $SOURCE_BASE_PATH | btrfs receive $TARGET_BASE_DIR
sync
fi
if [ -d "$SOURCE_PEND_PATH" ] then
echo "$SOURCE_PEND_PATH found removing..."
btrfs subvolume delete -c $SOURCE_PEND_PATH
sync
fi
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_PEND_PATH
sync
if [ -d "$TARGET_PEND_PATH" ] then
echo "$TARGET_PEND_PATH found removing..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Sending $SOURCE_PEND_PATH to $TARGET_PEND_PATH"
btrfs send -p $SOURCE_BASE_PATH $SOURCE_PEND_PATH | btrfs receive $TARGET_BASE_DIR
sync
TARGET_DATE_SUFFIX=$(suffix)
btrfs subvolume snapshot -r $TARGET_PEND_PATH "$TARGET_PATH$TARGET_DATE_SUFFIX"
sync
btrfs subvolume delete -c $SOURCE_BASE_PATH
sync
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
mv $SOURCE_PEND_PATH $SOURCE_BASE_PATH
mv $TARGET_PEND_PATH $TARGET_BASE_PATH
sync
}

function list()
{
LIST_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
LIST_TARGET_BASE_DIR="$(dirname $LIST_TARGET_BASE_PATH)"
LIST_TARGET_BASE_NAME="$(basename -s .$BASE_SUFFIX $LIST_TARGET_BASE_PATH)"
find "$LIST_TARGET_BASE_DIR" -maxdepth 1 -mindepth 1 -type d -printf "%fn" | grep "${LIST_TARGET_BASE_NAME/$BASE_SUFFIX/$SNAP_SUFFIX}.$DATE_REGEX"
}

function remove()
{
REMOVE_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
REMOVE_TARGET_BASE_DIR="$(dirname $REMOVE_TARGET_BASE_PATH)"
btrfs subvolume delete -c $REMOVE_TARGET_BASE_DIR/$2
sync
}

function removeall()
{
DATE_OFFSET="$2"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$1" "$SNAPSHOT"
done < <(list "$1" | grep "$FILTER")

}

(
COMMAND="$1"
shift

case "$COMMAND" in
"--help")
echo "Help"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1"
;;
"list")
list "$1"
;;
"remove")
wait_lock_or_terminate
remove "$1" "$2"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2"
;;
*)
echo "None.."
;;
esac
) 98>$LOCK_FILE

EOF

Cosa fa...?Contiene una serie di semplici comandi per creare istantanee BTRFS e copiarle su un altro FS utilizzando l'invio/ricezione BTRFS.

Il primo lancio può essere relativamente lungo, perché... All'inizio, tutti i dati verranno copiati. Gli ulteriori lanci saranno molto rapidi, perché... Verranno copiate solo le modifiche.

Un altro script che inseriremo in cron:

Ancora qualche 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 day"
$BACKUP_SCRIPT backup root/@
$BACKUP_SCRIPT removeall root/@ "$RETENTION"
$BACKUP_SCRIPT backup root/@home
$BACKUP_SCRIPT removeall root/@home "$RETENTION"
$BACKUP_SCRIPT backup boot/
$BACKUP_SCRIPT removeall boot/ "$RETENTION"
EOF

Che cosa fa..?Crea e sincronizza istantanee incrementali dei volumi BTRFS elencati sull'FS di backup. Successivamente, verranno eliminate tutte le immagini create 60 giorni fa. Dopo l'avvio, le istantanee datate dei volumi elencati verranno visualizzate nelle sottodirectory /backup/btrfs/back/remote/.

Diamo i diritti di esecuzione del codice:

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

Controlliamolo e inseriamolo nel 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 sottile LVM

Creiamo un thin pool sul dispositivo di backup:

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

Installiamo ddrescue, perché... gli script utilizzeranno questo strumento:

#apt-get install gddrescue

Creiamo una directory per gli script:

#mkdir /root/lvm-thin-backup

Copiamo gli script:

Un sacco di divertimento 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 "Wating for lock..."
wait_lock || terminate "Failed to get lock. Exiting..."
echo "Got lock..."
}

function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function read_thin_id {
lvs --rows --reportformat basic --quiet -othin_id "$1/$2" | awk '{print $2}'
}

function read_pool_lv {
lvs --rows --reportformat basic --quiet -opool_lv "$1/$2" | awk '{print $2}'
}

function read_lv_dm_path {
lvs --rows --reportformat basic --quiet -olv_dm_path "$1/$2" | awk '{print $2}'
}

function read_lv_active {
lvs --rows --reportformat basic --quiet -olv_active "$1/$2" | awk '{print $2}'
}

function read_lv_chunk_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -ochunk_size "$1/$2" | awk '{print $2}'
}

function read_lv_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -olv_size "$1/$2" | awk '{print $2}'
}

function activate_volume {
lvchange -ay -Ky "$1/$2"
}

function deactivate_volume {
lvchange -an "$1/$2"
}

function read_thin_metadata_snap {
dmsetup status "$1" | awk '{print $7}'
}

function thindiff()
{
DIFF_VG="$1"
DIFF_SOURCE="$2"
DIFF_TARGET="$3"
DIFF_SOURCE_POOL=$(read_pool_lv $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_POOL=$(read_pool_lv $DIFF_VG $DIFF_TARGET)

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

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

if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ] then
(>&2 echo "Source and target LVs belong to different thin pools.")
exit 1
fi

DIFF_POOL_PATH=$(read_lv_dm_path $DIFF_VG $DIFF_SOURCE_POOL)
DIFF_SOURCE_ID=$(read_thin_id $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_ID=$(read_thin_id $DIFF_VG $DIFF_TARGET)
DIFF_POOL_PATH_TPOOL="$DIFF_POOL_PATH-tpool"
DIFF_POOL_PATH_TMETA="$DIFF_POOL_PATH"_tmeta
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)

if [ "$DIFF_POOL_METADATA_SNAP" != "-" ] then
(>&2 echo "Thin pool metadata snapshot already exist. Assuming stale one. Will release metadata snapshot in 5 seconds.")
sleep 5
dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap
fi

dmsetup message $DIFF_POOL_PATH_TPOOL 0 reserve_metadata_snap
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)

if [ "$DIFF_POOL_METADATA_SNAP" == "-" ] then
(>&2 echo "Failed to create thin pool metadata snapshot.")
exit 1
fi

#We keep output in variable because metadata snapshot need to be released early.
DIFF_DATA=$(thin_delta -m$DIFF_POOL_METADATA_SNAP --snap1 $DIFF_SOURCE_ID --snap2 $DIFF_TARGET_ID $DIFF_POOL_PATH_TMETA)

dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap

echo $"$DIFF_DATA" | grep -E 'different|left_only|right_only' | sed 's/</"/g' | sed 's/ /"/g' | awk -F'"' '{print $6 "t" $8 "t" $11}' | sed 's/different/copy/g' | sed 's/left_only/copy/g' | sed 's/right_only/discard/g'

}

function thinsync()
{
SYNC_VG="$1"
SYNC_PEND="$2"
SYNC_BASE="$3"
SYNC_TARGET="$4"
SYNC_PEND_POOL=$(read_pool_lv $SYNC_VG $SYNC_PEND)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SYNC_VG $SYNC_PEND_POOL)
SYNC_PEND_PATH=$(read_lv_dm_path $SYNC_VG $SYNC_PEND)

activate_volume $SYNC_VG $SYNC_PEND

while read -r SYNC_ACTION SYNC_OFFSET SYNC_LENGTH ; do
SYNC_OFFSET_BYTES=$((SYNC_OFFSET * SYNC_BLOCK_SIZE))
SYNC_LENGTH_BYTES=$((SYNC_LENGTH * SYNC_BLOCK_SIZE))
if [ "$SYNC_ACTION" == "copy" ] then
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SYNC_PEND_PATH" "$SYNC_TARGET"
fi

if [ "$SYNC_ACTION" == "discard" ] then
blkdiscard -o $SYNC_OFFSET_BYTES -l $SYNC_LENGTH_BYTES "$SYNC_TARGET"
fi
done < <(thindiff "$SYNC_VG" "$SYNC_PEND" "$SYNC_BASE")
}

function discard_volume()
{
DISCARD_VG="$1"
DISCARD_LV="$2"
DISCARD_LV_PATH=$(read_lv_dm_path "$DISCARD_VG" "$DISCARD_LV")
if [ "$DISCARD_LV_PATH" != "" ] then
echo "$DISCARD_LV_PATH found"
else
echo "$DISCARD_LV not found in $DISCARD_VG"
exit 1
fi
DISCARD_LV_POOL=$(read_pool_lv $DISCARD_VG $DISCARD_LV)
DISCARD_LV_SIZE=$(read_lv_size "$DISCARD_VG" "$DISCARD_LV")
lvremove -y --quiet "$DISCARD_LV_PATH" || exit 1
lvcreate --thin-pool "$DISCARD_LV_POOL" -V "$DISCARD_LV_SIZE"B --name "$DISCARD_LV" "$DISCARD_VG" || exit 1
}

function backup()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
SOURCE_PEND_LV="$SOURCE_LV$PEND_SUFFIX"
TARGET_PEND_LV="$TARGET_LV$PEND_SUFFIX"
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ] then
echo "$SOURCE_BASE_LV_PATH found"
else
echo "Source base not found creating snapshot of $SOURCE_VG/$SOURCE_LV to $SOURCE_VG/$SOURCE_BASE_LV"
lvcreate --quiet --snapshot --name "$SOURCE_BASE_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo "Discarding $SOURCE_BASE_LV_PATH as we need to bootstrap."
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
sync
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found out of sync with source... removing..."
lvremove -y --quiet $TARGET_BASE_LV_PATH || exit 1
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
sync
fi
fi
SOURCE_BASE_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_BASE_LV")
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_VG/$TARGET_LV not found. Creating empty volume."
lvcreate --thin-pool "$BACKUPS_POOL" -V "$SOURCE_BASE_SIZE"B --name "$TARGET_BASE_LV" "$TARGET_VG" || exit 1
echo "Have to rebootstrap. Discarding source at $SOURCE_BASE_LV_PATH"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
TARGET_BASE_POOL=$(read_pool_lv $TARGET_VG $TARGET_BASE_LV)
TARGET_BASE_CHUNK_SIZE=$(read_lv_chunk_size $TARGET_VG $TARGET_BASE_POOL)
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
echo "Discarding target at $TARGET_BASE_LV_PATH"
discard_volume "$TARGET_VG" "$TARGET_BASE_LV"
sync
fi
if [ "$SOURCE_PEND_LV_PATH" != "" ] then
echo "$SOURCE_PEND_LV_PATH found removing..."
lvremove -y --quiet "$SOURCE_PEND_LV_PATH" || exit 1
sync
fi
lvcreate --quiet --snapshot --name "$SOURCE_PEND_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
sync
if [ "$TARGET_PEND_LV_PATH" != "" ] then
echo "$TARGET_PEND_LV_PATH found removing..."
lvremove -y --quiet $TARGET_PEND_LV_PATH
sync
fi
lvcreate --quiet --snapshot --name "$TARGET_PEND_LV" "$TARGET_VG/$TARGET_BASE_LV" || exit 1
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")
SOURCE_PEND_LV_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_PEND_LV")
lvresize -L "$SOURCE_PEND_LV_SIZE"B "$TARGET_PEND_LV_PATH"
activate_volume "$TARGET_VG" "$TARGET_PEND_LV"
echo "Synching $SOURCE_PEND_LV_PATH to $TARGET_PEND_LV_PATH"
thinsync "$SOURCE_VG" "$SOURCE_PEND_LV" "$SOURCE_BASE_LV" "$TARGET_PEND_LV_PATH" || exit 1
sync

TARGET_DATE_SUFFIX=$(suffix)
lvcreate --quiet --snapshot --name "$TARGET_LV$TARGET_DATE_SUFFIX" "$TARGET_VG/$TARGET_PEND_LV" || exit 1
sync
lvremove --quiet -y "$SOURCE_BASE_LV_PATH" || exit 1
sync
lvremove --quiet -y "$TARGET_BASE_LV_PATH" || exit 1
sync
lvrename -y "$SOURCE_VG/$SOURCE_PEND_LV" "$SOURCE_BASE_LV" || exit 1
lvrename -y "$TARGET_VG/$TARGET_PEND_LV" "$TARGET_BASE_LV" || exit 1
sync
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function verify()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ] then
echo "$SOURCE_BASE_LV_PATH found"
else
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_BASE_LV_PATH not found"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo Comparing "$SOURCE_BASE_LV_PATH" with "$TARGET_BASE_LV_PATH"
cmp "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
echo Done...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function resync()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ] then
echo "$SOURCE_BASE_LV_PATH found"
else
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_BASE_LV_PATH not found"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)

echo Syncronizing "$SOURCE_BASE_LV_PATH" to "$TARGET_BASE_LV_PATH"

CMP_OFFSET=0
while [[ "$CMP_OFFSET" != "" ]] ; do
CMP_MISMATCH=$(cmp -i "$CMP_OFFSET" "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH" | grep differ | awk '{print $5}' | sed 's/,//g' )
if [[ "$CMP_MISMATCH" != "" ]] ; then
CMP_OFFSET=$(( CMP_MISMATCH + CMP_OFFSET ))
SYNC_OFFSET_BYTES=$(( ( CMP_OFFSET / SYNC_BLOCK_SIZE ) * SYNC_BLOCK_SIZE ))
SYNC_LENGTH_BYTES=$(( SYNC_BLOCK_SIZE ))
echo "Synching $SYNC_LENGTH_BYTES bytes at $SYNC_OFFSET_BYTES from $SOURCE_BASE_LV_PATH to $TARGET_BASE_LV_PATH"
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
else
CMP_OFFSET=""
fi
done
echo Done...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function list()
{
LIST_SOURCE_VG="$1"
LIST_SOURCE_LV="$2"
LIST_TARGET_VG="$BACKUPS"
LIST_TARGET_LV="$LIST_SOURCE_VG-$LIST_SOURCE_LV"
LIST_TARGET_BASE_LV="$LIST_TARGET_LV$SNAP_SUFFIX"
lvs -olv_name | grep "$LIST_TARGET_BASE_LV.$DATE_REGEX"
}

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

function removeall()
{
DATE_OFFSET="$3"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$SNAPSHOT"
done < <(list "$1" "$2" | grep "$FILTER")

}

(
COMMAND="$1"
shift

case "$COMMAND" in
"--help")
echo "Help"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1" "$2"
;;
"list")
list "$1" "$2"
;;
"thindiff")
thindiff "$1" "$2" "$3"
;;
"thinsync")
thinsync "$1" "$2" "$3" "$4"
;;
"verify")
wait_lock_or_terminate
verify "$1" "$2"
;;
"resync")
wait_lock_or_terminate
resync "$1" "$2"
;;
"remove")
wait_lock_or_terminate
remove "$1"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2" "$3"
;;
*)
echo "None.."
;;
esac
) 98>$LOCK_FILE

EOF

Che cosa fa...?Contiene una serie di comandi per manipolare thin snapshot e sincronizzare la differenza tra due thin snapshot ricevuti tramite thin_delta su un altro dispositivo a blocchi utilizzando ddrescue e blkdiscard.

Un altro script che inseriremo in cron:

Un po' più di sfacciataggine#cat >/root/lvm-thin-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

BACKUP_SCRIPT="$SCRIPT_DIR/lvm-thin-backup.sh"
RETENTION="-60 days"

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

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

EOF

Che cosa fa...?Utilizza lo script precedente per creare e sincronizzare i backup dei volumi thin elencati. Lo script lascerà istantanee inattive dei volumi elencati, necessarie per tenere traccia delle modifiche dall'ultima sincronizzazione.

Questo script deve essere modificato, specificando l'elenco dei volumi thin per i quali devono essere effettuate le copie di backup. I nomi indicati sono solo a scopo illustrativo. Se lo desideri, puoi scrivere uno script che sincronizzerà tutti i volumi.

Diamo i diritti:

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

Controlliamolo e inseriamolo nel 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 lancio sarà lungo, perché... i volumi thin verranno completamente sincronizzati copiando tutto lo spazio utilizzato. Grazie ai metadati thin di LVM, sappiamo quali blocchi sono effettivamente in uso, quindi verranno copiati solo i blocchi di volume thin effettivamente utilizzati.

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

Vediamo cosa è successo:

#time /root/btrfs-backup/cron-daily.sh
real 0m2,967s
user 0m0,225s
sys 0m0,353s

#time /root/lvm-thin-backup/cron-daily.sh
real 1m2,710s
user 0m12,721s
sys 0m6,671s

#ls -al /backup/btrfs/back/remote/*
/backup/btrfs/back/remote/boot:
total 0
drwxr-xr-x 1 root root 1260 мар 26 09:11 .
drwxr-xr-x 1 root root 16 мар 6 09:30 ..
drwxr-xr-x 1 root root 322 мар 26 02:00 .@base
drwxr-xr-x 1 root root 516 мар 6 09:39 [email protected]
drwxr-xr-x 1 root root 516 мар 6 09:39 [email protected]
...
/backup/btrfs/back/remote/root:
total 0
drwxr-xr-x 1 root root 2820 мар 26 09:11 .
drwxr-xr-x 1 root root 16 мар 6 09:30 ..
drwxr-xr-x 1 root root 240 мар 26 09:11 @.@base
drwxr-xr-x 1 root root 22 мар 26 09:11 @home.@base
drwxr-xr-x 1 root root 22 мар 6 09:39 @[email protected]
drwxr-xr-x 1 root root 22 мар 6 09:39 @[email protected]
...
drwxr-xr-x 1 root root 240 мар 6 09:39 @[email protected]
drwxr-xr-x 1 root root 240 мар 6 09:39 @[email protected]
...

#lvs -olv_name,lv_size images && lvs -olv_name,lv_size backup
LV LSize
linux-dev 128,00g
linux-dev.base 128,00g
thin-pool 1,38t
win8 128,00g
win8-data 2,00t
win8-data.base 2,00t
win8.base 128,00g
LV LSize
backup 256,00g
images-linux-dev.base 128,00g
images-linux-dev.snap.2020-03-08-10-09-11 128,00g
images-linux-dev.snap.2020-03-08-10-09-25 128,00g
...
images-win8-data.base 2,00t
images-win8-data.snap.2020-03-16-14-11-55 2,00t
images-win8-data.snap.2020-03-16-14-19-50 2,00t
...
images-win8.base 128,00g
images-win8.snap.2020-03-17-04-51-46 128,00g
images-win8.snap.2020-03-18-03-02-49 128,00g
...
thin-pool <2,09t

Cosa c'entra questo con le bambole che nidificano?

Molto probabilmente, dato che i volumi logici LVM LV possono essere volumi fisici LVM PV per altri VG. LVM può essere ricorsivo, come le bambole che nidificano. Ciò conferisce a LVM un'estrema flessibilità.

PS

Nel prossimo articolo, proveremo a utilizzare diversi sistemi di archiviazione mobile/KVM simili come base per creare un cluster di archiviazione/VM geo-distribuito con ridondanza in diversi continenti utilizzando desktop domestici, Internet domestico e reti P2P.

Fonte: habr.com

Aggiungi un commento