Co łączy LVM i Matrioszkę?

Dzień dobry.
Chciałbym podzielić się ze społecznością moimi praktycznymi doświadczeniami w budowie systemu przechowywania danych dla KVM przy użyciu md RAID + LVM.

W programie znajdą się:

  • Budowa md RAID 1 z dysku SSD NVMe.
  • Montaż md RAID 6 z dysków SSD SATA i zwykłych.
  • Funkcje operacji TRIM/DISCARD na dysku SSD RAID 1/6.
  • Tworzenie rozruchowej macierzy md RAID 1/6 na wspólnym zestawie dysków.
  • Instalowanie systemu na NVMe RAID 1, gdy BIOS nie obsługuje NVMe.
  • Korzystanie z pamięci podręcznej LVM i cienkiego LVM.
  • Korzystanie z migawek BTRFS i wysyłanie/odbieranie w celu tworzenia kopii zapasowych.
  • Używanie cienkich migawek LVM i cienkiej delty do tworzenia kopii zapasowych w stylu BTRFS.

Jeśli jesteś zainteresowany, zobacz kot.

oświadczenie

Autor nie ponosi żadnej odpowiedzialności za skutki wykorzystania lub nie wykorzystania materiałów/przykładów/kodu/wskazówek/danych z tego artykułu. Czytając lub wykorzystując w jakikolwiek sposób ten materiał, bierzesz na siebie odpowiedzialność za wszelkie konsekwencje tych działań. Możliwe konsekwencje obejmują:

  • Smażone dyski SSD NVMe.
  • Całkowite wykorzystanie zasobów nagrywania i awaria dysków SSD.
  • Całkowita utrata wszystkich danych na wszystkich dyskach, łącznie z kopiami zapasowymi.
  • Wadliwy sprzęt komputerowy.
  • Stracony czas, nerwy i pieniądze.
  • Wszelkie inne konsekwencje, które nie są wymienione powyżej.

żelazo

Dostępne były:

Płyta główna z około 2013 roku z chipsetem Z87, w komplecie z procesorem Intel Core i7/Haswell.

  • Procesor 4 rdzenie, 8 wątków
  • 32GB RAM DDR3
  • 1 x 16 lub 2 x 8 PCIe 3.0
  • 1 x 4 + 1 x 1 PCIe 2.0
  • 6 złączy SATA 6 x 3 Gb/s

Adapter SAS LSI SAS9211-8I flashował do trybu IT / HBA. Oprogramowanie układowe obsługujące technologię RAID zostało celowo zastąpione oprogramowaniem sprzętowym karty HBA w celu:

  1. Możesz wyrzucić ten adapter w dowolnym momencie i zastąpić go dowolnym innym, na jaki się natkniesz.
  2. TRIM/Discard działało normalnie na dyskach, ponieważ... w oprogramowaniu sprzętowym RAID te polecenia nie są w ogóle obsługiwane, a HBA w ogóle nie przejmuje się, jakie polecenia są przesyłane przez magistralę.

Dyski twarde - 8 sztuk HGST Travelstar 7K1000 o pojemności 1 TB w obudowie 2.5, jak w przypadku laptopów. Dyski te znajdowały się wcześniej w macierzy RAID 6. Przydadzą się także w nowym systemie. Do przechowywania lokalnych kopii zapasowych.

Dodatkowo dodano:

6 sztuk SATA SSD model Samsung 860 QVO 2 TB. Te dyski SSD wymagały dużej objętości, pożądana była obecność pamięci podręcznej SLC, niezawodność i niska cena. Wymagana była obsługa odrzucania/zero, co sprawdza linia w dmesg:

kernel: ata1.00: Enabling discard_zeroes_data

2 sztuki modelu SSD NVMe Samsung SSD 970 EVO 500 GB.

W przypadku tych dysków SSD ważna jest losowa prędkość odczytu/zapisu i pojemność zasobów dostosowana do Twoich potrzeb. Grzejnik dla nich. Koniecznie. Absolutnie. W przeciwnym razie smaż je do chrupkości podczas pierwszej synchronizacji RAID.

Adapter StarTech PEX8M2E2 na 2 dyski SSD NVMe instalowane w gnieździe PCIe 3.0 8x. To znowu tylko HBA, ale dla NVMe. Różni się od tanich adapterów tym, że nie wymaga obsługi bifurkacji PCIe z płyty głównej ze względu na obecność wbudowanego przełącznika PCIe. Będzie działać nawet w najstarszym systemie z PCIe, nawet jeśli będzie to slot PCIe 1 x1.0. Oczywiście z odpowiednią prędkością. Nie ma tam żadnych RAIDów. Na płycie nie ma wbudowanego BIOS-u. Zatem dzięki temu urządzeniu Twój system nie nauczy się w magiczny sposób uruchamiania z NVMe, a tym bardziej z NVMe RAID.

Komponent ten powstał wyłącznie dzięki obecności w systemie tylko jednego wolnego złącza 8x PCIe 3.0, a jeśli są 2 wolne gniazda, można go łatwo zastąpić dwugroszowym PEX4M2E1 lub analogami, które można kupić wszędzie w cenie 600 ruble.

Odrzucenie wszelkiego rodzaju sprzętowych lub wbudowanych chipsetów/BIOS RAID zostało dokonane celowo, aby móc całkowicie wymienić cały system, z wyjątkiem samych dysków SSD/HDD, przy jednoczesnym zachowaniu wszystkich danych. Idealnie, aby można było zapisać nawet zainstalowany system operacyjny w przypadku przejścia na zupełnie nowy/inny sprzęt. Najważniejsze jest to, że istnieją porty SATA i PCIe. To jest jak Live CD lub bootowalny dysk flash, tylko bardzo szybki i trochę nieporęczny.

ЮморW przeciwnym razie wiesz, co się stanie – czasami trzeba pilnie zabrać ze sobą całą tablicę na wynos. Ale nie chcę stracić danych. Aby to zrobić, wszystkie wymienione nośniki są wygodnie umieszczone na prowadnicach w zatokach 5.25 standardowej obudowy.

No i oczywiście do eksperymentowania z różnymi metodami buforowania SSD w systemie Linux.

Naloty na sprzęt są nudne. Włącz to. Albo działa, albo nie. A w przypadku mdadm zawsze są dostępne opcje.

Miękkie

Wcześniej na sprzęcie bliskim EOL był zainstalowany Debian 8 Jessie. Z wyżej wymienionych dysków twardych sparowanych z LVM złożono RAID 6. Uruchamiał maszyny wirtualne w kvm/libvirt.

Ponieważ Autor ma odpowiednie doświadczenie w tworzeniu przenośnych bootowalnych dysków flash SATA/NVMe, a także, aby nie złamać utartego szablonu, jako system docelowy wybrano Ubuntu 18.04, które zostało już wystarczająco ustabilizowane, ale ma jeszcze 3 lata doświadczenia wsparcie w przyszłości.

Wspomniany system zawiera od razu po wyjęciu z pudełka wszystkie potrzebne nam sterowniki sprzętowe. Nie potrzebujemy żadnego oprogramowania ani sterowników innych firm.

Przygotowanie do instalacji

Do zainstalowania systemu potrzebujemy Ubuntu Desktop Image. System serwerowy ma jakiś energiczny instalator, który wykazuje nadmierną niezależność, której nie można wyłączyć, wpychając partycję systemową UEFI na jeden z dysków, psując całe piękno. W związku z tym jest instalowany tylko w trybie UEFI. Nie oferuje żadnych opcji.

Nie jesteśmy z tego zadowoleni.

Dlaczego?Niestety, rozruch UEFI jest wyjątkowo słabo kompatybilny z oprogramowaniem rozruchowym RAID, ponieważ... Nikt nie oferuje nam rezerwacji na partycję UEFI ESP. W Internecie można znaleźć przepisy sugerujące umieszczenie partycji ESP na dysku flash w porcie USB, ale jest to punkt krytyczny. Istnieją przepisy wykorzystujące oprogramowanie mdadm RAID 1 z metadanymi w wersji 0.9, które nie uniemożliwiają UEFI BIOSowi zobaczenia tej partycji, ale trwa to do szczęśliwego momentu, gdy BIOS lub inny sprzętowy system operacyjny zapisuje coś do ESP i zapomina zsynchronizować go z innymi lustra.

Ponadto rozruch UEFI zależy od pamięci NVRAM, która nie zostanie przeniesiona wraz z dyskami do nowego systemu, ponieważ jest częścią płyty głównej.

Nie będziemy więc wymyślać nowego koła. Mamy już gotowy, sprawdzony rower dziadka, teraz nazywany Legacy/BIOS boot, noszący dumną nazwę CSM na systemach kompatybilnych z UEFI. Po prostu zdejmiemy go z półki, nasmarujemy, napompujemy opony i przetrzemy wilgotną szmatką.

Komputerowej wersji Ubuntu również nie da się poprawnie zainstalować za pomocą bootloadera Legacy, ale tutaj, jak mówią, przynajmniej są opcje.

I tak zbieramy sprzęt i ładujemy system z rozruchowego dysku flash Ubuntu Live. Będziemy musieli pobrać pakiety, abyśmy mogli skonfigurować sieć, która będzie dla Ciebie odpowiednia. Jeśli to nie zadziała, możesz wcześniej załadować niezbędne pakiety na dysk flash.

Wchodzimy do środowiska Desktop, uruchamiamy emulator terminala i zaczynamy:

#sudo bash

Jak…?Powyższa linia jest kanonicznym wyzwalaczem holiwarów na temat sudo. C bоnadchodzą większe możliwości iоwiększa odpowiedzialność. Pytanie czy jesteś w stanie to wziąć na siebie. Wiele osób uważa, że ​​używanie sudo w ten sposób jest co najmniej nieostrożne. Jednakże:

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

Dlaczego nie ZFS...?Kiedy instalujemy oprogramowanie na naszym komputerze, zasadniczo pożyczamy nasz sprzęt twórcom tego oprogramowania do prowadzenia.
Powierzając temu oprogramowaniu bezpieczeństwo naszych danych, zaciągamy pożyczkę równą kosztowi przywrócenia tych danych, którą będziemy musieli kiedyś spłacić.

Z tego punktu widzenia ZFS to Ferrari, a mdadm+lvm to bardziej rower.

Subiektywnie autor woli pożyczać rower na kredyt nieznanym osobom niż Ferrari. Tam cena wydania nie jest wysoka. Nie ma potrzeby praw. Prostsze niż zasady ruchu drogowego. Parking jest bezpłatny. Zdolność do przełajów jest lepsza. Zawsze można do roweru doczepić nogi, a rower można naprawić własnymi rękami.

Dlaczego więc BTRFS...?Aby uruchomić system operacyjny, potrzebujemy systemu plików, który od razu obsługuje Legacy/BIOS GRUB, a jednocześnie obsługuje migawki na żywo. Użyjemy go dla partycji /boot. Ponadto autor woli używać tego FS dla / (root), nie zapominając o tym, że w przypadku każdego innego oprogramowania można utworzyć osobne partycje na LVM i zamontować je w niezbędnych katalogach.

Nie będziemy przechowywać żadnych obrazów maszyn wirtualnych ani baz danych na tym systemie plików.
Ten plik FS będzie używany wyłącznie do tworzenia migawek systemu bez wyłączania go, a następnie przesyłania tych migawek na dysk kopii zapasowej za pomocą funkcji wysyłania/odbierania.

Ponadto autor generalnie woli przechowywać minimalną ilość oprogramowania bezpośrednio na sprzęcie, a resztę oprogramowania uruchamiać na maszynach wirtualnych, korzystając z takich rzeczy, jak przekazywanie procesorów graficznych i kontrolerów hosta PCI-USB do KVM za pośrednictwem IOMMU.

Na sprzęcie pozostają jedynie przechowywanie danych, wirtualizacja i tworzenie kopii zapasowych.

Jeśli bardziej ufasz ZFS, to w zasadzie dla określonej aplikacji są one wymienne.

Jednak autor celowo ignoruje wbudowane funkcje dublowania/RAID i redundancji, które mają ZFS, BRTFS i LVM.

Jako dodatkowy argument BTRFS posiada możliwość zamiany zapisów losowych na sekwencyjne, co niezwykle pozytywnie wpływa na szybkość synchronizacji snapshotów/kopii zapasowych na dysku HDD.

Przeskanujmy ponownie wszystkie urządzenia:

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

Rozejrzyjmy się:

#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

Układ dysku

NVMe SSD

Ale nie będziemy ich w żaden sposób oznaczać. Mimo wszystko nasz BIOS nie widzi tych dysków. Pójdą więc w całości na programową macierz RAID. Nie będziemy tam nawet tworzyć sekcji. Jeśli chcesz postępować zgodnie z „kanonem” lub „głównie”, utwórz jedną dużą partycję, na przykład dysk twardy.

Dysk twardy SATA

Nie trzeba tu wymyślać niczego specjalnego. Stworzymy jedną sekcję na wszystko. Utworzymy partycję, ponieważ BIOS widzi te dyski i może nawet próbować z nich uruchomić komputer. Później nawet zainstalujemy GRUB-a na tych dyskach, aby system mógł to nagle zrobić.

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

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

SATA SSD

Tutaj sprawy stają się dla nas interesujące.

Po pierwsze, nasze dyski mają pojemność 2 TB. Mieści się to w akceptowalnym zakresie dla MBR, którego będziemy używać. W razie potrzeby można zastąpić GPT. Dyski GPT mają warstwę zgodności, która umożliwia systemom zgodnym z MBR zobaczenie pierwszych 4 partycji, jeśli znajdują się one w obrębie pierwszych 2 terabajtów. Najważniejsze, żeby partycja boot i bios_grub na tych dyskach znajdowały się na początku. Umożliwia to nawet rozruch z dysków GPT Legacy/BIOS.

Ale to nie jest nasz przypadek.

Tutaj utworzymy dwie sekcje. Pierwszy będzie miał rozmiar 1 GB i będzie używany do RAID 1 /boot.

Drugi będzie używany dla RAID 6 i zajmie całe pozostałe wolne miejsce z wyjątkiem małego nieprzydzielonego obszaru na końcu dysku.

Co to za nieoznaczony obszar?Według źródeł w sieci nasze dyski SSD SATA mają na pokładzie dynamicznie rozwijaną pamięć podręczną SLC o rozmiarze od 6 do 78 gigabajtów. 6 gigabajtów dostajemy „za darmo” ze względu na różnicę między „gigabajtami” a „gibibajtami” w karcie katalogowej dysku. Pozostałe 72 gigabajty są przydzielane z niewykorzystanej przestrzeni.

Tutaj należy zaznaczyć, że mamy pamięć podręczną SLC, a przestrzeń jest zajęta w 4-bitowym trybie MLC. Co dla nas w praktyce oznacza, że ​​na każde 4 gigabajty wolnego miejsca otrzymamy tylko 1 gigabajt pamięci podręcznej SLC.

Pomnóż 72 gigabajty przez 4 i uzyskaj 288 gigabajtów. Jest to wolna przestrzeń, której nie będziemy wyznaczać, aby dyski mogły w pełni wykorzystać pamięć podręczną SLC.

Tym samym efektywnie uzyskamy aż 312 gigabajtów pamięci podręcznej SLC z łącznie sześciu dysków. Ze wszystkich dysków 2 będą używane w macierzy RAID w celu zapewnienia nadmiarowości.

Taka ilość pamięci podręcznej pozwoli nam niezwykle rzadko w życiu spotkać się z sytuacją, w której zapis nie trafia do pamięci podręcznej. To wyjątkowo dobrze kompensuje najsmutniejszą wadę pamięci QLC - wyjątkowo niską prędkość zapisu, gdy dane są zapisywane z pominięciem pamięci podręcznej. Jeśli Twoje obciążenia nie odpowiadają temu, sugeruję dokładne przemyślenie, jak długo dysk SSD wytrzyma pod takim obciążeniem, biorąc pod uwagę TBW z arkusza danych.

#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

Tworzenie tablic

Najpierw musimy zmienić nazwę maszyny. Jest to konieczne, ponieważ nazwa hosta jest częścią nazwy tablicy gdzieś wewnątrz mdadm i gdzieś na coś wpływa. Oczywiście nazwy tablic można później zmienić, ale jest to niepotrzebny krok.

#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

Dlaczego -załóżmy-czyścimy...?Aby uniknąć inicjowania tablic. Dotyczy to obu poziomów RAID 1 i 6. Jest to ważne. Wszystko może działać bez inicjalizacji, jeśli jest to nowa tablica. Co więcej, inicjowanie macierzy SSD podczas tworzenia jest marnowaniem zasobów TBW. Tam, gdzie to możliwe, na zmontowanych macierzach SSD używamy TRIM/DISCARD w celu ich „inicjalizacji”.

W przypadku macierzy SSD obsługiwana jest funkcja RAID 1 DISCARD od razu po wyjęciu z pudełka.

W przypadku macierzy SSD RAID 6 DISCARD należy ją włączyć w parametrach modułu jądra.

Należy to zrobić tylko wtedy, gdy wszystkie dyski SSD używane w macierzach poziomu 4/5/6 w tym systemie obsługują działającą obsługę Discut_zeroes_data. Czasami można natknąć się na dziwne dyski, które mówią jądru, że ta funkcja jest obsługiwana, ale w rzeczywistości jej tam nie ma lub funkcja nie zawsze działa. W tej chwili wsparcie jest dostępne prawie wszędzie, jednak istnieją stare dyski i oprogramowanie sprzętowe z błędami. Z tego powodu obsługa DISCARD jest domyślnie wyłączona dla RAID 6.

Uwaga, poniższe polecenie zniszczy wszystkie dane na dyskach NVMe poprzez „inicjowanie” tablicy „zerami”.

#blkdiscard /dev/md0

Jeśli coś pójdzie nie tak, spróbuj określić krok.

#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

Dlaczego taki duży...?Zwiększanie rozmiaru fragmentu ma pozytywny wpływ na szybkość losowego odczytu bloków aż do rozmiaru fragmentu włącznie. Dzieje się tak dlatego, że jedną operację o odpowiedniej wielkości lub mniejszej można wykonać w całości na jednym urządzeniu. Dlatego sumuje się IOPS ze wszystkich urządzeń. Według statystyk 99% IO nie przekracza 512 tys.

RAID 6 IOPS na zapis zawsze mniejszy lub równy IOPS jednego dysku. Przy odczycie losowym IOPS może być kilkukrotnie większy niż na jednym dysku i tutaj wielkość bloku ma kluczowe znaczenie.
Autor nie widzi sensu w próbie optymalizacji parametru, który jest zły w konstrukcji RAID 6, i zamiast tego optymalizuje to, w czym RAID 6 jest dobry.
Zrekompensujemy słaby losowy zapis RAID 6 za pomocą pamięci podręcznej NVMe i sztuczek z cienkim alokowaniem.

Nie włączyliśmy jeszcze opcji DISCARD dla RAID 6. Dlatego na razie nie będziemy „inicjować” tej tablicy. Zrobimy to później, po zainstalowaniu systemu operacyjnego.

Dysk twardy SATA

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

LVM na macierzy RAID NVMe

Aby uzyskać szybkość, chcemy umieścić główny system plików na macierzy RAID 1 NVMe, czyli /dev/md0.
Jednak nadal będziemy potrzebować tej szybkiej macierzy do innych potrzeb, takich jak wymiana, metadane i pamięć podręczna LVM oraz metadane cienkie LVM, dlatego utworzymy LVM VG na tej macierzy.

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

Utwórzmy partycję dla głównego systemu plików.

#lvcreate -L 128G --name root root

Utwórzmy partycję do zamiany w zależności od rozmiaru pamięci RAM.

#lvcreate -L 32G --name swap root

Instalacja systemu operacyjnego

W sumie mamy wszystko co niezbędne do zainstalowania systemu.

Uruchom kreator instalacji systemu ze środowiska Ubuntu Live. Normalna instalacja. Dopiero na etapie wyboru dysków do instalacji należy określić:

  • /dev/md1, - punkt podłączenia /boot, FS - BTRFS
  • /dev/root/root (aka /dev/mapper/root-root), - punkt podłączenia / (root), FS - BTRFS
  • /dev/root/swap (aka /dev/mapper/root-swap), - użyj jako partycji wymiany
  • Zainstaluj bootloader na /dev/sda

Po wybraniu BTRFS jako głównego systemu plików instalator automatycznie utworzy dwa woluminy BTRFS o nazwach „@” dla / (root) i „@home” dla /home.

Rozpoczynamy instalację...

Instalacja zakończy się modalnym oknem dialogowym wskazującym błąd podczas instalacji programu ładującego. Niestety, nie będziesz mógł wyjść z tego okna dialogowego w standardowy sposób i kontynuować instalacji. Wylogowujemy się z systemu i logujemy ponownie, kończąc na czystym pulpicie Ubuntu Live. Otwórz terminal i ponownie:

#sudo bash

Utwórz środowisko chroot, aby kontynuować instalację:

#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

Skonfigurujmy sieć i nazwę hosta w chroot:

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

Przejdźmy do środowiska chroot:

#chroot /mnt/chroot

W pierwszej kolejności dostarczymy paczki:

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

Sprawdźmy i naprawmy wszystkie pakiety, które zostały krzywo zainstalowane z powodu niekompletnej instalacji systemu:

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

Jeśli coś nie wyjdzie, być może będziesz musiał najpierw edytować plik /etc/apt/sources.list

Dostosujmy parametry modułu RAID 6, aby włączyć TRIM/DISCARD:

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

Zmieńmy trochę nasze tablice:

#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

Co to było..?Stworzyliśmy zestaw reguł udev, które wykonają następujące czynności:

  • Ustaw rozmiar blokowej pamięci podręcznej dla RAID 2020 na odpowiedni dla roku 6. Wydaje się, że domyślna wartość nie zmieniła się od powstania Linuksa i przez długi czas nie była odpowiednia.
  • Zarezerwuj minimum IO na czas sprawdzania/synchronizacji macierzy. Ma to na celu zapobieganie utknięciu tablic w stanie wiecznej synchronizacji pod obciążeniem.
  • Ogranicz maksymalne IO podczas sprawdzania/synchronizacji tablic. Jest to konieczne, aby synchronizacja/sprawdzenie RAID dysków SSD nie przysmażyła dysków na ostro. Dotyczy to szczególnie NVMe. (Pamiętasz o grzejniku? Nie żartowałem.)
  • Zabroń dyskom zatrzymywania obrotów wrzeciona (HDD) za pośrednictwem APM i ustaw limit czasu uśpienia kontrolerów dysków na 7 godzin. Możesz całkowicie wyłączyć APM, jeśli twoje dyski to potrafią (-B 255). Przy wartości domyślnej napędy zatrzymają się po pięciu sekundach. Następnie system operacyjny chce zresetować pamięć podręczną dysku, dyski ponownie się uruchomią i wszystko zacznie się od nowa. Tarcze mają ograniczoną maksymalną liczbę obrotów wrzeciona. Taki prosty cykl domyślny może z łatwością zabić dyski w ciągu kilku lat. Nie wszystkie dyski na tym cierpią, ale nasze są „laptopowe”, z odpowiednimi ustawieniami domyślnymi, dzięki którym RAID wygląda jak mini-MAID.
  • Zainstaluj funkcję Readahead na dyskach (rotacyjnych) 1 megabajt – dwa kolejne bloki/fragment RAID 6
  • Wyłącz odczyt z wyprzedzeniem na samych tablicach.

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

Dlaczego..?Będziemy szukać partycji /boot według UUID. Nazewnictwo tablic może teoretycznie ulec zmianie.

Pozostałych sekcji będziemy szukać po nazwach LVM w notacji /dev/mapper/vg-lv, gdyż identyfikują partycje dość jednoznacznie.

Nie używamy UUID dla LVM, ponieważ Identyfikator UUID woluminów LVM i ich migawek może być taki sam.Zamontuj /dev/mapper/root-root.. dwa razy?Tak. Dokładnie. Cecha BTRFS. Ten system plików można zamontować kilka razy z różnymi subvolami.

Ze względu na tę samą funkcję zalecam, aby nigdy nie tworzyć migawek LVM aktywnych woluminów BTRFS. Po ponownym uruchomieniu możesz spotkać niespodziankę.

Zregenerujmy konfigurację mdadm:

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

Dostosujmy ustawienia 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

Co to było..?Włączyliśmy automatyczną rozbudowę cienkich pul LVM po osiągnięciu 90% zajętej przestrzeni o 5% objętości.

Zwiększyliśmy maksymalną liczbę bloków pamięci podręcznej dla pamięci podręcznej LVM.

Uniemożliwiliśmy LVM wyszukiwanie woluminów LVM (PV) na:

  • urządzenia zawierające pamięć podręczną LVM (cdata)
  • urządzenia buforowane przy użyciu pamięci podręcznej LVM, z pominięciem pamięci podręcznej ( _corig). W takim przypadku samo urządzenie w pamięci podręcznej będzie nadal skanowane w pamięci podręcznej (tylko ).
  • urządzenia zawierające metadane pamięci podręcznej LVM (cmeta)
  • wszystkie urządzenia w VG z nazwami obrazów. Tutaj będziemy mieli obrazy dysków maszyn wirtualnych i nie chcemy, aby LVM na hoście aktywował woluminy należące do systemu gościa.
  • wszystkie urządzenia w VG o nazwie kopia zapasowa. Tutaj będziemy mieli kopie zapasowe obrazów maszyn wirtualnych.
  • wszystkie urządzenia, których nazwa kończy się na „gpv” (wolumin fizyczny gościa)

Włączyliśmy obsługę DISCARD podczas zwalniania wolnego miejsca na LVM VG. Bądź ostrożny. To sprawi, że usuwanie LV na dysku SSD będzie dość czasochłonne. Dotyczy to zwłaszcza SSD RAID 6. Zgodnie z planem będziemy jednak stosować Thin Provisioning, więc w niczym nam to nie będzie przeszkadzać.

Zaktualizujmy obraz initramfs:

#update-initramfs -u -k all

Zainstaluj i skonfiguruj Gruba:

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

Jakie dyski wybrać?Wszyscy, którzy są SD*. System musi umożliwiać rozruch z dowolnego działającego dysku SATA lub SSD.

Dlaczego dodali os-prober..?Za nadmierną niezależność i zabawne ręce.

Nie działa poprawnie, jeśli jedna z macierzy RAID jest w złym stanie. Próbuje wyszukać system operacyjny na partycjach używanych w maszynach wirtualnych działających na tym sprzęcie.

Jeśli tego potrzebujesz, możesz go zostawić, ale pamiętaj o wszystkich powyższych. Polecam poszukać w internecie przepisów na pozbycie się niegrzecznych rąk.

W ten sposób zakończyliśmy wstępną instalację. Nadszedł czas na ponowne uruchomienie nowo zainstalowanego systemu operacyjnego. Nie zapomnij usunąć startowej płyty Live CD/USB.

#exit
#reboot

Wybierz dowolny dysk SSD SATA jako urządzenie rozruchowe.

LVM na dysku SSD SATA

W tym momencie uruchomiliśmy już nowy system operacyjny, skonfigurowaliśmy sieć, apt, otworzyliśmy emulator terminala i uruchomiliśmy:

#sudo bash

Kontynuujmy.

„Zainicjuj” macierz z dysku SSD SATA:

#blkdiscard /dev/md2

Jeśli to nie zadziała, spróbuj:

#blkdiscard --step 65536 /dev/md2
Utwórz LVM VG na dysku SSD SATA:

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

Dlaczego kolejny VG..?W rzeczywistości mamy już VG o nazwie root. Dlaczego nie dodać wszystkiego do jednego VG?

Jeśli w VG znajduje się kilka PV, to aby VG zostało poprawnie aktywowane, wszystkie PV muszą być obecne (online). Wyjątkiem jest LVM RAID, z którego świadomie nie korzystamy.

Naprawdę chcemy, aby w przypadku awarii (utraty danych odczytu) na którejkolwiek z macierzy RAID 6 system operacyjny uruchomił się normalnie i dał nam możliwość rozwiązania problemu.

Aby to zrobić, na pierwszym poziomie abstrakcji wyizolujemy każdy typ fizycznych „nośników” w oddzielnym VG.

Z naukowego punktu widzenia różne macierze RAID należą do różnych „domen niezawodności”. Nie powinieneś tworzyć dla nich dodatkowego wspólnego punktu awarii, wpychając je w jeden VG.

Obecność LVM na poziomie „sprzętowym” pozwoli nam dowolnie wycinać fragmenty różnych macierzy RAID, łącząc je na różne sposoby. Na przykład - biegnij naraz bcache + LVM cienki, bcache + BTRFS, LVM cache + LVM cienki, złożona konfiguracja ZFS z pamięciami podręcznymi lub jakakolwiek inna piekielna mieszanka, aby spróbować to wszystko porównać.

Na poziomie „sprzętowym” nie będziemy używać niczego innego niż stare, dobre „grube” woluminy LVM. Wyjątkiem od tej reguły może być partycja zapasowa.

Myślę, że w tym momencie wielu czytelników zaczęło już podejrzewać coś w związku z lalką lęgową.

LVM na dysku twardym SATA

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

Znowu nowe VG..?Bardzo nam zależy, aby w przypadku awarii macierzy dyskowej, której będziemy używać do tworzenia kopii zapasowych danych, nasz system operacyjny nadal działał normalnie, zachowując przy tym zwykły dostęp do danych niebędących kopiami zapasowymi. Dlatego, aby uniknąć problemów z aktywacją VG, tworzymy oddzielny VG.

Konfigurowanie pamięci podręcznej LVM

Utwórzmy LV na NVMe RAID 1, aby używać go jako urządzenia buforującego.

#lvcreate -L 70871154688B --name cache root

Dlaczego jest tak mało...?Faktem jest, że nasze dyski SSD NVMe również posiadają pamięć podręczną SLC. 4 gigabajty „wolne” i 18 gigabajtów dynamicznych ze względu na wolne miejsce zajmowane w 3-bitowym MLC. Po wyczerpaniu tej pamięci podręcznej dyski SSD NVMe nie będą dużo szybsze niż nasze dyski SSD SATA z pamięcią podręczną. Właściwie z tego powodu nie ma sensu, abyśmy tworzyli partycję pamięci podręcznej LVM znacznie większą niż dwukrotność wielkości pamięci podręcznej SLC dysku NVMe. W przypadku używanych dysków NVMe autor uważa za rozsądne wykonanie 32–64 gigabajtów pamięci podręcznej.

Podany rozmiar partycji jest wymagany do zorganizowania 64 gigabajtów pamięci podręcznej, przechowywania metadanych pamięci podręcznej i kopii zapasowej metadanych.

Dodatkowo zwracam uwagę, że po zamknięciu systemu z powodu błędu LVM oznaczy całą pamięć podręczną jako brudną i ponownie zsynchronizuje się. Co więcej, będzie to powtarzane za każdym razem, gdy zostanie użyta lvchange na tym urządzeniu, aż do ponownego uruchomienia systemu. Dlatego polecam od razu odtworzyć pamięć podręczną za pomocą odpowiedniego skryptu.

Utwórzmy LV na SATA RAID 6, aby używać go jako urządzenia buforowanego.

#lvcreate -L 3298543271936B --name cache data

Dlaczego tylko trzy terabajty..?Aby w razie potrzeby móc wykorzystać SATA SSD RAID 6 do innych celów. Rozmiar przestrzeni buforowanej można zwiększać dynamicznie, na bieżąco, bez zatrzymywania systemu. Aby to zrobić, musisz tymczasowo zatrzymać i ponownie włączyć pamięć podręczną, ale charakterystyczną zaletą pamięci podręcznej LVM w porównaniu na przykład z bcache jest to, że można to zrobić w locie.

Stwórzmy nowy VG do buforowania.

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

Utwórzmy LV na urządzeniu z pamięcią podręczną.

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

Tutaj natychmiast zajęliśmy całe wolne miejsce na /dev/data/cache, dzięki czemu wszystkie pozostałe niezbędne partycje zostały utworzone natychmiast na /dev/root/cache. Jeśli utworzyłeś coś w złym miejscu, możesz to przenieść za pomocą pvmove.

Utwórzmy i włączmy pamięć podręczną:

#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

Dlaczego taki rozmiar kawałka..?Dzięki praktycznym eksperymentom autorowi udało się przekonać, że najlepszy wynik osiąga się, jeśli rozmiar bloku pamięci podręcznej LVM pokrywa się z rozmiarem cienkiego bloku LVM. Co więcej, im mniejszy rozmiar, tym lepsza konfiguracja podczas nagrywania losowego.

64k to minimalny rozmiar bloku dozwolony dla cienkiego LVM.

Uważaj na pisanie zwrotne..!Tak. Ten typ pamięci podręcznej opóźnia synchronizację zapisu na urządzeniu buforowanym. Oznacza to, że w przypadku utraty pamięci podręcznej możesz utracić dane na urządzeniu buforowanym. Później autor powie, jakie środki oprócz NVMe RAID 1 można podjąć, aby zrekompensować to ryzyko.

Ten typ pamięci podręcznej został wybrany celowo, aby zrekompensować słabą wydajność zapisu losowego w macierzy RAID 6.

Sprawdźmy, co otrzymaliśmy:

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

Tylko [cachedata_corig] powinien znajdować się w /dev/data/cache. Jeśli coś jest nie tak, użyj pvmove.

W razie potrzeby możesz wyłączyć pamięć podręczną za pomocą jednego polecenia:

#lvconvert -y --uncache cache/cachedata

Odbywa się to on-line. LVM po prostu zsynchronizuje pamięć podręczną z dyskiem, usunie ją i zmieni nazwę cachedata_corig z powrotem na cachedata.

Konfigurowanie cienkiego LVM

Oszacujmy z grubsza, ile miejsca potrzebujemy na cienkie metadane 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"

Zaokrąglij w górę do 4 gigabajtów: 4294967296B

Pomnóż przez dwa i dodaj 4194304B, aby uzyskać metadane LVM PV: 8594128896B
Utwórzmy osobną partycję na NVMe RAID 1, aby umieścić na niej cienkie metadane LVM i ich kopię zapasową:

#lvcreate -L 8594128896B --name images root

Po co..?Tutaj może pojawić się pytanie: po co umieszczać cienkie metadane LVM osobno, skoro nadal będą buforowane na NVMe i będą działać szybko.

Chociaż prędkość jest tutaj ważna, nie jest to główny powód. Rzecz w tym, że pamięć podręczna jest punktem awarii. Coś może się z nim stać i jeśli cienkie metadane LVM zostaną zapisane w pamięci podręcznej, spowoduje to całkowitą utratę wszystkiego. Bez kompletnych metadanych złożenie cienkich woluminów będzie prawie niemożliwe.

Przenosząc metadane na oddzielny, niebuforowany, ale szybki wolumin, gwarantujemy bezpieczeństwo metadanych w przypadku utraty lub uszkodzenia pamięci podręcznej. W takim przypadku wszystkie uszkodzenia spowodowane utratą pamięci podręcznej zostaną zlokalizowane w cienkich woluminach, co znacznie uprości procedurę odzyskiwania. Z dużym prawdopodobieństwem uszkodzenia te zostaną naprawione przy użyciu logów FS.

Co więcej, jeśli wcześniej wykonano migawkę cienkiego woluminu, a następnie pamięć podręczna została w pełni zsynchronizowana przynajmniej raz, to dzięki wewnętrznej konstrukcji LVM Thin integralność migawki będzie gwarantowana w przypadku utraty pamięci podręcznej .

Stwórzmy nową grupę VG, która będzie odpowiedzialna za alokację elastyczną:

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

Stwórzmy pulę:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
Dlaczego -Z yOprócz tego do czego właściwie służy ten tryb - aby zapobiec wyciekaniu danych z jednej maszyny wirtualnej do innej maszyny wirtualnej podczas redystrybucji przestrzeni - zerowanie służy dodatkowo do zwiększenia szybkości losowego zapisu w blokach mniejszych niż 64k. Każdy zapis mniejszy niż 64 KB w wcześniej nieprzydzielonym obszarze cienkiego woluminu zostanie wyrównany do krawędzi 64 KB w pamięci podręcznej. Umożliwi to wykonanie operacji całkowicie poprzez pamięć podręczną, z pominięciem urządzenia buforowanego.

Przesuńmy LV do odpowiednich PV:

#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

Sprawdźmy:

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

Utwórzmy cienki wolumin do testów:

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

Zainstalujemy pakiety do testów i monitoringu:

#apt-get install sysstat fio

W ten sposób możesz w czasie rzeczywistym obserwować zachowanie naszej konfiguracji przechowywania:

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

W ten sposób możemy przetestować naszą konfigurację:

#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

Ostrożnie! Ratunek!Ten kod uruchomi 36 różnych testów, każdy trwający 4 sekundy. Połowa testów służy do nagrywania. Możesz nagrać wiele na NVMe w 4 sekundy. Do 3 gigabajtów na sekundę. Zatem każda seria pisania testów może pochłonąć do 216 gigabajtów zasobów SSD.

Czytanie i pisanie mieszane?Tak. Sensowne jest osobne uruchomienie testów odczytu i zapisu. Ponadto warto upewnić się, że wszystkie pamięci podręczne są zsynchronizowane, aby wcześniej wykonany zapis nie miał wpływu na odczyt.

Wyniki będą się znacznie różnić podczas pierwszego uruchomienia i kolejnych w miarę zapełniania się pamięci podręcznej i cienkiego woluminu, a także w zależności od tego, czy systemowi udało się zsynchronizować pamięci podręczne wypełnione podczas ostatniego uruchomienia.

Między innymi polecam zmierzyć prędkość na już zapełnionym cienkim wolumenie, z którego właśnie została zrobiona migawka. Autor miał okazję zaobserwować jak losowe zapisy gwałtownie przyspieszają zaraz po utworzeniu pierwszego snapshota, zwłaszcza gdy pamięć podręczna nie jest jeszcze całkowicie zapełniona. Dzieje się tak ze względu na semantykę zapisu typu „kopiuj przy zapisie”, wyrównanie pamięci podręcznej i cienkich bloków woluminów oraz fakt, że losowy zapis na RAID 6 zamienia się w losowy odczyt z RAID 6, po którym następuje zapis do pamięci podręcznej. W naszej konfiguracji losowy odczyt z RAID 6 jest do 6 razy (liczba dysków SSD SATA w macierzy) szybszy niż zapis. Ponieważ bloki dla CoW są przydzielane sekwencyjnie z cienkiej puli, wówczas nagrywanie w większości zamienia się również w sekwencyjne.

Obie te funkcje można wykorzystać na swoją korzyść.

Buforuj „spójne” migawki

Aby zmniejszyć ryzyko utraty danych w przypadku uszkodzenia/utraty pamięci podręcznej, autor proponuje wprowadzenie praktyki rotacji migawek, aby w tym przypadku zagwarantować ich integralność.

Po pierwsze, ponieważ metadane o niewielkiej objętości znajdują się na urządzeniu niezapisanym w pamięci podręcznej, metadane będą spójne, a ewentualne straty zostaną odizolowane w blokach danych.

Poniższy cykl rotacji migawek gwarantuje integralność danych wewnątrz migawek w przypadku utraty pamięci podręcznej:

  1. Dla każdego cienkiego woluminu o nazwie <nazwa> utwórz migawkę o nazwie <nazwa>.cached
  2. Ustawmy próg migracji na rozsądnie wysoką wartość: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. W pętli sprawdzamy ilość brudnych bloków w pamięci podręcznej: #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' dopóki nie osiągniemy zera. Jeśli zero brakuje zbyt długo, można je utworzyć, tymczasowo przełączając pamięć podręczną w tryb zapisu. Biorąc jednak pod uwagę charakterystykę szybkości naszych macierzy SSD SATA i NVMe, a także ich zasoby TBW, albo będziesz w stanie szybko uchwycić moment bez zmiany trybu pamięci podręcznej, albo Twój sprzęt całkowicie pochłonie całe zasoby w parę dni. Ze względu na ograniczenia zasobów system w zasadzie nie może przez cały czas pracować przy obciążeniu zapisu poniżej 100%. Nasze dyski SSD NVMe poniżej 100% obciążenia zapisu całkowicie wyczerpią zasób 3-4 dni. Dyski SSD SATA wytrzymają tylko dwa razy dłużej. Dlatego założymy, że większość obciążenia przypada na czytanie i mamy stosunkowo krótkotrwałe wybuchy niezwykle wysokiej aktywności połączone ze średnim średnim obciążeniem podczas pisania.
  4. Gdy tylko złapiemy (lub zbierzemy) zero, zmieniamy nazwę <name>.cached na <name>.committed. Stare <name>.committed zostało usunięte.
  5. Opcjonalnie, jeśli pamięć podręczna jest zapełniona w 100%, można ją odtworzyć za pomocą skryptu i w ten sposób ją wyczyścić. Dzięki w połowie pustej pamięci podręcznej system działa znacznie szybciej podczas zapisu.
  6. Ustaw próg migracji na zero: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata To tymczasowo uniemożliwi synchronizację pamięci podręcznej z głównymi mediami.
  7. Czekamy, aż w pamięci podręcznej zgromadzi się sporo zmian #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' lub timer się wyłączy.
  8. Powtarzamy ponownie.

Skąd trudności z progiem migracyjnym...?Rzecz w tym, że w praktyce „losowe” nagranie wcale nie jest całkowicie losowe. Jeśli zapiszemy coś w sektorze o wielkości 4 kilobajtów, istnieje duże prawdopodobieństwo, że w ciągu najbliższych kilku minut zostanie dokonany zapis w tym samym lub jednym z sąsiednich (+- 32 KB) sektorów.

Ustawiając próg migracji na zero, odraczamy synchronizację zapisu na dysku SSD SATA i agregujemy kilka zmian w jednym bloku 64K w pamięci podręcznej. To znacznie oszczędza zasoby dysku SSD SATA.

Gdzie jest kod..?Niestety autor uważa się za niewystarczająco kompetentny w tworzeniu skryptów basha, ponieważ jest 100% samoukiem i praktykuje programowanie oparte na „google”, dlatego uważa, że ​​okropny kod, który wychodzi z jego rąk, nie powinien być przez nikogo używany w przeciwnym razie.

Myślę, że profesjonaliści w tej dziedzinie będą w stanie samodzielnie zobrazować całą opisaną powyżej logikę, jeśli zajdzie taka potrzeba, a być może nawet pięknie zaprojektować ją jako usługę systemową, jak próbował to zrobić autor.

Taki prosty schemat rotacji migawek pozwoli nam nie tylko stale mieć jedną migawkę w pełni zsynchronizowaną na dysku SATA SSD, ale także pozwoli nam za pomocą narzędzia Thin_delta dowiedzieć się, które bloki zostały zmienione po jej utworzeniu, a tym samym zlokalizować uszkodzenia na główne woluminy, znacznie upraszczając odzyskiwanie.

PRZYTNIJ/ODRZUĆ w libvirt/KVM

Ponieważ magazyn danych będzie używany przez KVM z uruchomioną libvirt, wtedy dobrym pomysłem byłoby nauczenie naszych maszyn wirtualnych nie tylko zajmowania wolnego miejsca, ale także zwalniania tego, co nie jest już potrzebne.

Odbywa się to poprzez emulację obsługi TRIM/DISCARD na dyskach wirtualnych. Aby to zrobić, musisz zmienić typ kontrolera na virtio-scsi i edytować plik 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>

Takie DISCARD z systemów-gościa są poprawnie przetwarzane przez LVM, a bloki są prawidłowo zwalniane zarówno w pamięci podręcznej, jak i w cienkiej puli. W naszym przypadku dzieje się to głównie z opóźnieniem, przy usuwaniu kolejnego migawki.

Kopia zapasowa BTRFS

Użyj gotowych skryptów z skrajny ostrożność i na własne ryzyko. Autor napisał ten kod sam i wyłącznie dla siebie. Jestem pewien, że wielu doświadczonych użytkowników Linuksa ma podobne narzędzia i nie ma potrzeby kopiowania cudzego.

Utwórzmy wolumin na urządzeniu kopii zapasowej:

#lvcreate -L 256G --name backup backup

Sformatujmy to w BTRFS:

#mkfs.btrfs /dev/backup/backup

Utwórzmy punkty montowania i zamontujmy główne podsekcje systemu plików:

#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

Utwórzmy katalogi na kopie zapasowe:

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

Utwórzmy katalog dla skryptów kopii zapasowych:

#mkdir /root/btrfs-backup

Skopiujmy skrypt:

Mnóstwo przerażającego kodu bash. Używaj na własne ryzyko. Nie pisz gniewnych listów do autora...#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

Co to w ogóle robi..?Zawiera zestaw prostych poleceń do tworzenia migawek BTRFS i kopiowania ich na inny system FS za pomocą wysyłania/odbierania BTRFS.

Pierwsze uruchomienie może być stosunkowo długie, ponieważ... Na początku wszystkie dane zostaną skopiowane. Kolejne starty będą już bardzo szybkie, bo... Skopiowane zostaną tylko zmiany.

Kolejny skrypt, który umieścimy w cronie:

Trochę więcej kodu basha#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

Co to robi..?Tworzy i synchronizuje przyrostowe migawki wymienionych woluminów BTRFS na kopii zapasowej FS. Następnie usuwa wszystkie zdjęcia utworzone 60 dni temu. Po uruchomieniu datowane migawki wymienionych woluminów pojawią się w podkatalogach /backup/btrfs/back/remote/.

Nadajmy kodowi prawa wykonania:

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

Sprawdźmy to i umieśćmy w cronie:

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

Cienka kopia zapasowa LVM

Utwórzmy cienką pulę na urządzeniu kopii zapasowej:

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

Zainstalujmy ddrescue, ponieważ... skrypty będą korzystać z tego narzędzia:

#apt-get install gddrescue

Stwórzmy katalog na skrypty:

#mkdir /root/lvm-thin-backup

Skopiujmy skrypty:

Dużo bałaganu w środku...#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

Co to robi...?Zawiera zestaw poleceń do manipulowania cienkimi migawkami i synchronizowania różnicy między dwoma cienkimi migawkami otrzymanymi za pośrednictwem cienkiej_delta do innego urządzenia blokowego przy użyciu ddrescue i blkdiscard.

Kolejny skrypt, który umieścimy w cronie:

Trochę więcej basu#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

Co to robi...?Używa poprzedniego skryptu do tworzenia i synchronizowania kopii zapasowych wymienionych woluminów cienkich. Skrypt pozostawi nieaktywne migawki wymienionych woluminów, które są potrzebne do śledzenia zmian od ostatniej synchronizacji.

Skrypt ten należy edytować, określając listę woluminów cienkich, dla których należy wykonać kopie zapasowe. Podane nazwy służą wyłącznie celom ilustracyjnym. Jeśli chcesz, możesz napisać skrypt, który zsynchronizuje wszystkie woluminy.

Dajmy prawa:

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

Sprawdźmy to i umieśćmy w cronie:

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

Pierwsze uruchomienie będzie długie, bo... cienkie woluminy zostaną w pełni zsynchronizowane poprzez skopiowanie całej używanej przestrzeni. Dzięki cienkim metadanym LVM wiemy, które bloki są faktycznie używane, więc kopiowane będą tylko faktycznie używane bloki cienkich woluminów.

Kolejne uruchomienia będą kopiować dane przyrostowo dzięki śledzeniu zmian za pomocą cienkich metadanych LVM.

Zobaczmy co się stało:

#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

Co to ma wspólnego z lalkami gniazdującymi?

Najprawdopodobniej, biorąc pod uwagę, że woluminy logiczne LVM LV mogą być woluminami fizycznymi LVM PV dla innych VG. LVM może być rekurencyjny, jak lalki zagnieżdżające. Daje to LVM wyjątkową elastyczność.

PS

W kolejnym artykule postaramy się wykorzystać kilka podobnych mobilnych systemów pamięci masowej/KVM jako podstawę do stworzenia rozproszonego geograficznie klastra pamięci masowej/VM z redundancją na kilku kontynentach z wykorzystaniem domowych komputerów stacjonarnych, domowego Internetu i sieci P2P.

Źródło: www.habr.com

Dodaj komentarz