ZFS на корені: як встановити, щоб відкат дійсно працював

Було корисно?

Вам не потрібні просто «знімки». Вам потрібна впевненість, що можна відкотитися після невдалого оновлення о 02:00 і система чисто підніметься — мережа, SSH, сервіси, усе як слід — без потреби копирсатися в логах initramfs як спелеолог із налобним ліхтарем.

Більшість інструкцій з ZFS-on-root приводять до статусу «воно завантажилося один раз». Для продакшну потрібно більше: передбачувана структура датасетів, інтеграція завантажувача, яка поважає boot environments, та оперативні звички, що не перетворюють історію знімків на місце злочину.

Що насправді має означати «відкат» в реальній системі

Відкат, який «працює», — це не просто zfs rollback, який завершується без помилок. Відкат, який працює, означає:

  • Завантажувач може перерахувати та завантажити попередній стан кореневого розділу (boot environment), а не просто змонтувати якийсь датасет, якщо ви вручну підправите параметри ядра.
  • initramfs може послідовно й автоматично імпортувати ваш пул (і розблокувати його, якщо він зашифрований).
  • /boot узгоджений з ядром+initramfs у середовищі кореня, у яке ви завантажуєтесь — або навмисно відокремлений і керований як окремий виняток.
  • Стан сервісів і конфігурація мають відповідну область, щоб відкат кореня не воскресив несумісний на-диску стан для додатків, що постійно пишуть (бази даних, черги, сховища метрик).
  • Процедура оператора очевидна: створити BE, оновити, перезавантажитися в BE, перевірити, залишити або відкотитися.

Простими словами: ви не просто встановлюєте «ZFS», ви встановлюєте машину часу з панеллю керування у вигляді завантажувача. І панель керування має працювати після подорожі в часі.

Суб’єктивна думка: якщо ви не можете перерахувати свої boot environments у меню завантажувача або передбачуваному CLI і завантажитися в них без редагування, ваші відкати — це театр. Театр — це весело, але не о 02:00.

Короткий жарт #1: План відкату, що вимагає «деяких ручних кроків», — це просто план лише в один бік з прихованим запереченням.

Цікаві факти та контекст (бо історія пояснює гострі краї сьогодення)

  1. ZFS виник у Sun у середині 2000‑х з наскрізним контролем цілісності та copy-on-write; він був спроєктований, щоб зробити мовчазну корупцію менш привабливою.
  2. «Pooled storage» був змінним мислення: файлові системи перестали бути прив’язаними до одного блочного пристрою; ви керуєте пулом і вирізаєте з нього датасети й томи.
  3. Boot environments стали культурною нормою у Solaris/Illumos, де оновлення в новий BE і збереження старого завантажуваного середовища були звичною практикою, а не екзотичним трюком.
  4. Linux ZFS — це out-of-tree через ліцензійні несумісності; саме тому інтеграція в дистрибутиви різниться і чому «працює на моєму ноуті» — не план.
  5. GRUB отримав підтримку ZFS роками тому, але підтримка залежить від набору feature flags; увімкнення кожної блискучої функції пула може залишити ваш завантажувач у минулому.
  6. OpenZFS feature flags замінили номери версій на диску; це полегшило поступовий розвиток, але створило нове правило: завантажувальні компоненти мають підтримувати прапорці, які ви вмикаєте.
  7. Поводження ACL і xattr відрізняється залежно від очікувань ОС; ZFS може імітувати, але невідповідні налаштування можуть спричинити тонкі сюрпризи з правами після відкату.
  8. Стиснення перестало бути суперечливим після того, як сучасні CPU стали швидкими; сьогодні lz4 зазвичай дає безкоштовну користь, але все одно його застосовують свідомо.
  9. Знімки дешеві, поки раптом не стають дорогими: метадані й фрагментація можуть накопичитися, особливо якщо знімки зберігаються вічно, а datasets з високим рівнем churn живуть у тому самому дереві кореня.

Відкати зазнають поразки, коли ви розглядаєте ZFS-on-root як «вибір файлової системи», а не як «архітектуру завантаження». Файлова система — це легка частина. Завантаження — це те місце, куди йде помилка впевненості.

Цілі проєкту: правила, що роблять відкати нудними

Правило 1: Розділяйте те, що змінюється разом

Компоненти операційної системи (пакети, бібліотеки, конфігурації) мають бути в boot environment. Дані додатків із високим рівнем churn не повинні бути там. Якщо ви відкотите root і ваші файли бази даних також відкотяться, ви побудували машину часу, яка байдуже порушує причинність.

Робіть: розбийте /var/lib на датасети за додатком і помістіть бази даних на виділені датасети (або навіть на окремі пули) з власною політикою знімків.

Не робіть: не кладіть усе під монолітний rpool/ROOT датасет і не називайте це «просто». Це просто так само, як одна велика зона ураження — проста.

Правило 2: Завантаження бачить те, що потрібно, і ні більше

initramfs і завантажувач потребують стабільного способу знайти й змонтувати правильний root. Зазвичай це означає: передбачуваний bootfs, чітке іменування BE і уникнення функцій, які завантажувач не може прочитати.

Правило 3: Межа відкату — це boot environment, а не знімок

Знімки — це сировина. Boot environment — це відреставрований знімок+клон (або клон датасету) із цілісним / та зазвичай узгодженим сюжетом /boot. Використовуйте правильний інструмент. Перестаньте намагатися завантажувати випадкові знімки, ніби це фокус на вечірці.

Правило 4: Властивості за замовчуванням — такими й мають залишатися

Задавайте властивості на правильному рівні датасету. Уникайте встановлення всього на корінь пулу, якщо не хочете потім десятиліттями дебажити спадкування.

Структура датасетів, яка змушує boot environments поводитися правильно

Структура датасетів — це місце, де більшість інсталяцій «ZFS на корені» тихо провалюються. Вони, звісно, завантажуються. Але пізніше ви не можете чисто відкотитися, бо половина стану спільна, або mountpoint-и конфліктують, або /boot лежить на острові, що не відповідає вашому BE.

Практична структура (один пул, GRUB, типовий Linux)

Припустимо, пул називається rpool. Ми створюємо:

  • rpool/ROOT (контейнер, mountpoint=none)
  • rpool/ROOT/default (фактичний root BE, mountpoint=/)
  • rpool/ROOT/<be-name> (додаткові boot environments)
  • rpool/BOOT або окремий не-ZFS розділ для /boot, залежно від вашого дистрибутиву та вподобань
  • rpool/home (mountpoint=/home)
  • rpool/var (mountpoint=/var, зазвичай)
  • rpool/var/log (великий churn логів; інша політика знімків)
  • rpool/var/lib (контейнер, часто)
  • rpool/var/lib/docker або .../containers (якщо мусите, але подумайте про окремий пул)
  • rpool/var/lib/postgresql, rpool/var/lib/mysql тощо (цілком навмисно окремі)
  • rpool/tmp (mountpoint=/tmp, з sync=disabled тільки якщо ви любите ризик; інакше залиште як є)

А що з /boot?

Два життєздатні підходи:

  • /boot на EFI+ext4 (або vfat для ESP, ext4 для /boot): нудно, сумісно, просто. Відкати потребують уважного керування версіями ядра/initramfs, бо /boot спільний для BE. Це нормально, якщо ви використовуєте інструмент BE, що це обробляє.
  • /boot на ZFS: можливо з GRUB, що читає ZFS, але потрібно тримати сумісність feature flags з GRUB та дуже свідомо підходити до оновлень. Це може бути чистим варіантом, якщо інструменти вашого дистрибутива підтримують це; інакше — приваблива неприємність.

Мій ухил: якщо ви робите це на флоті машин, тримайте /boot на невеликому ext4 розділі і тримайте ESP окремо. Приберіть одну рухому частину з раннього завантаження. Ви все одно можете мати надійні семантики відкату з правильним управлінням BE; просто поводьтеся з артефактами ядра обережно.

План встановлення: чиста збірка ZFS-on-root, дружня до відкатів

Це загальний план, який добре мапується на системи типу Debian/Ubuntu і концептуально на інші. Підлаштуйте менеджер пакетів і інструменти initramfs відповідно. Суть не в точних командах, а в архітектурі та кроках перевірки.

Розмітка: обирайте передбачуваність

Використовуйте GPT. Створіть ESP. Зробіть виділений розділ для /boot, якщо ви не впевнені, що потрібен /boot на ZFS і ви протестували сумісність GRUB з вашим набором фіч пула. Решту помістіть в ZFS.

Створіть пул з думкою про завантаження

Виберіть ashift правильно (практично завжди 12). Оберіть розумні дефолти: compression=lz4, atime=off, xattr=sa і послідовний acltype для Linux (posixacl).

Шифрування: якщо ви шифруєте root, вирішіть, як його розблокувати (пароль на консолі, ключ у initramfs або розблокування по мережі). Не «вирішуйте пізніше». Пізніше — це коли система не завантажиться.

Створіть датасети й змонтуйте їх правильно

Задайте mountpoint=none для контейнерів. Помістіть фактичний root датасет під rpool/ROOT/<be-name> з mountpoint=/. Потім змонтуйте тимчасовий корінь у /mnt і встановіть ОС туди.

Встановіть завантажувач і initramfs з підтримкою ZFS

Потрібні модулі ZFS в initramfs та коректна логіка імпорту пула. Це включає:

  • Коректний kernel cmdline root=ZFS=rpool/ROOT/<be-name> (або еквівалент вашого дистрибутива).
  • Файл кешу пула, якщо initramfs його використовує (/etc/zfs/zpool.cache), плюс коректний hostid.
  • GRUB сконфігурований так, щоб знаходити ZFS і перераховувати BE (залежить від дистрибутива/інструментів).

Інструменти управління BE: оберіть і користуйтеся одним

Якщо ви хочете, щоб відкати «справді працювали», використовуйте інструмент BE, що інтегрується з потоком оновлення завантажувача вашого дистрибутива. На Linux це може бути щось на кшталт zsys (епоха Ubuntu), zfsbootmenu (інший підхід) або дистро-специфічні хуки. Я не нав’язую один інструмент, але наполягаю, щоб ви припинили ручне створення BE без повторюваної історії інтеграції з завантаженням.

Цитата про надійність, бо вона і досі справедлива: «Сподівання — не стратегія.» — General Gordon R. Sullivan

Практичні завдання: команди, виводи та рішення (12+)

Це перевірки, які ви виконуєте під час інсталяції, валідації або налагодження. Кожне завдання містить: команду, що означає вивід, і рішення, яке ви робите далі.

Завдання 1: Підтвердьте припущення про розмір секторів (щоб ashift не підкосив вас)

cr0x@server:~$ lsblk -o NAME,MODEL,SIZE,PHY-SEC,LOG-SEC,ROTA
NAME MODEL             SIZE PHY-SEC LOG-SEC ROTA
nvme0n1 Samsung SSD    1.8T    4096    512    0
sda    ST4000NM0035    3.6T    4096   4096    1

Значення: PHY-SEC — це фізичний розмір сектора. Більшість сучасних дисків фізично 4K. Якщо ви обираєте ashift=12 (2^12=4096), це буде добре вирівняно.

Рішення: Використовуйте ashift=12, якщо у вас немає незвичного пристрою, який справді потребує 8K (рідко), або ви на старому обладнанні з 512B (також рідко нині).

Завдання 2: Створіть пул із розумними дефолтами (і подивіться, що сталося)

cr0x@server:~$ sudo zpool create -f -o ashift=12 \
  -O compression=lz4 -O atime=off -O xattr=sa -O acltype=posixacl \
  -O mountpoint=none -R /mnt rpool /dev/nvme0n1p3
cr0x@server:~$ zpool status -v rpool
  pool: rpool
 state: ONLINE
config:

        NAME            STATE     READ WRITE CKSUM
        rpool           ONLINE       0     0     0
          nvme0n1p3     ONLINE       0     0     0

errors: No known data errors

Значення: Пул існує, ONLINE, без помилок. -R /mnt altroot означає, що монтування йде під /mnt для інсталяції.

Рішення: Якщо ви бачите не ONLINE, зупиніться. Виправте кабелі, шляхи пристроїв, розмітку або стан диска, перш ніж встановлювати ОС на ненадійну основу.

Завдання 3: Перевірте фіч-флаги пулу, щоб не здивувати завантажувач

cr0x@server:~$ zpool get -H feature@* rpool | head
rpool    feature@async_destroy              enabled     local
rpool    feature@bookmarks                  enabled     local
rpool    feature@embedded_data              enabled     local
rpool    feature@extensible_dataset         enabled     local
rpool    feature@filesystem_limits          enabled     local
rpool    feature@hole_birth                 enabled     local
rpool    feature@large_blocks               enabled     local
rpool    feature@lz4_compress               enabled     local
rpool    feature@spacemap_histogram         enabled     local
rpool    feature@enabled_txg                active      local

Значення: Фіч-флаги увімкнені/активні. Завантажувачі (особливо збірки GRUB) можуть не підтримувати кожну фічу на кожному поколінні дистрибутива.

Рішення: Якщо ви покладаєтеся на GRUB для читання ZFS у /boot, перевірте сумісність фіч. Якщо ви не на 100% впевнені, тримайте /boot поза ZFS і нехай GRUB читає ext4.

Завдання 4: Створіть контейнерні датасети з дисципліною mountpoint-ів

cr0x@server:~$ sudo zfs create -o mountpoint=none rpool/ROOT
cr0x@server:~$ sudo zfs create -o mountpoint=/ rpool/ROOT/default
cr0x@server:~$ sudo zfs create -o mountpoint=/home rpool/home
cr0x@server:~$ sudo zfs create -o mountpoint=/var rpool/var
cr0x@server:~$ sudo zfs create -o mountpoint=/var/log rpool/var/log
cr0x@server:~$ zfs list -o name,mountpoint -r rpool | head -n 10
NAME               MOUNTPOINT
rpool              none
rpool/ROOT         none
rpool/ROOT/default /mnt/
rpool/home         /mnt/home
rpool/var          /mnt/var
rpool/var/log      /mnt/var/log

Значення: З altroot /mnt mountpoint-и видно під /mnt. Контейнерні датасети не змонтовані.

Рішення: Якщо ви бачите контейнерні датасети змонтованими, виправте це зараз. Контейнерні датасети зазвичай повинні мати mountpoint=none, щоб уникнути конфліктів монтування й дивних зв’язків при відкаті.

Завдання 5: Перевірте, що буде змонтовано під час завантаження (і чи погоджується ZFS)

cr0x@server:~$ zfs get -r canmount,mountpoint rpool/ROOT/default | head
NAME               PROPERTY   VALUE     SOURCE
rpool/ROOT/default canmount   on        default
rpool/ROOT/default mountpoint /         local

Значення: Датасет root змонтується в /.

Рішення: Для не-root датасетів, які ви хочете мати per-BE (рідко, але буває), встановіть canmount=noauto і обробляйте явно. Не імпровізуйте.

Завдання 6: Підтвердьте, що властивість bootfs вказує на потрібний BE

cr0x@server:~$ sudo zpool set bootfs=rpool/ROOT/default rpool
cr0x@server:~$ zpool get bootfs rpool
NAME   PROPERTY  VALUE              SOURCE
rpool  bootfs    rpool/ROOT/default local

Значення: За замовчуванням boot filesystem пулу встановлено. Деякі потоки завантаження використовують це як підказку.

Рішення: Коли ви створюєте новий BE для оновлення, встановіть bootfs на цей BE перед перезавантаженням (або дайте вашому BE-інструменту зробити це). Якщо забудете, ви завантажитеся в старе середовище і дивуватиметесь, чому нічого не змінилося.

Завдання 7: Валідуйте hostid і zpool.cache (щоб initramfs імпортував надійно)

cr0x@server:~$ hostid
9a3f1c2b
cr0x@server:~$ sudo zgenhostid -f 9a3f1c2b
cr0x@server:~$ sudo zpool set cachefile=/etc/zfs/zpool.cache rpool
cr0x@server:~$ sudo ls -l /etc/zfs/zpool.cache
-rw-r--r-- 1 root root 2264 Jan 12 09:41 /etc/zfs/zpool.cache

Значення: Стабільний hostid зменшує драму «пул востаннє був доступний іншим хостом». cachefile допомагає ранньому завантаженню знайти пристрої надійно.

Рішення: На клонованих віртуальних машинах генеруйте hostid для кожного вузла. Дубльовані hostid — класична історія «працює в деві, вибухає в проді».

Завдання 8: Перевірте, що initramfs включає ZFS і імпортує пули

cr0x@server:~$ lsinitramfs /boot/initrd.img-$(uname -r) | grep -E 'zfs|zpool' | head
usr/sbin/zpool
usr/sbin/zfs
usr/lib/modules/6.8.0-31-generic/kernel/zfs/zfs.ko
usr/lib/modules/6.8.0-31-generic/kernel/zfs/zcommon.ko
scripts/zfs

Значення: Інструменти й модулі є в initramfs. Якщо їх там немає, раннє завантаження не зможе імпортувати пул, і ви опинитесь у shell initramfs зі стійким виразом обличчя.

Рішення: Якщо відсутні, встановіть відповідний пакет інтеграції ZFS для initramfs у вашому дистрибутиві і перегенеруйте initramfs.

Завдання 9: Підтвердьте, що kernel cmdline вказує на правильний root датасет

cr0x@server:~$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-6.8.0-31-generic root=ZFS=rpool/ROOT/default ro quiet

Значення: Ядру сказано змонтувати rpool/ROOT/default як корінь.

Рішення: Якщо це вказує на знімок або неправильний датасет, виправте конфіг GRUB або ваш BE-інструмент. Не намагайтеся «просто змонтувати» постфактум; межа завантаження неправильно визначена.

Завдання 10: Створіть новий boot environment (клон) і валідуйте його незалежність

cr0x@server:~$ sudo zfs snapshot rpool/ROOT/default@pre-upgrade
cr0x@server:~$ sudo zfs clone rpool/ROOT/default@pre-upgrade rpool/ROOT/upgrade-test
cr0x@server:~$ zfs list -o name,origin,mountpoint -r rpool/ROOT | grep -E 'default|upgrade-test'
rpool/ROOT/default        -                           /
rpool/ROOT/upgrade-test   rpool/ROOT/default@pre-upgrade  /

Значення: upgrade-test — клон зі знімка і може слугувати як BE. Зауважте: обидва показують mountpoint /; одночасно має бути змонтований тільки один.

Рішення: Встановіть canmount=noauto на неактивних BE, щоб вони випадково не змонтувалися при обслуговуванні.

Завдання 11: Нехай за замовчуванням монтується тільки один BE

cr0x@server:~$ sudo zfs set canmount=noauto rpool/ROOT/default
cr0x@server:~$ sudo zfs set canmount=noauto rpool/ROOT/upgrade-test
cr0x@server:~$ sudo zfs set canmount=on rpool/ROOT/upgrade-test
cr0x@server:~$ zfs get canmount rpool/ROOT/default rpool/ROOT/upgrade-test
NAME                   PROPERTY  VALUE   SOURCE
rpool/ROOT/default      canmount  noauto  local
rpool/ROOT/upgrade-test canmount  on      local

Значення: Ви контролюєте, що автомонтується. Це зменшує інциденти «два rootи змонтовані десь дивно».

Рішення: Для вашого флоту стандартизовуйте конвенцію властивостей BE. Ваше майбутнє «я» тихо подякує вам — це найкращий вид вдячності.

Завдання 12: Переключіть bootfs на новий BE і оновіть конфіги завантажувача

cr0x@server:~$ sudo zpool set bootfs=rpool/ROOT/upgrade-test rpool
cr0x@server:~$ zpool get bootfs rpool
NAME   PROPERTY  VALUE                   SOURCE
rpool  bootfs    rpool/ROOT/upgrade-test local
cr0x@server:~$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.8.0-31-generic
cr0x@server:~$ sudo update-grub
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.8.0-31-generic
Found initrd image: /boot/initrd.img-6.8.0-31-generic
done

Значення: Ви готуєте ранні артефакти завантаження й меню завантаження. Деякі дистрибутиви потребують додаткових хуків для перерахунку BE; тут показано загальну форму.

Рішення: Перезавантажтеся і валідуйте, що можете завантажити як новий BE, так і старий. Якщо ні — припиніть називати їх «boot environments». Це просто «датасети, які ви сподіваєтесь змонтувати пізніше».

Завдання 13: Підтвердьте, у який BE ви фактично завантажилися після перезавантаження

cr0x@server:~$ findmnt -n -o SOURCE /
rpool/ROOT/upgrade-test
cr0x@server:~$ zfs list -o name,mounted,canmount rpool/ROOT
NAME                   MOUNTED  CANMOUNT
rpool/ROOT             no       on
rpool/ROOT/default      no       noauto
rpool/ROOT/upgrade-test yes      on

Значення: Root — новий BE. Старий BE не змонтований.

Рішення: Якщо root все ще default, ви фактично не переключилися; виправте bootfs і/або конфіг завантажувача, потім спробуйте ще раз.

Завдання 14: Аудит знімків і вплив на простір (щоб «відкат» не став «закінчився простір»)

cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint rpool | head -n 8
NAME                  USED  AVAIL  REFER  MOUNTPOINT
rpool                38.2G  1.67T   192K  none
rpool/ROOT           14.1G  1.67T   192K  none
rpool/ROOT/default   7.20G  1.67T  7.20G  /
rpool/ROOT/upgrade-test 7.14G 1.67T 7.14G  /
rpool/var            11.5G  1.67T  7.31G  /var
rpool/var/log        1.84G  1.67T  1.84G  /var/log
cr0x@server:~$ zfs list -t snapshot -o name,used,refer -r rpool/ROOT | tail -n 5
rpool/ROOT/default@pre-upgrade  132M  7.20G

Значення: used у знімку показує простір, утримуваний через зміни після знімка. Велике «used» у знімків часто означає churn у датасетах, які ви не ізолювали.

Рішення: Якщо знімки надуваються через логи або шари контейнерів всередині BE, відокремте їх зараз. Тиск по простору — головний вбивця відкатів, бо ZFS потребує повітря для роботи.

Завдання 15: Перевірте здоров’я пула і лічильники помилок перед тим, як звинувачувати «дивність ZFS»

cr0x@server:~$ zpool status -xv
all pools are healthy

Значення: Відомих помилок даних немає, vdev-и не деградовані. Добре.

Рішення: Якщо ви бачите помилки контрольних сум, сприймайте це як інцидент апаратури/транспорту, поки не буде доведено протилежне. «Мій відкат не спрацював» іноді означає «мій SSD брешe».

Завдання 16: Виявлення затримки імпорту при завантаженні (мовчазний саботажник відкатів)

cr0x@server:~$ systemd-analyze blame | head -n 10
14.203s zfs-import-cache.service
 6.412s dev-nvme0n1p3.device
 3.901s systemd-udev-settle.service
 2.311s network-online.target
 1.988s zfs-mount.service

Значення: Час завантаження домінує через імпорт пулу, що чекає на пристрої або збій кешу.

Рішення: Якщо zfs-import-cache повільний, виправте стабільність імен пристроїв, перегенеруйте zpool.cache або перейдіть на імпорт за id. Якщо udev-settle довго чекає, перевірте, що його тригерить.

Три корпоративні міні-історії з країни наслідків

1) Інцидент через неправильне припущення: «Знімки = boot environments»

Середня SaaS-компанія впровадила ZFS-on-root на наборі API-вузлів. Інженер, що виступав ініціатором, був компетентний, але обмежений у часі. Вони створили один датасет для /, увімкнули знімки і оголосили перемогу. Інші в організації почули «ми можемо відкотитися після оновлення». Це речення має наслідки.

Під час рутинного оновлення безпеки один вузол піднявся з зламаною мережею після перезавантаження. Онкол почав робити те, що їм сказали: «відкотитися». Вони виконали відкат root датасету до останнього знімка, перезавантажилися і… отримали ту саму зламану мережу. Потім пробували старші знімки. Те ж саме. Тепер вони сердилися на ZFS, що несправедливо, але часто трапляється.

Корінь проблеми був у тому, що їх /boot жив на ext4 розділі, спільному для всіх «станів», і initramfs було перегенеровано з несумісним модулем. Відкат відновив вміст файлової системи root, але не відкотив пару ядро+initramfs у /boot. Вони фактично завантажували новий initramfs у старий root. Linux багато чого, але «прощаючий з приводу ABI-несумісностей при завантаженні» — не серед його чеснот.

Виправлення не було магічним. Вони ввели реальні boot environments: клони rpool/ROOT/<be> і робочий процес, де оновлення відбуваються в новому BE, а /boot оновлюється координовано. Також вони змінили внутрішню мову: перестали називати знімки «відкатами» і почали говорити «перемикання boot environment». Точність зменшила кількість нічних сюрпризів.

2) Оптимізація, що вийшла боком: «Давайте вмикаємо dedup на root, воно майже ідентичне»

Фінансова фірма любила ідею boot environments, але ненавиділа витрати простору. Хтось помітив, що багато BE схожі: ті самі бібліотеки, ті самі бінарні файли, лише кілька оновлень. Dedup здався приємним трюком: зберігати ідентичні блоки один раз, економити простір і мати «нескінченні» BE.

Вони увімкнули dedup на дереві root і деякий час працювали щасливо. Потім накотила хвиля оновлень: ядра, рантайми, інструменти контейнерів. Таблиця dedup зросла, пам’ять була під тиском, і машини почали трешитись. Спочатку не катастрофічно — просто трохи латентності, трохи дивності. Потім сталася перезавантаження, і час завантаження вибухнув. Імпорт уповільнився, сервіси таймаутнули, вузол вибув із балансувальника навантаження. Повторювалося по кластеру, один за одним, під час подальшого обслуговування.

Dedup не був «зламаний». Він зробив саме те, що і мав робити: обміняти пам’ять і метадані на простір. На root-файлових системах з churn і багатьма знімками/BE ця торгівля може бути жорстокою. Найгірше, що режим відмови не дає чіткої помилки; це повільна смерть тисячею операцій.

Відновлення було нудним і дорогим: вони мігрували з датасетів із dedup на нові без dedup, перезавантажилися в чисті BE і погодилися, що клонування + стиснення достатні. Вони перестали оптимізувати для дашборда storage-команди і почали оптимізувати для надійності перезавантаження. Це правильний напрям сорому.

3) Нудна, але правильна практика, що врятувала день: «Завжди тримати відомий добрий BE і тестувати завантажуваність»

Компанія з аналітики для охорони здоров’я мала практику, яка звучала нудно: кожного місяця вони створювали «known-good» boot environment, маркували його номером зміни і тримали принаймні один повний релізний цикл. Вони також вимагали: після будь-якого оновлення оператор має довести, що може завантажити попередній BE, вибравши його в меню завантаження і досягнувши multi-user режиму. Потім поверталися назад у новий BE.

Це займало, можливо, десять хвилин за вікно оновлення. Люди скаржилися. Люди завжди скаржаться на ритуали, що запобігають проблемам, яких вони ще не бачили особисто.

Одного кварталу оновлення драйвера від постачальника поламало порядок переліку дисків на партії серверів. Нічого драматичного, просто достатньо, щоб імпорт у initramfs уповільнився і інколи вибрав невірний шлях першим. Кілька машин не змогли завантажитися в оновлене середовище. Але оскільки команда мала протестований known-good BE і встановлену процедуру, on-call вибрав попередній BE, система піднялася, і вони мали час виправити новий BE без повного простою.

Оце виглядає як «правильно» в продакшні: непомітні кроки, що обмінюють п’ять хвилин зараз на п’ять годин, яких ви не витратите пізніше. Також це скорочує постмортеми, що є формою доброти.

Швидка діагностика: знайдіть вузьке місце швидко

Коли відкат/boot environment ZFS-on-root дає збій, ви можете витратити години на неправильному шарі. Не робіть цього. Перевіряйте в такому порядку.

Перше: Ви взагалі завантажуєтесь у BE, про який думаєте?

  • З працюючої системи: findmnt -n -o SOURCE /
  • Підтвердьте cmdline: cat /proc/cmdline
  • Підтвердьте: zpool get bootfs rpool

Тлумачення: Якщо перемикання BE не відбулося, усе інше — шум. Виправте вибір завантаження перед налагодженням юзерленду.

Друге: Чи може раннє завантаження швидко й послідовно імпортувати пул?

  • systemd-analyze blame | head для затримок zfs-import-*
  • journalctl -b -u zfs-import-cache.service для таймаутів і відсутніх пристроїв
  • Перевірте, чи існує /etc/zfs/zpool.cache і відповідає реальності
  • Перевірте узгодженість hostid: hostid

Тлумачення: Проблеми імпорту маскуються під «випадкові відмови завантаження». Вони зазвичай детерміновані, якщо подивитися на виявлення пристроїв і кеш.

Третє: Чи узгоджений /boot з BE кореня?

  • Перелічіть ядра й initramfs: ls -lh /boot
  • Перевірте, чи initramfs містить модулі ZFS: lsinitramfs ... | grep zfs
  • Перевірте генерацію меню GRUB і записи

Тлумачення: Якщо ви ділите /boot між BE, вам потрібна політика. Без політики ви маєте «що зробив останній апдейт», що не є інженерною стратегією.

Четверте: Якщо система завантажується, але відкат не «відновлює здоров’я», перевірте межі датасетів

  • Чи логи в BE? zfs list -o name,mountpoint | grep /var/log
  • Чи стан додатків випадково спільний між BE? Перегляньте датасети /var/lib
  • Тиск від знімків: zfs list -t snapshot, zpool list

Тлумачення: Більшість розчарувань відкату — самозавдані через погане розділення датасетів.

Поширені помилки: симптом → корінна причина → виправлення

1) Симптом: «Відкат успішний, але система після перезавантаження все ще зламана»

Корінна причина: /boot спільний і не був відкотений; несумісність ядра/initramfs з відкотеним root. Або ви відкотили датасет, але не переключили BE, який використовує завантажувач.

Виправлення: Використовуйте BE (на основі клонів) і координуйте оновлення ядра/initramfs. Підтвердьте, що root=ZFS=... вказує на бажаний BE. Розгляньте можливість зберігати артефакти ядра для кожного BE, якщо ваше середовище це підтримує, або впровадьте суворі правила збереження ядра.

2) Симптом: Завантаження опускає в initramfs shell: «cannot import ‘rpool’»

Корінна причина: Відсутні модулі ZFS в initramfs, неправильний zpool.cache, нестабільні шляхи пристроїв або невідповідність hostid (поширено після клонування).

Виправлення: Перегенеруйте initramfs з включеним ZFS; перегенеруйте /etc/zfs/zpool.cache; переконайтеся, що hostid унікальний; віддавайте перевагу стабільним ідентифікаторам пристроїв. Перевірте вміст initramfs для валідації.

3) Симптом: GRUB не бачить пул або не може читати /boot на ZFS

Корінна причина: У пулі ввімкнені фічі, які GRUB не може прочитати, або відсутній/старий модуль ZFS для GRUB.

Виправлення: Тримайте /boot поза ZFS, або заморожуйте фічі пулу до тих, які підтримує GRUB, або оновіть стек завантажувача контрольовано. Не робіть «просто zpool upgrade» на критичних пулах без планування.

4) Симптом: Список boot environment існує, але вибір старшого викликає паніку або зависання

Корінна причина: Старий BE має initramfs, що не може імпортувати пул з поточною номенклатурою, конфігурацією шифрування або набором модулів; або userland очікує новішого ядра.

Виправлення: Періодично тестуйте завантаження старих BE. Тримайте принаймні один known-good BE достатньо оновленим, щоб завантажуватися на сучасному обладнанні/прошивці. Якщо ви зберігаєте стародавні BE, вважайте їх архівами, а не завантажувальними цілями.

5) Симптом: «zfs rollback» не вдається через зайнятість датасету, або відкат ламає монтування

Корінна причина: Спроба відкотити змонтований live root датасет, або конфлікти mountpoint-ів через змонтовані контейнери, або неврегульоване canmount.

Виправлення: Відкат робіть шляхом переключення BE, а не відкатом змонтованого root. Переконайтеся, що контейнерні датасети мають mountpoint=none. Встановіть неактивні BE на canmount=noauto.

6) Симптом: Простір постійно зменшується, знімки «не видаляються»

Корінна причина: Знімки утримують блоки; клони залежать від знімків; видалення знімків, що є джерелом клонів, не звільнить простір так, як ви очікуєте.

Виправлення: Розумійте залежності клонів і знімків. Промотуйте клони за потреби. Відокремте churn-датасети. Впровадьте політики утримання для кожного датасету.

7) Симптом: Час завантаження з часом сповільнюється

Корінна причина: Затримки імпорту через виявлення пристроїв, роздутий метаданих знімків, або «оптимізація» типу dedup, що збільшила обсяг метаданих.

Виправлення: Вимірюйте за допомогою systemd-analyze. Виправте метод імпорту і кеш. Зменшіть churn знімків на root. Уникайте dedup на root, якщо у вас немає великого запасу RAM і строгих вимірювань.

Короткий жарт #2: Dedup на root — це як спортивний байк взимку: вражаюче, поки не познайомитеся з фізикою особисто.

Контрольні списки / поетапний план

Контрольний список A: Перед інсталяцією

  • Визначте: /boot на ext4 (рекомендовано для флоту) або /boot на ZFS (тільки з перевіреною сумісністю GRUB).
  • Виберіть ім’я пулу (rpool звично) і схему іменування BE (default, 2025q1-upgrade тощо).
  • Підтвердьте розміри секторів (lsblk) і задайте ashift=12.
  • Вирішіть метод шифрування і процедуру розблокування в експлуатації.
  • Запишіть межі датасетів: як мінімум відокремте /var/log і шляхи баз даних.

Контрольний список B: Створення пулу + датасетів (дружніх до відкатів)

  • Створіть пул з mountpoint=none, compression=lz4, atime=off, xattr=sa, acltype=posixacl.
  • Створіть контейнер rpool/ROOT і rpool/ROOT/default, змонтований у /.
  • Створіть окремі датасети для /home, /var, /var/log і даних додатків.
  • Встановіть bootfs на BE, в який ви плануєте завантажитися.
  • Вкажіть cachefile і згенеруйте стабільний hostid.

Контрольний список C: Встановіть ОС + зробіть її завантажуваною

  • Встановіть користувацький простір ZFS і модулі ядра.
  • Переконайтеся, що initramfs містить скрипти імпорту ZFS і модулі.
  • Встановіть завантажувач і переконайтеся, що kernel cmdline містить root=ZFS=rpool/ROOT/<be>.
  • Якщо /boot спільний: визначте політику утримання і процес оновлення ядра; не покладайтеся на інтуїцію.

Контрольний список D: Перевірте поведінку відкату (той етап, який усі пропускають)

  • Створіть новий BE (знімок + клон) перед першим реальним оновленням.
  • Переключіть bootfs на новий BE і перегенеруйте завантажувальні артефакти.
  • Перезавантажтеся в новий BE; підтвердьте за допомогою findmnt.
  • Завантажтеся в старий BE з меню завантаження; підтвердьте, що він ще завантажується.
  • Тільки потім: виконуйте реальну процедуру оновлення і повторюйте танець.

Питання й відповіді

1) Чи достатньо знімків ZFS для відкату ОС?

Ні. Знімки — це будівельні блоки. Для відкату ОС потрібен boot environment, який завантажувач може вибрати і завантажити чисто.

2) Чи варто класти /boot на ZFS?

Якщо ви керуєте флотом і хочете менше несподіванок на ранньому завантаженні, тримайте /boot на ext4. Помістіть його на ZFS лише якщо ви протестували сумісність GRUB з фічами пулу і процес оновлення налаштований.

3) Яке одне найважливіше рішення щодо датасетів для відкатів?

Відокремте churn-дані від BE: як мінімум /var/log і бази даних. Інакше знімки й клони стануть якірними для простору і семантики відкатів стануть дивними.

4) Скільки boot environments варто зберігати?

Тримайте як мінімум два завантажувальні: поточний і один відомо робочий. Більше — нормально, якщо у вас є політика утримання і ви розумієте залежності знімків/клонів.

5) Чому мій пул імпортується повільно при завантаженні?

Зазвичай через нестабільність виявлення пристроїв або застарілий zpool.cache. Іноді через дубльований hostid на клонованих системах. Виміряйте за допомогою systemd-analyze blame і перевірте логи zfs-import-*.

6) Чи можна просто виконати zfs rollback на rpool/ROOT/default поки система працює?

Не робіть цього. Відкат змонтованого root — це спосіб створити нові й захопливі режими відмов. Переключайте BE замість цього, потім перезавантажуйтеся.

7) Чи безпечно шифрування root на ZFS в експлуатації?

Так, якщо ви спроєктуєте процедуру розблокування. Вирішіть наперед, чи розблоковуєте за паролем на консолі, ключем у initramfs або віддаленим механізмом. «Зробимо це пізніше» часто стає «ми не можемо завантажитися».

8) Чи вмикати dedup, щоб зекономити простір між BE?

У загальному випадку — ні для root. Стиснення зазвичай правильний дефолт. Dedup може бути виправданий у вузьких випадках з великою кількістю RAM і ретельними вимірюваннями, але це не випадковий перемикач.

9) Які властивості зазвичай безпечні за замовчуванням для root-датасетів?

Зазвичай: compression=lz4, atime=off, xattr=sa, acltype=posixacl. Перевірте очікування додатків щодо ACL і xattr, особливо з рантаймами контейнерів.

10) Як дізнатися, у якому BE я зараз перебуваю?

findmnt -n -o SOURCE / має показати щось на кшталт rpool/ROOT/<be>. Також перевірте cat /proc/cmdline на наявність root=ZFS=....

Висновок: наступні кроки, які ви можете зробити цього тижня

Якщо у вас вже є ZFS на корені і ви не впевнені, чи дійсно відкати працюють, не чекайте наступного невдалого оновлення, щоб дізнатися. Зробіть три речі:

  1. Доведіть свою поточну ідентичність завантаження: перевірте /proc/cmdline, findmnt / і zpool get bootfs. Переконайтеся, що історія сходиться.
  2. Створіть реальний boot environment і завантажтеся в нього: зробіть знімок, клон, переключіть bootfs, перегенеруйте завантажувальні артефакти, перезавантажтеся. Потім завантажтеся в старий BE теж. Якщо щось не виходить — виправте це зараз, поки ви спокійні.
  3. Перепроєктуйте межі датасетів там, де живе churn: відокремте /var/log і дані додатків з BE. Додайте політики утримання. Перестаньте дозволяти логам тримати ваші відкати в заручниках.

ZFS-on-root вартий зусиль, коли його встановлюють з урахуванням того, як ви плануєте його використовувати: як контрольована система відкатів із завантажувачем, який розуміє ваші наміри. Мета не в хитрості. Мета — нудне відновлення.

← Попередня
HBM у процесорах: коли пам’ять переміщується в пакет
Наступна →
Docker «Too Many Requests» при завантаженні образів: виправте обмеження реєстру як слід

Залишити коментар