В предыдущей статье мы подготовили базовую систему.
Здесь мы создадим новую систему Arch Linux, способную загружаться по сети и автоматически запускать браузер Firefox, а между делом разберёмся с необходимой функциональностью загрузочного сервера. Потом настроим сам сервер и попробуем с него загрузиться. Всё произойдёт в точности, как на картинке, которую нашёл гугл по запросу «PXE»:
Снова устанавливаем Linux
Archlinux выгодно отличается от готовых дистрибутивов тем, что для его установки куда-то ещё не нужно иметь установочный диск. Вполне достаточны небольшие установочные скрипты:
pacman -S arch-install-scripts
Вам даже не придётся разбираться как с ними работать, потому что вы уже пользовались ими в предыдущий раз.
Совершенно предсказуемое начало:
export root=/srv/nfs/diskless
mkdir -p $root
Установим только базовые пакеты, поэтому:
pacstrap -d -c -i $root base
Далее повторите все действия вплоть до установки загрузчика согласно предыдущей статье. Вот чек-лист:
- проведите русификацию (интернационализацию);
- укажите часовой пояс и настройте автозапуск службы NTP;
- добавьте пользователя username и заблокируйте его пароль от изменения.
Сравним загрузку с диска и загрузку по сети
В предыдущей статье мы рассматривали процесс загрузки Linux с точки зрения внутреннего накопителя. Сейчас мы представим происходящее глазами сетевой карты. Картинка из заголовка хорошо иллюстрирует события за исключением того, что все серверы в нашем случае будут работать на одном компьютере.
Сразу после включения компьютера, срабатывает код PXE (Preboot eXecution Environment, произносится пикси — спасибо вики), разместившийся непосредственно в ПЗУ сетевой карты. Его задача — найти загрузчик и передать ему управление.
Сетевой адаптер совершенно не представляет в какой сети сейчас находится, поэтому назначает себе адрес 0.0.0.0 и отправляет сообщение DHCPDISCOVER. К сообщению прикреплются паспортные данные, которые обязательно нам пригодятся:
- ARCH Option 93 — архитектура PXE клиента (UEFI, BIOS);
- Vendor-Class Option 60 — идентификатор, который у всех PXE клиентов имеет вид «PXEClient:Arch:xxxxx:UNDI:yyyzzz», где цифры xxxxx – архитектура клиента, yyyzzz – мажорная и минорная версии драйвера UNDI (Universal Network Driver Interface).
Адаптер ожидает получить ответ от DHCP сервера по протоколу BOOTP (Bootstrap Protocol), где помимо нужного IP адреса, маски подсети и адреса шлюза, присутствует информация об адресе TFTP-сервера и названии файла загрузчика, который с него нужно забрать. Сервер TFTP, в свою очередь, просто отдаёт любому желающему любые файлы, которые у него попросят.
После получения ответа и применения сетевых настроек, дальнейшее управление загрузкой передаётся полученному файлу, размер которого не может превышать 32 кБ, поэтому используется двухстадийная загрузка. Всё необходимое для отображения на экране загрузочного меню докачивается следом по тому же протоколу TFTP. Подавляющее большинство руководств по сетевой загрузке использует загрузчик pxelinux, но GRUB умеет то же самое, и даже больше: в нём есть разные загрузчики для разных архитектур, включая UEFI.
Далее загрузка приостанавливается на время отображения загрузочного меню, а потом по тому же протоколу TFTP докачиваются выбранные файлы vmlinuz и initramfs, которым передается дальнейшее управление загрузкой. На этом этапе уже нет вообще никакой разницы в механизме загрузки по сети или с внутреннего накопителя.
Настраиваем загрузку по сети с помощью GRUB
Поскольку GRUB на нашем сервере уже есть, создадим с его помощью структуру папок для сетевого клиента вот таким образом:
grub-mknetdir --net-directory=$root/boot --subdir=grub
В папке $root/boot появится папка grub и несколько других. Эту файловую структуру мы будем целиком «отдавать» с помощью TFTP-сервера. Сейчас мы используем 64-битный ArchLinux по той причине, что в 32-битной системе нет папки /grub/x86_64-efi/, которая требуется для загрузки систем UEFI. Можно взять эту папку с нашего 64-битного сервера и в неизменном виде перенести на 32-битный сервер, тогда в нём также появится поддержка UEFI.
Создайте файл конфигурации загрузчика со следующим содержимым:
function load_video {
if [ x$feature_all_video_module = xy ]; then
insmod all_video
else
insmod efi_gop
insmod efi_uga
insmod ieee1275_fb
insmod vbe
insmod vga
insmod video_bochs
insmod video_cirrus
fi
}
if [ x$feature_default_font_path = xy ] ; then
font=unicode
else
font="fonts/unicode.pf2"
fi
if loadfont $font ; then
set gfxmode=auto
load_video
insmod gfxterm
set locale_dir=locale
set lang=ru_RU
insmod gettext
fi
terminal_input console
terminal_output gfxterm
set timeout=5
set default=0
menuentry "Автозапуск Firefox" {
load_video
set gfxpayload=keep
insmod gzio
echo "Загружается ядро..."
linux /vmlinuz-linux
add_efi_memmap
ip="$net_default_ip":"$net_default_server":192.168.1.1:255.255.255.0::eth0:none
nfsroot=${net_default_server}:/diskless
echo "Загружается инициирующая файловая система..."
initrd /initramfs-linux.img
}
Я взял файл grub.cfg с сервера и убрал из него всё то, что не участвует в отображении загрузочного меню GRUB или как-то связано с дисками.
Обратите внимание на знакомую нам строку с параметрами ядра:
linux /vmlinuz-linux add_efi_memmap ip="$net_default_ip":"$net_default_server":192.168.1.1:255.255.255.0::eth0:none nfsroot=${net_default_server}:/diskless
Как и в предыдущий раз присваиваем значение переменной «ip». Напоминаю, что она используется в обработчике «net», который мы приспособили для настройки сетевой карты в загрузочном сервере. Здесь снова указывается статический IP адрес и постоянное имя сетевой карты eth0. Значения $net_default_ip и $net_default_server подставляются GRUB самостоятельно на основании данных, полученных из самого первого DHCP запроса. $net_default_ip – это выделенный для нашей машины IP адрес, а $net_default_server — IP адрес загрузочного сервера.
Большинство руководств по сетевой загрузке (среди обнаруженных на просторах рунета), предлагают устанавливать переменную так «ip=::::::eth0:dhcp», что вынуждает обработчик net отправлять новый запрос DHCPDISCOVER для повторного получения сетевых настроек.
Нет объективной причины лишний раз «спамить» DHCP-сервер и ждать, пока он откликнется, поэтому снова используем статику и не забываем указать DNS-серверы. Такую задачу мы уже решали, поэтому просто копируем нужные файлы и добавляем службу в автозагрузку:
cp {,$root}/etc/systemd/system/update_dns@.service && cp {,$root}/etc/default/dns@eth0 && arch-chroot $root systemctl enable update_dns@eth0
Возвращаемся к строке с параметрами ядра. Ещё незнакомая нам команда add_efi_memmap (EFI memory map) добавляет EFI memory map доступной RAM. В прошлый раз мы её намеренно пропустили, из-за сравнительно сложной предварительной разметки носителя для поддержки UEFI. Сейчас нам ничего размечать не нужно, потому что файловая система на загрузочном сервере уже существует и будет использоваться в неизменном виде.
Переменная ядра — nfsroot показывает, где именно в сети нужно искать корневую файловую систему. Она выполняет ту же самую функцию, что и переменная root в загрузочном сервере. В данном случае указан адрес NFS-сервера, который в нашем случае совпадает с TFTP-сервером, но это совершенно необязательно.
Подготавливаем initramfs
За подключение корневой файловой системы по протоколу NFS отвечает обработчик net. В прошлый раз мы убирали из него эту функциональность, но сейчас она нам понадобится, правда, в немного доработанном виде. Дело в том, что обработчик net из коробки поддерживает подключение только по протоколу NFS версии 3. К счастью, поддержка 4-й версии добавляется очень просто.
Сначала установим пакет, в который входит нужным нам обработчиком net, а также пакет, добавляющий утилиты NFS (модуль nfsv4 и программа mount.nfs4):
pacman --root $root --dbpath $root/var/lib/pacman -S mkinitcpio-nfs-utils nfs-utils
Исправим обработчик net из папки hooks (вместо команды для монтирования nfsmount, теперь будем использовать mount.nfs4):
sed s/nfsmount/mount.nfs4/ "$root/usr/lib/initcpio/hooks/net" > "$root/etc/initcpio/hooks/net_nfs4"
С помощью установщика обработчика из папки install добавим модуль nfsv4 и программу mount.nfsv4 в iniramfs. Сначала копируем и переименовываем заготовку:
cp $root/usr/lib/initcpio/install/net $root/etc/initcpio/install/net_nfs4
Теперь исправляем только одну функцию build() (кроме неё ничего не меняем!):
nano $root/etc/initcpio/install/net_nfs4
build() {
add_checked_modules '/drivers/net/'
add_module nfsv4?
add_binary "/usr/lib/initcpio/ipconfig" "/bin/ipconfig"
add_binary "/usr/bin/mount.nfs4" "/bin/mount.nfs4"
add_runscript
}
Добавляем обработчик в initramfs путём исправления строки в файле mkinitcpio.conf:
nano $root/etc/mkinitcpio.conf
HOOKS="base udev net_nfs4"
Если ничего не трогать, то обычно для сжатия файла initramfs используется быстрый архиватор gzip. Мы не настолько торопимся, насколько хотим сжать посильнее, поэтому воспользуемся xz. Снимаем комментарий с этой строки в файле mkinitcpio.conf:
COMPRESSION="xz"
Архивация xz происходит значительно дольше, но файл initramfs при этом сжимается как минимум в пару раз сильнее, из-за чего значительно быстрее передается TFTP сервером по сети. Копируем пресет с нашего сервера, чтобы в ходе работы генерировался только один файл initramfs, после чего запускаем mkinitcpio:
cp /etc/mkinitcpio.d/habr.preset $root/etc/mkinitcpio.d/habr.preset && arch-chroot $root mkinitcpio -p habr
Напоследок отредактируем fstab. Здесь можно подобрать опции монтирования корневой файловой системы, чтобы оптимизировать её работу, но мы ничего трогать не будем:
echo "192.168.1.100:/diskless / nfs defaults 0 0" >> $root/etc/fstab
Базовая установка клиентской системы на этом закончена, но мы хотим добавить графическое окружение и автоматический запуск Firefox.
Загружаемся в Firefox
Для уменьшения объема памяти, занимаемого нашей системой, мы откажемся от использования оконного менеджера и остановимся на простейшем окружении рабочего стола — openbox с автоматической авторизацией пользователя username. Кроме того, использование «облегченных» компонентов позволит системе замечательно запускаться и работать даже на самом древнем железе.
Установим модули для поддержки VirtualBox, сервер X, симпатичный TTF-шрифт, openbox и firefox (все нужные модули будут установлены как зависимости):
pacman --root $root --dbpath $root/var/lib/pacman -S virtualbox-guest-modules virtualbox-guest-utils xorg-xinit ttf-dejavu openbox firefox
Включаем автозагрузку службы virtualbox:
arch-chroot $root systemctl enable vboxservice
Добавим автоматический вход пользователя username без ввода пароля, для этого изменим строку запуска agetty:
mkdir $root/etc/systemd/system/getty@tty1.service.d &&
echo -e "[Service]nExecStart=nExecStart=-/usr/bin/agetty --autologin username --noclear %I 38400 linux Type=simple %I" > $root/etc/systemd/system/getty@tty1.service.d/autologin.conf
Сразу же после авторизации пользователя выполняется файл ~/.bash_profile, из его домашней папки, куда мы добавляем автоматический запуск графического сервера:
echo '[[ -z $DISPLAY && $XDG_VTNR -eq 1 ]] && exec startx &> /dev/null' >> $root/home/username/.bash_profile
За запуском X-сервера должен следовать запуск openbox:
cp $root/etc/X11/xinit/xinitrc $root/home/username/.xinitrc && echo 'exec openbox-session' >> $root/home/username/.xinitrc
Закомментируйте следующие строки в самом конце файла (от строки twm до добавленной нами строки с запуском openbox, но не включая её):
cat $root/home/username/.xinitrc
# twm &
# xclock -geometry 50x50-1+1 &
# xterm -geometry 80x50+494+51 &
# xterm -geometry 80x20+494-0 &
# exec xterm -geometry 80x66+0+0 -name login
exec openbox-session
Копируем конфигурационные файлы openbox
mkdir -p $root/home/username/.config/openbox && cp -R $root/etc/xdg/openbox/* $root/home/username/.config/openbox
Добавляем firefox в автозагрузку в окружении openbox:
echo -e 'exec firefox habrahabr.ru' >> $root/home/username/.config/openbox/autostart
Поскольку мы только что с правами root хозяйничали в домашней папке пользователя username, нам нужно вернуть ему права на все файлы, расположенные в его домашней папке:
chown -R username $root/home/username
Подготовка системы к загрузке по сети закончена, и настала пора переходить к настройке загрузочного сервера. Теперь мы знаем, что для загрузки нам понадобятся:
- DHCP-сервер с поддержкой протокола BOOTP для настройки сетевой карты;
- TFTP-сервер для передачи загрузчика и файлов vmlinuz и initramfs, которые у нас находятся в папке $root/boot/grub;
- NFS-сервер для размещения корневой файловой системы, которая лежит у нас в папке $root.
Настраиваем загрузочный сервер
Дальнейшие шаги с небольшими изменениями повторяют эту статью из вики, поэтому минимум комментариев с моей стороны.
Устанавливаем DHCP сервер
Скачиваем пакет:
pacman -S dhcp
и приводим содержимое конфигурационного файла /etc/dhcpd.conf к следующему виду:
mv /etc/dhcpd.conf /etc/dhcpd.conf.old
allow booting;
allow bootp;
# Утверждаем, что сервер является авторитетным (обычно роутеры либо не авторитетны, либо BOOTP не поддерживают, поэтому их PXE слушать не будет)
authoritative;
# Здесь, как обычно (не забывайте исправлять под себя)
default-lease-time 600;
max-lease-time 7200;
option domain-name-servers 192.168.1.1;
option routers 192.168.1.1;
# получаем архитектуру клиента (это обсуждалось выше)
option architecture code 93 = unsigned integer 16;
# работаем в такой подсети (исправляйте под себя)
subnet 192.168.1.0 netmask 255.255.255.0 {
# в этот класс попадут все те, кто пытается загружаться
class «pxe_client» {
match if exists architecture;
}
pool {
# Разным архитектурам отдаём разные файлы:
if option architecture = 7 {
filename "/grub/x86_64-efi/core.efi";
} else {
filename "/grub/i386-pc/core.0";
}
# Рекомендую указать адрес TFTP сервера, несмотря на то, что это необязательно
next-server 192.168.1.100;
# Кто загружается, идёт сюда
range 192.168.1.128 192.168.1.192;
allow members of «pxe_client»;
}
}
Как видите, DHCP-сервер будет отвечать только на те запросы DHCPDISCOVER, которые придут от PXE клиентов, а остальные просто проигнорируем.
Запускаем DHCP сервер:
systemctl start dhcpd4
Устанавливаем TFTP сервер
Скачиваем и устанавливаем необходимый пакет:
pacman -S tftp-hpa
Нам нужно, чтобы TFTP сервер предоставлял доступ к файлам загрузчика, которые мы разместили в папке $root/boot. Для этого модифицируем запуск службы уже проверенным способом:
mkdir -p /etc/systemd/system/tftpd.service.d && echo -e '[Service]nExecStart=nExecStart=/usr/bin/in.tftpd -s /srv/nfs/boot' > /etc/systemd/system/tftpd.service.d/directory.conf
Первая строка отменяет выполнение команды, указанной в оригинальном файле, а вместо нее выполняется "/usr/bin/in.tftpd -s /srv/nfs/diskless/boot".
Запускаем TFTP сервер:
systemctl start tftpd.socket tftpd.service
Устанавливаем NFS сервер
Скачиваем пакет:
pacman -S nfs-utils
Добавляем папку, в которую мы установили систему, в список экспортируемых:
echo -e "/srv/nfs 192.168.1.0/24(rw,fsid=root,no_subtree_check,no_root_squash)n$root 192.168.1.0/24(rw,no_subtree_check,no_root_squash)" >> /etc/exports
Не забываем использовать синтаксис NFS v.4 указывая путь относительно папки с fsid=root (корневой по отношению ко всем остальным экспортируемым папкам, без указания которой ничего работать не будет).
Запускаем службы, обеспечивающие работу NFS-сервера:
systemctl start rpcbind nfs-server
На этом загрузочный сервер готов к работе.
Пробуем загрузиться по сети
Проследим за процессом загрузки с сервера с помощью программы tcpdump
pacman -S tcpdump
tcpdump -lv '(
src host 0.0.0.0 and udp[247:4] = 0x63350101) or (
dst host HabraBoot and dst port tftp) or (
dst host HabraBoot and tcp[tcpflags] == tcp-syn)'
Первая строка «ловит» запрос DHCPDISCOVER от PXE клиента. В выводе, отфильтрованном второй строкой, будут перечислены имена всех файлов, запрашиваемых по TFTP. Третья строка показывает два tcp-syn запроса, отправляемых в самом начале подключения по протоколу NFS (первое соединение осуществляется обработчиком net, а второе переподключение происходит во время обработки файла fstab).
Создаём новую виртуальную машину, для краткости будем называть её «клиент». В настройках сети снова указываем тип подключения «Сетевой мост» и включаем машину. Сразу же нажимаем клавишу F12 на клавиатуре для выбора загрузочного устройства, а потом клавишу l, чтобы загрузиться по сети.
Дождитесь окончания загрузки. Если всё в порядке, то на сервере добавляем используемые службы в автозагрузку:
systemctlt enable tftpd.socket tftpd.service dhcpd4 rpcbind nfs-server
Все серверы DHCP, TFTP и NFS мы запустили на одном загрузочном сервере. Делать так необязательно. Например, роутеры Mikrotik поддерживают Bootp и позволяют использовать себя в качестве TFTP — просто закачайте туда все нужные файлы и проверьте сетевые настройки.
Сейчас графическое окружение будет работать только в VirtualBox, потому что мы не устанавливали драйверы для «железных» видеокарт. Мы решим проблему автоматического подбора нужных драйверов в следующий раз. Заодно ускорим загрузку системы и сделаем из неё «живой образ».
Автор: Roshalsky