Instalacja Debian Bullseye na ZFS

Założenia

Opis będzie dotyczył instalacja na całym dysku, na którym jedynym systemem będzie nasz Debian. Wszystkie dane które wcześniej były na dysku zostaną utracone. Używamy sektorów o rozmiarach 4KB, nawet jeśli nasz dysk ma sektory 512B warto użyć sektorów logicznych 4K, ponieważ po zmianie dysku na 4KB utracimy bardzo dużo na wydajności. Trzeba zaznaczyć, że poprawne działanie GRUBa z sektorami 4KB jest możliwe tylko na komputrach obsługujących UEFI. Wymóg posiadania minimum 2GB RAM (lepiej 4GB+) przy komputerach z UEFI raczej nie jest już problemem. Zakładamy, że nasz system będzie instalowany na dwóch dyskach na puli "lustrzanej".

Przygotowanie do środowiska instalacyjnego

  1. Na komputrze uruchamiamy Debiana Bullseye w wersji live, konfigurujemy dostęp do Internetu. W systemie live jest domyślnie użytkownik user z hasłem live.

  2. Dodajemy do repozytoriów standardowych repozytorium contrib

    sudo su -
    apt update
    apt install mc mdadm
    mcedit /etc/apt/sources.list
    deb http://deb.debian.org/debian bullseye main contrib
    apt update
  3. Jeżeli zależy nam na tym by wygodnie kontynuować proces z innego komputera możemy doinstalować serwer SSH:

    apt install --yes openssh-server
    systemctl restart ssh
  4. Wyłączamy automontowanie, które może przeszkadzać w dalszej pracy

    gsettings set org.gnome.desktop.media-handling automount false
  5. Instalujemy ZFS w środowisku instalacyjnym

    apt install --yes debootstrap gdisk zfsutils-linux

Formatowanie dysków

Należy pamiętać by dyski wskazywać zawsze po id ponieważ zmiana kolejności dysków bądź dopięcie nowego nie będzie wtedy miała wpływu na naszą pulę. Pozwoli to też na łatwą identyfikację dysków - zwykle na etykiecie jest numer dysku który jest użyty w identyfikatorze.

  1. Ustawiamy zmienne odwołujące się do naszych dysków:

    DISK_A=/dev/disk/by-id/ata-XXX-YYY1
    DISK_B=/dev/disk/by-id/ata-XXX-YYY2

    Możemy łatwo sprawdzić do jakiego urządzenia odnośi się alias by-id używając ls -la /dev/disk/by-id Jeżeli używamy QEMU/KVM to można wskazywać urządzenia przez z /dev/vdX ponieważ domyślnie nie mamy przypisanych identyfikatorów na podstawie numerów seryjnych. Numery seryjne można dodać w przypadku jeśli używamy sterownika dysków virtio. Numer seryjny podajemy jako parametr serial=123123 bądź w przypadku virt-manager w genialnie przetłumaczonym na polski polu "szeregowy" ;) VMware wymaga ustawienia disk.EnableUUID = "TRUE"

  2. Czyścimy dyski Upewniamy się, że na żadym z dysków nie jest aktywny swap

    swapoff -all

    Jeżeli na dyskach była wcześniej macierz MD usuwamy ją:

    # Sprawdzamy aktywne macierze:
    cat /proc/mdstat
    # Jeśli są to zatrzymujemy je (zastępujemy md0 właściwym identyfikatorem):
    mdadm --stop /dev/md0
    # Dla macierzy na całym dysku:
    mdadm --zero-superblock --force $DISK
    # dla macierzy na partycji
    mdadm --zero-superblock --force ${DISK}-part2

    Czyściemy tablicę partycji:

    sgdisk --zap-all $DISK_A
    sgdisk --zap-all $DISK_B

    Jeżeli pojawił się komunikat o tym, że kernel nadal używa starej tablicy partycji to możemy użyć part-probe a jeśli to nie pomoże to musimy przeładować system.

  3. Partycjonowanie Partycja EFI:

    sgdisk -n1:0:+512M -t1:EF00 $DISK_A
    sgdisk -n1:0:+512M -t1:EF00 $DISK_B

    Partycja /boot

    sgdisk -n2:0:+1G -t2:BF01 $DISK_A
    sgdisk -n2:0:+1G -t2:BF01 $DISK_B

    Partycja na pulę:

    sgdisk -n3:0:0 -t3:BF00 $DISK_A
    sgdisk -n3:0:0 -t3:BF00 $DISK_B

    Jeżeli dysków w macierzy ma być więcej to powyższy układ partycji należy skopiować na pozostałe dyski.

    sgdisk -R /dev/sdb /dev/sda
    sgdisk -G /dev/sdb

    :warning Koleność dysków w tym poleceniu jest nietypowa. Pierwsze polecenie skopiuje układ partycji z dysku sda na dysk sdb. Drugie polecenie nada nowy, indywidualny GUID na dysku sdb.

  4. Tworzymy pule na partycję /boot

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@livelist=enabled \
    -o feature@lz4_compress=enabled \
    -o feature@spacemap_histogram=enabled \
    -o feature@zpool_checkpoint=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 ${DISK_A}-part2 ${DISK_B}-part2 

Ze względu na to, że GRUB nie obsługuje wszystkich funkcji ZFS zaleca się nie zmieniać powyższych parametrów. GRUB montuje bpool tylko w trybie do odczytu, dopiero po załadowaniu całego systemu Debian może modyfikować dane w puli bpool.

  1. Tworzenie puli na główny system plików
    zpool create \
    -o ashift=12 \
    -O acltype=posixacl -O canmount=off -O compression=lz4 \
    -O dnodesize=auto -O normalization=formD -O atime=off \
    -O xattr=sa -O mountpoint=/ -R /mnt \
    rpool mirror ${DISK_A}-part3 ${DISK_B}-part3
    • Ustawiamy atime=off wyłączając oznaczanie dat dostępu do plików zyskując największą wydaność. Jeżeli chcemy zachować balans między wydajnością a działaniem czasów dostępu można zamiast tego dać parametr relatime=on.
    • Trzeba bezwzględnie pamiętać o -part3 przy wskazywaniu miejsca na pulę. Pominięcie wskazania partycji spowoduje, że ZFS na pulę przydzieli cały dysk i utracimy przy tym partycję /boot i EFI.

Instalacja systemu

  1. Tworzymy datasety które będą kontenerami na pozostałe datasety
    zfs create -o canmount=off -o mountpoint=none rpool/ROOT
    zfs create -o canmount=off -o mountpoint=none bpool/BOOT

Robiąc snapshot/kopię kontenera uzyskujemy prostą możliwość wersjnowania całego systemu.

  1. Tworzymy datasety dla katalogu głównego i /boot

    zfs create -o canmount=noauto -o mountpoint=/ rpool/ROOT/debian
    zfs mount rpool/ROOT/debian
    zfs create -o mountpoint=/boot bpool/BOOT/debian

    Systemów plików ZFS nie trzeba normalnie montować używając mount lub zfs mount. Tutaj mamy wyjątek ponieważ użyliśmy opcji canmount=noauto.

  2. Tworzymy pozostałe datasety

    zfs create                                 rpool/home
    zfs create -o mountpoint=/root             rpool/home/root
    chmod 700 /mnt/root
    zfs create -o canmount=off                 rpool/var
    zfs create -o canmount=off                 rpool/var/lib
    zfs create                                 rpool/var/log
    zfs create                                 rpool/var/spool

    Opcjonalne datasety

    zfs create -o com.sun:auto-snapshot=false  rpool/var/cache
    zfs create -o com.sun:auto-snapshot=false  rpool/var/tmp
    chmod 1777 /mnt/var/tmp
    zfs create                                 rpool/opt
    zfs create -o canmount=off                 rpool/usr
    zfs create                                 rpool/usr/local
    zfs create                                 rpool/var/games
    zfs create                                 rpool/var/mail
    zfs create                                 rpool/var/snap
    zfs create                                 rpool/var/lib/AccountsService
    #Docker używa swojego własnego systemu wersjonowania i snapshotów
    zfs create -o com.sun:auto-snapshot=false  rpool/var/lib/docker
    zfs create -o com.sun:auto-snapshot=false  rpool/var/lib/nfs

    Celem rozbudowanego zestawu datasetów jest rozdzielenie danych użytkownika od systemu. Dzięki temu można dowolnie manipulować danymi użytkowników bez wpływu na system bądź systemem z zachowaniem danych użytkowników w stanie nietkniętym.

Katalog /tmp umieścimy w pamięci RAM (tmpfs) i zrobimy to później

  1. Instalujemy system w wersji minimalnej:

    debootstrap bullseye /mnt

    System który w ten sposób będzie utworzyny będzie nieskonfigurowany, później skonfigurujemy go ręcznie.

  2. zpool.cache

    mkdir /mnt/etc/zfs
    cp /etc/zfs/zpool.cache /mnt/etc/zfs/

Konfiguracja systemu

  1. Nazwa hosta

    hostname HOSTNAME
    hostname > /mnt/etc/hostname
    mcedit /mnt/etc/hosts
    #Dodajemy linię:
    127.0.1.1       HOSTNAME
  2. Konfiguracja interfejsów sieciowych Odnajdujemy nazwy interfejsów

    ip addr show | grep mtu

    Tworzymy pliki dla potrzebnych interfejsów zastępując NAZWA właściwą nazwą interfejsu

    mcedit /mnt/etc/network/interfaces.d/NAZWA
    auto NAME
    iface NAME inet dhcp

    Jeżeli interfejs nie jest konfigurowany po DHCP to należy odpowiednio zmodyfikować zawartość pliku.

  3. Konfiguracja repozytoriów pakietów

    mcedit /mnt/etc/apt/sources.list
    deb http://deb.debian.org/debian bullseye main contrib
    deb-src http://deb.debian.org/debian bullseye main contrib
    deb http://deb.debian.org/debian-security bullseye-security main contrib
    deb-src http://deb.debian.org/debian-security bullseye-security main contrib
    deb http://deb.debian.org/debian bullseye-updates main contrib
    deb-src http://deb.debian.org/debian bullseye-updates main contrib
  4. Przepięcie wirtualnych systemów plików z systemu live do nowego systemu i wykonanie chroot:

    mount --rbind /dev  /mnt/dev
    mount --rbind /proc /mnt/proc
    mount --rbind /sys  /mnt/sys
    chroot /mnt bash --login
  5. Konfiguracja podstawowego systemu

    DISK_A=/dev/disk/by-id/ata-XXX-YYY1
    DISK_B=/dev/disk/by-id/ata-XXX-YYY2
    ln -s /proc/self/mounts /etc/mtab
    apt update
    apt install --yes console-setup locales mc

    Nawet jeśli nasz system będzie używał języka inny niż angielski zawsze dodajemy en_US.UTF-8

    dpkg-reconfigure locales tzdata keyboard-configuration console-setup
  6. Instalacja ZFS w nowym systemie

    apt install --yes dpkg-dev linux-headers-amd64 linux-image-amd64
    apt install --yes zfs-initramfs
    echo REMAKE_INITRD=yes > /etc/dkms/zfs.conf
  7. Instalacja GRUB na systemie z UEFI

    apt install dosfstools
    mkdosfs -F 32 -s 1 -n EFI ${DISK_A}-part1
    mkdir /boot/efi
    echo /dev/disk/by-uuid/$(blkid -s UUID -o value ${DISK_A}-part1) /boot/efi vfat defaults 0 0 >> /etc/fstab
    mount /boot/efi
    apt install --yes grub-efi-amd64 shim-signed

    GRUB jest instalowany tylko na jednym dysku - później zostanie zainstalowany na drugim by mirror działał prawidłowo.

  8. Usunięcie os-prober

    apt purge --yes os-prober

    Pakiet jest potrzebny tylko przy instalacji wielu systemów. W innym przypadku generuje tylko niepotrzebne komunikaty.

  9. Ustawiamy hasło roota

    passwd
  10. Import puli bpool Tworzymy moduł systemd który będzie zawsze importował pulę bpool przy stacie systemu.

    mcedit /etc/systemd/system/zfs-import-bpool.service
    [Unit]
    DefaultDependencies=no
    Before=zfs-import-scan.service
    Before=zfs-import-cache.service
    [Service]
    Type=oneshot
    RemainAfterExit=yes
    ExecStart=/sbin/zpool import -N -o cachefile=none bpool
    # Work-around to preserve zpool cache:
    ExecStartPre=-/bin/mv /etc/zfs/zpool.cache /etc/zfs/preboot_zpool.cache
    ExecStartPost=-/bin/mv /etc/zfs/preboot_zpool.cache /etc/zfs/zpool.cache
    [Install]
    WantedBy=zfs-import.target
    systemctl enable zfs-import-bpool.service
  11. Montowanie /tmp w pamięci RAM (tmpfs) Proces sprowadza się do dodaniu odpowiedniego modułu dla systemd

    cp /usr/share/systemd/tmp.mount /etc/systemd/system/
    systemctl enable tmp.mount
  12. Instalacja serwera SSH

    apt install --yes openssh-server
    mcedit /etc/ssh/sshd_config
    # Ustawiamy: PermitRootLogin yes
    # to tylko tymczasowe póki w systemie nie ma innych użytkowników
  13. Instalacja popcon Nie jest to krok obowiązkowy ale pomaga twórcom Debiana w dobieraniu optymalnych składników systemu

    apt install --yes popularity-contest

Konfiguracja GRUB

  1. Sprawdzamy czy GRUB rozpoznaje pulę /boot

    grub-probe /boot
  2. Odświeżamy plik initrd

    update-initramfs -c -k all
  3. Obejście pozwalające uniknąć problemów z niewspieraniem przez GRUB niektórych funkcji ZFS

    mcedit /etc/default/grub
    # Ustawiamy: GRUB_CMDLINE_LINUX="root=ZFS=rpool/ROOT/debian"
  4. Rozszerzenie debugowania GRUB

    mcedit /etc/default/grub
    # Usuwamy quiet z: GRUB_CMDLINE_LINUX_DEFAULT
    # Odkomentowujemy: GRUB_TERMINAL=console
  5. Aktualizacja GRUBa

    update-grub
  6. Instalacja bootloadera

    grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck --no-floppy
  7. Ustalenie prawidłowej kolejności montowania systemów plików Uruchomienie zfs-mount-generator przekaże systemd informacje o tym, że system ma wiele punktów montowania dla różnych katalogów co jest szczególnie ważne w przypadku /var/log i /var/tmp.

    mkdir /etc/zfs/zfs-list.cache
    touch /etc/zfs/zfs-list.cache/bpool
    touch /etc/zfs/zfs-list.cache/rpool
    zed -F &

    Sprawdzamy czy zed zaktualizował cache patrząc na zawartość plików:

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

    Jeżeli są puste to wymuszamy ponowną i aktualizacje i sprawdzamy

    zfs set canmount=off bpool/BOOT/debian
    zfs set canmount=on  bpool/BOOT/debian
    zfs set canmount=off bpool/ROOT/debian
    zfs set canmount=noauto  bpool/ROOT/debian

    Jeżeli pliki nadal są puste to zatrzymujemy zed (jak poniżej) i uruchamiamy procedurę jeszcze raz.

    fg
    #Naciskamy Ctrl+C

Usuwamy ze ścieżek /mnt

sed -Ei "s|/mnt/?|/|" /etc/zfs/zfs-list.cache/*

Pierwsze uruchomienie nowego systemu

  1. Możemy utworzyć snapshoty świeżego systemu

    zfs snapshot bpool/BOOT/debian@install
    zfs snapshot rpool/ROOT/debian@install

    Warto robić snapshota przed każdą poważniejszą aktualizacją bądź zmianą. Stare snapshoty (w tym ten poinstalacyjny) można w przyszłości usunąć odzyskując nieco miejsca.

  2. Wychodzimy z chroot wracając do środowiska live

    exit
  3. Odmontowujemy wszystkie systemy plików

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

    reboot
  5. Tworzenie nowych użytkowników Logujemy się jako root z ustalonym wcześniej hasłem i wydajemy polecenia zastępująć NAZWA_UŻYTKOWNIKA odpowiednim loginem

    username=NAZWA_UŻYTKOWNIKA
    zfs create rpool/home/$username
    adduser $username
    cp -a /etc/skel/. /home/$username
    chown -R $username:$username /home/$username
    usermod -a -G audio,cdrom,dip,floppy,netdev,plugdev,sudo,video $username
  6. Tworzymy mirror GRUBa

    umount /boot/efi
    dd if=/dev/disk/by-id/ata-SATA_diskA-part1 of=/dev/disk/by-id/ata-SATA_diskB-part1
    efibootmgr -c -g -d /dev/disk/by-id/ata-SATA_diskB -p 2 -L "debian-2" -l '\EFI\debian\grubx64.efi'
    mount /boot/efi

Instalacja pozostałego oprogramowania

  1. Aktualizacja systemu

    apt update
    apt dist-upgrade --yes
  2. Instalacja zestawów oprogramowania

    tasksel

    Wybieramy potrzebne zestawy oprogramowania.

  3. Instalacja pozostałego oprogramowania

    apt install mc iptraf iperf nmon htop iotop smartmontools lm-sensors wget
  4. Wyłączamy kompresję logów

    for file in /etc/logrotate.d/* ; do
    if grep -Eq "(^|[^#y])compress" "$file" ; then
        sed -i -r "s/(^|[^#y])(compress)/\1#\2/" "$file"
    fi
    done

    Dane w datasetach są kompresowane przez ZFS - podówjna kompresja nic nie daje poza stratą zasobów. Dodatkowo przy braku kompresji logów na poziomie plików mamy cały czas nieskrępowany dostęp do wszystkich logów.

  5. Reboot

    reboot

Finalne porządki

  1. Logujemy się do systemu, sprawdzamy czy nowi użytkownicy działają i czy nie ma problemów z funkcjonowaniem sieci.

  2. Opcjonalnie usuwamy snapshoty poinstalacyjne

    sudo zfs destroy bpool/BOOT/debian@install
    sudo zfs destroy rpool/ROOT/debian@install
  3. Opcjonalnie wyłączamy konto root

    sudo usermod -p '*' root
  4. Wyłączamy dostęp dla roota po SSH

    sudo vi /etc/ssh/sshd_config
    # Usuwamy: PermitRootLogin yes
    sudo systemctl restart ssh

Poprzedni wpis Następny wpis