Bună ziua.
Vreau să împărtășesc cu comunitatea experiența practică în construirea unui sistem de stocare pentru KVM folosind md RAID + LVM.
În program vor fi:
- Construcția md RAID 1 din NVMe SSD.
- Construcția md RAID 6 din SATA SSD și hard disk-uri obișnuite.
- Particularitățile funcționării TRIM/DISCARD pe SSD RAID 1/6.
- Crearea unui array de boot md RAID 1/6 pe un set comun de discuri.
- Instalarea sistemului pe NVMe RAID 1 în lipsa suportului NVMe în BIOS.
- Utilizarea LVM cache și LVM thin.
- Utilizarea instantaneelor BTRFS și send/receive pentru backup.
- Utilizarea instantaneelor LVM thin și thin_delta pentru backup în stil BTRFS.
Dacă ești interesat, te rog să continui.
Declarație
Autorul nu își asumă nicio responsabilitate pentru consecințele utilizării sau neutilizării materialelor/exemplelor/codului/sfaturilor/datelor din acest articol. Citind sau folosind în vreun fel acest material, îți asumi responsabilitatea pentru toate consecințele acestor acțiuni. Consecințele posibile includ:
- SSD NVMe prăjite până devin crocante.
- Resursa de scriere complet consumată și defectarea SSD-urilor.
- Pierderea totală a tuturor datelor de pe toate unitățile, inclusiv copiile de rezervă.
- Hardware defectuos.
- Timp, nervi și bani irosiți.
- Orice alte consecințe care nu sunt enumerate mai sus.
Hardware
În dotare a fost:
Placă de bază din jurul anului 2013 pe chipset Z87 împreună cu Intel Core i7/Haswell.
- Procesor cu 4 nuclee, 8 fire de execuție
- 32 de gigabytes de memorie RAM DDR3
- 1 x 16 sau 2 x 8 PCIe 3.0
- 1 x 4 + 1 x 1 PCIe 2.0
- 6 x 6 GBps conector SATA 3
Adaptor SAS LSI SAS9211-8I reprogramat în modul IT/HBA. Firmware-ul cu suport RAID a fost înlocuit intenționat cu firmware HBA pentru:
- Pentru a putea oricând să arunc acest adaptor și să-l înlocuiesc cu orice altul la întâmplare.
- TRIM/DISCARD a funcționat normal pe discuri, deoarece în firmware-ul RAID aceste comenzi nu sunt deloc suportate, iar HBA, în general, nu îi pasă ce comenzi transmit pe bus.
Hard disk-uri — 8 bucăți HGST Travelstar 7K1000 cu capacitate de 1 TB în format de 2.5, ca pentru laptopuri. Aceste discuri au fost anterior într-un array RAID 6. În noul sistem vor avea, de asemenea, aplicație. Pentru stocarea copiilor de rezervă locale.
Au fost adăugate suplimentar:
6 unități SATA SSD model Samsung 860 QVO 2TB. Aceste SSD-uri necesitau un volum mare, prezența cache-ului SLC, dorința de fiabilitate și un preț accesibil. Era necesară suportarea discard/zero, care este verificată cu o linie în dmesg:
kernel: ata1.00: Activare discard_zeroes_data
2 unități NVMe SSD model Samsung SSD 970 EVO 500GB.
Pentru aceste SSD-uri, viteza de citire/scriere aleatorie și resursele trebuie să se potrivească nevoilor tale. Un radiator este absolut necesar. Cu siguranță. În mod sigur. Altfel, — le vei frige până devin crocante la prima sincronizare a RAID-ului.
Adaptor StarTech PEX8M2E2 pentru 2 x NVMe SSD cu instalare în slot PCIe 3.0 8x. Acesta este, din nou, simplu un HBA, dar pentru NVMe. Se distinge de adaptoarele ieftine prin absența cerinței de suport pentru bifurcația PCIe de la placa de bază, datorită comutatorului PCIe integrat. Va funcționa chiar și în cele mai vechi sisteme unde există PCIe, chiar dacă este un slot x1 PCIe 1.0. În mod evident, cu viteza corespunzătoare. Nu există RAID-uri acolo. Nu există BIOS integrat la bord. Așadar, sistemul tău nu va învăța magic să pornească de pe NVMe și cu atât mai puțin să facă RAID NVMe datorită acestui dispozitiv.
Componenta a fost determinată exclusiv de faptul că exista doar un singur slot liber 8x PCIe 3.0 în sistem, și, în cazul în care erau disponibile 2 sloturi libere, poate fi ușor înlocuit cu două PEX4M2E1 ieftine sau аналогии, care pot fi cumpărate de oriunde la un preț de la 600 de ruble.
Abandonul tuturor RAID-urilor hardware sau încorporate în chipset/BIOS a fost făcut conștient, cu scopul de a putea înlocui complet sistemul, cu excepția SSD/HDD-urilor, păstrând toate datele. Ideal ar fi să se poată păstra chiar și sistemul de operare instalat atunci când faci tranziția la un hardware complet nou/alt. Important este să existe porturi SATA și PCIe. Este ca un CD live sau un stick USB bootabil, dar foarte rapid și puțin voluminos.
UmorȘi, bineînțeles, știți cum se întâmplă, — uneori trebuie să iei întregul RAID cu tine. Și nu vrei să pierzi datele. Pentru aceasta, toate mediile menționate sunt aranjate convenabil pe șine în bay-uri standard de 5.25.
Ei bine, și, desigur, pentru experimente cu diferite metode de caching SSD în Linux.
RAID-urile hardware sunt plictisitoare. Îi dai drumul. Fie funcționează, fie nu funcționează. Dar cu mdadm întotdeauna există opțiuni.
Software
Anterior, pe hardware a fost instalat Debian 8 Jessie care este aproape de EOL. A fost creat un RAID 6 din HDD-urile menționate mai sus în combinație cu LVM. Pe el rulează mașini virtuale în kvm/libvirt.
Având în vedere că autorul are experiența necesară în crearea unităților portabile de încărcare SATA/NVMe și pentru a nu distruge modelul obișnuit apt, am ales ca sistem țintă Ubuntu 18.04, care s-a stabilizat suficient, dar care mai are încă 3 ani de suport în perspectiva viitoare.
În sistemul menționat există toate driverele necesare pentru hardware din fabrică. Nu este nevoie de software sau drivere externe.
Pregătirea pentru instalare
Pentru instalarea sistemului, avem nevoie de Imaginea Desktop a Ubuntu. Instalatorul sistemului server are un nucleu care manifestă o autonomie excesivă, forțând în mod inexorabil să plaseze partiția UEFI pe unul dintre discuri, stricând astfel întreaga estetică. Prin urmare, se instalează doar în modul UEFI. Nu oferă alte opțiuni.
Acest lucru nu ne mulțumește.
De ce?Din păcate, bootarea UEFI este extrem de incompatibilă cu RAID-ul software de boot, deoarece nu ni se oferă niciun fel de rezervare pentru partiția UEFI ESP. Există diverse soluții care sugerează plasarea partiției ESP pe o unitate flash în portul USB, dar aceasta este un punct de eșec. Există soluții care folosesc RAID 1 software mdadm cu metadate versiunea 0.9, care nu împiedică BIOS-ul UEFI să vadă această partiție, dar aceasta funcționează până când BIOS-ul sau alt sistem de operare de pe hardware scrie ceva în ESP fără a sincroniza cu celelalte oglinzi.
În plus, boot-ul UEFI depinde de NVRAM, care nu se mută împreună cu discurile pe noul sistem, deoarece este parte din placa de bază.
Așadar, nu ne vom complica cu un nou sistem. Deja avem un sistem vechi, testat de ani de zile, numit Legacy/BIOS boot, cu denumirea mândră CSM pe sistemele compatibile UEFI. Îl vom scoate de pe raft, îl vom unge, ne vom umfla roțile și îl vom șterge cu o cârpă umedă.
Versiunea Desktop a Ubuntu nu se poate instala corect cu bootloader-ul Legacy, dar, după cum se spune, aici avem cel puțin opțiuni.
Așadar, adunăm hardware-ul și încărcăm sistemul de pe unitatea flash bootabilă Ubuntu Live. Va trebui să descărcăm pachetele, așa că configurăm rețeaua care a funcționat. Dacă nu a funcționat, pachetele necesare pot fi încărcate pe flash dinainte.
Intrăm în mediul Desktop, deschidem emulatorul de terminal și, să începem:
#sudo bash
Cum...?Linia de mai sus este un declanșator canonic pentru polemici despre sudo. Cu bmareOdată cu cele mai mari posibilități vine și omaremai mare responsabilitate. Întrebarea este dacă vei putea să ți-o asumi. Mulți cred că utilizarea sudo în acest fel este, cel puțin, imprudent. Totuși:

#apt-get install mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc
De ce nu ZFS…?Atunci când instalăm software pe computerul nostru, în esență, împrumutăm puterea de procesare a hardware-ului nostru dezvoltatorilor acestui software.
Atunci când avem încredere în acest software pentru a ne păstra datele, ne asumăm un credit echivalent cu costul de recuperare a acestor date, pe care într-o zi va trebui să-l plătim.
Din acest punct de vedere, ZFS este un Ferrari, iar mdadm+lvm este mai apropiat de o bicicletă.
Subiectiv, autorul preferă să împrumute o bicicletă luată cu credit de la străini în loc de un Ferrari. Acolo și prețul nu este mare. Nu sunt necesare permise. Regulile de circulație sunt mai simple. Parcările sunt gratuite. Manevrabilitatea este mai bună. La bicicletă întotdeauna poți să montezi picioare, iar bicicleta poate fi reparată de tine.
Atunci, de ce BTRFS…?Pentru a încărca sistemul de operare, avem nevoie de un sistem de fișiere acceptat în mod nativ de Legacy/BIOS GRUB, și, în plus, care să susțină instantanee în timp real. O vom folosi pentru partea /boot. În plus, autorul preferă să utilizeze acest sistem de fișiere pentru / (rădăcină), fără a uita să menționeze că pentru orice alt software pot fi create partiții separate pe LVM și montate în directoarele necesare.
Nici imagini mașini virtuale, nici baze de date nu vom păstra pe acest sistem de fișiere.
Acest sistem de fișiere va fi folosit doar pentru a crea instantanee ale sistemului fără a-l opri, cu transferul ulteror al acestor instantanee pe un disc de rezervă prin send/receive.
În plus, autorul preferă să păstreze un minim de software direct pe hardware și să ruleze tot restul de software în mașini virtuale folosind astfel de tehnologii, cum ar fi trecerea GPU-ului și a controlerelor PCI-USB în KVM prin IOMMU.
Pe hardware rămân doar - stocarea datelor, virtualizarea și backup-ul.
Dacă ai mai multă încredere în ZFS, atunci, în principiu, pentru aplicația menționată, acestea sunt interschimbabile.
Cu toate acestea, autorul ignoră în mod conștient funcțiile integrate de mirroring/RAID și redundanță disponibile în ZFS, BTRFS și LVM.
Ca argument suplimentar, BTRFS are proprietatea de a transforma scrierea aleatorie în scriere secvențială, ceea ce influențează foarte pozitiv viteza de sincronizare a instantaneelor / copiilor de rezervă pe HDD.
Re-scanăm toate dispozitivele:
#udevadm control --reload-rules && udevadm trigger
Să ne uităm:
#lsscsi && nvme list
[0:0:0:0] disc ATA Samsung SSD 860 2B6Q /dev/sda
[1:0:0:0] disc ATA Samsung SSD 860 2B6Q /dev/sdb
[2:0:0:0] disc ATA Samsung SSD 860 2B6Q /dev/sdc
[3:0:0:0] disc ATA Samsung SSD 860 2B6Q /dev/sdd
[4:0:0:0] disc ATA Samsung SSD 860 2B6Q /dev/sde
[5:0:0:0] disc ATA Samsung SSD 860 2B6Q /dev/sdf
[6:0:0:0] disc ATA HGST HTS721010A9 A3J0 /dev/sdg
[6:0:1:0] disc ATA HGST HTS721010A9 A3J0 /dev/sdh
[6:0:2:0] disc ATA HGST HTS721010A9 A3J0 /dev/sdi
[6:0:3:0] disc ATA HGST HTS721010A9 A3B0 /dev/sdj
[6:0:4:0] disc ATA HGST HTS721010A9 A3B0 /dev/sdk
[6:0:5:0] disc ATA HGST HTS721010A9 A3B0 /dev/sdl
[6:0:6:0] disc ATA HGST HTS721010A9 A3J0 /dev/sdm
[6:0:7:0] disc ATA HGST HTS721010A9 A3J0 /dev/sdn
Nod SN Model Namespace Utilizare 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
Partiționare «discuri»
NVMe SSD
Nu le vom partiționa deloc. Oricum, BIOS-ul nostru nu vede aceste unități. Așa că, vor merge complet în RAID software. Nici măcar nu vom crea partiții acolo. Dacă doriți să respectați „canonul” sau „principial” – creați o partiție mare, la fel ca pe HDD.
SATA HDD
Aici nu trebuie să inventăm nimic special. Vom crea o partiție pentru toate. O vom crea deoarece aceste unități sunt vizibile pentru BIOS și chiar ar putea încerca să booteze de pe ele. Chiar vom instala mai târziu GRUB pe aceste unități pentru ca sistemul să reușească să pornească brusc.
#cat >hdd.part << EOF
eticheta: dos
eticheta-id: 0x00000000
dispozitiv: /dev/sdg
unitate: sectoare
/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
Aici e cel mai interesant.
În primul rând, unitățile noastre au o capacitate de 2 TB. Aceasta este în limita acceptabilă pentru MBR, ceea ce vom folosi. Dacă este necesar, se poate înlocui cu GPT. Unitățile GPT au un strat de compatibilitate care permite sistemelor compatibile cu MBR să vadă primele 4 partiții dacă acestea sunt situate în limitele primelor 2 terabaiți. Principalul lucru este ca partiția de boot și partiția bios_grub de pe aceste unități să fie la început. Aceasta permite chiar și pornirea din Legacy/Bios de pe unități GPT.
Dar, acesta nu este cazul nostru.
Aici vom crea două partiții. Prima va avea o dimensiune de 1 GB și va fi utilizată pentru RAID 1 /boot.
A doua va fi utilizată pentru RAID 6 și va ocupa tot spațiul liber rămas, cu excepția unei mici zone nepartiționate la sfârșitul unității.
Ce este zona nepartiționată?Conform surselor de pe internet, unitățile noastre SATA SSD au un cache SLC dinamic, extins de la 6 la 78 de gigaocteți. Obținem 6 gigaocteți „gratuite” datorită diferenței dintre „gigaocteți” și „gibiocteți” în pașaportul tehnic al unității. Cei 72 de gigaocteți rămași sunt alocați din spațiul neutilizat.
Aici trebuie menționat că, cache-ul nostru este SLC, iar spațiul este ocupat în modul 4 bit MLC. Ce înseamnă eficient pentru noi este că pentru fiecare 4 gigaocteți de spațiu liber, obținem doar 1 gigaoctet de cache SLC.
Înmulțim 72 de gigaocteți cu 4 și obținem 288 de gigaocteți. Aceasta este zona liberă pe care nu o vom partiționa, pentru a permite unităților să utilizeze pe deplin cache-ul SLC.
Astfel, vom obține eficient până la 312 GB de cache SLC în total de la șase unități de stocare. Dintre toate unitățile, 2 vor fi utilizate în RAID pentru redundanță.
Această cantitate de cache ne va permite să ne confruntăm extrem de rar în practică cu situații în care scrierea se face fără cache. Acest lucru compensează extrem de bine cea mai dezavantajoasă caracteristică a memoriei QLC, - viteza de scriere foarte scăzută atunci când datele sunt scrise în afara cache-ului. Dacă sarcinile dvs. nu se potrivesc, atunci vă recomand să vă gândiți serios cât timp vor rezista SSD-urile dvs. la o astfel de sarcină, având în vedere TBW din specificațiile tehnice.
#cat >ssd.part << EOF
eticheta: dos
eticheta-id: 0x00000000
dispozitiv: \/dev\/sda
unitate: sectoare
/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
Crearea de array-uri
Pentru început, trebuie să redenumim mașina. Este necesar deoarece numele gazdelor face parte din numele array-ului undeva în interiorul mdadm și influențează ceva. Array-urile pot fi redenumite mai târziu, dar aceasta ar fi o acțiune suplimentară.
#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
De ce -assume-clean...?Pentru a nu inițializa array-urile. Pentru ambele niveluri RAID 1 și 6 acest lucru este acceptabil. Totul poate funcționa și fără inițializare, dacă este un array nou. Mai mult, inițializarea unui array SSD la crearea sa este o pierdere de resursă TBW. Folosim TRIM/DISCARD unde este posibil pe array-urile SSD pentru a le „inițializa”.
Pentru array-urile SSD RAID 1, DISCARD este suportat din fabrică.
Pentru array-urile SSD RAID 6, DISCARD trebuie activat în parametrii modulului kernel.
Acest lucru ar trebui făcut doar în cazul în care toate SSD-urile utilizate în array-urile de nivel 4/5/6 din acest sistem au suport funcțional pentru discard_zeroes_data. Uneori apar unități ciudate care informează kernelul despre suportul acestei funcții, dar în realitate nu există, sau funcția nu funcționează întotdeauna. În prezent, suportul există aproape peste tot, totuși, unitățile vechi și firmware-urile cu erori sunt întâlnite. Din acest motiv, suportul DISCARD este dezactivat implicit pentru RAID 6.
Atenție, următoarea comandă va distruge toate datele de pe unitățile NVMe „inițializând” array-ul „cu zero”.
#blkdiscard /dev/md0
Dacă ceva nu a mers bine, încercați să specificați pasul.
#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
De ce atât de mare...?Creșterea chunk-size influențează pozitiv viteza de citire aleatorie bloc cu bloc până la chunk-size inclusiv. Acest lucru se întâmplă deoarece o operațiune de dimensiune corespunzătoare sau mai mică poate fi finalizată complet pe un singur dispozitiv. Prin urmare, IOPS de la toate dispozitivele se acumulează. Potrivit statisticilor, 99% din IO nu depășește 512K.
În RAID 6, IOPS la scriere întotdeauna este mai mic sau egal cu IOPS de la un singur dispozitiv. Pe de altă parte, în cazul citirii aleatoare, IOPS poate fi de câteva ori mai mare decât cel de la un singur dispozitiv, iar dimensiunea blocului joacă un rol esențial.
Autorul nu vede sensul încercărilor de a optimiza un parametru care este slab în RAID 6 din design și, în schimb, optimizează ceea ce RAID 6 face bine.
Scrierea aleatoare slabă în RAID 6 va fi compensată cu un cache pe NVMe și trucuri cu thin-provisioning.
Deocamdată, nu am activat DISCARD pentru RAID 6. Așa că nu vom «inițializa» acest volum încă. Ne ocupăm de asta mai târziu, după instalarea sistemului de operare.
SATA HDD
#mdadm --create --verbose --assume-clean /dev/md3 --chunk-size=512 --level=6 --raid-devices=8 /dev/sd[g-n]1
LVM pe RAID NVMe
Pentru viteză, dorim să plasăm sistemul de fișiere rădăcină pe RAID 1 NVMe, care este /dev/md0.
Cu toate acestea, acest volum rapid va mai fi necesar pentru alte nevoi, cum ar fi swap, metadate și cache LVM-cache și metadate LVM-thin, așa că pe acest volum vom crea VG LVM.
#pvcreate /dev/md0
#vgcreate root /dev/md0
Să creăm o partiție pentru sistemul de fișiere rădăcină.
#lvcreate -L 128G --name root root
Să creăm o partiție pentru swap de dimensiunea memoriei RAM.
#lvcreate -L 32G --name swap root
Instalarea sistemului de operare
Prin urmare, avem tot ce este necesar pentru a instala sistemul.
Lansăm wizardul de instalare a sistemului din mediul Ubuntu Live. Instalare obișnuită. Numai că, în etapa de alegere a discurilor pentru instalare, trebuie menționat următoarele:
- /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), — использовать как раздел подкачки
- Instalatorul se va plasa pe /dev/sda
Când alegi BTRFS ca sistem de fișiere rădăcină, instalatorul va crea automat două volume BTRFS cu numele "@" pentru / (rădăcină) și "@home" pentru /home.
Inițiem instalarea...
Instalarea se va termina cu un dialog modal care anunță o eroare la instalarea bootloader-ului. Din păcate, nu va fi posibil să ieși din acest dialog prin metode standard și să continui instalarea. Facem logout din sistem și ne conectăm din nou, ajungând pe un desktop curat Ubuntu Live. Deschidem terminalul și din nou:
#sudo bash
Creăm un mediu chroot pentru a continua instalarea:
#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
Configurăm rețeaua și hostname-ul în chroot:
#cat /etc/hostname >/mnt/chroot/etc/hostname
#cat /etc/hosts >/mnt/chroot/etc/hosts
#cat /etc/resolv.conf >/mnt/chroot/etc/resolv.conf
Intru în mediul chroot:
#chroot /mnt/chroot
Primul lucru pe care îl facem este să livrăm pachetele:
apt-get install --reinstall mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc debsums hdparm
Verificăm și reparăm toate pachetele care s-au instalat greșit din cauza instalării incomplete a sistemului:
#CORRUPTED_PACKAGES=$(debsums -s 2>&1 | awk '{print $6}' | uniq)
#apt-get install --reinstall $CORRUPTED_PACKAGES
Dacă ceva nu a funcționat, s-ar putea să trebuia să editați mai întâi /etc/apt/sources.list
Vom ajusta parametrii pentru modulul RAID 6 pentru a activa TRIM/DISCARD:
#cat >/etc/modprobe.d/raid456.conf << EOF
opțiuni raid456 dispozitive_tratare_răzuit_sigur=1
EOF
Vom ajusta puțin volumul nostru:
#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
Ce a fost asta..?Am creat un set de reguli udev care vor face următoarele:
- Setați o dimensiune adecvată a memoriei cache pentru RAID 6 pentru anul 2020. Valoarea implicită, pare că nu s-a schimbat din vremurile creației Linux și nu mai este adecvată de mult timp.
- Rezervați o minimă capacitate IO în timpul verificărilor/sincronizărilor array-urilor. Acest lucru este necesar pentru a evita ca array-urile dumneavoastră să rămână în starea de sincronizare continuă sub sarcină.
- Limitați capacitatea maximă IO în timpul verificărilor/sincronizărilor array-urilor. Acest lucru este necesar pentru a preveni arderea unităților SSD RAID până la un punct de criză. Este deosebit de important pentru NVMe. (Țineți minte despre radiator? Nu glumeam.)
- Interziceți prin APM oprirea rotației axului (HDD) și setați un timp de așteptare pentru somnul controlerelor de discuri la 7 ore. Puteți dezactiva complet APM dacă unitățile dumneavoastră o permit (-B 255). Cu valoarea implicită, unitățile vor opri după cinci secunde. Apoi, sistemul de operare va dori să reseteze memoria cache a discului, unitățile se vor roti din nou și totul va lua de la capăt. Unitățile au un număr maxim limitat de rotații ale axului. Acest ciclu simplu implicit poate ucide ușor unitățile dumneavoastră în câțiva ani. Nu toate unitățile suferă de această problemă, dar ale noastre, „de laptop”, cu setările corespunzătoare implicite, transformă RAID-ul într-o versiune imperfectă de mini-MAID.
- Setați readahead pentru unitățile (rotative) la 1 megabyte - două blocuri consecutive/chunk RAID 6
- Interziceți readahead pe array-uri.
Să edităm /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
De ce așa..?Partiția /boot o vom căuta după UUID deoarece denumirea array-urilor se poate schimba teoretic.
Celelalte partiții le vom căuta după numele LVM în notația /dev/mapper/vg-lv, deoarece acestea identifică suficient de unic partițiile.
Nu folosim UUID pentru LVM deoarece UUID-urile volumelor LVM și ale snapshot-urilor acestora pot coincide.Montăm de două ori /dev/mapper/root-root..?Da. Exact așa. Aceasta este o caracteristică a BTRFS. Această FS poate fi montată de mai multe ori cu subvolumuri diferite.
Din cauza aceleași caracteristici, recomand să nu creați niciodată snapshot-uri LVM pentru volume active BTRFS. Ați putea avea o surpriză la repornire.
Regenerăm configurația mdadm:
#/usr/share/mdadm/mkconf | sed 's/#DEVICE/DEVICE/g' >/etc/mdadm/mdadm.conf
Să corectăm setările 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
Ce a fost asta..?Am activat extinderea automată a pool-urilor LVM thin la atingerea a 90% din locul ocupat cu 5% din volum.
Am crescut numărul maxim de blocuri de cache pentru LVM cache.
Am interzis LVM să caute volume LVM (PV) pe:
- dispozitive care conțin LVM cache (cdata)
- dispozitivele cache-uite prin LVM cache pe ocolind cache-ul (<lv_name>_corig). Totuși, dispozitivul cache-uit va fi scanat prin cache (pur și simplu <lv_name>).
- dispozitive care conțin metadatele LVM cache (cmeta)
- toate dispozitivele din VG numit images. Aici vor fi imaginile discurilor mașinilor virtuale, și nu dorim ca LVM pe gazdă să activeze volumele aparținând sistemului de operare invitat.
- toate dispozitivele din VG numit backup. Aici vor fi backup-urile imaginilor mașinilor virtuale.
- toate dispozitivele al căror nume se termină cu „gpv” (guest physical volume)
Am activat suportul DISCARD la eliberarea spațiului liber pe LVM VG. Fiți atenți. Acest lucru va face ca ștergerea LV pe SSD să fie destul de lungă. În special, se aplică SSD RAID 6. Totuși, planificăm să folosim thin provisioning, așa că, aceasta nu ne va afecta deloc.
Vom actualiza imaginea initramfs:
#update-initramfs -u -k all
Vom instala și configura grub:
#apt-get install grub-pc
#apt-get purge os-prober
#dpkg-reconfigure grub-pc
Ce discuri să alegem?Toate cele care sunt sd*. Sistemul ar trebui să fie capabil să pornească de pe orice disc SATA sau SSD funcțional.
De ce am dezactivat os-prober..?Pentru prea multă independență și mâini neastâmărate.
Nu funcționează corect dacă unul dintre RAID-uri se află în stare degradată. Încearcă să caute OS pe partițiile utilizate în mașinile virtuale care rulează pe acest hardware.
Dacă îți este util, poți să-l lași, dar fii conștient de tot ce s-a menționat mai sus. Recomand să cauți rețete pentru a scăpa de mâinile neastâmărate pe internet.
Am finalizat instalarea inițială. A sosit timpul să ne repornim în OS-ul proaspăt instalat. Nu uitați să scoateți CD-ul/USB-ul Live de boot.
#exit
#reboot
Ca dispozitiv de boot alegem oricare dintre SSD-urile SATA.
LVM pe SSD SATA
Până acum ne-am pornit în noul OS, am configurat rețeaua, apt, am deschis emulatorele de terminal și am rulat:
#sudo bash
O să continuăm.
«Inițializăm» array-ul de SSD-uri SATA:
#blkdiscard /dev/md2
Dacă nu a mers, încercăm:
#blkdiscard --step 65536 /dev/md2
Creăm LVM VG pe SSD SATA:
#pvcreate /dev/md2
#vgcreate data /dev/md2
De ce încă un VG..?De fapt, avem deja un VG numit root. De ce să nu adăugăm totul într-un singur VG?
Dacă există mai multe PV în VG, atunci pentru activarea corectă a VG, toate PV-urile trebuie să fie prezente (online). O excepție este LVM RAID, pe care intenționăm să nu-l folosim.
Ne dorim foarte mult ca, în cazul unei defecțiuni (citiți pierdere de date) pe oricare dintre matricele RAID 6, sistemul de operare să pornească normal și să ne ofere posibilitatea de a rezolva problema.
Pentru aceasta, la primul nivel de abstracție, vom izola fiecare tip de „mediu” fizic într-un VG separat.
Dacă vrem să fim științifici, diferitele matrice RAID aparțin diferitelor „domenii de fiabilitate”. Nu ar trebui să creăm un punct adițional de eșec comun, introducându-le într-un singur VG.
Prezența LVM la nivel „hardware” ne va permite să împărțim aleator diverse matrice RAID în moduri diferite, combinându-le. De exemplu, să lansăm simultan bcache + LVM thin, bcache + BTRFS, LVM cache + LVM thin, o configurație complexă ZFS cu cache-uri sau orice alt amestec provocator, pentru a explora și a compara totul.
La nivel „hardware”, nu vom folosi nimic altceva decât vechile „volume groase” LVM. O excepție de la această regulă poate fi partiția pentru backup.
Cred că, până în acest moment, mulți cititori au început să suspecteze ceva în legătură cu matrioșka.
LVM pe HDD SATA
#pvcreate /dev/md3
#vgcreate backup /dev/md3
Încă un nou VG..?Ne dorim foarte mult ca, în cazul unei defecțiuni a matricei de discuri pe care o vom folosi pentru backup-ul datelor, sistemul nostru de operare să continue să funcționeze normal, păstrând totodată accesul la datele nerezerve. Din acest motiv, pentru a evita problemele de activare a VG-ului, creăm un VG separat.
Configurarea LVM cache
Vom crea un LV pe RAID NVMe 1 pentru a-l folosi ca dispozitiv de cache.
#lvcreate -L 70871154688B --name cache root
De ce atât de puțin...?Realitatea este că SSD-urile noastre NVMe au, de asemenea, un cache SLC. 4 gigabytes „gratuit” și 18 gigabytes dinamici din spațiul liber ocupat în MLC 3-bit. Odată ce acest cache se epuizează, SSD-urile NVMe nu vor fi semnificativ mai rapide decât SSD-urile noastre SATA cu cache. De fapt, din acest motiv, nu este logic să facem o partiție LVM cache mult mai mare decât volumul de două ori al cache-ului SLC al unității NVMe. Pentru unitățile NVMe utilizate, autorul consideră prudent să facă 32-64 de gigabytes de cache.
Dimensinea dată a partiției este necesară pentru organizarea a 64 de gigabytes de cache, plasarea metadatelor cache-ului și backup-ul metadatelor.
De asemenea, voi menționa că după o oprire bruscă a sistemului, LVM va marca întreaga memorie cache ca fiind murdară și va sincroniza din nou. Mai mult, acest lucru se va repeta la fiecare utilizare a comenzii lvchange pe acest dispozitiv până la o nouă repornire a sistemului. Prin urmare, recomand recrearea imediată a memoriei cache cu scriptul corespunzător.
Vom crea un LV pe SATA RAID 6 pentru a-l folosi ca dispozitiv de memorie cache.
#lvcreate -L 3298543271936B --name cache data
De ce doar trei terabiți..?Pentru a putea folosi, dacă este necesar, SATA SSD RAID 6 pentru alte scopuri. Dimensiunea spațiului cache poate fi mărită dinamic, pe loc, fără oprirea funcționării sistemului. Pentru aceasta, este necesar să opriți temporar și să reporniți memoria cache, dar un avantaj distinct al LVM-cache comparativ cu, de exemplu, bcache, este că aceasta poate fi realizată fără a opri sistemul.
Vom crea un nou VG pentru memorie cache.
#pvcreate /dev/root/cache
#pvcreate /dev/data/cache
#vgcreate cache /dev/root/cache /dev/data/cache
Vom crea un LV pe dispozitivul de memorie cache.
#lvcreate -L 3298539077632B --name cachedata cache /dev/data/cache
Aici am ocupat imediat tot spațiul liber pe /dev/data/cache astfel încât toate celelalte partiții necesare să fie create imediat pe /dev/root/cache. Dacă ați creat ceva în altă parte, îl puteți muta cu ajutorul pvmove.
Vom crea și activa memoria 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
De ce un chunksize atât de mic..?Prin experimente practice, autorul a descoperit că cel mai bun rezultat este obținut dacă dimensiunea blocului LVM cache coincide cu dimensiunea blocului LVM thin. În acest caz, cu cât dimensiunea este mai mică, cu atât configuranția se comportă mai bine în cazul scrierilor aleatorii.
64k este dimensiunea minimă a unui bloc permisă pentru LVM thin.
Fiți atenți cu writeback..!Da. Acest tip de memorie cache întârzie sincronizarea scrierii pe dispozitivul de memorie cache. Acest lucru duce la pierderea de date pe dispozitivul de memorie cache în cazul pierderii cache-ului. Mai târziu, autorul va discuta despre ce măsuri, pe lângă NVMe RAID 1, se pot lua pentru a compensa acest risc.
Acest tip de memorie cache a fost ales intenționat pentru a compensa performanța scăzută a RAID 6 în cazul scrierilor aleatorii.
Să verificăm ce am realizat:
#lvs -a -o lv_name,lv_size,devices --units B cache
Dispozitive LSize LV
[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)
Pe /dev/data/cache ar trebui să se afle doar [cachedata_corig]. Dacă ceva nu este în regulă, folosiți pvmove.
Pentru a dezactiva memoria cache, este suficient să folosiți o comandă:
#lvconvert -y --uncache cache/cachedata
Aceasta se face online. LVM pur și simplu sincronizează memoria cache pe disc, o elimină și redenumește cachedata_corig înapoi în cachedata.
Configurarea LVM thin
Să estimăm aproximativ câte spațiu ne va trebui pentru metadatele LVM thin:
#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - 3385794560 bytes dimensiunea estimată a zonei de metadate pentru "--block-size=64kibibytes --pool-size=6terabytes --max-thins=100000"
Să rotunjim la 4 gigabiți: 4294967296B
Înmulțim cu doi și adăugăm 4194304B pentru meta datele LVM PV: 8594128896B
Vom crea o partiție separată pe NVMe RAID 1 pentru a stoca meta datele LVM thin și copia lor de rezervă:
#lvcreate -L 8594128896B --name images root
De ce..?Aici poate apărea întrebarea, de ce să stocăm meta datele LVM thin separat, dacă oricum vor fi cache-uite pe NVMe și vor funcționa rapid.
Viteza este importantă aici, dar nu este principala cauză. Totul se reduce la faptul că cache-ul este un punct de eșec. Se poate întâmpla ceva cu el, iar dacă meta datele LVM thin sunt cache-uite, acest lucru va duce la pierderea totală a tot. Fără meta date întregi, reconstruirea volumelor thin va fi practic imposibilă.
Prin mutarea meta datelor pe un volum rapid, dar ne-cache-uit, garantăm păstrarea meta datelor în cazul pierderii sau deteriorării cache-ului. În acest caz, toate deteriorările cauzate de pierderea cache-ului vor fi localizate în interiorul volumelor thin, ceea ce va simplifica semnificativ procedura de recuperare. Cu o mare probabilitate, aceste deteriorări vor putea fi recuperate folosind jurnalele FS.
Mai mult, dacă anterior a fost realizat un instantaneu al volumului thin și, după aceea, cache-ul a fost sincronizat complet măcar o dată, atunci, datorită particularităților interne ale LVM thin, integritatea instantaneului va fi garantată în cazul pierderii cache-ului.
Vom crea un nou VG care va răspunde de thin-provisioning:
#pvcreate /dev/root/images
#pvcreate /dev/cache/cachedata
#vgcreate images /dev/root/images /dev/cache/cachedata
Vom crea un pool:
#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
De ce -Z yÎn afară de ceea ce este destinat acest mod — de a nu permite datelor dintr-o mașină virtuală să evapore în altă mașină virtuală la redistribuirea spațiului — zeroing este utilizat suplimentar pentru a crește viteza scrierii aleatorii cu blocuri mai mici de 64k. Orice scriere sub 64k într-o zonă care nu a fost alocată anterior a volumului thin va deveni 64K aliniat pe marginea cache-ului. Acest lucru va permite efectuarea operației complet prin cache, ocolind dispozitivul cache-uit.
Vom muta LV pe PV-urile corespunzătoare:
#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
Să verificăm:
#lvs -a -o lv_name,lv_size,devices --units B images
Dispozitive LSize LV
[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)
Vom crea un volum thin pentru teste:
#lvcreate -V 64G --thin-pool thin-pool --name test images
Vom instala pachetele pentru teste și observație:
#apt-get install sysstat fio
Așa putem observa comportamentul configurației noastre de stocare în timp real:
#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)'
Așa putem testa configurația noastră:
#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
Atenție! Resursă!Acest cod va rula 36 de teste diferite, fiecare dintre ele fiind executat timp de 4 secunde. Jumătate dintre teste sunt de scriere. În 4 secunde pe un NVMe se pot scrie foarte multe date. Până la 3 gigaocteți pe secundă. Așadar, fiecare execuție a testelor de scriere poate consuma până la 216 gigaocteți din resursele SSD.
Citire și scriere alternată?Da. Testele de citire și scriere ar trebui să fie rulante separat. Mai mult, este important să ne asigurăm că toate cache-urile sunt sincronizate, astfel încât scrierea anterioară să nu afecteze citirea.
Rezultatele vor varia semnificativ la prima execuție și ulterior pe măsură ce cache-ul și volumul subțire se umplu, precum și în funcție de timpul necesar sistemului pentru a sincroniza cache-urile umplute la execuția anterioară.
Printre altele, recomand măsurarea vitezei pe un volum subțire deja umplut, de la care tocmai a fost creat un snapshot. Autorul a observat cum scrierea aleatoare devine brusc mai rapidă imediat după crearea primului snapshot, mai ales atunci când cache-ul nu este complet umplut. Aceasta se datorează semanticei copy-on-write a scrierii, alinierea blocurilor cache și volumului subțire, și faptului că scrierea aleatoare pe RAID 6 se transformă într-o citire aleatoare cu RAID 6 urmată de o scriere în cache. În configurația noastră, citirea aleatoare cu RAID 6 este de până la 6 ori (numărul de SSD-uri SATA în array) mai rapidă decât scrierea. Deoarece blocurile pentru CoW sunt alocate secvențial din puțul subțire, scrierea se transformă, în mare parte, și în secvențială.
Ambele aceste caracteristici pot fi utilizate în avantajul nostru.
Snapshot-uri de cache "coerente"
Pentru a reduce riscul de pierdere a datelor în cazul deteriorării/pierderii cache-ului, autorul propune introducerea unei practici de rotație a snapshot-urilor care garantează integritatea acestora în acest caz.
În primul rând, datorită faptului că metadatele volumelor subțiri se află pe un dispozitiv necache-uit, metadatele vor fi intacte, iar eventualele pierderi vor fi izolate în blocurile de date.
Următorul ciclu de rotație a snapshot-urilor oferă o garanție a integrității datelor în interiorul snapshot-urilor în cazul pierderii cache-ului:
- Pentru fiecare volum subțire cu numele <nume>, creăm un snapshot cu numele <nume>.cached
- Să stabilim pragul de migrare la o valoare rezonabil de mare:
#lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata - În ciclu, verificăm numărul de blocuri murdare din cache:
#lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}'până nu obținem zero. Dacă zero nu este disponibil prea mult timp, poate fi creat temporar prin trecerea cache-ului în modul writethrough. Totuși, având în vedere caracteristicile de viteză ale aranjamentelor noastre SATA și NVMe SSD, precum și resursa lor TBW, fie veți putea prinde momentul suficient de repede fără a schimba modul cache-ului, fie hardware-ul dvs. va consuma complet întreaga sa resursă în câteva zile. Din cauza limitărilor de resursă, sistemul nu poate fi, în principiu, supus constant unei încărcări de 100% pe scriere. SSD-urile noastre NVMe sub o sarcină constantă de 100% pe scriere vor epuiza complet resursa în 3-4 zile. SSD-urile SATA vor dura de două ori mai mult. Așadar, vom considera că cea mai mare parte a sarcinii este pe citire, iar pe scriere avem, - în mod relativ, vârfuri scurte de activitate extrem de intensă, în combinație cu o sarcină scăzută în medie. - Odată ce am prins (sau am creat) zero - redenumim <name>.cached în <name>.committed. Stergem vechiul <name>.committed.
- Opțional, dacă cache-ul este umplut la 100%, acesta poate fi recreat cu un script, astfel curățându-l. Cu un cache semi-gol, sistemul funcționează mult mai repede pe scriere.
- Să setăm migration threshold la zero:
#lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedataAceasta va interzice temporar sincronizarea cache-ului pe suportul principal. - Așteptăm până când în cache se acumulează suficiente modificări
#lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}'sau începem timer-ul. - Repetăm din nou.
De ce complicarea cu migration threshold…?Totul se reduce la faptul că, în practică, scrierea „aleatoare” nu este de fapt atât de aleatoare. Dacă am scris ceva într-un sector de 4 kilobyte, există o mare probabilitate ca în următoarele câteva minute să se facă o scriere în acest sector sau în unul dintre cele adiacente (+- 32K).
Setând migration threshold la zero, amânăm sincronizarea scrierii pe SSD-uri SATA și agregăm mai multe modificări ale unui bloc de 64K în cache. Astfel, se economisește semnificativ resursa SSD-urilor SATA.
Și codul unde..?Din păcate, autorul se consideră insuficient de competent în dezvoltarea scripturilor bash, deoarece este 100% autodidact și practică „dezvoltarea ghidată de google”, așa că consideră că acel cod înfricoșător care iese din mâinile sale nu ar trebui folosit de nimeni altcineva.
Cred că profesioniștii în domeniu vor putea să reprezinți singuri toată logica descrisă mai sus în caz de necesitate și, poate, chiar să o organizeze frumos sub forma unui serviciu systemd, așa cum a încercat să facă autorul.
O așa simplă schemă de rotație a instantaneelor ne va permite nu doar să avem constant un instantaneu complet sincronizat pe SSD-ul SATA, ci și să folosim utilitarul thin_delta pentru a identifica ce blocuri au fost modificate după crearea acestuia, facilitând astfel localizarea daunelor pe volumele principale, făcând recuperarea mult mai simplă.
TRIM/DISCARD în libvirt/KVM
Deoarece stocarea datelor va fi utilizată pentru KVM sub libvirt, ar fi bine să învățăm VM-urile noastre nu doar să ocupe spațiu liber, ci și să elibereze ce nu mai este necesar.
Acest lucru se face prin simularea suportului TRIM/DISCARD pe discurile virtuale. Pentru aceasta, trebuie să schimbăm tipul controlerului în virtio-scsi și să modificăm xml-ul.
#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>
Astfel de DISCARD-uri din OS-urile oaspete sunt gestionate corect de LVM, iar blocurile sunt eliberate atât în cache, cât și în pool-ul subțire. În cazul nostru, acest lucru se întâmplă, în principal, în mod întârziat, la ștergerea unui instantaneu.
Backup BTRFS
Utilizați scripturi gata făcute cu extremă prudență și pe riscul dumneavoastră. Autorul a scris acest cod singur și exclusiv pentru sine. Sunt sigur că mulți utilizatori experimentați Linux au astfel de soluții improvizate, iar copierea celorlalte nu va fi necesară.
Vom crea un volum pe dispozitivul de rezervă:
#lvcreate -L 256G --name backup backup
Formatăm în BTRFS:
#mkfs.btrfs /dev/backup/backup
Vom crea puncte de montare și vom monta subdirectoarele rădăcină ale FS:
#mkdir /backup
#mkdir /backup/btrfs
#mkdir /backup/btrfs/root
#mkdir /backup/btrfs/back
#ln -s /boot /backup/btrfs
# cat >>/etc/fstab << EOF
/dev/mapper/root-root /backup/btrfs/root btrfs defaults,space_cache,noatime,nodiratime 0 2
/dev/mapper/backup-backup /backup/btrfs/back btrfs defaults,space_cache,noatime,nodiratime 0 2
EOF
#mount -a
#update-initramfs -u
#update-grub
Vom crea directoare pentru backup-uri:
#mkdir /backup/btrfs/back/remote
#mkdir /backup/btrfs/back/remote/root
#mkdir /backup/btrfs/back/remote/boot
Vom crea un director pentru scripturile de backup:
#mkdir /root/btrfs-backup
Vom copia scriptul:
Mult cod bash terifiant. Utilizați pe răspunderea dumneavoastră. Nu trimiteți scrisori furioase autorului...#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 "Așteptând pentru lock..."
wait_lock || terminate "Nu s-a reușit obținerea lock-ului. Ieșire..."
echo "Lock obținut..."
}
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"
dacă [ -d "$SOURCE_BASE_PATH" ]
atunci
echo "$SOURCE_BASE_PATH găsit"
altfel
echo "$SOURCE_BASE_PATH Fișierul nu a fost găsit, se creează un snapshot din $SOURCE_PATH în $SOURCE_BASE_PATH"
btrfs subvolum snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
dacă [ -d "$TARGET_BASE_PATH" ]
atunci
echo "$TARGET_BASE_PATH găsit, sincronizarea cu sursa este absentă... se șterge..."
btrfs subvolum delete -c $TARGET_BASE_PATH
sync
fi
fi
dacă [ -d "$TARGET_BASE_PATH" ]
atunci
echo "$TARGET_BASE_PATH găsit"
altfel
echo "$TARGET_BASE_PATH nu a fost găsit. Sincronizând la $TARGET_BASE_DIR"
btrfs send $SOURCE_BASE_PATH | btrfs receive $TARGET_BASE_DIR
sync
fi
dacă [ -d "$SOURCE_PEND_PATH" ]
atunci
echo "$SOURCE_PEND_PATH găsit, se șterge..."
btrfs subvolum delete -c $SOURCE_PEND_PATH
sync
fi
btrfs subvolum snapshot -r $SOURCE_PATH $SOURCE_PEND_PATH
sync
dacă [ -d "$TARGET_PEND_PATH" ]
atunci
echo "$TARGET_PEND_PATH găsit, se șterge..."
btrfs subvolum delete -c $TARGET_PEND_PATH
sync
fi
echo "Trimit $SOURCE_PEND_PATH la $TARGET_PEND_PATH"
btrfs send -p $SOURCE_BASE_PATH $SOURCE_PEND_PATH | btrfs receive $TARGET_BASE_DIR
sync
TARGET_DATE_SUFFIX=$(suffix)
btrfs subvolum snapshot -r $TARGET_PEND_PATH "$TARGET_PATH$TARGET_DATE_SUFFIX"
sync
btrfs subvolum delete -c $SOURCE_BASE_PATH
sync
btrfs subvolum delete -c $TARGET_BASE_PATH
sync
mv $SOURCE_PEND_PATH $SOURCE_BASE_PATH
mv $TARGET_PEND_PATH $TARGET_BASE_PATH
sync
}
funcție 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)"
găsește "$LIST_TARGET_BASE_DIR" -maxdepth 1 -mindepth 1 -type d -printf "%fn" | grep "${LIST_TARGET_BASE_NAME/$BASE_SUFFIX/$SNAP_SUFFIX}.$DATE_REGEX"
}
funcție remove()
{
REMOVE_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
REMOVE_TARGET_BASE_DIR="$(dirname $REMOVE_TARGET_BASE_PATH)"
btrfs subvolum delete -c $REMOVE_TARGET_BASE_DIR/$2
sync
}
funcție removeall()
{
DATE_OFFSET="$2"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$1" "$SNAPSHOT"
done < <(list "$1" | grep "$FILTER")
}
(
COMMAND="$1"
shift
caz "$COMMAND" în
"--help")
echo "Ajutor"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
așteaptă_blocare_ sau_termină
backup "$1"
;;
"list")
list "$1"
;;
"remove")
așteaptă_blocare_ sau_termină
remove "$1" "$2"
;;
"removeall")
așteaptă_blocare_ sau_termină
removeall "$1" "$2"
;;
*)
echo "Nimic.."
;;
esac
) 98>$LOCK_FILE
EOF
Ce face de fapt..?Conține un set simplu de comenzi pentru crearea de snapshots BTRFS și copierea acestora pe un alt FS prin BTRFS send/receive.
Prima rulare poate dura relativ mult, deoarece toate datele vor fi copiate la început. Rulările ulterioare vor fi foarte rapide, deoarece vor fi copiate doar modificările.
Încă un script pe care îl vom introduce în cron:
Încă puțin cod 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"
RETENȚIE="-60 zile"
$BACKUP_SCRIPT backup root/@
$BACKUP_SCRIPT removeall root/@ "$RETENȚIE"
$BACKUP_SCRIPT backup root/@home
$BACKUP_SCRIPT removeall root/@home "$RETENȚIE"
$BACKUP_SCRIPT backup boot/
$BACKUP_SCRIPT removeall boot/ "$RETENȚIE"
EOF
Ce face acesta..?Creează și sincronizează pe FS de backup snapshots incremental ale volumelor BTRFS enumerate. După aceasta, va șterge toate snapshots create acum 60 de zile. După rulare, în subdirectoarele /backup/btrfs/back/remote/ vor apărea snapshots datate ale volumelor enumerate.
Să dăm codului permisiuni de execuție:
#chmod +x /root/btrfs-backup/cron-daily.sh
#chmod +x /root/btrfs-backup/btrfs-backup.sh
Să verificăm și să-l adăugăm în 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
Să creăm un pool subțire pe dispozitivul de rezervă:
#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T backup/thin-pool
Să instalăm ddrescue, deoarece scripturile vor folosi acest instrument:
#apt-get install gddrescue
Vom crea un catalog pentru scripturi:
#mkdir /root/lvm-thin-backup
Vom copia scripturile:
Foarte multe bash în interior...#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 "Așteptând pentru lock..."
wait_lock || terminate "Nu s-a reușit obținerea lock-ului. Ieșire..."
echo "Lock obținut..."
}
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" == "" ]
atunci
(>&2 echo "LV-ul sursă nu este thin.")
exit 1
fi
if [ "$DIFF_TARGET_POOL" == "" ]
atunci
(>&2 echo "LV-ul țintă nu este thin.")
exit 1
fi
if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ]
atunci
(>&2 echo "Sursa și ținta LVs aparțin unor pool-uri thin diferite.")
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" != "-" ]
atunci
(>&2 echo "Snapshot-ul de metadata pentru thin pool deja există. Presupunând că este unul vechi. Se va elibera snapshot-ul de metadata în 5 secunde.")
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" == "-" ]
atunci
(>&2 echo "A eșuat crearea snapshot-ului de metadata pentru 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/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" ]
atunci
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" ]
atunci
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" != "" ]
atunci
echo "$DISCARD_LV_PATH găsit"
altfel
echo "$DISCARD_LV nu a fost găsit în $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" != "" ]
atunci
echo "$SOURCE_BASE_LV_PATH found"
altfel
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" != "" ]
atunci
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" != "" ]
atunci
echo "$TARGET_BASE_LV_PATH found"
altfel
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" != "" ]
atunci
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" != "" ]
atunci
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" != "" ]
atunci
echo "$SOURCE_BASE_LV_PATH found"
altfel
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
atunci
echo "$TARGET_BASE_LV_PATH found"
altfel
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" != "" ]
atunci
echo "$SOURCE_BASE_LV_PATH found"
altfel
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
atunci
echo "$TARGET_BASE_LV_PATH found"
altfel
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"
altfel
CMP_OFFSET=""
fi
gata
echo Done...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}
funcție 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"
}
funcție remove()
{
REMOVE_TARGET_VG="$BACKUPS"
REMOVE_TARGET_LV="$1"
lvremove -y "$REMOVE_TARGET_VG/$REMOVE_TARGET_LV"
sync
}
funcție removeall()
{
DATE_OFFSET="$3"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$SNAPSHOT"
gata < <(list "$1" "$2" | grep "$FILTER")
}
(
COMMAND="$1"
shift
caz "$COMMAND" în
"--help")
echo "Ajutor"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
așteaptă_blocare_ sau_termină
backup "$1" "$2"
;;
"list")
list "$1" "$2"
;;
"thindiff")
thindiff "$1" "$2" "$3"
;;
"thinsync")
thinsync "$1" "$2" "$3" "$4"
;;
"verify")
așteaptă_blocare_ sau_termină
verify "$1" "$2"
;;
"resync")
așteaptă_blocare_ sau_termină
resync "$1" "$2"
;;
"remove")
așteaptă_blocare_ sau_termină
remove "$1"
;;
"removeall")
așteaptă_blocare_ sau_termină
removeall "$1" "$2" "$3"
;;
*)
echo "Nimic.."
;;
esac
) 98>$LOCK_FILE
EOF
Ce face…?Contine un set de comenzi pentru manipularea instantaneelor subțiri și pentru sincronizarea diferențelor între două instantanee subțiri, obținute prin thin_delta, pe un alt dispozitiv de bloc folosind ddrescue și blkdiscard.
O altă script pe care o vom adăuga în cron:
Încă puțin 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"
RETENȚIE="-60 zile"
$BACKUP_SCRIPT backup images linux-dev
$BACKUP_SCRIPT backup images win8
$BACKUP_SCRIPT backup images win8-data
#etc
$BACKUP_SCRIPT removeall images linux-dev "$RETENȚIE"
$BACKUP_SCRIPT removeall images win8 "$RETENȚIE"
$BACKUP_SCRIPT removeall images win8-data "$RETENȚIE"
#etc
EOF
Ce face…?Folosește scriptul anterior pentru a crea și sincroniza copii de siguranță ale volumelor subțiri enumerate. Scriptul va lăsa instantaneele inactive ale volumelor enumerate, necesare pentru a urmări modificările din ultima sincronizare.
Acest script trebuie editat, indicând lista volumelor subțiri pentru care trebuie să facem copii de siguranță. Numele furnizate sunt doar pentru exemplu. Dacă se dorește, se poate scrie un script care să sincronizeze toate volumele.
Să dăm permisiuni:
#chmod +x /root/lvm-thin-backup/cron-daily.sh
#chmod +x /root/lvm-thin-backup/lvm-thin-backup.sh
Să verificăm și să-l adăugăm în 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
Prima rulare va fi lungă, deoarece volumele subțiri vor fi complet sincronizate prin copierea întregului spațiu utilizat. Datorită metafl torului LVM thin, știm ce blocuri sunt realmente utilizate, așa că vor fi copiate doar blocurile efectiv utilizate ale volumelor subțiri.
Rulările ulterioare vor copia datele incremental datorită urmării modificărilor prin metafl torul LVM thin.
Să vedem ce a ieșit:
#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 mar 26 09:11 .
drwxr-xr-x 1 root root 16 mar 6 09:30 ..
drwxr-xr-x 1 root root 322 mar 26 02:00 .@base
drwxr-xr-x 1 root root 516 mar 6 09:39 .@snap.2020-03-06-09-39-37
drwxr-xr-x 1 root root 516 mar 6 09:39 .@snap.2020-03-06-09-39-57
...
/backup/btrfs/back/remote/root:
total 0
drwxr-xr-x 1 root root 2820 mar 26 09:11 .
drwxr-xr-x 1 root root 16 mar 6 09:30 ..
drwxr-xr-x 1 root root 240 mar 26 09:11 @.@base
drwxr-xr-x 1 root root 22 mar 26 09:11 @home.@base
drwxr-xr-x 1 root root 22 mar 6 09:39 @home.@snap.2020-03-06-09-39-35
drwxr-xr-x 1 root root 22 mar 6 09:39 @home.@snap.2020-03-06-09-39-57
...
drwxr-xr-x 1 root root 240 mar 6 09:39 @.@snap.2020-03-06-09-39-26
drwxr-xr-x 1 root root 240 mar 6 09:39 @.@snap.2020-03-06-09-39-56
...
#lvs -olv_name,lv_size images && lvs -olv_name,lv_size backup
LV LSize
linux-dev 128,00g
linux-dev.base 128,00g
thin-pool 1,38t
win8 128,00g
win8-data 2,00t
win8-data.base 2,00t
win8.base 128,00g
LV LSize
backup 256,00g
images-linux-dev.base 128,00g
images-linux-dev.snap.2020-03-08-10-09-11 128,00g
images-linux-dev.snap.2020-03-08-10-09-25 128,00g
...
images-win8-data.base 2,00t
images-win8-data.snap.2020-03-16-14-11-55 2,00t
images-win8-data.snap.2020-03-16-14-19-50 2,00t
...
images-win8.base 128,00g
images-win8.snap.2020-03-17-04-51-46 128,00g
images-win8.snap.2020-03-18-03-02-49 128,00g
...
thin-pool <2,09t
Ce legătură are cu matrioshka?
Probabil că legătura este că volumele logice LVM LV pot fi volume fizice LVM PV pentru alte VG. LVM poate fi recursiv, precum matrioshka. Aceasta conferă LVM o flexibilitate extraordinară.
P.S.
În articolul următor, vom încerca să utilizăm mai multe astfel de unități mobile / KVM ca bază pentru a crea un storage / cluster vm geo-distribuit cu rezervare pe mai multe continente prin intermediul desktopurilor de acasă, internetului de acasă și rețelelor P2P.
Sursa: habr.com
