На моей домашней машине вот уже 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.

Комментарии (10)


  1. 13werwolf13
    09.02.2022 07:04
    +2

    позволь предложить поправочку:

    efi boot partition может прекрастно жить на mdadm зеркале

    пишу это потому что несколько раз столкнулся с ситуацией когда на двух efi разделах данные разъехались, на одном был свежак а на втором ещё от предыдущего релиза системы до апгрейда что вызвало боль когда первый диск немножко посыпался


    1. kt97679 Автор
      09.02.2022 19:20

      К сожалению этот подход работает ровно до того момента, пока uefi не сохранит что-то в процессе загрузки в обход mdadm. Больше подробностей тут:

      HOWEVER there is one nasty risk with this setup: if UEFI writes anything to one of the drives (which this firmware did when it wrote out a “boot variable cache” file), it may lead to corrupted results once Linux mounts the RAID (since the member drives won’t have identical block-level copies of the FAT32 any more).


      1. 13werwolf13
        09.02.2022 19:31

        Насколько я понимаю это не происходит само по себе а должно быть явно вызвано. Но может я и не прав.


  1. edo1h
    09.02.2022 19:43

    Так что создание загрузочного раздела требует явным образом указать только то, что понимает grub

    по мне проще сделать этот раздел ext4
    или вообще обойтись без него (и без grub), копируя ядро и initramfs на vfat-раздел
    https://habr.com/ru/post/314412/


    Пароль на доступ к диску придется вводить вслепую через несколько секунд после начала загрузки. Вы можете убрать quiet из параметров или просто поставить десктопную среду

    я не понял, как эта десктопная среда будет грузиться до ввода пароля


    Я предпочитаю иметь небольшой своп:

    а какой смысл в свопе, существенно меньшем размера памяти?


  1. kt97679 Автор
    09.02.2022 20:30

    я не понял, как эта десктопная среда будет грузиться до ввода пароля

    В статье я использовал мета пакет ubuntu-minimal. Если вы поставите ubuntu-dektop-minimal, то получите при загрузке splash screen с логотипом ubuntu и вместе с ним поля ввода пароля доступа к диску.
    а какой смысл в свопе, существенно меньшем размера памяти?

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


    1. edo1h
      09.02.2022 23:31

      В статье я использовал мета пакет ubuntu-minimal. Если вы поставите ubuntu-dektop-minimal, то получите при загрузке splash screen с логотипом ubuntu и вместе с ним поля ввода пароля доступа к диску.

      понятия не прибавилось.
      если у вас корень файловой системы зашифрован, то пароль должен запрашиваться из initramfs до монтирования корня.
      хотите сказать, что ubuntu пихает в initramfs графические утилиты для ввода пароля?


      1. kt97679 Автор
        10.02.2022 02:05
        +1

        Пароль действительно запрашивается из initramfs. А за красивости вокруг этого процесса отвечает пакет plymouth. У меня в минимальной установке его не было, но вы правы в том, что поставить его можно и без десктопного окружения. Сейчас поправлю статью. Спасибо за замечание!


  1. Jsty
    09.02.2022 23:20

    В чем плюсы zfs для домашнего использования?


    1. Gaernebjorn
      10.02.2022 04:12
      +1

      Защитой от вымогателей-шифровальщиков с помощью "бесплатных" снапшотов, например


      1. Jsty
        10.02.2022 23:03

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

        Можно накидывать причин так не делать или запускать каждое приложение в отдельной виртуалке. А виртуалку каждый раз из снэпшота поднимать.