[^^^]


Установка FreeBSD

Написано: 2020-01-11.

Эта статья является примером установки FreeBSD без использования sysinstall, позволяя делать более тонкую настройку, а также понимая насколько просто и интуитивно просто работать с FreeBSD.

Загрузка с LiveCD
Разметка диска, установка загрузчика
Без разбиения диска, голый ZFS, CSM/BIOS режим

Использовать голый ZFS легко. Создаём pool на диске и копируем zfsboot загрузчик в зарезервированные пустые области ZFS ФС:

zpool create zroot ada0
dd if=/boot/zfsboot of=/dev/ada0 count=1
dd if=/boot/zfsboot of=/dev/ada0 iseek=1 oseek=1024

Выглядит красиво и минималистично, работает, но я бы не стал рекомендовать такой подход, как минимум, потому что swap придётся размещать внутри zvol-а, что существенный overhead. Если же swap захочется делать, то желательно использовать такие параметры для zvol-а:

volblocksize=4k       # размер страницы памяти amd64
sync=always           # сразу помещать данные на диск, не держа в памяти
logbias=throughput    # сразу писать на диск checkpoint-ом, без ZIL
primarycache=metadata # держать в кэше только метаданные
GPT разбиение диска, CSM/BIOS режим

Создаём GPT схему, разделы для загрузчика, swap, корня. Всё с метками (label). Выравниванием по 4 KiB границе, так как современные диски имеют 4K физические сектора и не выровненный доступ катастрофично может сказаться на производительности.

gpart create -s GPT diskid/DISK-SERIAL
gpart add -t freebsd-boot -a 4K -s 512K -l SERIAL-BOOT diskid/DISK-SERIAL
gpart add -t freebsd-swap -s 2G -l SERIAL-SWAP diskid/DISK-SERIAL
gpart add -t freebsd-zfs -l SERIAL-ROOT diskid/DISK-SERIAL

Устанавливаем MBR загрузчик и загрузчик с ZFS раздела.

gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 diskid/DISK-SERIAL
GPT разбиение, UEFI режим

UEFI режим загрузки может понадобиться например в таком случае: modesetting драйвер Xorg работает только если ОС загружена в UEFI режиме. В противном случае, доступен только VESA режим, ощутимо более медленный, без XrandR и ускорения.

В FreeBSD есть заранее подготовленный FAT32 образ с EFI загрузчиком для установки прямо в раздел диска. В целом всё аналогично предыдущему пункту, разница только в установке загрузчика:

gpart create -s GPT diskid/DISK-SERIAL
gpart add -t efi -s 800K diskid/DISK-SERIAL
gpart add -t freebsd-swap -a 4K -s 2G -l SERIAL-SWAP diskid/DISK-SERIAL
gpart add -t freebsd-zfs -l SERIAL-ROOT diskid/DISK-SERIAL
gpart bootcode -p /boot/boot1.efifat -i 1 diskid/DISK-SERIAL

Также у меня есть ещё заметка о том как я создавал EFI раздел загрузки:

gpart add -t efi -s XXX diskid/DISK-SERIAL
newfs_msdos -F 32 -c 1 diskid/DISK-SERIALp1
mount -t msdosfs /dev/diskid/DISK-SERIALp1 /mnt
mkdir -p /mnt/EFI/BOOT
cp /boot/loader.efi /mnt/EFI/BOOT/BOOTx64.efi
umount /mnt

Если хочется использовать UFS2 вместо ZFS, то тип разделов freebsd-zfs стоит заменить на freebsd-ufs, а загрузчик на gptboot.

Создание файловых систем
sysctl vfs.zfs.min_auto_ashift=12
zpool create zroot gpt/SERIAL-ROOT
zfs set checksum=sha256 compression=lz4 atime=off mountpoint=/mnt zroot

Если нужно создать сразу зеркало, то можно сразу указать: zpool create zroot mirror gpt/SERIAL1-ROOT gpt/SERIAL2-ROOT.

Всюду и везде я рекомендую использовать диски только с метками, без нестабильной нумерации вида adaX или отталкивающиеся от географии. Проблем с diskid/XXX (где используется серийный номер диска) я с ходу не вижу, кроме как его смены, если я сделаю dd копию на другой диск. Метки записанные прямо на диск будут точно неизменными.

Для меток можно использовать glabel, который в последний сектор диска запишет саму метку и диск будет доступен по label/XXX пути. Но это BSD-специфичное решение. GPT раздел с меткой будет точно работать и виден везде. Кроме того, раздел можно выровнять по 4K секторам. Кроме того, некоторые люди задают раздел немного меньшего размера (например на гигабайт), чтобы при добавлении диска стороннего производителя, небольшое несовпадение размеров нивелировалось. Поэтому GPT очень удобен всем этим. Solaris документация рекомендует отдавать диск полностью под управление ZFS, но, насколько слышал, это связано исключительно с тем, что только так ZFS сможет отключить write кэш диска. В FreeBSD такой проблемы нет. То есть, чтобы делать ZFS pool на дисках (не для установочной системы, а для хранилищ), стоит создавать GPT и использовать метки:

gpart create -s GPT diskid/SERIAL1
gpart create -s GPT diskid/SERIAL2
gpart add -t freebsd-zfs -a 4K -l SERIAL1-STORAGE diskid/SERIAL1
gpart add -t freebsd-zfs -a 4K -l SERIAL2-STORAGE diskid/SERIAL2
vfs.zfs.min_auto_ashift=12
zpool create storage gpt/SERIAL1-STORAGE gpt/SERIAL2-STORAGE

Для zroot pool-а я сразу же указал использование сжатия LZ4. Не использовать сжатие (быстрое и с fallback-ом при несжимаемых данных – LZ4) почти никогда не имеет смысла. А для корневого раздела оно точно даст выигрыш, так как данные там хорошо сжимаемы.

atime тоже отключён, так как я с ходу и не вспомню где для корня системы он бы пригодился. Если какая-то программа опирается на него, то стоит для её рабочих директорий сделать отдельный dataset и в нём atime включить, не применяя для всего zroot.

Checksum-ы я всегда использую только криптографические. Для всех pool-ов и dataset-ов кроме корневого я выбираю skein или, если бы его не было, sha512 – первый существенно быстрее остальных, а второй быстрее работает на 64-бит системах чем sha256. Но FreeBSD загрузчик поддерживает только sha256. Для dataset-ов внутри zroot можно указать более быстрые checksum-ы. Использование криптографически сильных, а не fletcher4, контрольных сумм это уверенность и возможность дедупликации (если её захочется).

Критично использование корректного ashift. Его значение является степенью двойки и указывает на размер сектора диска. Важно его задать во время создания pool, так как позже его уже не изменить. Проблема в том, что современные не SAS/enterprise диски врут по поводу размера своего сектора: на самом деле он уже давно 4K, но диски говорят, для совместимости с убогими Windows системами, что он 512 байт. ZFS честно смотрит на то, что говорят диски. Можно черезе sysctl указать минимальный ashift или обмануть ZFS можно создав NOP диск с изменённым размером сектора:

gnop create -S4K gpt/LABEL
zpool ... gpt/LABEL.nop

Если планируется использование зашифрованных GELI дисков, то тоже нельзя забывать про 4K размеры секторов уже самого GELI, так как это существенно сократит накладные расходы CPU на генерирование ключей для каждого сектора: geli init -s 4K ....

Если хочется использовать UFS2 (для некритичных слабых систем), то ФС я делаю просто: newfs -Ut [-E] /dev/gpt/SERIAL-ROOT. Включается soft-updates, существенно повышающий производительность и TRIM, актуальный для SSD дисков (в ZFS TRIM автоматически делается). -E форсирует полную TRIM очистку диска, что может быть полезно для SSD.

Установка ОС
for what in base kernel doc src ports ; do
    tar xfC /usr/freebsd-dist/$what.txz /mnt
done

Минимально нужны только base и kernel. Далее всё делается в пределах /mnt:

chroot /mnt
Настройка загрузчика
# cat > /boot/loader.conf <<EOF
zfs_load="YES"
vfs.root.mountfrom="zfs:zroot"

# У HyperThreading всё очень плохо в плане стабильности и безопасности
machdep.hyperthreading_allowed=0

# Meltdown/Spectre mitigation отключение
vm.pmap.pti=0
hw.ibrs_disable=1

aesni_load="YES" # чтобы GELI сразу ускорялся

# Можно забыть о том, что ipfw по умолчанию делает deny
#net.inet.ip.fw.default_to_accept=0
EOF
Настройка псевдо-ФС и swap
# cat > /etc/fstab <<EOF
tmpfs   /tmp    tmpfs   rw,nosuid,mode=1777     0       0
fdescfs /dev/fd fdescfs rw      0       0
proc    /proc   procfs  rw      0       0

/dev/gpt/SERIAL-SWAP.eli none swap sw 0 0
EOF

Важно указать .eli в конце пути к swap разделу – это заставит шифровать GELI этот раздел одноразовым ключом.

Настройка sysctl
# cat >> /etc/sysctl.conf <<EOF
kern.msgbuf_show_timestamp=1       # время в dmesg выводе
kern.cam.ada.write_cache=0         # с ZFS никакой буферизации не надо
security.bsd.stack_guard_page=1
security.bsd.see_other_uids=0
security.bsd.unprivileged_idprio=1 # idprio я часто использую
kern.randompid=1
#security.jail.allow_raw_sockets=1 # если хочется делать ping в jail
net.inet.tcp.tso=0                 # TSO на маршрутизаторах только вредит, отключаю везде
net.inet.tcp.drop_synfin=1
net.inet6.ip6.use_tempaddr=0       # не делать рандомизацию link-local адресов

# Отключить возможность сжатия трафика в IPsec, а также позволять
# фильтровать его firewall-ом
net.inet.ipcomp.ipcomp_enable=0
net.inet.ipsec.filtertunnel=1
net.inet6.ipsec6.filtertunnel=1

# Позволять пользователям открывать TAP интерфейсы и сразу их поднимать
#net.link.tap.user_open=1
#net.link.tap.up_on_open=1

#vfs.usermount=1 # может захотеться делать mount-ы из под пользователя

# Отключить prefetch, если мы на SSD
#vfs.zfs.prefetch_disable=1
#vfs.zfs.no_scrub_prefetch=1

vfs.nfsd.server_min_nfsvers=4 # форсированное использование только NFSv4
EOF
Базовая настройка самой ОС
# cat > /etc/rc.conf <<EOF
hostname="stargrave.org"
zfs_enable="YES"
clear_tmp_enable="YES" # на всякий пожарный, хотя у нас tmpfs
sshd_enable="YES"
ntpd_enable="YES"

# В приоритете использовать IPv6 адреса полученные от DNS
ip6addrctl_enable="YES"
ip6addrctl_policy="ipv6_prefer"

ipv6_ipv4mapping="YES" # удобно иметь IPv4 доступными демонов слушающих на [::]
nfsv4_server_enable="YES" # нужно для работы NFSv4

# Первым делом я ставлю Postfix вместо Sendmail
postfix_enable="YES"
sendmail_enable="NO"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"

firewall_enable="YES"
firewall_script="/etc/ipfw.rules"

# Чтобы после перезагрузки поднялась сеть
ifconfig_em0="up"
ifconfig_em0_ipv6="inet6 -ifdisabled"
EOF
Настройка временной зоны
tzsetup /usr/share/zoneinfo/Europe/Moscow
Настройка ipfw firewall
# cat > /etc/ipfw.rules <<EOF
#!/bin/sh -x

ipfw -f flush
ipfw -f table all destroy
ipfw zero
ipfw disable one_pass

add="ipfw add"

\$add deny all from any to any frag
\$add allow { icmp or icmp6 } from any to any keep-state
\$add allow esp from any to any keep-state
\$add allow udp from any to any isakmp keep-state
\$add allow all from any to any via lo0

\$add deny all from any to any not verrevpath in via em0

\$add check-state
\$add deny tcp from any to any established via em0

\$add allow tcp from any to me ssh keep-state
\$add allow all from me to any out keep-state

#\$add deny log all from any to any
EOF
# chmod 600 /etc/ipfw.rules
Отключение периодичных заданий в cron
sed -i.tmp "/periodic/ s/^/#/" /etc/crontab
Настройка NTP клиента
echo server gw.stargrave.org iburst >> /etc/ntp.conf
Настройка NFSv4 экспорта
echo V4: / > /etc/exports
Завершение работы с ФС и перезагрузка
zfs umount zroot
zfs set mountpoint=none zroot
zpool export zroot
reboot
Установка и настройка Postfix:

После перезагрузки можно установить Postfix и сделать его тривиальную настройку, использующую relay для всей корреспонденции:

# cat >> /usr/local/etc/postfix/main.cfg <<EOF
inet_interfaces = loopback-only
mynetworks_style = host
relayhost = [gw.stargrave.org]
EOF

[^^^]