На моей домашней машине вот уже 7 лет работает пара дисков, объединенная в soft raid1. И вот на днях один диск в зеркале наконец начал сыпаться. Появился повод переустановить систему с нуля и начать использовать шифрование, которое 7 лет назад не было задействовано. В процессе гугления о состоянии дел в конфигурации LUKS поверх mdadm я вышел на статью сравнивающей производительность zfs vs mdadm/ext4. А потом нашел другую статью с тестированием производительности зашифрованных дисков использующих LUKS и zfs. Согласно обеим статьям zfs демонстрирует весьма неплохую производительность и я решил попробовать ее в деле.
На хабре некоторое время назад уже были статьи на ту же тему:
2019 Ubuntu 18.04 Root on ZFS
2018 Установка Debian с корнем на шифрованном ZFS зеркале
2018 /boot на ZFS зеркале
Я решил написать свою статью, поскольку использую для загрузки UEFI (в прошлых статьях использовалась legacy загрузка), ну и плюс с момента последней статьи прошло 3 года и мне показалось, что проверенная в деле современная инструкция может быть полезна для сообщества.
При установке я в основном ориентировался на вот эти статьи:
Ubuntu 20.04 Root on ZFS
Installing UEFI ZFS Root on Ubuntu 20.04 with Native Encryption
Я буду описывать установку на виртуальную машину virtualbox. Установка на настоящее железо ничем не отличается.
Итак, я создал виртуальную машину с парой дисков по 20гб и 8гб памяти. Загрузился с ubuntu-20.04.1-desktop-amd64.iso, кликнул на try ubuntu и запустил терминал. В терминале я сразу перешел под рута, так как все используемые команды требуют рутовых привилегий. Первым делом я определил несколько переменных:
export DISK1=/dev/disk/by-id/ata-VBOX_HARDDISK_VBad5107ca-df268eef
export DISK2=/dev/disk/by-id/ata-VBOX_HARDDISK_VBaf134a71-943e2d11
export HOSTNAME=ubuntu-zfs-vm
export USERNAME=toor
Теперь можно разметить диски:
sgdisk --zap-all $DISK1
sgdisk --zap-all $DISK2
sgdisk -n1:1M:+256M -t1:EF00 -c1:EFI $DISK1
sgdisk -n1:1M:+256M -t1:EF00 -c1:EFI $DISK2
sgdisk -n2:0:+1024M -t2:be00 -c2:Boot $DISK1
sgdisk -n2:0:+1024M -t2:be00 -c2:Boot $DISK2
sgdisk -n3:0:0 -t3:bf00 -c3:Ubuntu $DISK1
sgdisk -n3:0:0 -t3:bf00 -c3:Ubuntu $DISK2
Для загрузки я буду использовать UEFI, так что надо создать диск с типом EF00, отформатированный под vfat:
mkfs.msdos -F 32 -n EFI ${DISK1}-part1
mkfs.msdos -F 32 -n EFI ${DISK2}-part1
Настала пора создавать zfs. Я буду использовать grub, который поддерживает загрузку с zfs, хотя и не со всеми опциями. Так что создание загрузочного раздела требует явным образом указать только то, что понимает grub:
zpool create -f -o cachefile=/etc/zfs/zpool.cache -o ashift=12 \
-o autotrim=on -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}-part2 ${DISK2}-part2
Загрузочный раздел создан, можно создавать корневой раздел (поскольку мы используем шифрование нам надо будет ввести пароль):
zpool create -f -o ashift=12 -o autotrim=on -O encryption=aes-256-gcm \
-O keylocation=prompt -O keyformat=passphrase -O acltype=posixacl \
-O canmount=off -O compression=lz4 -O dnodesize=auto \
-O normalization=formD -O relatime=on -O xattr=sa -O mountpoint=/ \
-R /mnt rpool mirror ${DISK1}-part3 ${DISK2}-part3
Можно создавать датасеты. Я решил ограничиться минимумом:
zfs create -o canmount=off -o mountpoint=none rpool/ROOT
zfs create -o canmount=off -o mountpoint=none bpool/BOOT
UUID=$(dd if=/dev/urandom bs=1 count=100 2>/dev/null \
|tr -dc 'a-z0-9' | cut -c-6)
zfs create -o mountpoint=/ -o com.ubuntu.zsys:bootfs=yes \
-o com.ubuntu.zsys:last-used=$(date +%s) \
rpool/ROOT/ubuntu_$UUID
zfs create -o mountpoint=/boot bpool/BOOT/ubuntu_$UUID
zfs create -o canmount=off -o mountpoint=/ rpool/USERDATA
zfs create -o com.ubuntu.zsys:bootfs-datasets=rpool/ROOT/ubuntu_$UUID \
-o canmount=on -o mountpoint=/home/$USERNAME \
rpool/USERDATA/${USERNAME}_$UUID
Для установки системы давайте использовать debootstrap:
apt-get install -y debootstrap
debootstrap focal /mnt
Копируем на новую файловую систему недостающие компоненты:
echo $HOSTNAME >/mnt/etc/hostname
sed '/cdrom/d' /etc/apt/sources.list > /mnt/etc/apt/sources.list
sed "s/ubuntu/$HOSTNAME/" /etc/hosts > /mnt/etc/hosts
cp /etc/netplan/*.yaml /mnt/etc/netplan/
И монтируем псевдофс, нужные для продолжения установки:
mount --make-private --rbind /dev /mnt/dev
mount --make-private --rbind /proc /mnt/proc
mount --make-private --rbind /sys /mnt/sys
Заходим в chroot среду:
chroot /mnt /usr/bin/env DISK1=$DISK1 DISK2=$DISK2 USERNAME=$USERNAME \
/bin/bash –login
Обновляем индексы бинарных пакетов, устанавливаем локаль:
apt-get update
locale-gen --purge "en_US.UTF-8"
update-locale LANG=en_US.UTF-8 LANGUAGE=en_US
dpkg-reconfigure --frontend noninteractive locales
Устанавливаем нужный нам часовой пояс:
dpkg-reconfigure tzdata
Монтируем EFI партицию. Обычно ее монтируют под /boot/efi, но в нашем случае партиций 2, плюс есть проблема очередности монтирования дисков. Я решил монтировать диск в другой иерархии и использовать симлинку:
mkdir /run/efi1
mount $DISK1-part1 /run/efi1
ln -s /run/efi1 /boot/efi
echo /dev/disk/by-uuid/$(blkid -s UUID -o value \
${DISK1}-part1) /run/efi1 vfat defaults 0 0 >> /etc/fstab
echo /dev/disk/by-uuid/$(blkid -s UUID -o value \
${DISK2}-part1) /run/efi2 vfat defaults 0 0 >> /etc/fstab
Устанавливаем прочие необходимые пакеты:
apt-get install -y grub-efi-amd64 grub-efi-amd64-signed linux-image-generic \
shim-signed zfs-initramfs zsys ubuntu-minimal network-manager
Из-за регрессии надо добавить параметр ядра init_on_alloc=0:
sed -ie 's/\(GRUB_CMDLINE_LINUX_DEFAULT="[^"]*\)/\1 init_on_alloc=0/' \
/etc/default/grub
Я предпочитаю иметь небольшой своп:
zfs create -V 4G -b $(getconf PAGESIZE) -o compression=off \
-o logbias=throughput -o sync=always -o primarycache=metadata \
-o secondarycache=none rpool/swap
mkswap -f /dev/zvol/rpool/swap
echo "/dev/zvol/rpool/swap none swap defaults 0 0" >> /etc/fstab
echo RESUME=none > /etc/initramfs-tools/conf.d/resume
Добавляем пользователя:
adduser $USERNAME
find /etc/skel/ -type f|xargs cp -t /home/$USERNAME
chown -R $USERNAME:$USERNAME /home/$USERNAME
usermod -a -G adm,cdrom,dip,plugdev,sudo $USERNAME
Обновляем inird и grub и выходим из chroot среды:
update-initramfs -c -k all
update-grub
grub-install --target=x86_64-efi --efi-directory=/boot/efi \
--bootloader-id=ubuntu --recheck --no-floppy
exit
Размонтируем то, что было смонтировано в chroot среде и перезагружаемся:
mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {}
zpool export -a
reboot
Поскольку все происходит в virtualbox отмечу, что с включенным UEFI виртуалка отказывалась грузиться с оптического привода. Так что в этом месте я убираю диск из виртуального привода и включаю UEFI загрузку.
Если не случилось ничего непредвиденного вы увидите меню grub. Но не спешите жать enter! Вместо этого загрузитесь в recovery mode, потому что при импорте корневого пула zfs произойдет ошибка, вызванная тем, что последний раз пул использовался на машине с другим именем. Исправляется это просто:
zpool import -f rpool
exit
После этого у вас спросят пароль для доступа к диску и загрузка продолжится вплоть до момента, когда вам будет предложено воспользоваться аварийной консолью (потому что мы загрузились в recovery mode) или нажать ctrl-d для загрузки в обычном режиме. Нажимайте ctrl-d. Через несколько секунд вы сможете войти в систему используя созданного ранее пользователя. На этом, впрочем, наши злоключения не заканчиваются. Посмотрите на директорию /boot и вы увидите, что она пустая. Загрузочный пул тоже не был импортирован. Исправим это:
zpool import bpool
И последний штрих — отметим обе партиции EFI, как требующие обновления при изменениях grub:
dpkg-reconfigure grub-efi-amd64
Вот теперь установка системы полностью завершена и вы можете перезагрузиться и воспользоваться пунктом меню grub по умолчанию. Из-за того, что по умолчанию в параметрах ядра присутствует quiet вы увидите черный экран. Пароль на доступ к диску придется вводить вслепую через несколько секунд после начала загрузки. Вы можете убрать quiet из параметров или поставить пакет plymouth.
Все команды выше можно скачать единым скриптом, которому для работы необходимо определить переменные DISK1, DISK2, HOSTNAME и USERNAME.