Dzień dobry.
Chcę podzielić się z społecznością praktycznym doświadczeniem w budowaniu systemu przechowywania danych dla KVM z użyciem md RAID + LVM.
Program będzie zawierał:
- Budowa md RAID 1 z NVMe SSD.
- Budowa md RAID 6 z SATA SSD i zwykłych dysków.
- Specyfika działania TRIM/DISCARD na SSD RAID 1/6.
- Tworzenie rozruchowego md RAID 1/6 z ogólnego zestawu dysków.
- Instalacja systemu na NVMe RAID 1 przy braku wsparcia NVMe w BIOS-ie.
- Wykorzystanie LVM cache i LVM thin.
- Wykorzystanie zrzutów BTRFS oraz send/receive do tworzenia kopii zapasowych.
- Wykorzystanie zrzutów LVM thin i thin_delta do tworzenia kopii zapasowych w stylu BTRFS.
Jeśli jesteś zainteresowany, zapraszam poniżej.
Oświadczenie
Autor nie ponosi żadnej odpowiedzialności za konsekwencje używania lub nieużywania materiałów/przykładów/kodu/porad/danych zawartych w tym artykule. Czytając lub w jakikolwiek sposób wykorzystując ten materiał, bierzesz na siebie odpowiedzialność za wszystkie konsekwencje tych działań. Do możliwych konsekwencji należą:
- Przypadki z nieprzyjemnym skutkiem działania NVMe SSD.
- Całkowite zużycie zasobów zapisu i awaria nośników SSD.
- Całkowita utrata wszystkich danych na wszystkich nośnikach, w tym kopiach zapasowych.
- Uszkodzone podzespoły komputerowe.
- Stracony czas, nerwy i pieniądze.
- Jakiekolwiek inne konsekwencje, które nie zostały wymienione powyżej.
Sprzęt
W dostępności było:
Płyta główna z około 2013 roku, na chipsecie Z87 z procesorem Intel Core i7 / Haswell.
- Procesor 4 rdzenie, 8 wątków.
- 32 Gigabajty pamięci RAM DDR3.
- 1 x 16 lub 2 x 8 PCIe 3.0.
- 1 x 4 + 1 x 1 PCIe 2.0.
- 6 x 6 GBps złącza SATA 3.
Adapter SAS LSI SAS9211-8I przerobiony na tryb IT / HBA. Pamięć z obsługą RAID została celowo zastąpiona pamięcią HBA, aby:
- Można było w każdej chwili wyrzucić ten adapter i zastąpić go jakimkolwiek innym pierwszym lepszym.
- Normalnie działał TRIM/Discard na dyskach, ponieważ w firmware RAID te komendy są całkowicie nieobsługiwane, a HBA w ogóle nie ma nic do przekazywania po magistrali.
Dyski twarde — 8 sztuk HGST Travelstar 7K1000 o pojemności 1 TB w formacie 2.5, jak do laptopów. Te dyski wcześniej były w macierzy RAID 6. W nowym systemie również znajdą zastosowanie. Do przechowywania lokalnych kopii zapasowych.
Dodatkowo dodano:
6 sztuk SATA SSD modelu Samsung 860 QVO 2TB. Od tych SSD oczekiwano dużej pojemności, obecności pamięci SLC, pożądanej niezawodności oraz niskiej ceny. Obowiązkowa była obsługa discard/zero, która była weryfikowana przez linijkę w dmesg:
jądro: ata1.00: Włączanie discard_zeroes_data
2 sztuki NVMe SSD modelu Samsung SSD 970 EVO 500GB.
Dla tych SSD kluczowa jest prędkość losowego odczytu/zapisu oraz zasoby dostosowane do Twoich potrzeb. Radiator do nich. Obowiązkowo. Naprawdę obowiązkowo. W przeciwnym razie, — przypieczesz je na chrupko przy pierwszej synchronizacji RAID.
Adapter StarTech PEX8M2E2 dla 2 x NVMe SSD z instalacją w slocie PCIe 3.0 8x. To, znowu, po prostu HBA, ale dla NVMe. Różni się od tanich adapterów brakiem wymogu wsparcia bifurkacji PCIe od płyty głównej, dzięki wbudowanemu przełącznikowi PCIe. Będzie działać nawet w najstarszym systemie, gdzie jest PCIe, nawet jeśli będzie to slot x1 PCIe 1.0. Oczywiście, z odpowiednią prędkością. Żadnych RAIDów tam nie ma. Wbudowanego BIOSu na pokładzie nie ma. Tak więc, Twój system nie nauczy się magicznie bootować z NVMe, a tym bardziej tworzyć NVMe RAID dzięki temu urządzeniu.
Ten komponent był podyktowany wyłącznie posiadaniem tylko jednego wolnego 8x PCIe 3.0 w systemie i, przy posiadaniu 2 wolnych slotów, można go łatwo zastąpić dwoma tanimi PEX4M2E1 lub odpowiednikami, które można kupić wszędzie w cenie od 600 rubli.
Rezygnacja z wszelkich sprzętowych lub wbudowanych w chipsecie/BIOS RAID była świadoma, mając na celu możliwość całkowitej wymiany całego systemu, z wyjątkiem samych SSD/HDD, zachowując wszystkie dane. W idealnym przypadku, aby móc zachować nawet zainstalowany system operacyjny podczas przeprowadzki na zupełnie nowy/demontowany sprzęt. Najważniejsze, by były porty SATA i PCIe. To jak live CD lub bootowalny pendrive, tylko bardzo szybki i nieco gabarytowy.
HumorA wiesz jak to bywa, — czasem trzeba nagle wziąć cały zestaw ze sobą na wynos. A danych nie chce się tracić. W tym celu wszystkie wspomniane nośniki są wygodnie rozmieszczone w szynach w komorach standardowej obudowy 5.25.
No i, oczywiście, do eksperymentów z różnymi sposobami cachowania SSD w Linuxie.
Sprzętowe RAIDy są nudne. Włączasz. Działa lub nie. A z mdadm zawsze są możliwości.
Oprogramowanie
Wcześniej na sprzęcie była zainstalowana Debian 8 Jessie, która jest bliska EOL. Zbudowano RAID 6 z wyżej wspomnianych HDD w parze z LVM. Na nim działały maszyn wirtualnych w kvm/libvirt.
Ponieważ autor ma odpowiednie doświadczenie w tworzeniu przenośnych bootowalnych pamięci SATA/NVMe, a także, aby nie rozrywać znanego szablonu apt, jako docelowy system wybrano Ubuntu 18.04, które już wystarczająco się ustabilizowało, ale wciąż ma 3-letnie wsparcie w perspektywie.
W wymienionym systemie znajdują się wszystkie niezbędne nam sterowniki sprzętowe z pudełka. Nie będzie potrzebne żadne dodatkowe oprogramowanie ani sterowniki.
Przygotowanie do instalacji
Do instalacji systemu potrzebujemy obrazu Ubuntu Desktop. W przypadku systemu serwerowego występuje jakiś nieprzyjemny instalator, który przejawia nadmierną, nieodłączalną samodzielność, nieodmiennie wpychając partycję systemową UEFI na jeden z dysków, psując całą estetykę. Odpowiednio, można go zainstalować tylko w trybie UEFI. Innych opcji nie oferuje.
Nas to nie satysfakcjonuje.
Dlaczego?Niestety, rozruch UEFI jest ekstremalnie źle kompatybilny z programowym RAIDem, ponieważ nikt nie oferuje rezerwacji dla partycji UEFI ESP. W sieci są przepisy, które sugerują umieszczenie partycji ESP na pamięci USB, ale to punkt awarii. Są przepisy z wykorzystaniem programowego mdadm RAID 1 z metadanymi wersji 0.9, które nie przeszkadzają BIOSowi UEFI w widzeniu tej partycji, ale żyje to do szczęśliwego momentu, w którym BIOS lub inny system operacyjny na sprzęcie zapisuje coś w ESP, zapominając zsynchronizować z innymi lustrami.
Ponadto, rozruch UEFI zależy od NVRAM, który nie przeniesie się razem z dyskami do nowego systemu, ponieważ jest częścią płyty głównej.
Zatem nie będziemy wynajdować nowego roweru. Mamy już gotowy, sprawdzony od lat rower, zwany dziś rozruchem Legacy/BIOS, noszący dumne imię CSM w systemach zgodnych z UEFI. Po prostu wyciągniemy go z półki, nasmarujemy, dokompletujemy powietrza w oponach i przetrzemy wilgotną szmatką.
Wersja Desktop Ubuntu również nie obsługuje prawidłowego rozruchu z bootloaderem Legacy, ale, jak się mówi, przynajmniej są jakieś opcje.
I tak, zbieramy sprzęt i uruchamiamy system z bootowalnej pamięci USB Ubuntu Live. Będziemy musieli pobrać pakiety, więc konfigurujemy sieć, która u was działa. Jeśli nie działa, — potrzebne pakiety można wcześniej pobrać na pamięć USB.
Wchodzimy w środowisko Desktop, uruchamiamy emulator terminala i, jedziemy:
#sudo bash
Jak…?Powyższa linia jest kanonicznym zapalnikiem kontrowersji dotyczących sudo. Z bwiększąZ większymi możliwościami wiąże się także większa odpowiedzialność.większąPytanie brzmi, czy jesteś w stanie ją zaakceptować. Wiele osób uważa, że korzystanie z sudo w ten sposób jest przynajmniej nierozważne. Jednak:

#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, de facto pożyczamy moc obliczeniową naszego sprzętu jego twórcom.
Kiedy powierzamy temu oprogramowaniu bezpieczeństwo naszych danych, bierzemy na siebie zobowiązanie równoważne kosztowi ich przywrócenia, za które kiedyś będziemy musieli zapłacić.
Z tej perspektywy ZFS jest Ferrari, a mdadm+lvm bardziej przypomina rower.
Subiektywnie autor woli pożyczyć nieznanym osobom rower na kredyt zamiast Ferrari. Koszt nie jest wysoki. Nie ma potrzeby prawnika. Przepisy drogowe są prostsze. Parkowanie jest darmowe. Możliwości terenowe są lepsze. Do roweru zawsze można dodać dodatkowe nogi, a naprawić go można własnymi rękami.
Dlaczego więc BTRFS…?Aby załadować system operacyjny, potrzebujemy systemu plików wspieranego przez Legacy/BIOS GRUB od razu, który jednocześnie obsługuje migawki na żywo. Użyjemy go dla partycji /boot. Ponadto autor preferuje użycie tego systemu plików dla / (root), nie zapominając, że dla każdego innego oprogramowania można utworzyć osobne partycje na LVM i zamontować je w odpowiednich katalogach.
Nie będziemy na tym systemie plików przechowywać ani obrazów, maszyn wirtualnychani baz danych.
Ten system plików będzie używany tylko do tworzenia błyskawicznych migawek systemu bez jego wyłączania, a następnie przesyłania tych migawek na dysk zapasowy za pomocą send/receive.
Ponadto autor woli trzymać minimum oprogramowania bezpośrednio na sprzęcie i uruchamiać pozostałe oprogramowanie w maszynach wirtualnych, wykorzystując takie możliwości jak przekazywanie GPU i kontrolerów PCI-USB w KVM przez IOMMU.
Na sprzęcie pozostaje tylko — przechowywanie danych, wirtualizacja i kopie zapasowe.
Jeżeli bardziej ufasz ZFS, to w zasadzie do tego zastosowania są one zamienne.
Jednakże autor świadomie ignoruje wbudowane funkcje lustrzania/RAID oraz redundancji, które są dostępne w ZFS, BTRFS i LVM.
Jako dodatkowy argument, BTRFS ma właściwość przekształcania losowego zapisu w sekwencyjny, co ma bardzo pozytywny wpływ na szybkość synchronizacji migawek / kopii zapasowych na HDD.
Ponownie przeskanujemy wszystkie urządzenia:
#udevadm control --reload-rules && udevadm trigger
Rozejrzymy się:
#lsscsi && nvme list
[0:0:0:0] dysk ATA Samsung SSD 860 2B6Q /dev/sda
[1:0:0:0] dysk ATA Samsung SSD 860 2B6Q /dev/sdb
[2:0:0:0] dysk ATA Samsung SSD 860 2B6Q /dev/sdc
[3:0:0:0] dysk ATA Samsung SSD 860 2B6Q /dev/sdd
[4:0:0:0] dysk ATA Samsung SSD 860 2B6Q /dev/sde
[5:0:0:0] dysk ATA Samsung SSD 860 2B6Q /dev/sdf
[6:0:0:0] dysk ATA HGST HTS721010A9 A3J0 /dev/sdg
[6:0:1:0] dysk ATA HGST HTS721010A9 A3J0 /dev/sdh
[6:0:2:0] dysk ATA HGST HTS721010A9 A3J0 /dev/sdi
[6:0:3:0] dysk ATA HGST HTS721010A9 A3B0 /dev/sdj
[6:0:4:0] dysk ATA HGST HTS721010A9 A3B0 /dev/sdk
[6:0:5:0] dysk ATA HGST HTS721010A9 A3B0 /dev/sdl
[6:0:6:0] dysk ATA HGST HTS721010A9 A3J0 /dev/sdm
[6:0:7:0] dysk ATA HGST HTS721010A9 A3J0 /dev/sdn
Węzeł SN Model Przestrzeń Nazwa Użycie 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
Rozdzielenie «dysków»
NVMe SSD
Nie zamierzamy ich w ogóle rozdzielać. Nasz BIOS i tak nie widzi tych nośników. Zatem pójdą całkowicie w programowy RAID. Nawet nie będziemy tam tworzyć partycji. Jeśli ktoś chce po «kanonie» lub «zasadniczo» — utwórzcie jedną dużą partycję, jak na HDD.
SATA HDD
Tutaj nie ma co wymyślać. Utworzymy jedną partycję na wszystko. Utworzymy partycję, ponieważ te dyski widzi BIOS i mogą nawet próbować z nich bootować. Później zainstalujemy GRUB na tych dyskach, aby system mógł to nagle zrobić.
#cat >hdd.part << EOF
etykieta: dos
id-etykiety: 0x00000000
urządzenie: /dev/sdg
jednostka: sektory
/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 jest u nas najciekawiej.
Po pierwsze, nośniki mają rozmiar 2 TB. To w granicach dopuszczalnych dla MBR, z czego skorzystamy. W razie potrzeby można zmienić na GPT. Dyski GPT mają warstwę zgodności, która pozwala systemom zgodnym z MBR widzieć pierwsze 4 partycje, jeśli znajdują się w pierwszych 2 terabajtach. Ważne, aby partycja rozruchowa i partycja bios_grub na tych dyskach były na początku. To pozwala nawet na bootowanie w trybie Legacy / BIOS z dysków GPT.
Jednak to nie nasz przypadek.
Zamierzamy tu stworzyć dwie partycje. Pierwsza będzie miała rozmiar 1 GB i zostanie użyta dla RAID 1 /boot.
Druga będzie używana dla RAID 6 i zajmie całe pozostałe wolne miejsce z wyjątkiem niewielkiego niepodzielonego obszaru na końcu nośnika.
Co to za niepodzielony obszar?Zgodnie z informacjami w sieci nasze SATA SSD mają na pokładzie dynamicznie rozszerzany cache SLC o rozmiarze od 6 do 78 gigabajtów. 6 gigabajtów otrzymujemy «za darmo» dzięki różnicy między «gigabajtami» a «gibibajtami» w specyfikacji technicznej nośnika. Pozostałe 72 gigabajty przydzielane są na podstawie niewykorzystanej przestrzeni.
Należy zauważyć, że nasz cache to SLC, a przestrzeń zajmuje w trybie 4 bit MLC. Co dla nas oznacza, że za każde 4 gigabajty wolnej przestrzeni otrzymujemy tylko 1 gigabajt cache SLC.
Mnożymy 72 gigabajty przez 4 i otrzymujemy 288 gigabajtów. To jest ta wolna przestrzeń, której nie będziemy dzielić, aby umożliwić nośnikom pełne wykorzystanie cache SLC.
W ten sposób uzyskamy efektywnie do 312 gigabajtów pamięci podręcznej SLC łącznie z sześciu nośników. Spośród wszystkich nośników 2 będą używane w RAID dla redundancji.
Taka ilość pamięci podręcznej pozwoli nam niezwykle rzadko w praktyce napotykać sytuacje, w których zapis odbywa się poza pamięcią podręczną. To niezwykle dobrze rekompensuje najpoważniejszą wadę pamięci QLC — bardzo niską prędkość zapisu, gdy dane są zapisywane poza pamięcią podręczną. Jeśli twoje obciążenia nie odpowiadają temu, to zalecam mocno przemyśleć, jak długo będą żyły twoje SSD w takim obciążeniu, biorąc pod uwagę TBW z karty katalogowej.
#cat >ssd.part << EOF
etykieta: dos
id-etykiety: 0x00000000
urządzenie: /dev/sda
jednostka: sektory
/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 macierzy
Na początek musimy zmienić nazwę maszyny. Konieczne jest to, ponieważ nazwa hosta jest częścią nazwy macierzy gdzieś wewnątrz mdadm i gdzieś na coś wpływa. Oczywiście macierze można później zmienić, ale są to zbędne czynności.
#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 —assume-clean…?Aby nie inicjować macierzy. Dla obu poziomów RAID 1 i 6 to dopuszczalne. Wszystko może działać bez inicjalizacji, jeśli jest to nowa macierz. Co więcej, inicjalizacja macierzy SSD podczas tworzenia to marnotrawstwo zasobów TBW. Używamy TRIM/DISCARD tam, gdzie to możliwe, na zbudowanych macierzach SSD, aby je 'zainicjować'.
Macierze SSD RAID 1 obsługują DISCARD od razu.
W macierzach SSD RAID 6 DISCARD trzeba włączyć w parametrach modułu jądra.
Należy to robić tylko wtedy, gdy wszystkie SSD używane w macierzach poziomów 4/5/6 w tym systemie mają działające wsparcie dla discard_zeroes_data. Czasami trafiają się dziwne nośniki, które informują jądro o wsparciu tej funkcji, ale w rzeczywistości go nie mają, lub funkcja działa nie zawsze. Na chwilę obecną wsparcie jest praktycznie wszędzie, jednak starsze nośniki i błędne firmware mogą się zdarzać. Z tego powodu wsparcie DISCARD jest domyślnie wyłączone dla RAID 6.
Uwaga, następne polecenie zniszczy wszystkie dane na nośnikach NVMe 'inicjalizując' macierz 'zerami'.
#blkdiscard /dev/md0
Jeśli coś poszło 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 tak duży…?Zwiększenie rozmiaru chunk-size pozytywnie wpływa na prędkość losowego odczytu blokami do chunk-size włącznie. Dzieje się tak, ponieważ jedna operacja odpowiedniego rozmiaru lub mniejsza może być całkowicie wykonana na jednym urządzeniu. Dlatego IOPS ze wszystkich urządzeń sumuje się. Z danych statystycznych 99% IO nie przekracza 512K.
W RAID 6 IOPS zapisu zawsze jest mniejsze lub równe IOPS pojedynczego dysku. Natomiast w przypadku losowego odczytu IOPS może być kilkukrotnie większe od IOPS pojedynczego dysku, a tutaj kluczowe znaczenie ma rozmiar bloku.
Autor nie widzi sensu w próbach optymalizacji parametrów, które są złe w RAID 6 z założenia i zamiast tego optymalizuje to, w czym RAID 6 radzi sobie dobrze.
Złą losową zapisową operację RAID 6 zrekompensujemy pamięcią podręczną na NVMe oraz sztuczkami z thin-provisioning.
Na razie nie włączyliśmy DISCARD dla RAID 6. Więc nie będziemy ‘inicjować’ tej macierzy, zrobimy to później, po zainstalowaniu systemu.
SATA HDD
#mdadm --create --verbose --assume-clean /dev/md3 --chunk-size=512 --level=6 --raid-devices=8 /dev/sd[g-n]1
LVM na NVMe RAID
Dla prędkości chcemy umieścić główny system plików na NVMe RAID 1, który to /dev/md0.
Jednak ta szybka macierz będzie nam jeszcze potrzebna do innych celów, takich jak swap, metadane i pamięć podręczna LVM-cache oraz metadane LVM-thin, dlatego na tej macierzy stworzymy LVM VG.
#pvcreate /dev/md0
#vgcreate root /dev/md0
Stworzymy partycję dla głównego systemu plików.
#lvcreate -L 128G --name root root
Stworzymy partycję dla swapu o rozmiarze pamięci RAM.
#lvcreate -L 32G --name swap root
Instalacja systemu operacyjnego
Podsumowując, mamy wszystko, co potrzebne do zainstalowania systemu.
Uruchamiamy kreator instalacji systemu w środowisku Ubuntu Live. Zwykła instalacja. Tylko na etapie wyboru dysków do instalacji należy wskazać następujące:
- /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), — использовать как раздел подкачки
- Zainstalować bootloader na /dev/sda
Gdy wybierzemy BTRFS jako główny system plików, instalator automatycznie stworzy dwa wolumeny BTRFS o nazwach "@" dla / (root) oraz "@home" dla /home.
Rozpoczynamy instalację…
Instalacja zakończy się modalnym oknem informującym o błędzie podczas instalacji bootloadera. Niestety, nie da się wyjść z tego dialogu standardowymi środkami i kontynuować instalacji. Wykonujemy logout z systemu i logujemy się ponownie, trafiając na czysty pulpit Ubuntu Live. Otwieramy terminal i ponownie:
#sudo bash
Tworzymy ś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
Skonfigurujemy sieć i hostname 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
Wchodzimy do środowiska chroot:
#chroot /mnt/chroot
Na początek dostarczymy pakiety:
apt-get install --reinstall mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc debsums hdparm
Sprawdzimy i naprawimy wszystkie pakiety, które błędnie się zainstalowały z powodu niedokończonej instalacji systemu:
#CORRUPTED_PACKAGES=$(debsums -s 2>&1 | awk '{print $6}' | uniq)
#apt-get install --reinstall $CORRUPTED_PACKAGES
Jeśli coś poszło nie tak, być może przed tym należy edytować /etc/apt/sources.list
Poprawimy parametry dla modułu RAID 6, aby włączyć TRIM/DISCARD:
#cat >/etc/modprobe.d/raid456.conf << EOF
opcje raid456 devices_handle_discard_safely=1
EOF
Trochę dostosujemy nasze macierze:
#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 będą robić następujące:
- Ustawić odpowiedni rozmiar pamięci podręcznej bloków dla RAID 6 na rok 2020. Domyślna wartość zdaje się nie zmieniać od czasów powstania systemu Linux i od dawna nie jest adekwatna.
- Zarezerwować minimalny IO na czas kontroli/synchronizacji macierzy. Jest to wymagane, aby Twoje macierze nie utknęły w stanie wiecznej synchronizacji pod obciążeniem.
- Ograniczyć maksymalny IO na czas kontroli/synchronizacji macierzy. To ma zapobiec przegrzaniu dysków SSD RAID w trakcie synchronizacji/sprawdzania. Szczególnie ważne dla NVMe. (Pamiętaj o radiatorze? Nie żartowałem.)
- Zabronić dyskom poprzez APM zatrzymywania obrotów talerzy (HDD) i ustawić czas oczekiwania dla kontrolerów dysków na 7 godzin. Można zupełnie wyłączyć APM, jeśli Twoje dyski to potrafią (-B 255). Przy domyślnej wartości dyski będą zatrzymywać się po pięciu sekundach. Następnie system operacyjny będzie chciał zresetować pamięć podręczną dysku, dyski znowu się rozkręcą i wszystko zacznie się od nowa. Dyski mają ograniczoną maksymalną liczbę obrotów talerzy. Taki prosty cykl domyślny może łatwo zniszczyć twoje dyski w ciągu kilku lat. Nie wszystkie dyski na to cierpią, ale nasze „laptopowe”, z odpowiednimi domyślnymi ustawieniami, które przekształcają RAID w krzywe podobieństwo mini-MAID.
- Ustawić readahead na dyskach (obrotowych) na 1 megabajt — dwa kolejno następujące bloki/chunki RAID 6.
- Zabronić readahead na samych macierzach.
Poprawimy /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 domyślne, cache_przestrzeni, 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 tak..?Podział /boot będziemy szukać według UUID, ponieważ nazewnictwo macierzy teoretycznie może się zmienić.
Pozostałe podziały będziemy szukać według nazw LVM w notacji /dev/mapper/vg-lv, ponieważ dostatecznie unikalnie identyfikują podziały.
Nie używamy UUID dla LVM, ponieważ UUID wolumenów LVM i ich snapshotów mogą się pokrywać.Czy montujemy dwukrotnie /dev/mapper/root-root..?Tak. Dokładnie tak. To cecha systemu BTRFS. Tego systemu plików można montować wielokrotnie z różnymi subwolumenami.
W związku z tą samą cechą, zalecam nigdy nie tworzyć zrzutów LVM dla aktywnych wolumenów BTRFS. Możesz otrzymać niespodziankę przy ponownym uruchomieniu.
Przebudujemy konfigurację mdadm:
#/usr/share/mdadm/mkconf | sed 's/#DEVICE/DEVICE/g' >/etc/mdadm/mdadm.conf
Dostosujemy 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 automatyczne rozszerzanie pul LVM thin po osiągnięciu 90% zajętego miejsca o 5% objętości.
Zwiększyliśmy maksymalną liczbę bloków pamięci podręcznej dla LVM cache.
Zabroniliśmy LVM wyszukiwania LVM tomów (PV) na:
- urządzeniach zawierających LVM cache (cdata)
- urządzeniach z pamięcią podręczną LVM cache, omijając pamięć podręczną (_corig). Samo urządzenie kachem będzie przeszukiwane przez cache (po prostu ).
- urządzeniach zawierających metadane LVM cache (cmeta)
- wszystkich urządzeniach w VG o nazwie images. Znajdą się tu obrazy dysków maszyn wirtualnych, a nie chcemy, aby LVM na hoście aktywował tomy należące do systemu gościa.
- wszystkich urządzeniach w VG o nazwie backup. Znajdą się tu kopie zapasowe obrazów maszyn wirtualnych.
- wszystkich urządzeniach, których nazwy kończą się na „gpv” (guest physical volume)
Włączyliśmy wsparcie DISCARD podczas zwalniania wolnego miejsca na LVM VG. Bądź ostrożny. To wydłuży czas usuwania LV na SSD, szczególnie w przypadku RAID 6 SSD. Planujemy używać thin provisioning, więc nam to w ogóle nie przeszkadza.
Zaktualizujemy obraz initramfs:
#update-initramfs -u -k all
Zainstalujemy i skonfigurujemy grub:
#apt-get install grub-pc
#apt-get purge os-prober
#dpkg-reconfigure grub-pc
Jakie dyski wybrać?Wszystkie, które zaczynają się od sd*. System powinien być w stanie uruchomić się z dowolnego działającego dysku SATA lub SSD.
Dlaczego wyłączyliśmy os-prober..?Za zbytnią samodzielność i figlarskie rączki.
Nie działa poprawnie, gdy jeden z RAID-ów jest w stanie degradowanym. Próbuje szukać OS na partycjach, które są używane w wirtualnych maszynach działających na tym sprzęcie.
Jeśli jest ci potrzebny, możesz go zostawić, ale pamiętaj o powyższym. Polecam poszukać w sieci przepisów na pozbycie się figlarskich rączek.
Na tym kończymy początkową instalację. Czas zrestartować się w nowo zainstalowanym OS. Nie zapomnij wyjąć bootowalnego Live CD/USB.
#exit
#reboot
Jako urządzenie rozruchowe wybieramy dowolny z SATA SSD.
LVM na SATA SSD
W tym momencie już uruchomiliśmy nowy system operacyjny, skonfigurowaliśmy sieć, apt, otworzyliśmy emulator terminala i uruchomiliśmy:
#sudo bash
Kontynuujmy.
„Inicjalizujemy” macierz z SATA SSD:
#blkdiscard /dev/md2
Jeśli nie zadziałało, spróbuj:
#blkdiscard --step 65536 /dev/md2
Tworzymy LVM VG na SATA SSD:
#pvcreate /dev/md2
#vgcreate data /dev/md2
Dlaczego jeszcze jedna VG..?W rzeczy samej, już mamy VG o nazwie root. Czemu nie połączyć wszystkiego w jedną VG?
Jeśli w VG jest kilka PV, to aby poprawnie aktywować VG, wszystkie PV muszą być obecne (online). Wyjątkiem jest LVM RAID, którego zamierzamy celowo nie używać.
Bardzo chcemy, aby w przypadku awarii (czytaj utraty danych) na którymkolwiek z macierzy RAID 6 system operacyjny uruchomił się normalnie i dał nam możliwość rozwiązania problemu.
W tym celu, na pierwszym poziomie abstrakcji, będziemy izolować każdy typ fizycznego «nośnika» w oddzielnej VG.
Jeśli mówimy naukowo, różne macierze RAID odnoszą się do różnych «domen niezawodności». Nie warto tworzyć dla nich dodatkowego wspólnego punktu awarii, umieszczając je w jednej VG.
Obecność LVM na poziomie «sprzętowym» pozwoli nam dowolnie dzielić kawałki różnych macierzy RAID, różnie je łącząc. Na przykład — uruchomić równocześnie bcache + LVM thin, bcache + BTRFS, LVM cache + LVM thin, skomplikowaną konfigurację ZFS z cache'ami lub jakąkolwiek inną piekielną mieszankę, aby móc to wszystko dotknąć i porównać.
Na poziomie «sprzętowym» nie będziemy używać niczego poza starymi dobrymi «grubymi» wolumenami LVM. Wyjątkiem z tej zasady być może będzie partycja do kopii zapasowej.
Myślę, że w tym momencie wielu czytelników zaczęło już coś podejrzewać odnośnie matrioszki.
LVM na SATA HDD
#pvcreate /dev/md3
#vgcreate backup /dev/md3
Znowu nowa VG..?Bardzo chcemy, aby w przypadku awarii macierzy dysków, którą będziemy używać do kopii zapasowej danych, nasz system operacyjny nadal działał normalnie, zachowując dostęp do danych nieobjętych kopią zapasową. Dlatego, aby zapobiec problemom z aktywacją VG, tworzymy oddzielną VG.
Konfiguracja LVM cache
Utworzymy LV na NVMe RAID 1, aby używać go jako urządzenia cache.
#lvcreate -L 70871154688B --name cache root
Czemu tak mało…?Faktem jest, że nasze NVMe SSD również mają pamięć podręczną SLC. 4 gigabajty «darmowego» i 18 gigabajtów dynamicznego dzięki wolnej przestrzeni zajmowanej w 3-bitowym MLC. Po wyczerpaniu tej pamięci podręcznej, NVMe SSD nie będą znacznie szybsze od naszego SATA SSD z pamięcią podręczną. Właściwie z tego powodu nie ma sensu tworzyć partii LVM cache w znacznym stopniu większej niż dwukrotny rozmiar pamięci podręcznej SLC dysku NVMe. Dla używanych NVMe dysków autor uważa za rozsądne stworzyć 32-64 gigabajty pamięci podręcznej.
Podany rozmiar partii jest niezbędny do zorganizowania 64 gigabajtów pamięci podręcznej, umiejscowienia metadanych pamięci podręcznej i kopii zapasowej metadanych.
Dodatkowo zauważę, że po nieczystym wyłączeniu systemu LVM oznaczy całą pamięć podręczną jako brudną i będzie ją synchronizować na nowo. Co więcej, będzie to powtarzane przy każdym użyciu lvchange na tym urządzeniu aż do nowego uruchomienia systemu. Dlatego zalecam natychmiastowe odtworzenie pamięci podręcznej odpowiednim skryptem.
Utwórzmy LV na SATA RAID 6, aby używać go jako urządzenia do buforowania.
#lvcreate -L 3298543271936B --name cache data
Dlaczego tylko trzy terabajty..?Aby w razie potrzeby można było użyć SATA SSD RAID 6 do innych zadań. Rozmiar buforowanej przestrzeni można zwiększyć dynamicznie, w locie, bez zatrzymywania pracy systemu. W tym celu konieczne jest tymczasowe zatrzymanie i ponowne włączenie pamięci podręcznej, ale różnicą w stosunku do na przykład bcache jest to, że można to robić w locie.
Utwórzmy nową VG do buforowania.
#pvcreate /dev/root/cache
#pvcreate /dev/data/cache
#vgcreate cache /dev/root/cache /dev/data/cache
Utwórzmy LV na buforowanym urządzeniu.
#lvcreate -L 3298539077632B --name cachedata cache /dev/data/cache
Tutaj zajęliśmy od razu całe wolne miejsce na /dev/data/cache, aby wszystkie inne potrzebne partycje były tworzone od razu na /dev/root/cache. Jeśli coś powstało w złym miejscu, można 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 chunksize..?Metodą praktycznych eksperymentów autorowi udało się ustalić, że najlepszy wynik osiąga się, gdy rozmiar bloku pamięci podręcznej LVM odpowiada rozmiarowi bloku LVM thin. Przy tym, im mniejszy rozmiar, tym lepiej sprawdza się konfiguracja w przypadku losowego zapisu.
64k to minimalny rozmiar bloku dopuszczalny dla LVM thin.
Uważaj na writeback..!Tak. Ten typ pamięci podręcznej odkłada synchronizację zapisu na buforowane urządzenie. Prowadzi to do tego, że w przypadku utraty pamięci podręcznej można stracić dane na buforowanym urządzeniu. Później autor omówi, jakie dodatkowe środki, poza NVMe RAID 1, można zastosować, aby zrekompensować to ryzyko.
Ten typ pamięci podręcznej został wybrany celowo, aby zrekompensować niską wydajność RAID 6 w przypadku losowego zapisu.
Sprawdźmy, co udało nam się osiągnąć:
#lvs -a -o lv_name,lv_size,devices --units B cache
Urządzenia LV LSize
[cache] 68719476736B cache_cdata(0)
[cache_cdata] 68719476736B /dev/root/cache(0)
[cache_cmeta] 1073741824B /dev/root/cache(16384)
cachedata 3298539077632B cachedata_corig(0)
[cachedata_corig] 3298539077632B /dev/data/cache(0)
[lvol0_pmspare] 1073741824B /dev/root/cache(16640)
Na /dev/data/cache powinien znajdować się tylko [cachedata_corig]. Jeśli coś jest nie tak, użyj pvmove.
Aby wyłączyć pamięć podręczną w razie potrzeby, wystarczy jedna komenda:
#lvconvert -y --uncache cache/cachedata
To odbywa się online. LVM po prostu synchronizuje pamięć podręczną na dysk, usuwa ją i zmienia nazwę cachedata_corig z powrotem na cachedata.
Konfiguracja LVM thin
Przybliżmy, ile miejsca będziemy potrzebować na metadane LVM thin:
#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - oszacowana wielkość obszaru metadanych wynosząca 3385794560 bajtów dla "--block-size=64kibibytes --pool-size=6terabytes --max-thins=100000"
Zaokrąglijmy do 4 GB: 4294967296B
Pomnóżmy przez dwa i dodajmy 4194304B dla metadanych LVM PV: 8594128896B
Utworzymy oddzielną partycję na NVMe RAID 1, aby umieścić na niej metadane LVM thin i ich kopię zapasową:
#lvcreate -L 8594128896B --name images root
Dlaczego..?Może powstać pytanie, dlaczego warto umieszczać metadane LVM thin osobno, skoro i tak będą buforowane na NVMe i będą działać szybko.
Szybkość, chociaż ważna, nie jest głównym powodem. Chodzi o to, że bufor to punkt awarii. Może się zdarzyć, że coś się z nim stanie i jeśli metadane LVM thin będą buforowane, może to doprowadzić do całkowitej utraty wszystkiego. Bez kompletnych metadanych odbudowanie cienkich woluminów będzie praktycznie niemożliwe.
Przemieszczając metadane na oddzielny, niebuforowany, ale szybki wolumin, gwarantujemy zachowanie metadanych w przypadku utraty lub uszkodzenia bufora. W takim przypadku wszelkie uszkodzenia spowodowane utratą bufora będą zlokalizowane wewnątrz cienkich woluminów, co znacznie uprości proces przywracania. Istnieje duże prawdopodobieństwo, że te uszkodzenia będą odbudowywalne za pomocą dzienników FS.
Co więcej, jeśli wcześniej utworzono migawkę cienkiego woluminu, a następnie bufor został przynajmniej raz całkowicie zsynchronizowany, to ze względu na szczególności wewnętrznej budowy LVM thin, integralność migawki będzie gwarantowana w przypadku utraty bufora.
Utworzymy nowy VG, który będzie odpowiedzialny za thin-provisioning:
#pvcreate /dev/root/images
#pvcreate /dev/cache/cachedata
#vgcreate images /dev/root/images /dev/cache/cachedata
Utworzymy pulę:
#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
Dlaczego -Z yOprócz głównego celu, do którego ten tryb jest właściwie przeznaczony — zapobiegania wyciekaniu danych z jednej maszyny wirtualnej do drugiej podczas redistribucji przestrzeni — zerowanie dodatkowo wykorzystuje się do zwiększenia prędkości losowego zapisu blokami mniejszymi niż 64k. Każde zapisanie poniżej 64k w wcześniej nieprzydzielonym obszarze cienkiego woluminu będzie przekształcane w 64K wyrównane do granicy bufora. Umożliwi to wykonanie operacji całkowicie przez bufor, omijając urządzenie buforowane.
Przemieścimy LV na odpowiednie 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
Urządzenia LV LSize
[lvol0_pmspare] 4294967296B /dev/root/images(0)
thin-pool 274877906944B thin-pool_tdata(0)
[thin-pool_tdata] 274877906944B /dev/cache/cachedata(0)
[thin-pool_tmeta] 4294967296B /dev/root/images(1024)
Utworzymy cienki wolumin do testów:
#lvcreate -V 64G --thin-pool thin-pool --name test images
Zainstalujemy pakiety do testów i monitorowania:
#apt-get install sysstat fio
W ten sposób możemy obserwować zachowanie naszej konfiguracji magazynu w czasie rzeczywistym:
#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
Uwaga! Zasób!Ten kod uruchomi 36 różnych testów, z których każdy będzie trwał 4 sekundy. Połowa z tych testów dotyczy zapisu. W ciągu 4 sekund na NVMe można zrealizować bardzo dużo - do 3 gigabajtów na sekundę. Tak więc każdy uruchomienie testów zapisu może zużyć do 216 gigabajtów zasobów SSD.
Czy mieszane odczyty i zapisy?Tak. Testy odczytu i zapisu najlepiej uruchamiać osobno. Co więcej, warto upewnić się, że wszystkie cache są zsynchronizowane, aby wcześniejsze zapisy nie wpływały na odczyt.
Wyniki będą się znacznie różnić przy pierwszym uruchomieniu oraz kolejnych, w miarę wypełniania cache i cienkiego wolumenu, a także w zależności od tego, czy system zdążył zsynchronizować cache wypełnione podczas wcześniejszego uruchomienia.
Oprócz tego, zalecam zmierzenie prędkości na już zapełnionym cienkim wolumenie, z którego właśnie zrobiono snapshot. Autor miał okazję zaobserwować, jak losowy zapis znacznie przyspiesza natychmiast po utworzeniu pierwszego snapshotu, szczególnie, gdy cache nie jest jeszcze w pełni wypełniony. Dzieje się tak dzięki semantyce copy-on-write, wyrównywaniu bloków cache i cienkiego wolumenu oraz temu, że losowy zapis na RAID 6 staje się losowym odczytem z RAID 6 z późniejszym zapisem do cache. W naszej konfiguracji losowy odczyt z RAID 6 jest do 6 razy (liczba SATA SSD w macierzy) szybszy od zapisu. Ponieważ bloki dla CoW są przydzielane sekwencyjnie z cienkiego puli, zapis w dużej mierze także przekształca się w sekwencyjny.
Obie te cechy można korzystnie wykorzystać.
Cache-«koherentne» snapshoty
Aby zmniejszyć ryzyko utraty danych w przypadku uszkodzenia/utraty cache, autor proponuje wprowadzenie praktyki rotacji snapshotów, gwarantującej ich integralność w takim przypadku.
Przede wszystkim, dzięki temu, że metadane cienkich wolumenów znajdują się na urządzeniu niecache'owanym, metadane będą integralne, a możliwe straty będą izolowane wewnątrz bloków danych.
Następna cykl rotacji snapshotów daje gwarancję integralności danych w snapshotach w przypadku utraty cache:
- Dla każdego cienkiego wolumenu o nazwie <nazwa> tworzymy snapshot o nazwie <nazwa>.cached
- Ustawimy próg migracji na rozsądnie wysoką wartość:
#lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata - W pętli sprawdzamy liczbę brudnych bloków w cache:
#lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}'dopóki nie osiągniemy zera. Jeśli zera nie ma zbyt długo, można je tymczasowo stworzyć, przełączając pamięć podręczną w tryb writethrough. Jednak biorąc pod uwagę szybkość naszych dysków SATA i NVMe SSD oraz ich zasoby TBW, albo uda się wystarczająco szybko złapać moment bez zmiany trybu pamięci podręcznej, albo sprzęt całkowicie wykorzysta swoje zasoby w ciągu kilku dni. Z powodu ograniczeń zasobów system w zasadzie nie może być pod 100% obciążeniem zapisu przez cały czas. Nasze NVMe SSD pod 100% obciążeniem zapisu wyczerpią swoje zasoby w ciągu 3-4 dni.. SATA SSD będą działać około dwa razy dłużej. Dlatego uznamy, że większość obciążenia jest na odczycie, a przy zapisie mamy stosunkowo krótkotrwałe wzrosty ekstremalnej aktywności w połączeniu z niskim obciążeniem w średnim. - Jak tylko zauważymy (lub stworzymy) zero — zmieniamy nazwę <imię>.cached na <imię>.committed. Stary <imię>.committed w tym czasie usuwamy.
- Opcjonalnie, jeśli pamięć podręczna jest wypełniona w 100%, można ją odtworzyć za pomocą skryptu, w ten sposób ją czyszcząc. Z półpustą pamięcią podręczną system działa znacznie szybciej przy zapisie.
- Ustawimy próg migracji na zero:
#lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedataTo tymczasowo zablokuje synchronizację pamięci podręcznej z głównym nośnikiem. - Czekamy, aż w pamięci podręcznej zbierze się wystarczająco dużo zmian
#lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}'lub zadziała timer. - Powtarzamy od nowa.
Po co te trudności z progiem migracji…?Chodzi o to, że w praktyce „losowy” zapis w rzeczywistości nie jest do końca losowy. Jeśli zapisaliśmy coś w sektorze o rozmiarze 4 kilobajty, istnieje duża szansa, że w najbliższych kilku minutach zostanie zapisane coś w tym samym lub jednym z sąsiednich (+- 32K) sektorów.
Ustawiając próg migracji na zero, odkładamy synchronizację zapisu na SATA SSD i agregujemy kilka zmian jednego bloku 64K w pamięci podręcznej. W ten sposób znacznie oszczędzamy zasoby SATA SSD.
A gdzie kod…?Niestety, autor uważa się za niewystarczająco kompetentnego w zakresie tworzenia skryptów bash, ponieważ jest w 100% samoukiem i praktykuje rozwój oparty na „google”, dlatego uważa, że ten straszny kod, który wychodzi spod jego rąk, lepiej niech nie używa nikt inny.
Myślę, że profesjonaliści w tej dziedzinie będą w stanie samodzielnie odtworzyć całą opisaną powyżej logikę w razie potrzeby, a może nawet ładnie to przedstawić w formie usługi systemd, tak jak próbował to zrobić autor.
Taki prosty schemat rotacji zrzutów pozwoli nam nie tylko na ciągłe posiadanie jednego w pełni zsynchronizowanego zrzutu na SATA SSD, ale także umożliwi przy pomocy narzędzia thin_delta sprawdzenie, które bloki zostały zmienione po jego utworzeniu, co znacznie ułatwi lokalizację uszkodzeń na głównych wolumenach i przyspieszy odzyskiwanie.
TRIM/DISCARD w libvirt/KVM
Ponieważ przechowalnia danych będzie używana dla KVM zarządzanego przez libvirt, warto nauczyć nasze VM nie tylko zajmować wolne miejsce, ale również zwalniać to, które jest już niepotrzebne.
Robi się to poprzez emulację wsparcia TRIM/DISCARD na wirtualnych dyskach. W tym celu należy zmienić typ kontrolera na virtio-scsi i edytować xml.
#virsh edit vmname
<disk type='block' device='disk'>
<driver name='qemu' type='raw' cache='writethrough' io='threads' discard='unmap'/>
<source dev='/dev/images/vmname'/>
<backingStore/>
<target dev='sda' bus='scsi'/>
<alias name='scsi0-0-0-0'/>
</disk>
<controller type='scsi' index='0' model='virtio-scsi'>
<alias name='scsi0'/>
</controller>
Podobne DISCARD-y z systemów gości są prawidłowo przetwarzane przez LVM i bloki są poprawnie zwalniane zarówno w pamięci podręcznej, jak i w cienkim pule. W naszym przypadku odbywa się to głównie opóźnione, przy usuwaniu kolejnego zrzutu.
Kopia zapasowa BTRFS
Użyj gotowych skryptów z ostrożnością i . Autor napisał ten kod sam i wyłącznie dla siebie. Jestem pewien, że wielu doświadczonych użytkowników Linuxa ma podobne rozwiązania, i nie będzie potrzeby kopiowania cudzych. na własne ryzykoStworzymy wolumen na urządzeniu zapasowym:
Sformatujemy w BTRFS:
#lvcreate -L 256G --name backup backup
Stworzymy punkty montowania i zamontujemy podurozbudowy systemu plików:
#mkfs.btrfs /dev/backup/backup
Stworzymy katalogi na kopie zapasowe:
#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
Stworzymy katalog na skrypty kopii zapasowej:
#mkdir /backup/btrfs/back/remote
#mkdir /backup/btrfs/back/remote/root
#mkdir /backup/btrfs/back/remote/boot
Skopiujemy skrypt:
#mkdir /root/btrfs-backup
Dużo strasznego kodu bash. Używaj na własne ryzyko. Nie pisać do autora z oburzonymi listami...
SCRIPT_FILE="$(realpath $0)"#cat >/root/btrfs-backup/btrfs-backup.sh << EOF
#!/bin/bash
ŚCIEŻKA="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
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 "Czekam na blokadę..."
{
wait_lock || terminate "Nie udało się uzyskać blokady. Kończę..."
echo "Otrzymano blokadę..."
function suffix()
}
FORMATTED_DATE=$(date +"$DATE_FORMAT")
{
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
function filter()
}
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
{
function backup()
function filter()
}
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
then
echo "$SOURCE_BASE_PATH znaleziono"
inaczej
echo "$SOURCE_BASE_PATH Plik nie znaleziony, tworzę migawkę z $SOURCE_PATH do $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
jeśli [ -d "$TARGET_BASE_PATH" ]
then
echo "$TARGET_BASE_PATH znaleziono, jest niesynchroniczny ze źródłem... usuwam..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
jeśli [ -d "$TARGET_BASE_PATH" ]
then
echo "$TARGET_BASE_PATH znaleziono"
inaczej
echo "$TARGET_BASE_PATH nie znaleziono. Synchronicznie do $TARGET_BASE_DIR"
btrfs send $SOURCE_BASE_PATH | btrfs receive $TARGET_BASE_DIR
sync
fi
jeśli [ -d "$SOURCE_PEND_PATH" ]
then
echo "$SOURCE_PEND_PATH znaleziono, usuwam..."
btrfs subvolume delete -c $SOURCE_PEND_PATH
sync
fi
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_PEND_PATH
sync
jeśli [ -d "$TARGET_PEND_PATH" ]
then
echo "$TARGET_PEND_PATH znaleziono, usuwam..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Wysyłanie $SOURCE_PEND_PATH do $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
}
funkcja 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"
}
funkcja 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
}
funkcja 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 "Pomoc"
;;
"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 "Nic.."
;;
esac
) 98>$LOCK_FILE
EOF
Co to właściwie robi..?Zawiera zestaw prostych poleceń do tworzenia migawek BTRFS i ich kopiowania na inny system plików za pomocą BTRFS send/receive.
Pierwsze uruchomienie może być stosunkowo długie, ponieważ na początku skopiowane zostaną wszystkie dane. Dalsze uruchomienia będą bardzo szybkie, ponieważ będą kopiowane tylko zmiany.
Jeszcze jeden skrypt, który dodamy do cron:
Jeszcze trochę kodu bash#cat >/root/btrfs-backup/cron-daily.sh << EOF
#!/bin/bash
ŚCIEŻKA="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"
LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
BACKUP_SCRIPT="$SCRIPT_DIR/btrfs-backup.sh"
RETENCJA="-60 dni"
$BACKUP_SCRIPT backup root/@
$BACKUP_SCRIPT removeall root/@ "$RETENCJA"
$BACKUP_SCRIPT backup root/@home
$BACKUP_SCRIPT removeall root/@home "$RETENCJA"
$BACKUP_SCRIPT backup boot/
$BACKUP_SCRIPT removeall boot/ "$RETENCJA"
EOF
Co to robi..?Tworzy i synchronizuje na backupowym systemie plików inkrementalne migawki wymienionych woluminów BTRFS. Następnie usuwa wszystkie migawki utworzone 60 dni temu. Po uruchomieniu w podkatalogach /backup/btrfs/back/remote/ pojawią się datowane migawki wymienionych woluminów.
Nadajemy kodowi prawa do wykonania:
#chmod +x /root/btrfs-backup/cron-daily.sh
#chmod +x /root/btrfs-backup/btrfs-backup.sh
Sprawdzamy i dodajemy do crona:
#/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
Kopia zapasowa LVM thin
Utworzymy cienką pulę na urządzeniu zapasowym:
#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T backup/thin-pool
Zainstalujemy ddrescue, ponieważ skrypty będą używać tego narzędzia:
#apt-get install gddrescue
Utworzymy katalog dla skryptów:
#mkdir /root/lvm-thin-backup
Skopiuj skrypty:
Bardzo wiele bash wewnątrz...#cat >/root/lvm-thin-backup/lvm-thin-backup.sh << EOF
#!/bin/bash
ŚCIEŻKA="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
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"
BASE_SUFFIX=".base"
PEND_SUFFIX=".pend"
SNAP_SUFFIX=".snap"
BACKUPS="backup"
BACKUPS_POOL="thin-pool"
export LVM_SUPPRESS_FD_WARNINGS=1
echo "$1" >&2
{
exit 1
function wait_lock()
}
flock 98
{
function wait_lock_or_terminate()
}
echo "Czekam na blokadę..."
{
wait_lock || terminate "Nie udało się uzyskać blokady. Kończę..."
echo "Otrzymano blokadę..."
function suffix()
}
FORMATTED_DATE=$(date +"$DATE_FORMAT")
{
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
function filter()
}
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
{
function backup()
function filter()
}
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 "Źródłowy LV nie jest cienki.")
function wait_lock()
fi
if [ "$DIFF_TARGET_POOL" == "" ]
then
(>&2 echo "Celowy LV nie jest cienki.")
function wait_lock()
fi
if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ]
then
(>&2 echo "Źródłowe i celowe LV należą do różnych cienkich pul.")
function wait_lock()
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 "Zrzut metadanych puli cienkiej już istnieje. Zakładając, że jest przestarzały. Zwolnij zrzut metadanych za 5 sekund.")
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 "Nie udało się utworzyć zrzutu metadanych puli cienkiej.")
function wait_lock()
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 znaleziono"
inaczej
echo "$DISCARD_LV nie znaleziono w $DISCARD_VG"
function wait_lock()
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
}
SOURCE_PATH="$MOUNTS$1"
{
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"
inaczej
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"
inaczej
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 "Syncing $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"
inaczej
echo "$SOURCE_BASE_LV_PATH not found"
function wait_lock()
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH found"
inaczej
echo "$TARGET_BASE_LV_PATH not found"
function wait_lock()
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"
inaczej
echo "$SOURCE_BASE_LV_PATH not found"
function wait_lock()
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
then
echo "$TARGET_BASE_LV_PATH found"
inaczej
echo "$TARGET_BASE_LV_PATH not found"
function wait_lock()
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 Synchronizing "$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 "Syncing $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"
inaczej
CMP_OFFSET=""
fi
zrobione
echo Done...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}
funkcja 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"
}
funkcja remove()
{
REMOVE_TARGET_VG="$BACKUPS"
REMOVE_TARGET_LV="$1"
lvremove -y "$REMOVE_TARGET_VG/$REMOVE_TARGET_LV"
sync
}
funkcja removeall()
{
DATE_OFFSET="$3"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
usuń "$SNAPSHOT"
zrobione < <(list "$1" "$2" | grep "$FILTER")
}
(
COMMAND="$1"
shift
case "$COMMAND" in
"--help")
echo "Pomoc"
;;
"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
usuń "$1"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2" "$3"
;;
*)
echo "Nic.."
;;
esac
) 98>$LOCK_FILE
EOF
Co to robi…?Zawiera zbiór poleceń do manipulowania cienkimi zrzutami i synchronizacji różnic między dwoma cienkimi zrzutami, uzyskanymi przez thin_delta, na inne urządzenie blokowe z użyciem ddrescue i blkdiscard.
Kolejny skrypt, który umieścimy w cron:
Jeszcze trochę bash#cat >/root/lvm-thin-backup/cron-daily.sh << EOF
#!/bin/bash
ŚCIEŻKA="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"
LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
BACKUP_SCRIPT="$SCRIPT_DIR/lvm-thin-backup.sh"
RETENTION="-60 dni"
$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, aby utworzyć i zsynchronizować kopie zapasowe wymienionych cienkich woluminów. Skrypt pozostawi nieaktywne zrzuty wymienionych woluminów, które są potrzebne do śledzenia zmian od ostatniej synchronizacji.
Ten skrypt należy edytować, wskazując listę cienkich woluminów, dla których mają być wykonywane kopie zapasowe. Podane nazwy są tylko przykładem. Można napisać skrypt, który będzie synchronizował wszystkie woluminy.
Nadajemy uprawnienia:
#chmod +x /root/lvm-thin-backup/cron-daily.sh
#chmod +x /root/lvm-thin-backup/lvm-thin-backup.sh
Sprawdzamy i dodajemy do crona:
#/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, ponieważ cienkie woluminy będą w pełni synchronizowane przez kopiowanie całej używanej przestrzeni. Dzięki metadanym LVM thin wiemy, które bloki są faktycznie używane, więc kopiowane będą tylko rzeczywiście używane bloki cienkich woluminów.
Następne uruchomienia będą kopiować dane inkrementalnie dzięki śledzeniu zmian przez metadane LVM thin.
Zobaczmy, co się wydarzył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 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 Rozmiar
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 Rozmiar
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 ma wspólnego z matrioszkami?
Prawdopodobnie chodzi o to, że logiczne wolumeny LVM LV mogą być fizycznymi wolumenami LVM PV dla innych VG. LVM może być rekurencyjny, jak matrioszki. To daje LVM niezwykłą elastyczność.
P.S.
W następnym artykule spróbujemy wykorzystać kilka podobnych mobilnych macierzy RAID/KVM jako bazę do stworzenia geo-rozproszonego storage/vm klastra z replikacją na kilku kontynentach przez domowe desktopy, domowy internet oraz sieci P2P.
Źródło: habr.com
