Што агульнага паміж LVM і матрошка?

Добрага часу сутак.
Жадаю падзяліцца з супольнасцю практычным досведам пабудовы сістэмы захоўвання дадзеных для KVM з выкарыстаннем md RAID + LVM.

У праграме будзе:

  • Зборка md RAID 1 з NVMe SSD.
  • Зборка md RAID 6 з SATA SSD і звычайных дыскаў.
  • Асаблівасці працы TRIM / DISCARD на SSD RAID 1/6.
  • Стварэнне загрузнага md RAID 1/6 масіва на агульным наборы дыскаў.
  • Ўстаноўка сістэмы на NVMe RAID 1 пры адсутнасці падтрымкі NVMe у BIOS.
  • Выкарыстанне LVM cache і LVM thin.
  • Выкарыстанне BTRFS здымкаў і send/recieve для рэзервовага капіявання.
  • Выкарыстанне LVM thin здымкаў і thin_delta для рэзервовага капіявання ў стылі BTRFS.

Калі зацікавіла, прашу пад кат.

Заява

Аўтар не нясе ніякай адказнасці за наступствы выкарыстання або не выкарыстання матэрыялаў/прыкладаў/кода/саветаў/даных з гэтага артыкула. Чытаючы ці нейкім чынам выкарыстаючы дадзены матэрыял вы бераце на сябе адказнасць за ўсе наступствы ад гэтых дзеянняў. Да магчымых наступстваў адносяцца:

  • Засмажаныя да хрумсткай скарыначкі NVMe SSD.
  • Цалкам выдаткаваны рэсурс запісу і выхад са строю SSD назапашвальнікаў.
  • Поўная страта ўсіх даных на ўсіх назапашвальніках, у тым ліку рэзервовых копій.
  • Няспраўнае кампутарнае жалеза.
  • Выдаткаваны час, нервы і грошы.
  • Любыя іншыя наступствы, якія не пералічаны вышэй.

жалеза

У наяўнасці было:

Матчын поплатак дзесьці 2013 года выпуску на чыпсэце Z87 у камплекце з Intel Core i7/Haswell.

  • Працэсар 4 ядра, 8 патокаў
  • 32 Гігабайта аператыўнай памяці DDR3
  • 1 x 16 ці 2 x 8 PCIe 3.0
  • 1 x 4 + 1 x 1 PCIe 2.0
  • 6 x 6 GBps SATA 3 раздыма

SAS адаптар LSI SAS9211-8I перапрашытая ў рэжым IT / HBA. Прашыўка з падтрымкай RAID наўмысна заменена на прашыўку HBA каб:

  1. Можна было ў любы момант выкінуць гэты адаптар і замяніць на любы іншы першы які трапіў.
  2. Нармальна працаваў TRIM/Discard на дысках, т.я. у RAID прашыўцы гэтыя каманды не падтрымліваюцца зусім, а HBA увогуле-то ўсё роўна якія каманды па шыне перадаваць.

Жорсткія дыскі, – 8 штук HGST Travelstar 7K1000 аб'ёмам 1 TB у формаў-фактары 2.5, як для наўтбукаў. Гэтыя дыскі раней былі ў RAID 6 масіве. У новай сістэме ім таксама знойдзецца ўжыванне. Для захоўвання лакальных рэзервовых копій.

Дадаткова было дададзена:

6 штук SATA SSD мадэлі Samsung 860 QVO 2TB. Ад гэтых SSD патрабаваўся вялікі аб'ём, наяўнасць SLC кэша, пажаданая надзейнасць, і, невысокі кошт. Абавязкова была падтрымка discard/zero якая правяраецца радком у dmesg:

kernel: ata1.00: Enabling discard_zeroes_data

2 штукі NVMe SSD мадэлі Samsung SSD 970 EVO 500GB.

Для гэтых SSD важна хуткасць выпадковага чытання/запісы і рэсурс пад вашыя патрэбы. Радыятар да іх. Абавязкова. Зусім абавязкова. Інакш, - прасмажыце іх да хрумсткай скарыначкі пры першай-ж сінхранізацыі RAIDa.

Адаптар StarTech PEX8M2E2 для 2 x NVMe SSD з усталёўкай у PCIe 3.0 8x слот. Гэта, зноў жа, проста HBA, але для NVMe. Адрозніваецца ад танных адаптараў адсутнасцю патрабавання падтрымкі PCIe bifurcation ад матчынай платы дзякуючы наяўнасці ўбудаванага PCIe камутатара. Будзе працаваць нават у самай старажытнай сістэме, дзе ёсць PCIe, нават калі гэта будзе x1 PCIe 1.0 слот. Натуральна, з адпаведнай хуткасцю. Ніякіх RAIDаў тамака няма. Убудаванага BIOS на борце няма. Так што, ваша сістэма магічна не навучыцца загружацца з NVMe і тым больш рабіць NVMe RAID дзякуючы гэтай прыладзе.

Кампанент гэты быў абумоўлены выключна наяўнасцю толькі аднаго вольнага 8x PCIe 3.0 у сістэме, і, пры наяўнасці 2х вольных слотаў, лёгка замяняецца на два капейкавых PEX4M2E1 або аналагі, якіх можна купіць дзе заўгодна па кошце ад 600 рублёў.

Адмова ад разнастайных апаратных ці ўбудаваных у чыпсэт/BIOS RAIDаў быў зроблены свядома, з мэтай мець магчымасць цалкам замяніць усю сістэму, за выключэннем саміх SSD/HDD, захаваўшы ўсе дадзеныя. У ідэале, каб можна было захаваць нават усталяваную аперацыйную сістэму пры пераездзе на зусім новае/іншае жалеза. Галоўнае каб былі SATA і PCIe парты. Гэта як live CD ці загрузная флэшка, толькі вельмі хуткая і крыху габарытная.

ГумарА то, ведаеце як бывае, - часам трэба тэрмінова ўзяць увесь масіў з сабой на вынас. А дадзеныя губляць не жадаецца. Для гэтага ўсе згаданыя носьбіты зручна размяшчаюцца па санках у адсеках 5.25 стандартнага корпуса.

Ну, і, вядома-жа, для эксперыментаў з рознымі спосабамі SSD кэшавання ў Linux.

Апаратныя рэйды, гэта сумна. Уключаеш. Яно ці працуе, ці не. А з mdadm заўсёды ёсць варыянты.

Софт

Раней на жалезе была ўсталявана Debian 8 Jessie, якая блізкая да EOL. Быў сабраны RAID 6 з вышэй згаданых HDD у пары з LVM. На ім круціліся віртуальныя машыны ў kvm/libvirt.

Т.к. аўтар мае прыдатны досвед стварэння партатыўных загрузных SATA/NVMe флэшак, а таксама, каб не рваць звыклы apt-шаблон, у якасці мэтавай сістэмы была абраная Ubuntu 18.04 якая ўжо дастаткова стабілізавалася, але да гэтага часу мае 3 гады падтрымкі ў перспектыве.

У згаданай сістэме прысутнічаюць усе неабходныя нам драйверы жалеза са скрынкі. Ніякага старонняга софту і драйвераў нам не запатрабуецца.

Падрыхтоўка да ўстаноўкі

Для ўсталёўкі сістэмы нам запатрабуецца Ubuntu Desktop Image. У сервернай сістэмы нейкі крамяны ўсталёўнік, які выяўляе залішнюю не якая адключаецца самастойнасць абавязкова ўпіхваючы UEFI сістэмная частка на адзін з дыскаў псуючы ўсю прыгажосць. Адпаведна усталёўваецца яно толькі ў UEFI рэжыме. Варыянтаў не прапануе.

Нас гэта не задавальняе.

Чаму?Нажаль, UEFI загрузка вельмі дрэнна сумяшчальная з загрузным праграмным RAIDам, т.к. рэзервавання для UEFI ESP падзелу нам ніхто не прапануе. У сетцы ёсць рэцэпты, якія прапануюць размясціць ESP раздзел на флэшцы ў USB порце, але, гэта кропка адмовы. Ёсць рэцэпты з выкарыстаннем праграмнага mdadm RAID 1 з метададзенымі версіі 0.9 якія не перашкаджаюць UEFI BIOS бачыць гэты раздзел, але, гэта жыве да шчаслівага моманту калі BIOS ці іншая АС на жалезе запіша нешта ў ESP забыўшыся сінхранізаваць на іншыя люстэркі.

Апроч гэтага, загрузка UEFI залежыць ад NVRAM, якая не пераедзе разам з кружэлкамі на новую сістэму, т.к. з'яўляецца часткай мацярынскай платы.

Так што, мы не будзем вынаходзіць новы ровар. У нас ужо ёсць гатовы, правераны гадамі дзедаўскі ровар цяпер званы Legacy/BIOS boot, які носіць ганарлівае імя CSM на UEFI-сумяшчальных сістэмах. Мы проста дастанем яго з паліцы, вышмаруем, падпампуем колы і пратром вільготнай анучкай.

Desktop версія Ubuntu таксама не ўмее нармальна ставіцца з Legacy загрузчыкам, але тут, як той казаў, хоць-бы ёсць варыянты.

І так, збіраем жалеза і грузім сістэму з загрузнай флэшкі Ubuntu Live. Нам трэба будзе спампоўваць пакеты, так што наладжваем сетку, якая ў вас зарабіла. Калі не зарабіла, - патрэбныя пакеты можна падгрузіць на флешку загадзя.

Заходзім у Desktop асяроддзе, запускаем эмулятар тэрмінала, і, паехалі:

#sudo bash

Як…?Радок вышэй з'яўляецца кананічным трыгерам халівараў пра sudo. З большымі магчымасцямі прыходзіць і бобольшая адказнасць. Пытанне ў тым, ці зможаце вы ўзяць яе на сябе. Многія лічаць што выкарыстанне sudo такім чынам гэта, па меншай меры не асцярожна. Аднак:

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

Чаму не ZFS…?Калі мы ўсталёўваем праграмнае забеспячэнне на свой кампутар, – па сутнасці, мы пазычаем паганяць сваё жалеза распрацоўшчыкам гэтага праграмнага забеспячэння.
Калі мы давяраем гэтаму праграмнаму забеспячэнню захаванасць сваіх дадзеных, - мы бярэм крэдыт роўны кошту аднаўлення гэтых дадзеных, па якім калісьці давядзецца расплачвацца.

З гэтага пункту гледжання ZFS - гэта Ферары, а mdadm+lvm больш паходзіць на ровар.

Суб'ектыўна аўтар аддае перавагу пазычаць невядомым асобам узяты ў крэдыт ровар замест Ферары. Там і кошт пытання не высокая. Не трэба правоў. Прасцей ПДР. Паркоўкі бясплатныя. Праходнасць лепш. Да ровара заўсёды можна прымайстраваць ногі, ды і паправіць ровар можна сваімі рукамі.

Навошта тады BTRFS…?Для таго каб загрузіць аперацыйную сістэму нам запатрабуецца файлавая сістэма падтрымоўваная ў Legacy/BIOS GRUB са скрынкі, і, пры гэтым, якая падтрымлівае здымкі на-жывую. Мы будзем выкарыстоўваць яе для /boot часткі. Акрамя гэтага, аўтар аддае перавагу выкарыстоўваць гэтую ФС для / (кораня) не забываючы адзначыць, што для любога іншага софту можна стварыць асобныя часткі на LVM і мантаваць у патрэбныя каталогі.

Ні выявы віртуальных машын, ні базы дадзеных мы на гэтай ФС захоўваць не будзем.
Выкарыстоўвацца гэтая ФС будзе толькі для стварэння імгненных здымкаў сістэмы без яе выключэння з наступным перапампоўваннем гэтых здымкаў на рэзервовую кружэлку пры дапамозе send/recieve.

Акрамя гэтага, аўтар наогул аддае перавагу трымаць мінімум праграмнага забеспячэння непасрэдна на жалезе і ганяць увесь астатні софт у віртуальных машынах выкарыстоўваючы такія штукі як пракідванне GPU і PCI-USB Host-кантролераў у KVM праз IOMMU.

На жалезе застаюцца толькі - захоўванне дадзеных, віртуалізацыя і рэзервовае капіраванне.

Калі вы больш давяраеце ZFS, то, у прынцыпе, для паказанага ўжывання яны ўзаемазаменныя.

Тым не менш, аўтар свядома ігнаруе ўбудаваныя функцыі люстэркавання / RAID і надмернасці якія ёсць у ZFS, BRTFS і LVM.

У якасці дадатковага аргумента, BTRFS мае ўласцівасць ператвараць выпадковую запіс у паслядоўную, што вельмі пазітыўна адбіваецца на хуткасці сінхранізацыі здымкаў/рэзервовых дзід на HDD.

Нанова перасканаваны ўсе прылады:

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

Агледзімся:

#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

Разметка «дыскаў»

NVMe SSD

А вось ніяк мы іх не будзем размячаць. Усё роўна наш BIOS не бачыць гэтыя назапашвальнікі. Так што яны цалкам пойдуць у праграмны RAID. Нават раздзелаў ствараць там не будзем. Калі хочацца па «канону» або «прынцыпова» — стварыце адну вялікую частку, як у HDD.

SATA HDD

Тут асабліва вынаходзіць нічога не трэба. Мы створым адзін раздзел на ўсё. Раздзел створым таму, што гэтыя дыскі бачыць BIOS і нават можа паспрабаваць з іх загрузіцца. Мы нават усталюем пазней на гэтыя дыскі GRUB каб у сістэмы гэта раптам атрымалася.

#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

Тут у нас цікавей за ўсё.

Па-першае назапашвальнікі ў нас памерам 2 ТБ. Гэта ў межах дапушчальнага для MBR, чым мы і скарыстаемся. Пры неабходнасці можна замяніць на GPT. У GPT дыскаў ёсць пласт сумяшчальнасці які дазваляе MBR-сумяшчальным сістэмам бачыць першыя 4 часткі калі яны размешчаны ў межах першых 2х тэрабайт. Галоўнае, каб загрузная частка і частка bios_grub на гэтых дысках былі ў пачатку. Гэта дазваляе нават рабіць з GPT дыскаў Legacy/BIOS загрузку.

Але гэта не наш выпадак.

Тут мы будзем ствараць два раздзелы. Першы будзе памерам 1 Гб і скарыстаны для RAID 1 /boot.

Другі будзе будзе выкарыстоўвацца для RAID 6 і займаць усё пакінутае вольнае месца за выключэннем невялікай не размечанай вобласці ў канцы назапашвальніка.

Што за неразмечаная вобласць?Паводле крыніц у сеткі нашы SATA SSD маюць на борце дынамічна які пашыраецца SLC кэш памерам ад 6 да 78 гігабайт. 6 гігабайт мы атрымліваем "бясплатна" за кошт розніцы паміж "гігабайтамі" і "гібібайтамі" у тэхпашпарце назапашвальніка. Астатнія 72 гігабайта вылучаюцца за кошт невыкарыстоўваемай прасторы.

Тут трэба заўважыць, што кэш у нас SLC, а месца займаецца ў рэжыме 4 bit MLC. Што для нас эфектыўна азначае, што за кожныя 4 гігабайта вольнай прасторы мы атрымаем толькі 1 гігабайт SLC кэша.

Памнажаем 72 гігабайта на 4 і атрымліваем 288 гігабайт. Гэта і ёсць тое вольнае месца якое мы не будзем размячаць, каб дазволіць назапашвальнікам на поўную выкарыстоўваць SLC кэш.

Такім чынам, мы атрымаем эфектыўна да 312 гігабайт SLC кэша сумарна ад шасці назапашвальнікаў. З усіх назапашвальнікаў 2 будуць выкарыстоўвацца ў RAID для надмернасці.

Такая колькасць кэша дазволіць нам вельмі рэдка ў жывой практыцы сутыкацца з сітуацыяй, калі запіс ідзе не ў кэш. Гэта надзвычай добра кампенсуе самы сумны недахоп QLC памяці, – вельмі нізкую хуткасць запісу калі дадзеныя пішуцца ў абыход кэша. Калі вашы нагрузкі гэтаму не адпавядаюць, то, я рэкамендую вам моцна задумацца аб тым, колькі пражывуць вашыя SSD пад такой нагрузкай улічваючы TBW з тэхпашпарта.

#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

Стварэнне масіваў

Для пачатку нам трэба перайменаваць машыну. Трэба таму, што імя хаста з'яўляецца часткай імя масіва дзесьці ўсярэдзіне mdadm і дзесьці на нешта ўплывае. Масівы вядома можна пазней перайменаваць, аднак, гэта лішнія дзеянні.

#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

Навошта -assume-clean ...?Каб не ініцыялізаваць масівы. Для абодвух узроўняў RAID 1 і 6 гэта дапушчальна. Усё можа працаваць і без ініцыялізацыі, калі гэта новы масіў. Больш за тое, ініцыялізацыя масіва SSD пры стварэнні – гэта пустая трата рэсурсу TBW. Мы выкарыстоўваем TRIM/DISCARD дзе магчыма на сабраных масівах SSD каб іх ініцыялізаваць .

У масіваў SSD RAID 1 DISCARD падтрымліваецца са скрынкі.

У масіваў SSD RAID 6 DISCARD трэба ўключаць у параметрах модуля ядра.

Гэта варта рабіць толькі ў тым выпадку, калі наогул усё SSD выкарыстоўваныя ў масівах узроўняў 4/5/6 у гэтай сістэме маюць працавальную падтрымку discard_zeroes_data. Часам трапляюцца дзіўныя назапашвальнікі, якія паведамляюць ядру аб падтрымцы гэтай функцыі, але, па-факце, яе няма, ці функцыя працуе не заўсёды. На дадзены момант падтрымка ёсць практычна ўсюды, аднак, старыя назапашвальнікі і прашыўкі з памылкамі сустракаюцца. Па гэтай прычыне падтрымка DISCARD па-змаўчанні выключана для RAID 6.

Увага, наступная каманда знішчыць усе дадзеныя на NVMe назапашвальніках "ініцыялізаваўшы" масіў "нулямі".

#blkdiscard /dev/md0

Калі нешта пайшло не так, то паспрабуйце пазначыць крок.

#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

Навошта такі вялікі…?Павелічэнне chunk-size пазітыўна ўплывае на хуткасць выпадковага чытання блокамі да chunk-size улучна. Адбываецца гэта таму, што адна аперацыя адпаведнага памеру ці менш можа быць цалкам выканана на адной прыладзе. Таму, IOPS ад усіх прылад сумуецца. Па статыстыцы 99% IO не перавышае 512K.

У RAID 6 IOPS на запіс заўсёды менш ці роўна IOPS у аднаго назапашвальніка. Калі як на выпадковае чытанне IOPS можа быць больш такога ў аднаго назапашвальніка ў некалькі разоў, і тут памер блока мае ключавое значэнне.
Аўтар не бачыць сэнсу ў спробах аптымізаваць параметр які дрэнны ў RAID 6 by-design і замест гэтага аптымізуе тое, у чым RAID 6 паказвае сябе добра.
Дрэнны выпадковы запіс RAID 6 мы будзем кампенсаваць кэшам на NVMe і трукамі з thin-provisioning.

Мы пакуль не ўлучылі DISCARD для RAID 6. Так што ініцыялізаваць гэты масіў пакуль не будзем. Зробім гэта пазней, - пасля ўстаноўкі АС.

SATA HDD

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

LVM на NVMe RAID

Для хуткасці мы жадаем размясціць каранёвую ФС на NVMe RAID 1 які /dev/md0.
Тым не менш, гэты хуткі масіў нам яшчэ спатрэбіцца для іншых патрэб, такіх як swap, метададзеныя і кэш LVM-cache і метададзеныя LVM-thin, таму, на гэтым масіве мы створым LVM VG.

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

Створым раздзел для каранёвай ФС.

#lvcreate -L 128G --name root root

Створым раздзел для падпампоўкі па памеры аператыўнай памяці.

#lvcreate -L 32G --name swap root

Ўстаноўка АС

Разам, у нас ёсць усё неабходнае, каб усталяваць сістэму.

Запускаем майстар усталёўкі сістэмы з асяроддзя Ubuntu Live. Звычайная ўстаноўка. Толькі на этапе выбару дыскаў для ўстаноўкі трэба ўказаць наступнае:

  • /dev/md1, - кропка мантавання /boot, ФС - BTRFS
  • /dev/root/root (aka /dev/mapper/root-root), - кропка мантавання / (корань), ФС - BTRFS
  • /dev/root/swap (aka /dev/mapper/root-swap), — выкарыстоўваць як падзел падпампоўкі
  • Загрузнік усталяваць на /dev/sda

Пры выбары BTRFS у якасці каранёвай ФС, - усталёўшчык аўтаматычна створыць два BTRFS-тамы з імёнамі "@" для / (кораня), і "@home" для /home.

Запускаем усталёўку…

Усталёўка завершыцца мадальным дыялогавым акном, якое паведамляе пра памылку ўсталёўкі загрузніка. На жаль, выйсці з гэтага дыялогу штатнымі сродкамі і прадоўжыць устаноўку не атрымаецца. Які робіцца логаўт з сістэмы і зноў лагінімся, пападаючы ў чысты працоўны стол Ubuntu Live. Адкрываем тэрмінал, і зноў:

#sudo bash

Ствараем chroot асяроддзе, каб працягнуць усталёўку:

#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

Наладзім сетку і hostname у chroot:

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

Заходзім у chroot асяроддзе:

#chroot /mnt/chroot

Перш за ўсё даставім пакеты:

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

Праверым і выправім усе пакеты якія крыва ўсталяваліся з-за няскончанай усталёўкі сістэмы:

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

Калі нешта не зраслося, магчыма, вам спатрэбіцца перад гэтым падрэдагаваць /etc/apt/sources.list

Паправім параметры для модуля RAID 6 каб уключыць TRIM/DISCARD:

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

Трохі падналадзім нашы масівы:

#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

Што гэта было..?Мы стварылі набор udev правілаў, якія будуць рабіць наступнае:

  • Выстаўляць адэкватны для 2020. Памер кэша блокаў для RAID 6. Значэнне па-змаўчанні, здаецца, не змянялася са часоў стварэння Linux, і ўжо даўно не адэкватна.
  • Рэзерваваць на час праверак/сінхранізацый масіваў мінімум IO. Гэта трэба, каб вашыя масівы не захрасалі ў стане вечнай сінхранізацыі пад нагрузкай.
  • Абмяжоўваць на час праверак/сінхранізацый масіваў максімум IO. Гэта трэба, каб сінхранізацыя/праверка SSD RAID-ов не прасмажыла вашы назапашвальнікі да хрумсткай скарыначкі. Асабліва актуальна для NVMe. ( Памятаеце пра радыятар? Я ж не жартаваў. )
  • Забараняць праз APM дыскам спыняць кручэнне шпіндзеля (HDD) і ўсталёўваць таймаўт для сну кантролераў дыскаў на 7 гадзін. Можна зусім адключыць APM калі вашы дыскі гэта ўмеюць (-B 255). Са значэннем па-змаўчанні дыскі будуць спыняцца праз пяць секунд. Затым АС захоча скінуць дыскавы кэш, дыскі раскруцяцца зноў, і, усё па-новай. У дыскаў абмежавана максімальная колькасць раскручванняў шпіндзеля. Такі няхітры цыкл па-змаўчанні можа лёгка забіць вашыя кружэлкі за пару гадоў. Гэтым пакутуюць не ўсе кружэлкі, але, нашы-то «наўтбучныя», з адпаведнымі наладамі па-змаўчанні, якія робяць з RAID-а крывое падабенства mini-MAID-а.
  • Усталёўваць readahead на дысках (якія верцяцца) у 1 мегабайт – два паслядоўных блока/chunk RAID 6
  • Забараняць readahead на саміх масівах.

Падрэдагуем /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

Чаму так..?Раздзел /boot мы будзем шукаць па UUID т.я. найменне масіваў тэарэтычна можа змяніцца.

Астатнія раздзелы мы будзем шукаць па LVM імёнах у натацыі /dev/mapper/vg-lv, т.я. яны дастаткова ўнікальна ідэнтыфікуюць раздзелы.

Не выкарыстоўваем UUID для LVM т.к. UUID у LVM тамоў і іх снапшотаў можа супадаць.Двойчы мантуем /dev/mapper/root-root..?Так. Менавіта так. Асаблівасць BTRFS. Гэтую ФС можна мантаваць некалькі разоў з рознымі subvol.

У выніку гэтай жа асаблівасці раю ніколі не ствараць LVM снапшоты актыўных BTRFS тамоў. Можаце атрымаць неспадзеўку пры перазагрузцы.

Перагенераваны канфіг mdadm:

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

Падкарэктуем налады 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

Што гэта было..?Мы ўключылі аўтаматычнае пашырэнне пулаў LVM thin па дасягненні 90% занятага месца на 5% ад аб'ёму.

Мы павялічылі максімальную колькасць блокаў кэша для LVM cache.

Мы забаранілі LVM шукаць LVM тамы (PV) на:

  • прыладах утрымоўвальных LVM cache (cdata)
  • прыладах кэшаваных пры дапамозе LVM cache у абыход кэша ( _corig). Пры гэтым сама кэшаваная прылада ўсё роўна будзе прасканаваная праз кэш (проста ).
  • прыладах змяшчаюць метададзеныя LVM cache (cmeta)
  • усіх прыладах у VG з назвай images. Тут у нас будуць выявы дыскаў віртуальных машын, і, мы не жадаем каб LVM на хасце актываваў тамы якія належаць гасцёўні АС.
  • ўсіх прыладах у VG з назвай backup. Тут у нас будуць рэзервовыя копіі выяў віртуальных машын.
  • усіх прыладах імя якіх сканчаецца на "gpv" (guest physical volume)

Мы ўключылі падтрымку DISCARD пры вызваленні вольнай прасторы на LVM VG. Будзьце асцярожныя. Гэта зробіць выдаленне LV на SSD дастаткова доўгім. Асабліва гэта адносіцца да SSD RAID 6. Аднак, па плане, мы будзем выкарыстоўваць thin provisioning, так што, гэта нам зусім не перашкодзіць.

Абновім выяву initramfs:

#update-initramfs -u -k all

Усталюем і сканфігуруем grub:

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

Якія дыскі выбіраць?Усе якія sd*. Сістэма павінна быць здольная загрузіцца з любога які працуе SATA дыска або SSD.

Навошта прыбілі os-prober..?За залішнюю самастойнасць і гарэзлівыя ручкі.

Ён не працуе карэктна калі адзін з RAID-ов знаходзіцца ў дэградаваным стане. Ён спрабуе шукаць АС на раздзелах, якія выкарыстоўваюцца ў віртуальных машынах якія працуюць на гэтым жалезе.

Калі ён вам патрэбен, то можаце пакінуць, аднак, майце на ўвазе ўсё вышэйпералічанае. Рэкамендую пашукаць рэцэпты збавення ад гарэзлівых ручак у сетцы.

На гэтым мы завяршылі пачатковую ўстаноўку. Нетутэйша час перазагрузіцца ў толькі што ўсталяваную АС. Не забудзьцеся дастаць загрузны Live CD/USB.

#exit
#reboot

У якасці прылады для загрузкі выбіраемы любы з SATA SSD.

LVM на SATA SSD

Да гэтага моманту мы ўжо загрузіліся ў новую АС, наладзілі сетку, apt, адкрылі эмулятар тэрмінала, і запусцілі:

#sudo bash

Працягнем.

"Ініцыялізуем" масіў з SATA SSD:

#blkdiscard /dev/md2

Калі не пракаціла, то спрабуем:

#blkdiscard --step 65536 /dev/md2
Ствараем LVM VG на SATA SSD:

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

Навошта яшчэ адна VG..?На самой справе, у нас ужо ёсць VG з імем root. Чаму б не дадаць усё ў адну VG?

Калі ў VG ёсць некалькі PV, то для карэктнай актывацыі VG усе PV павінны прысутнічаць (online). Выключэннем з'яўляецца LVM RAID, які мы наўмысна не выкарыстоўваем.

Мы вельмі жадаем, каб пры паліцы (чытай страты дадзеных) на любым з RAID 6 масіваў аперацыйная сістэма загрузілася штатна і дала нам магчымасць вырашыць праблему.

Для гэтага, на першым узроўні абстракцыі мы будзем ізаляваць кожны тып фізічнага "носьбіта" у асобную VG.

Калі па-навуковаму, то розныя RAID масівы ставяцца да розных «даменаў надзейнасці». Не варта ствараць для іх дадатковую агульную кропку адмовы, запіхваючы ў адну VG.

Наяўнасць LVM на "жалезным" узроўні дазволіць нам адвольна нарэзаць кавалачкі розных RAID масіваў па-рознаму іх камбінуючы. Напрыклад, - запусціць адначасова bcache + LVM thin, bcache + BTRFS, LVM cache + LVM thin, складаную канфігурацыю ZFS з кэшамі ці любую іншую пякельную сумесь, каб усё гэта памацаць і параўнаць.

На "жалезным" узроўні мы нічога, акрамя старых-добрых "тоўстых" LVM-томаў, выкарыстоўваць не будзем. Выключэннем з гэтага правіла, магчыма, будзе падзел для рэзервовага капіявання.

Думаю, да гэтага моманту, многія чытачы ўжо пачалі нешта падазраваць датычна матрошкі.

LVM на SATA HDD

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

Ізноў новая VG..?Мы вельмі хочам, каб пры адвале масіва дыскаў, які мы будзем выкарыстоўваць для рэзервовага капіявання дадзеных, наша аперацыйная сістэма працягвала працаваць штатна, штатна-ж захоўваючы доступ да нерэзервовых дадзеных. Таму, каб пазбегнуць праблем актывацыі VG, – мы ствараем асобную VG.

Настройка LVM cache

Створым LV на NVMe RAID 1 каб выкарыстоўваць яго ў якасці кэшуе прылады.

#lvcreate -L 70871154688B --name cache root

Чаго так мала...?Справа ў тым, што ў нашых NVMe SSD таксама ёсць SLC кэш. 4 гігабайта «бясплатнага» і 18 гігабайт дынамічнага за кошт вольнай прасторы займанага ў 3-bit MLC. Па-вычарпанні гэтага кэша NVMe SSD стануць не нашмат хутчэй нашага SATA SSD з кэшам. Уласна, па гэтым чынніку нам няма сэнсу рабіць LVM cache падзел моцна больш двухразовага аб'ёму SLC кэша NVMe назапашвальніка. Для выкарыстоўваных NVMe назапашвальнікаў аўтар лічыць разумным зрабіць 32-64 гігабайта кэша.

Прыведзены памер часткі неабходны для арганізацыі 64 гігабайт кэша, размяшчэнні метададзеных кэша і рэзервовай копіі метададзеных.

Дадаткова заўважу, што пасля бруднага выключэння сістэмы LVM пазначыць увесь кэш як брудны і будзе сінхранізаваць нанова. Больш за тое, гэта будзе паўтарацца пры кожным выкарыстанні lvchange на гэтай прыладзе да новай перазагрузкі сістэмы. Таму, рэкамендую адразу перастварыць кэш які адпавядае скрыптам.

Створым LV на SATA RAID 6 каб выкарыстоўваць яго ў якасці кэшаванага прылады.

#lvcreate -L 3298543271936B --name cache data

Чаму толькі тры тэрабайта..?Каб, пры неабходнасці, можна было выкарыстоўваць SATA SSD RAID 6 для нейкіх іншых патрэб. Памер кэшаванай прасторы можна павялічыць дынамічна, на лета, без прыпынку працы сістэмы. Для гэтага неабходна часова спыніць і зноўку ўключыць кэш, але, адметным перавагам LVM-cache перад, напрыклад, bcache, з'яўляецца тое, што гэта можна рабіць на лета.

Створым новую VG для кэшавання.

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

Створым LV на які кэшуецца прыладзе.

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

Тут мы адразу занялі ўсё вольнае месца на /dev/data/cache каб усе астатнія патрэбныя часткі ствараліся адразу на /dev/root/cache. Калі ў вас нешта стварылася не тамака, можна перамясціць пры дапамозе pvmove.

Створым і ўключым кэш:

#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

Чаму такі chunksize..?Метадам практычных эксперыментаў аўтару ўдалося высветліць, што найлепшы вынік дасягаецца калі памер блока LVM cache супадае з памерам блока LVM thin. Пры гэтым, чым меншы памер, тым лепш сябе паказвае канфігурацыя на выпадковым запісе.

64к - гэта мінімальны памер блока дапушчальны для LVM thin.

Асцярожна writeback..!Так. Гэты тып кэша адкладае сінхранізацыю запісу на якая кэшуецца прылада. Гэта прыводзіць да таго, што, у выпадку страты кэша, можна страціць дадзеныя на якая кэшуецца прыладзе. Пазней аўтар раскажа, якія меры, акрамя NVMe RAID 1 можна зрабіць, каб кампенсаваць гэтую рызыку.

Дадзены тып кэша абраны мае намер, каб кампенсаваць нізкую прадукцыйнасць RAID 6 на выпадковым запісе.

Праверым, што ў нас атрымалася:

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

На /dev/data/cache павінен размяшчацца толькі [cachedata_corig]. Калі нешта не так, то выкарыстоўвайце pvmove.

Адключыць кэш пры неабходнасці можна адной камандай:

#lvconvert -y --uncache cache/cachedata

Гэта робіцца on-line. LVM проста сінхранізуе кэш на дыск, выдаліць яго і перайменуе cachedata_corig назад у cachedata.

Настройка LVM thin

Прыблізна ацэнім колькі месца нам спатрэбіцца для метададзеных LVM thin:

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

Акруглім да 4х гігабайт: 4294967296B

Памножым на два і дадамо 4194304B для метададзеных LVM PV: 8594128896B
Створым асобную частку на NVMe RAID 1 каб размесціць на ім метададзеныя LVM thin і іх рэзервовую копію:

#lvcreate -L 8594128896B --name images root

Навошта..?Тут можа паўстаць пытанне, навошта размяшчаць метададзеныя LVM thin асобна, калі яны ўсё роўна будуць кэшавацца на NVMe і будуць працаваць хутка.

Хуткасць тут хоць і з'яўляецца важным, але далёка не асноўным чыннікам. Уся справа ў тым, што кэш, гэта кропка адмовы. З ім можа нешта здарыцца, і, калі метададзеныя LVM thin будуць кэшаваны, гэта прывядзе да поўнай страты за ўсё. Без цэлых метададзеных сабраць тонкія тамы будзе практычна немагчыма.

Перамяшчаючы-ж метададзеныя на асобны не-кэшаваны, але хуткі, тым, мы гарантуем захаванасць метададзеных у выпадку страты або пашкоджанні кэша. У гэтым выпадку ўсе пашкоджанні выкліканыя стратай кэша будуць лакалізаваны ўнутры тонкіх тамоў, што на парадкі спросціць працэдуру аднаўлення. З вялікай верагоднасцю гэтыя пашкоджанні будуць аднаўляльныя з дапамогай часопісаў ФС.

Больш таго, калі раней быў зроблены імгненны здымак тонкага тома, і, пасля гэтага, кэш быў хоць бы адзін раз цалкам сінхранізаваны, то, у сілу асаблівасцяў унутранай прылады LVM thin, цэласнасць здымка будзе гарантаваная ў выпадку страты кэша.

Створым новую VG якая будзе адказваць за thin-provisioning:

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

Створым пул:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
Навошта -Z yАкрамя таго, для чаго гэта рэжым уласна і прызначаны, - не дазваляць дадзеным з адной віртуальнай машыны выцякаць у іншую віртуальную машыну пры пераразмеркаванні прасторы, - zeroing дадаткова выкарыстоўваецца для павелічэння хуткасці выпадковага запісу блокамі менш 64k. Любая запіс менш 64k у раней не выдзеленую вобласць тонкага тома будзе ператварацца ў 64K выраўнаваныя па мяжы кэша. Гэта дазволіць выканаць аперацыю цалкам праз кэш абыходзячы якая кэшуецца прылада.

Перамесцім LV на адпаведныя 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

Праверым:

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

Створым тонкі том для тэстаў:

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

Паставім пакеты для тэстаў і назіранні:

#apt-get install sysstat fio

Вось так можна назіраць за паводзінамі нашай канфігурацыі сховішчы ў рэальным часе:

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

Вось так можна пратэставаць нашу канфігурацыю:

#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

Асцярожна! Рэсурс!Гэты код запусціць 36 розных тэстаў, кожны з якіх будзе выконвацца па 4 секунды. Палова з тэстаў на запіс. За 4 секунды на NVMe можна паспець запісаць вельмі шмат. Да 3х гігабайт у секунду. Так што кожны запуск тэстаў на запіс можа з'есці ў вас да 216 гігабайт рэсурсу SSD.

Чытанне і запіс уперамешку?Так. Тэсты на чытанне і на запіс мае сэнс запускаць асобна. Больш за тое, мае сэнс пераканацца, што ўсе кэшы сінхранізаваныя, каб раней зроблены запіс не ўплываў на чытанне.

Вынікі будуць моцна адрознівацца пры першым запуску і наступных па меры запаўнення кэша і тонкага тома, а таксама, у залежнасці ад таго, ці паспела сістэма сінхранізаваць кэшы запоўненыя пры мінулым запуску.

Апроч іншага, рэкамендую замераць хуткасць на ўжо запоўненым тонкім томе, з якога толькі што быў зроблены снапшот. Аўтар меў магчымасць назіраць, як выпадковы запіс рэзка паскараецца адразу пасля стварэння першага снапшота, асабліва, калі кэш яшчэ не цалкам запоўнены. Адбываецца гэта дзякуючы copy-on-write семантыцы запісу, выраўноўваю блокаў кэша і тонкага тома, і таму, што выпадковы запіс на RAID 6 ператвараецца ў выпадковае чытанне з RAID 6 з наступным запісам у кэш. У нашай жа канфігурацыі выпадковае чытанне з RAID 6 да 6ти раз (колькасць SATA SSD у масіве) хутчэй запісы. Т.к. блокі для CoW вылучаюцца паслядоўна з тонкага пула, то запіс, па большай частцы, яшчэ і ператвараецца ў паслядоўную.

Абедзве гэтыя асаблівасці можна выгадна выкарыстоўваць.

Кэш-«кагерэнтныя» снапшоты

Для памяншэння рызыкі страты дадзеных у выпадку пашкоджання/страты кэша аўтар прапануе ўвесці практыку ратацыі снапшотаў якая гарантуе іх цэласнасць у гэтым выпадку.

Па-першае, дзякуючы таму, што метададзеныя тонкіх тамоў размяшчаюцца на некэшаванай прыладзе, метададзеныя будуць цэласныя, і магчымыя страты будуць ізаляваныя ўсярэдзіне блокаў дадзеных.

Наступны цыкл ратацыі снапшотаў дае гарантыю цэласнасці дадзеных усярэдзіне снапшотаў у выпадку страты кэша:

  1. Для кожнага тонкага тома з імем <імя> ствараем снапшот з імем <імя>.cached
  2. Усталюем migration threshold на разумнае высокае значэнне: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. У цыкле правяраем колькасць брудных блокаў у кэшы: #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' пакуль не атрымаем нуль. Калі нуля няма занадта доўга, яго можна стварыць часова перавядучы кэш у writethrough рэжым. Аднак, улічваючы хуткасныя характарыстыкі нашых масіваў SATA і NVMe SSD, а таксама, іх рэсурс TBW, вы альбо зможаце дастаткова хутка злавіць момант і без змены рэжыму кэша, альбо ваша жалеза цалкам з'яве ўвесь свой рэсурс за некалькі дзён. З-за абмежаванняў рэсурсу сістэма ў прынцыпе не здольная знаходзіцца пад 100% нагрузкай на запіс увесь час. Нашы NVMe SSD пад 100% нагрузкай на запіс цалкам патрацяць рэсурс за 3-4 дня. SATA SSD пражывуць усяго вось разы ў два даўжэй. Таму, мы будзем лічыць, што большая частка нагрузкі ідзе на чытанне, а на запіс у нас, - адносна кароткачасовыя воплескі вельмі высокай актыўнасці ў спалучэнні з нізкай нагрузкай у сярэднім.
  4. Як толькі злавілі (ці зрабілі) нулік - пераназываем <імя>.cached у <імя>.committed. Стары <імя>.committed пры гэтым выдаляем.
  5. Апцыянальна, калі кэш запоўнены на 100%, яго можна перастварыць скрыптам, такім чынам ачысціўшы. З напаўпустым кэшам сістэма працуе значна хутчэй на запіс.
  6. Усталюем migration threshold на нуль: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata Гэта часова забароніць сінхранізаваць кэш на асноўны носьбіт.
  7. Чакаем, пакуль у кэшы назапасіцца дастаткова шмат змен #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' ці спрацуе таймер.
  8. Паўтараем нанова.

Навошта складанасці з migration threshold…?Уся справа ў тым, што ў рэальнай практыцы «выпадковы» запіс насамрэч не зусім выпадковы. Калі мы запісалі нешта ў сектар памерам 4 кілабайта, вялікая верагоднасць таго, што бліжэйшую пару хвілін будзе зроблена запіс у гэты ж ці адзін з суседніх (+- 32K) сектараў.

Выстаўляючы migration threshold у нуль мы адкладаем сінхранізацыю запісу на SATA SSD і агрэгуем некалькі змен аднаго блока 64K у кэшы. Такім чынам прыкметна эканоміцца ​​рэсурс SATA SSD.

А код дзе..?Нажаль, аўтар лічыць сябе нядосыць кампетэнтным па частцы распрацоўкі bash скрыптоў бо з'яўляецца на 100% самавукам і практыруе «google»-driven development, таму лічыць, што той страшны код, які выходзіць з-пад яго рук, лепш не выкарыстоўваць нікому іншаму.

Думаю, што прафесіяналы гэтай справы змогуць і самастойна адлюстраваць усю апісаную вышэй логіку ў выпадку неабходнасці, і, можа быць, нават хораша аформіць у выглядзе systemd сэрвісу, як гэта паспрабаваў зрабіць аўтар.

Падобная няхітрая схема ратацыі снапшотаў дазволіць нам не толькі ўвесь час мець адзін цалкам сінхранізаваны на SATA SSD снапшот, але і дазволіць пры дапамозе ўтыліты thin_delta пазнаць, якія блокі былі змененыя пасля яго стварэння, і, такім чынам, лакалізаваць пашкоджанні на асноўных тамах, шматкроць спрашчаючы ўзнаўленне .

TRIM/DISCARD у libvirt/KVM

Т.к. сховішча дадзеных будзе выкарыстоўвацца для KVM пад кіраваннем libvirt, тое было б нядрэнна навучыць нашы VM не толькі займаць вольнае месца, але і вызваляць ужо непатрэбнае.

Гэта робіцца з дапамогай эмуляцыі падтрымкі TRIM/DISCARD на віртуальных дысках. Для гэтага трэба змяніць тып кантролера на virtio-scsi і падрэдагаваць 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>

Падобныя DISCARDы з гасцявых АС карэктна апрацоўваюцца LVMам, і блокі карэктна вызваляюцца як у кэшы, так і ў тонкім кулі. У нашым выпадку, гэта адбываецца, у асноўным, адкладзена, пры выдаленні чарговага снапшота.

Рэзервовае капіраванне BTRFS

Выкарыстоўваць гатовыя скрыпты з крайняй асцярожнасцю і на свой страх і рызыку. Аўтар пісаў гэты код сам і выключна для сябе. Упэўнены, што ў шматлікіх дасведчаных карыстачоў Linux маюцца падобныя мыліцы напрацоўкі, і капіяваць чужыя не спатрэбіцца.

Створым том на рэзервовым прыладзе:

#lvcreate -L 256G --name backup backup

Адфарматуем у BTRFS:

#mkfs.btrfs /dev/backup/backup

Створым кропкі мантавання і прымантаваны каранёвыя падраздзелы ФС:

#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

Створым каталогі для рэзервовых копій:

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

Створым каталог для скрыптоў рэзервовага капіявання:

#mkdir /root/btrfs-backup

Скапіюем скрыпт:

Шмат страшнага bash-кода. Выкарыстоўваць на свой страх і рызыку. Аўтару гнеўныя лісты не пісаць…#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

Што хоць яно робіць..?Змяшчае набор найпростых каманд для стварэння снапшотаў BTRFS і іх капіявання на іншую ФС пасродкам BTRFS send/recieve.

Першы запуск можа быць адносна доўгім, т.я. у пачатку будуць скапіяваныя ўсе дадзеныя. Далейшыя запускі будуць вельмі хуткімі, т.я. капіявацца будуць толькі змены.

Яшчэ адзін скрыпт які запхнем у cron:

Яшчэ крыху bash-кода#cat >/root/btrfs-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

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

BACKUP_SCRIPT="$SCRIPT_DIR/btrfs-backup.sh"
RETENTION="-60 day"
$BACKUP_SCRIPT backup root/@
$BACKUP_SCRIPT removeall root/@ "$RETENTION"
$BACKUP_SCRIPT backup root/@home
$BACKUP_SCRIPT removeall root/@home "$RETENTION"
$BACKUP_SCRIPT backup boot/
$BACKUP_SCRIPT removeall boot/ "$RETENTION"
EOF

Што яно робіць..?Стварае і і сінхранізуе на backup ФС інкрыментальныя здымкі пералічаных BTRFS-томаў. Пасля гэтага выдаляе ўсе здымкі, створаныя 60 дзён таму. Пасля запуску ў падкаталогах /backup/btrfs/back/remote/ з'явяцца датаваныя здымкі пералічаных тамоў.

Дамо коду права на выкананне:

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

Праверым і запіхнем у крон:

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

Рэзервовае капіраванне LVM thin

Створым тонкі пул на рэзервовым прыладзе:

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

Усталюем ddrescue, т.к. скрыпты будуць выкарыстоўваць гэты інструмент:

#apt-get install gddrescue

Створым каталог для скрыптоў:

#mkdir /root/lvm-thin-backup

Скапіюем скрыпты:

Вельмі шмат bash унутры…#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

Што яно робіць…?Змяшчае набор каманд для маніпуляцыі тонкімі здымкамі і сінхранізацыі розніцы паміж двума тонкімі снапшотамі, атрыманай праз thin_delta, на іншую блокавую прыладу з выкарыстаннем ddrescue і blkdiscard.

Яшчэ адзін скрыпт, які мы запіхнем у крон:

Яшчэ крыху bash#cat >/root/lvm-thin-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

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

BACKUP_SCRIPT="$SCRIPT_DIR/lvm-thin-backup.sh"
RETENTION="-60 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

Што яно робіць…?Выкарыстоўвае папярэдні скрыпт, каб стварыць і сінхранізаваць рэзервовыя копіі пералічаных тонкіх тамоў. Скрыпт пакіне неактыўныя снапшоты пералічаных тамоў, якія патрэбныя для адсочвання змен з апошняй сінхранізацыі.

Гэты скрыпт трэба падрэдагаваць, паказваючы спіс тонкіх тамоў, для якіх патрабуецца рабіць рэзервовыя копіі. Прыведзеныя назвы дадзены толькі для прыкладу. Пры жаданні можна напісаць скрыпт, які будзе сінхранізаваць усе тамы.

Дамо правы:

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

Праверым і запіхнем у крон:

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

Першы запуск будзе доўгім, т.я. тонкія тамы будуць цалкам сінхранізаваныя капіраваннем усёй выкарыстоўванай прасторы. Дзякуючы метададзеным LVM thin мы ведаем якія блокі выкарыстоўваюцца на самай справе, так што, капіявацца будуць толькі рэальна выкарыстоўваныя блокі тонкіх тамоў.

Наступныя запускі будуць капіяваць дадзеныя інкрыментальна дзякуючы адсочванню змен праз метададзеныя LVM thin.

Паглядзім, што атрымалася:

#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

Прычым тут матрошкі?

Хутчэй за ўсё пры тым, што лагічныя тамы LVM LV могуць быць фізічнымі тамамі LVM PV для іншых VG. LVM можа быць рэкурсіўны, як матрошкі. Гэта дае LVM надзвычайную гнуткасць.

PS

У наступным артыкуле мы з вамі паспрабуем выкарыстаць некалькі падобных мабільных СХД/KVM як аснову для стварэння гео-размеркаванага storage/vm-кластара з рэзерваваннем на некалькіх кантынентах пасродкам хатніх дэсктопаў, хатняга-ж інтэрнэту і P2P сетак.

Крыніца: habr.com

Дадаць каментар