кореневий розділ linux на дзеркалі zfs

| щоденник, підказка, linux, сервер

нещодавно я переробив свій сервер v2 із гіпервізором, відмовившись від розваг із mergerfs/snapraid на користь zfs, але я полінувався занотувати детально процедуру налаштування дзеркала zfs на двох дисках, обмежившись посиланням на сайт проєкту zfs. тепер шкодую — варт було зробити собі підказку, бо рано чи пізно щось поламається, і доведеться пригадувати.

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

отже, ось підказка з налаштування дзеркала zfs під кореневий розділ для встановлення ubuntu 20.04 від туристичного елементаля (aka piktor, aka злий викладач).


Дана інструкція описує встановлення Ubuntu 20.04 разом із коренем на ZFS mirror. За основу взято офіційну інструкцію із проекту OpenZFS, проте звідси виключено zsys та пов’язані із ним аспекти, в якому для мене відсутня потреба.

Інсталяція призначена для домашнього сервера, в якому працюватиме файлосховище та Docker або гіпервізор для експериментів, тож хост-система передбачається стабільною і незмінною.

Що таке ZFS, ZVOL та датасети в даній інструкції не пояснюється. Завантаження буде через EFI.

Дана інструкція написана шляхом збереження .bash_history після інсталяції, видалення зайвого та коментування усіх команд.

Для інсталяції використовується звичайний ISO десктопного Ubuntu, що може працювати в режимі LiveCD — якщо комп’ютер локальний, то прямо в ньому можна відкрити термінал та браузер, не підіймаючи ssh-сервер. Також буде простіше підключитися до мережі, в моєму випадку до WiFi.

Підготовка

Для початку дивимося, як визначаються в системі наші диски. Пропонується ідентифікувати їх за WWN, оскільки він незмінний та має однакову довжину.

ls -1d /dev/disk/by-id/*

Для зручності записуємо обидва ідентифікатори у змінні:

DISK1=/dev/disk/by-id/wwn-0x50014ee269dc0b16
DISK2=/dev/disk/by-id/wwn-0x50014ee2bf323665

Ставимо необхідні для роботи утиліти та бібліотеки:

apt install --yes debootstrap gdisk zfsutils-linuxvim

Відключємо автомонтування у LiveCD, щоб воно не намагалося самостійно підключити створені в подальшому розділи (потрібно, якщо працюєте з графічної оболонки LiveCD):

gsettings set org.gnome.desktop.media-handling automount false

Також зупиняємо ZED (ZFS Event Daemon), щоб не заважав. І вимикаємо swap.

systemctl stop zed
swapoff -all

Підготовка дисків

Якщо жорсткі диски не нові й на них був софтовий RAID із mdadm, маємо також його встановити і вилучити раніше створений масив. Для нових HDD не треба.

apt install --yes mdadm
cat /proc/mdstat
mdadm --stop /dev/md0
mdadm --zero-superblock --force ${DISK1}-part2
mdadm --zero-superblock --force ${DISK2}-part2

Також не нові HDD можна очистити від старих розділів.

sgdisk -Z $DISK1
sgdisk -Z $DISK2

Тепер створюємо нові розділи. Їх буде чотири на кожен диск — 1) завантажувальний для EFI, 2) область резервної пам’яті, 3) завантажувальний розділ та 4) основний розділ для ZFS-пулу. Розміри підбирайте під свої носії, у мене на WD Red 4 TB розподілення має наступний вигляд:

sgdisk     -n1:1M:+512M   -t1:EF00 $DISK1
sgdisk     -n1:1M:+512M   -t1:EF00 $DISK2
sgdisk     -n2:0:+4G    -t2:FD00 $DISK1
sgdisk     -n2:0:+4G    -t2:FD00 $DISK2
sgdisk     -n3:0:+1G      -t3:BE00 $DISK1
sgdisk     -n3:0:+1G      -t3:BE00 $DISK2
sgdisk     -n4:0:+3809810M        -t4:BF00 $DISK1
sgdisk     -n4:0:+3809810M        -t4:BF00 $DISK2

Особливості:

  • останній розділ рекомендується робити так, щоб на диску залишалося кілька мегабайт нерозміченого простору; бо якщо доведеться заміняти на диск іншої моделі, або просто з іншої партії, то кількість секторів може незначним чином відрізнятися — навіть у менший бік; і якщо розбивка буде до самого останнього сектора, то новий носій ви не зможете розбити аналогічно;
  • третій розділ передбачається для файлів завантажувача GRUB, він буде підмонтований у /boot; Можна обійтися без нього — але GRUB не підтримує всіх опцій файлової системи ZFS (лише ті, що позначені як read-only); в такому разі мусите уважно вивчити усі бажані опції для створення ZFS-пула.

Дивимося, що у нас вийшло:

sgdisk -p $DISK1
sgdisk -p $DISK2

Підготовка ZFS

Створюємо ZFS-пули — завантажувальний та основний. Якщо у вас SSD, то потрібно до обох пулів додати опцію -o autotrim=on. Перелік опцій взято із офіційної інструкції — я, переглянувши їх призначення, не став нічого міняти. Хоча, можна спростити.

zpool create -o cachefile=/etc/zfs/zpool.cache -o ashift=12 -d -o feature@async_destroy=enabled -o feature@bookmarks=enabled -o feature@embedded_data=enabled -o feature@empty_bpobj=enabled -o feature@enabled_txg=enabled -o feature@extensible_dataset=enabled -o feature@filesystem_limits=enabled -o feature@hole_birth=enabled -o feature@large_blocks=enabled -o feature@lz4_compress=enabled -o feature@spacemap_histogram=enabled -O acltype=posixacl -O canmount=off -O compression=lz4 -O devices=off -O normalization=formD -O relatime=on -O xattr=sa -O mountpoint=/boot -R /mnt bpool mirror ${DISK1}-part3 ${DISK2}-part3

Створюємо основний пул.

zpool create -o ashift=12 -O acltype=posixacl -O canmount=off -O compression=lz4 -O normalization=formD -O relatime=on -O xattr=sa -O mountpoint=/ -R /mnt rpool mirror ${DISK1}-part4 ${DISK2}-part4

Тут проявляється перша відмінність від офіційної інструкції. В ній, для створення основного пулу, пропонується опція -O dnodesize=auto, але вона несумісна із опцією bootfs. Там завантажувальний датасет визначається через zsys, якого ми в даному разі не ставимо.

Створюємо набір датасетів. Корінь системи буде розташований на окремому незалежному датасеті — це спрощує оновлення системи в разі потреби

zfs create -o canmount=off -o mountpoint=none rpool/ROOT
zfs create -o mountpoint=/ rpool/ROOT/s1

Якщо ви захочете оновити систему, скажімо до Ubuntu 22.04, тож не проблема створити rpool/ROOT/s2, примонтувати його, встановити туди нову систему та налаштувати її через chroot і просто перезавантажитися в неї. А, в разі чого, швидко повернутися до попередньої версії. В офіційні інструкції для найменування системних датасетів уводиться змінна UUID з рандомним значенням — це потрібно для zsys, тому я цим не заморочувався.

Робимо системний датасет завантажувальним. Цю опцію бачить GRUB і знає, звідки продовжувати завантаження ОС:

zpool set bootfs=rpool/ROOT/s1 rpool

Після системного датасету робимо завантажувальний — так як він підключається у /boot, тому корінь вже має існувати.

zfs create -o canmount=off -o mountpoint=none bpool/BOOT
zfs create -o mountpoint=/boot bpool/BOOT/ubuntu

Відокремлюємо необхідні папки в різні датасети, їх перелік індивідуальний:

zfs create -o mountpoint=/srv rpool/srv
zfs create -o mountpoint=/home rpool/home

Сенс наступних рядків у тому, що до кореневого датасету не потрапляють логи, тимчасові файли та кеш.

zfs create -o mountpoint=none -o canmount=off rpool/var
mkdir -p /mnt/var/log /mnt/var/cache
zfs create -o mountpoint=legacy rpool/var/log
zfs create -o mountpoint=legacy rpool/var/cache 
zfs create rpool/tmp
chmod 1777 /mnt/tmp

Перевіряємо, чи правильно все підмонтувалося. Зараз коренем нової системи слугує /mnt “живого” диску, але ми це згодом змінимо.

mount

Встановлення нової системи

debootstrap focal /mnt

Переносимо інформацію про датасети

mkdir /mnt/etc/zfs
cp /etc/zfs/zpool.cache /mnt/etc/zfs/
hostname host.tld
host.tld > /mnt/etc/hostname

Далі редагуємо:

vim /mnt/etc/hosts

В ньому:

------------
Вміст файлу:
------------
127.0.1.1       HOSTNAME
------------

або якщо система має ім’я у DNS:

------------
Вміст файлу:
------------
127.0.1.1       FQDN HOSTNAME
------------

Додаємо репозиторії

vim /mnt/etc/apt/sources.list

------------
Вміст файлу:
------------
deb http://archive.ubuntu.com/ubuntu focal main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu focal-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu focal-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu focal-security main restricted universe multiverse
------------

Підключаємо необхідні для роботи в chroot папки:

mount --rbind /dev  /mnt/dev
mount --rbind /proc /mnt/proc
mount --rbind /sys  /mnt/sys

Входимо у нову систему

chroot /mnt /usr/bin/env DISK1=$DISK1 DISK2=$DISK2 bash --login

Тепер потрібно встановити необхідні утиліти і тут

apt update
apt upgrade
apt --yes install vim mc network-manager dosfstools

Щоб було що курити:

apt install man

При усій крутості ZFS, EFI-розділ мусить бути у vfat і без дзеркала.

mkdosfs -F 32 -s 1 -n EFI ${DISK1}-part1
mkdosfs -F 32 -s 1 -n EFI ${DISK2}-part1
mkdir /boot/efi

Додаємо його у fstab

echo /dev/disk/by-uuid/$(blkid -s UUID -o value ${DISK1}-part1) /boot/efi vfat defaults 0 0 >> /etc/fstab
mount /boot/efi

Доставляємо GRUB та ядро:

apt install --yes grub-efi-amd64 grub-efi-amd64-signed linux-image-generic shim-signed zfs-initramfs
apt autoremove
apt purge --yes os-prober

Задаємо пароль для головного:

passwd

У інструкціях для попередніх версій пропонувалося робити swap у окремому ZVOL на основному пулі, але це може призвести до підвисання системи намертво в разі проблем із одним із дисків — у колеги навіть вдалося це відтворити. Тож область резервної пам’яті житиме у окремому масиві, створеному з mdadm.

apt install --yes mdadm
mdadm --create /dev/md0 --metadata=1.2 --level=mirror --raid-devices=2 ${DISK1}-part2 ${DISK2}-part2
mkswap -f /dev/md0
echo /dev/disk/by-uuid/$(blkid -s UUID -o value /dev/md0) none swap discard 0 0 >> /etc/fstab

Створюємо потрібні системні групи.

addgroup --system lpadmin
addgroup --system lxd
addgroup --system sambashare

Якщо передбачається використання шифрування ZFS або LUKS, то необхідно дещо пропатчити. Я тут не розібрався, що і як, просто виконав по офіційній інструкції.

apt install --yes curl patch
curl https://launchpadlibrarian.net/478315221/2150-fix-systemd-dependency-loops.patch | sed "s|/etc|/lib|;s|\.in$||" | (cd / ; patch -p1)

На помилки, що виникають, відповідаємо n двічі.

Доставляємо OpenSSH

apt install --yes openssh-server

Дозволяємо входити головному:

vim /etc/ssh/sshd_config

------------
Вміст файлу:
------------
…
PermitRootLogin yes
…
------------

Після встановлення SSH-ключів цей параметр можна поміняти на PermitRootLogin without-password, або вимкнути взагалі, якщо ви адміните усе через sudo.

Перевіряємо, чи GRUB розпізнає завантажувальний розділ:

grub-probe /boot

Має видати, що бачить ZFS.

ZFS ARC cache

Файлова система ZFS досить вимоглива до оперативної пам’яті, крім того вона має свій кеш в оперативній пам’яті, що може розростатися до величезних розмірів. У моєму випадку воно спожило більше 7 ГБ — це за кілька годин роботи Samba і заливки більше терабайту даних на новостворене сховище. Апетити ZFS можна зробити скромнішими:

vim /etc/modprobe.d/zfs.conf
------------
Вміст файлу:
------------
options zfs zfs_arc_max=1073741824
------------

Це можна зробити уже після перезавантаження у встановлену систему, але тоді доведеться перестворити InintRAMFS, як у наступному кроці.

Створюємо InitRAMFS:

update-initramfs -c -k all
update-grub

Копіюємо EFI в інший диск. Тут я не обзивав диск по WWN, бо це тимчасово.

mkdir /boot/efi2
mount -t vfat /dev/sdb1 /boot/efi2

Встановлюємо EFI-файли в обидва розділи:

grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu --recheck --no-floppy
cp -a /boot/efi/EFI /boot/efi2
grub-install --target=x86_64-efi --efi-directory=/boot/efi2 --bootloader-id=ubuntu2 --recheck —no-floppy

Перевіряємо, чи все на місці — має грепатися:

grep prefix= /boot/efi/EFI/ubuntu/grub.cfg /boot/efi/EFI/ubuntu/grub.cfg
grep prefix= /boot/efi/EFI/ubuntu/grub.cfg /boot/efi2/EFI/ubuntu/grub.cfg

Ця штука із ZFS-mirror не працює, відключаємо:

systemctl mask grub-initrd-fallback.service

Для того, щоб усі датасети правильно і по порядку монтувалися, робимо наступне. В старих інструкціях можна зустріти рішення через опцію датасету mountpoint=legacy та прописування усіх датасетів до fstab для правильного та своєчасного монтування. Тут це питання вирішується так:

mkdir /etc/zfs/zfs-list.cache
touch /etc/zfs/zfs-list.cache/bpool
touch /etc/zfs/zfs-list.cache/rpool
ln -s /usr/lib/zfs-linux/zed.d/history_event-zfs-list-cacher.sh /etc/zfs/zed.d
zed -F &

Запустивши ZED, тиснемо Enter, лишаючи його у фоні. Якщо наступні команди видадуть красиву табличку із вашими датасетами, то все зроблено правильно:

cat /etc/zfs/zfs-list.cache/bpool
cat /etc/zfs/zfs-list.cache/rpool

Якщо відповідь порожня, то командуємо наступне і повертаємося до попереднього кроку:

zfs set canmount=on bpool/BOOT/ubuntu
zfs set canmount=on rpool/ROOT/s1

Якщо все ок, то викликаємо ZED назад із фону і зупиняємо його:

fg
^C

Виправляємо /mnt на /

sed -Ei "s|/mnt/?|/|" /etc/zfs/zfs-list.cache/*
umount /boot/efi
umount /boot/efi2

Налаштуємо мережу.

vim /mnt/etc/netplan/01-netcfg.yaml
------------
Вміст файлу:
------------
network:
  version: 2
  renderer: networkd
  ethernets:
    NAME:
      dhcp4: true
------------

Де NAME мережевого інтерфейсу ви дізнаєтеся із команди ip addr show. У мене на материнці є вбудований WiFi і я, щоб не тягнути кабеля, налаштував його згідно документації netplan. Також встановив network-manager і зробив його бекендом, замінивши renderer: NetworkManager замість networkd.

Після ребуту в нову систему конфіг треба застосувати командами netplan generate та netplan apply.

Виходимо із chroot? відмонтовуємо усе та експортуємо пули:

exit
mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {}
zpool export -a

Після перезавантаження в нову систему:

netplan generate
netplan apply
networkctl
ip a

І поправляємо завантажувач:

dpkg-reconfigure grub-efi-amd64

У вікні конфігурації позначаємо обидва диски.


оце усе? виглядає страшно — переважно через те, що більшість кроків, специфічних для zfs, дзеркалювання розділів та збірки системи в chroot, виглядають незрозуміло навіть для когось із чималим досвідом «звичайного» розведення пінгвінів (один диск, ext3/4). ну, але це питання невеликого дослідження щоразу, коли «перечіпляєшся» через незнайоме — а як інакше навчитися чогось цікавого в it?!

якщо побачите помилки форматування — не мовчіть, лишайте зауваги в коментарях.