Стань повелителем загрузки Linux

Сначала мы научимся исследовать установленные в компьютере устройства прямо во время загрузки с помощью udev (на примере подбора настроек видеокарт для Xorg). Затем оптимизируем систему для сетевой загрузки, и переведём её в режим «только для чтения» с помощью обработчика в файле initramfs, что позволит одновременную работу с одним образом на десятках компьютеров. Попробуем NFS заменить на NBD, а TFTP на HTTP, чтобы ускорить загрузку и снизить нагрузку на сеть. В конце вернёмся в начало — к загрузочному серверу.7a7b69abca3b47c69975721d79c531f9.jpgДанная статья скорее исследование, а не готовое руководство (все решения работают, просто они не всегда оптимальны). Тем не менее, у вас появится достаточно знаний, чтобы сделать всё так, как захотите именно вы.Начало смотрите здесь: Первоначальная настройка сервераПодготовка образа для загрузки по сетиВ предыдущей статье мы загрузили по сети машину VirtualBox и запустили Firefox. Если сейчас попытаться загрузить по сети обычный компьютер, то вы увидите только циклическую авторизацию пользователя username и безуспешные попытки запустить графическое окружение. Проблема в том, что Xorg не находит нужный драйвер.

Запускаем видеокартыМы уже установили всё необходимое для работы видео в VirtualBox. Изначально планировалось, что наша бездисковая система должна функционировать на любом «железе», но из-за лени мы не будем пытаться объять необъятное и ограничимся поддержкой графических решений доминирующих производителей (nVidia, Intel и AMD). Переключимся на машине-клиенте во второй терминал нажатием Ctrl+Alt+F2 и установим открытые драйверы: pacman -S xf86-video-ati xf86-video-nouveau xf86-video-intel Итак, драйверы есть, но вероятнее всего, что Xorg теперь не сможет самостоятельно выбрать подходящий для каждого случая, и нам придётся ему помочь.Простейший способ узнать какие видеоустройства имеются в системе, это ввести в консоли команду:

lspci | grep -i vga

00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter На этот раз мы не будем искать лёгких путей, а в награду получим новую порцию знаний.Ближе знакомимся с udev Раньше я уже упоминал, что менеджер устройств в Archlinux называется udev, и входит в пакет systemd под именем systemd-udevd. Systemd при загрузке параллельно запускает службы, а udev параллельно инициализирует устройства, в связи с чем проявляются некоторые особенности. Например, если в компьютере установлены две видеокарты, то сначала первой может быть найдена одна из них, а после перезагрузки — другая. То же самое может произойти с накопителями и сетевыми картами, и от загрузки к загрузке будет меняться их имя. Поэтому для udev придуманы правила, которые должны внести порядок в этот хаос, и хранятся они в /etc/udev/rules.d/ (на самом деле, как и в случае обработчиков (hooks), есть ещё одна папка с правилами /usr/lib/udev/rules.d/, имеющая более низкий приоритет). Udev применяет подходящие правила к обнаруженным устройствам, сортирует и распределяет полученную информацию в каталогах /sys и /dev.С точки зрения xorg видеокарты относятся к подсистеме или классу drm, поэтому для его удобства сведения о них дублируются в каталоге /sys/class/drm. Первая обнаруженная видеокарта по-умолчанию получает имя «card0», если в ней имеется несколько видеовыходов, то они получают имена вида «card0-CON-n», где «CON» — тип разъема (VGA, HDMI, DVI и др.), а «n» — порядковый номер разъема (причём одни производители нумеруют разъёмы начиная с »0», а другие — с »1»). Чтобы узнать то же самое о видеокарте, что знает про неё udev, введём команду:

udevadm info -a -p /sys/class/drm/card0 вывод команды Udevadm info starts with the device specified by the devpath and thenwalks up the chain of parent devices. It prints for every devicefound, all possible attributes in the udev rules key format.A rule to match, can be composed by the attributes of the deviceand the attributes from one single parent device.looking at device '/devices/pci0000:00/0000:00:02.0/drm/card0': KERNEL==«card0«SUBSYSTEM==«drm«DRIVER==»

looking at parent device '/devices/pci0000:00/0000:00:02.0': KERNELS==»0000:00:02.0«SUBSYSTEMS==«pci«DRIVERS==«ATTRS{irq}==»18«ATTRS{subsystem_vendor}==»0×0000«ATTRS{broken_parity_status}==»0«ATTRS{class}==»0×030000«ATTRS{driver_override}==»(null)«ATTRS{consistent_dma_mask_bits}==»32«ATTRS{dma_mask_bits}==»32«ATTRS{local_cpus}==»00000000,00000000,00000000,00000001«ATTRS{device}==»0xbeef«ATTRS{enable}==»1«ATTRS{msi_bus}==»1«ATTRS{local_cpulist}==»0«ATTRS{vendor}==»0×80ee«ATTRS{subsystem_device}==»0×0000«ATTRS{boot_vga}==»1«ATTRS{numa_node}==»-1«ATTRS{d3cold_allowed}==»0»

looking at parent device '/devices/pci0000:00': KERNELS==«pci0000:00«SUBSYSTEMS==«DRIVERS==»

Обратите внимание на древовидную структуру с использованием парадигмы родительских и дочерних устройств. В строках, начинающихся с «looking at …» указан путь к данному устройству относительно каталога /sys, т. е. обратившись к видеокарте по пути /sys/class/drm/card0, мы обнаружили, что на самом деле это ссылка на /sys/devices/pci0000:00/0000:00:02.0/drm/card0.У родительского устройства /devices/pci0000:00/0000:00:02.0 есть атрибут vendor с идентификатором производителя. Udev располагает доступом к обширной базе данных и может перевести этот код в удобоваримый вид:

udevadm info -q property -p /sys/devices/pci0000:00/0000:00:02.0

DEVPATH=/devices/pci0000:00/0000:00:02.0 ID_MODEL_FROM_DATABASE=VirtualBox Graphics Adapter ID_PCI_CLASS_FROM_DATABASE=Display controller ID_PCI_INTERFACE_FROM_DATABASE=VGA controller ID_PCI_SUBCLASS_FROM_DATABASE=VGA compatible controller ID_VENDOR_FROM_DATABASE=InnoTek Systemberatung GmbH MODALIAS=pci: v000080EEd0000BEEFsv00000000sd00000000bc03sc00i00 PCI_CLASS=30000 PCI_ID=80EE: BEEF PCI_SLOT_NAME=0000:00:02.0 PCI_SUBSYS_ID=0000:0000 SUBSYSTEM=pci USEC_INITIALIZED=24450 Сравните с выводом команды: lspci | grep -i vga 00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter. Динамическая настройка видеокарты с помощью udev Подключитесь к загрузочному серверу. И создайте файл с правилами: export root=/srv/nfs/diskless nano $root/etc/udev/rules.d/10-graphics.rules

KERNEL==«card[0–9]*», SUBSYSTEM==«drm», RUN+=»/etc/default/xdevice %n» KERNEL==«card*», SUBSYSTEM==«drm», ATTR{enabled}==«enabled», ATTR{status}==«connected», RUN+=»/etc/default/xdevice %n %k» Каждое правило записывается в новой строке. Первая часть служит для идентификации устройства, к которому нужно применить действие, указанное в конце строки. Для опознания используются данные, которые получаем в выводе команды «udevadm info -a -p /sys…». Правило из первой строки сработает для всех устройств с именем (ядром) card0, card1… подсистемы drm. Второе правило сработает только для активных устройств из подсистемы drm, к которым в данный момент подключен монитор (оно не сработает для card0, card1, а только для имен вида card0-HDMI-1, т. к. только у таких устройств есть атрибуты enabled и status). При совпадении устройства с описанием выполняется одна и та же программа, в которую в первом случае передаётся один параметр %n (порядковый номер, который для card0 будет »0»), а во втором — дополнительный параметр %k (само имя «card0»).Программа /etc/default/xdevice будет изменять содержимое файла в папке /etc/X11/xorg.conf.d/, в котором содержится информация о настройках видеоадаптера для xorg. Достаточно указать минимально необходимую информацию для однозначной идентификации устройства, а остальное xorg сделает сам:

Section «Device» Identifier «уникальный идентификатор устройства» Driver «используемый драйвер» Option «AccelMethod» «метод ускорения» BusID «PCI: идентификатор шины PCI, куда физически установлен адаптер» EndSection Необходимые данные мы получим исследуя вывод команды «udevadm info». Программа будет срабатывать для каждого выхода каждой видеокарты, к которому подключен монитор. Для упрощения задачи заставим работать последний найденный вариант. Это не самый оптимальный способ настройки, но он рабочий и подходит для изучения правил udev в действии (было бы лучше проверить графическую подсистему один раз перед достижением graphical.target). Создаём файл программы со следующим содержанием: nano $root/etc/default/xdevice Скрытый текст #!/bin/sh

# в этом файле будем хранить настройки устройства для xorg CONF_FILE=/etc/X11/xorg.conf.d/20-device.conf

# получаем первое слово в названии производителя в «человеческом» виде # лучше было бы использовать идентификаторы, но для наглядности оставим как есть get_vendor (){ local card=$(get_path $1) udevadm info -q property -p ${card%\/drm*} | \ awk '/^ID_VENDOR_FROM_DATABASE/{split ($1, a,»=»); print tolower (a[2])}' }

# получаем идентификатор шины PCI из пути устройства и приводим его к виду x: y: z get_bus (){ local bus=$(get_path $1) echo ${bus%\/drm*} | \ sed 's\:\.\g' | \ awk '{n=split ($0, a,».»); printf »%i:%i:%i», a[n-2], a[n-1], a[n]}' }

# получаем полный путь к устройству get_path (){ udevadm info -q path -p /sys/class/drm/$1 }

# Выбираем шаблон на основании имени производителя и заполняем его данными. make_conf (){ local filename=«xorg-device-$(get_vendor $1).conf» cat /etc/X11/$filename | \ sed 's\%BUS%\'$(get_bus $1)'\g'| \ sed 's\%ID%\'$1'\g' > $CONF_FILE }

# если мы в virtualbox, то запускаем для него службу check_vbox (){ local vendor=$(get_vendor $1) [ »$vendor» == «innotek» ]] && systemctl start vboxservice }

#начало программы card_numb=$1

if [ -z »$2» ] # обрабатываем исключение для virtualbox then card_name=«card$card_numb» check_vbox $card_name && make_conf $card_name else card_name=$2 make_conf $card_name fi Сделаем файл исполняемым chmod +x $root/etc/default/xdevice Отключаем автоматическую загрузку службы VirtualBox, т. к. теперь она будет запускаться только при необходимости: systemctl disable vboxservice Добавляем шаблоны конфигурационных файлов xorg, с оптимизированными под основных производителей настройками: nano $root/etc/X11/xorg-device-intel.conf

Section «Device» Identifier «Intel %ID%» Driver «intel» Option «AccelMethod» «uxa» BusID «PCI:%BUS%» EndSection AMD, nVidia, VirtualBox nano $root/etc/X11/xorg-device-innotek.conf

Section «Device» Identifier «VirtualBox %ID%» Driver «vboxvideo» BusID «PCI:%BUS%» EndSection nano $root/etc/X11/xorg-device-advanced.conf

Section «Device» Identifier «AMD %ID%» Driver «radeon» Option «AccelMethod» «exa» BusID «PCI:%BUS%» EndSection nano $root/etc/X11/xorg-device-nvidia.conf

Section «Device» Identifier «nVidia %ID%» Driver «nouveau» Option «AccelMethod» «exa» BusID «PCI:%BUS%» EndSection Добавляйте свои шаблоны и не забывайте устанавливать драйверы для этих устройств.

В завершение настройки xorg сделаем переключение раскладки клавиатуры комбинацией Alt+Shift: nano $root/etc/X11/xorg.conf.d/50-keyboard.conf

Section «InputClass» Identifier «keyboard-layout» MatchIsKeyboard «on» Option «XkbLayout» «us, ru» Option «XkbVariant» », winkeys» Option «XkbOptions» «grp: alt_shift_toggle» EndSection Оптимизируем систему Логи работы всех составляющих Archlinux сохраняются в журнале. Если всё оставить как есть, то журнал может довольно сильно раздуть, поэтому ограничим его размер, скажем 30Мб (добавьте или раскомментируйте строку): nano $root/etc/systemd/journald.conf … SystemMaxUse=30M … Каждое действие протоколируется в папку /var/log/journal. В нашем случае передача данных осуществляется по сети, которая имеет невысокую пропускную способность. Если удалить папку с журналом, то он будет сохраняться только в оперативной памяти, что нам идеально подходит: rm -r $root/var/log/journal При различных ошибках в работе приложений в папке /var/lib/systemd/coredump создаются автоматические дампы ядра. Мы их отключим по той же причине: nano $root/etc/systemd/coredump.conf … Storage=none … Отключаем SWAP:

echo -e 'vm.swappiness=0\nvm.vfs_cache_pressure=50' > $root/etc/sysctl.d/99-sysctl.conf Удалим ненужные локализации. Это простое действие поможет сэкономить более 65 Мб. Сейчас мы увидим, как устанавливаются программы из AUR (фактически они собираются из исходников). Зайдите на загрузочный сервер с правами обычного пользователя и выполните следующие действия:

curl -o localepurge.tar.gz https://aur.archlinux.org/packages/lo/localepurge/localepurge.tar.gz tar -xvvzf localepurge.tar.gz cd localepurge makepkg -s Пакет готов. Устанавливаем его из файла, а не из репозитория, поэтому ключ S заменяется на U (исправьте название файла, если версия собранной вами програмы не совпадает с моей): sudo pacman --root $root --dbpath $root/var/lib/pacman -U localepurge-0.7.3.4–1-any.pkg.tar.xz Теперь настроим. Закомментируйте строку «NEEDCONFIGFIRST» в начале файла и укажите используемые локализации в самом конце: nano $root/etc/locale.nopurge … # NEEDSCONFIGFIRST … ru ru_RU ru_RU.UTF-8 en en_US en_US.UTF-8 Конфигурируем и запускаем программу: arch-chroot $root /usr/bin/localepurge-config arch-chroot $root localepurge Переходим в read-only Если мы попробуем загрузить существующую систему на нескольких компьютерах одновременно, то все копии будут изменять одни и те же папки на сервере. Если один клиент удалит какой-то файл, то он неожиданно исчезнет и у другого. Самый надежный способ защититься от изменений — перейти в режим только для чтения.Проблема в том, что для нормальной работы системы необходима возможность записывать данные в некоторые папки. Решение на поверхности — подключать эти папки через fstab как tmpfs, что замечательно подойдёт для /var/log, например. Но как поступить, например, с папкой /etc, ведь наше правило udev меняет там файлы? Можно перед монтированием информацию где-то сохранить, а потом переписать обратно. Можно сразу всё перенести куда-то ещё и после монтирования переписать куда надо. Ясно одно: придётся долго тестировать и следить за работой системы, чтобы понять какие ещё папки сделать доступными для записи, или же настроить все программы так, чтобы они оставляли продукты своей жизнедеятельности строго в отведённом месте. Слишком мудрёно. Предлагаю всю систему развернуть в RAM. Останется только переписать туда всё самое нужное для работы.

Существует одна папка, в которую во время работы ничего не записывается, если мы ничего не устанавливаем — /usr (для работы Firefox этого достаточно). Сравните её размер с размером всего остального, и получится, что копировать придётся не так много, а если при этом исключить всё лишнее… Вы тоже подумали о rsync?

Переделываем файловую систему на лету Устанавливаем rsync на клиента: pacman -S rsync Заниматься копированием нам придётся на этапе работы intramfs, следовательно, понадобится новый обработчик, назовём его «live». Для начала сохраним все необходимые параметры монтирования корневого каталога, а анализ оригинального файла /etc/fstab проведём с помощью утилиты findmnt. Новый корневой каталог внутри initramfs всегда монтируется в /new_root, откуда мы его отмонтируем, и на его месте создадим ramfs с возможностью записи. Подготовим точку монтирования /srv/new_root внутри ramfs, куда вернём оригинальный корневой каталог. Перепишем в ramfs все файлы и каталоги, за исключением папки /usr, которую забиндим в режиме только для чтения. nano $root/etc/initcpio/hooks/live cat $root/etc/initcpio/hooks/live #!/usr/bin/bash run_latehook () {local source options fstypelocal target=»/«local fstab=/new_root/etc/fstablocal place=/new_root/srv/new_rootlocal filter=${place}/etc/default/live_filter

if source=$(findmnt -snero source --tab-file=$fstab -T $target); thenoptions=$(findmnt -snero options --tab-file=$fstab -T $target)fstype=$(findmnt -snero fstype --tab-file=$fstab -T $target)

umount /new_rootmount -t ramfs none /new_root -o rw, defaults

[! -d »$place» ] && mkdir -p $placemount ${fstype:±t ${fstype}} ${options:±o ${options}} $source $placemount -o remount, ro${options:±,${options}} $source $place

rsync -aAX ${place}/* /new_root --filter=«merge $filter»

! findmnt -snero source --tab-file=$fstab -T /usr && bind_usr $place

# чтобы не допустить перемонтирование »/» во время загрузки,# удаляем информацию по нему из fstabcat ${place}/etc/fstab | grep -v $source > $fstabfi}

bind_usr (){local place=$1mount --bind ${place}/usr /new_root/usrmount -o remount, ro, bind ${place}/usr /new_root/usr}

К файлу /etc/fstab мы обращаемся дважды: первый раз получаем информацию по параметрам монтирования корневого каталога, а второй раз проверяем, есть ли в fstab какая-нибудь информация по /usr. Для позднего монтирования /usr в Archlinux есть специальный обработчик usr, которому мы не будем мешать выполнять свою работу. Если /usr монтируется каким-то особым образом, то наш обработчик его пропускает.

В тексте упомянут файл /etc/default/live_filter с правилами фильтрации, предназначенными для rsync, нам нужно не забыть его подготовить. Сделаем это автоматически из установщика обработчика:

nano $root/etc/initcpio/install/live #!/usr/bin/bash

build () { make_filter > /etc/default/live_filter add_binary »/usr/bin/rsync» »/bin/rsync» add_binary findmnt add_runscript }

make_filter () { cat <

Замечания Правила для rsync, находящиеся во внешнем файле /etc/default/live_filter, вы можете менять по своему усмотрению без необходимости заново создавать initramfs. Буду рад увидеть ваш вариант правил в комментариях.Возможностей у rsync очень много (man rsync — почти 3000 строк). Предложите в комментариях какой-нибудь экзотический способ использования rsync внутри initramfs?

Теоретически rsync можно заменить на какой-нибудь torrent, и собирать корневую файловую систему с его помощью.

Добавляем обработчик в initramfs: cat $root/etc/mkinitcpio.conf … HOOKS=«base udev net_nfs4 live» Генерируем initramfs: arch-chroot $root mkinitcpio -p habr nfs4+live Сервер и клиент работают в VirtualBoxИсходная файловая система: cat $root/etc/fstab #

192.168.1.100:/diskless / nfs4 defaults, noatime 0 0 Состояние файловой системы на загруженном клиенте после выполнения обработчика live: mount … none on / type ramfs (rw, relatime) 192.168.1.100://diskless on /srv/new_root type nfs4 (ro, noatime, vers=4.1, rsize=131072, wsize=131072, namlen=255, hard, proto=tcp, timeo=600, retrans=2, sec=sys, clientaddr=192.168.1.131, local_lock=none, addr=192.168.1.100) 192.168.1.100://diskless/usr on /usr type nfs4 (ro, noatime, vers=4.1, rsize=131072, wsize=131072, namlen=255, hard, proto=tcp, timeo=600, retrans=2, sec=sys, clientaddr=192.168.1.131, local_lock=none, addr=192.168.1.100) … Во время загрузки клиента на сервере были собраны следующие данные: vnstat -l … eth0 / traffic statistics

rx | tx --------------------------------------±----------------- bytes 7,23 MiB | 252,33 MiB --------------------------------------±----------------- max 5,11 Mbit/s | 235,23 Mbit/s average 1,48 Mbit/s | 51,68 Mbit/s min 0 kbit/s | 1 kbit/s --------------------------------------±----------------- packets 82060×199036 --------------------------------------±----------------- max 6550 p/s | 21385 p/s average 2051 p/s | 4975 p/s min 0 p/s | 0 p/s --------------------------------------±----------------- time 40 seconds Разгоняем сеть Физически, естественно, разгон сети сейчас невозможен без замены оборудования, зато программные оптимизации не запрещаются. Нам нужно передавать содержимое связанной папки /usr по сети. Не отправлять эти данные мы не можем, зато способны уменьшить объём занимаемого ими места — заархивировать. На сервере сжимаем, а на клиенте — распаковываем, и через ту же самую сеть теоретически передаётся больше данных за единицу времени.Файловая система squashfs совмещает в себе возможности архиватора и монтирования архивов через fstab, как обычную файловую систему. Основной недостаток данной файловой системы — невозможность работать в режиме записи (только для чтения) — для нас недостатком не является:

pacman -S squashfs-tools && mksquashfs $root/usr $root/srv/source_usr.sfs -b 4096 -comp xz Монтировать будем так:

nano $root/etc/fstab #

192.168.1.100:/diskless / nfs4 defaults, noatime 0 0 /srv/new_root/srv/source_usr.sfs /usr squashfs loop, compress=xz 0 0 На позднем этапе работы initramfs монтированием папки /usr занимается обработчик usr, который нужно немного подправить: cp $root/{usr/lib, etc}/initcpio/install/usr && cp $root/{usr/lib, etc}/initcpio/hooks/usr Нужно, чтобы строка монтирования выглядела так: nano $root/etc/initcpio/hooks/usr mount »/new_root$usr_source» /new_root/usr -o »$mountopts» Чем не устраивает usr? Обработчик требует указание поля «file system» в файле fstab в виде »/new_root/srv/new_root/usr/source_usr.sfs». Этот файл обрабатывается systemd на раннем этапе загрузки целевой системы, и производится перемонтирование всех указанных там директорий. Папка /new_root cуществует только на этапе ранней загрузки initrams, поэтому systemd фиксирует ошибку. Ошибка ни на что не влияет, но убрать её не сложно.

cat $root/etc/mkinitcpio.conf HOOKS=«base udev net_nfs4 live usr» arch-chroot $root mkinitcpio -p habr nfs4+live+squashed /usr Исходная файловая система: cat $root/etc/fstab #

192.168.1.100:/diskless / nfs4 defaults, noatime 0 0 /srv/new_root/srv/source_usr.sfs /usr squashfs ro, loop, compress=xz 0 0 Состояние файловой системы на загруженном клиенте после выполнения обработчиков live и usr: mount … none on / type ramfs (rw, relatime) 192.168.1.100://diskless on /srv/new_root type nfs4 (ro, noatime, vers=4.1, rsize=131072, wsize=131072, namlen=255, hard, proto=tcp, timeo=600, retrans=2, sec=sys, clientaddr=192.168.1.131, local_lock=none, addr=192.168.1.100) /srv/new_root/srv/source_usr.sfs on /usr type squashfs (ro, relatime) … Во время загрузки клиента на сервере были собраны следующие данные: vnstat -l … eth0 / traffic statistics

rx | tx --------------------------------------±----------------- bytes 5,07 MiB | 205,67 MiB --------------------------------------±----------------- max 4,02 Mbit/s | 191,82 Mbit/s average 1,04 Mbit/s | 42,12 Mbit/s min 0 kbit/s | 1 kbit/s --------------------------------------±----------------- packets 65524×159941 --------------------------------------±----------------- max 5954 p/s | 17170 p/s average 1638 p/s | 3998 p/s min 0 p/s | 0 p/s --------------------------------------±----------------- time 40 seconds Данных пришлось передать примерно на 20% меньше, чем в предыдущий раз. Можно упаковать весь корневой каталог в один файл, тогда обработчик live для заполнения ramfs будет забирать с сервера данные в сжатом виде.

Можно скопировать файл /srv/source_usr.sfs в ramfs поменяв правила в фильтре rsync, а потом примонтировать его через fstab из нового места, и, когда вся система целиком окажется в RAM, попробовать отключиться от загрузочного сервера.

Убираем лишнее Если вы заглядывали сюда, то у вас не возникнет вопрос: «Как мы будем отдавать с сервера файл?». Можно, конечно, передавать данные squashfs посредством NFS (что и происходило выше), но существует менее документированное решение Network Block Device, с которым можно работать как с обычным диском. Поскольку это «блочное устройство», а не «файловая система», мы можем использовать на нём любую файловую систему с возможностью сжатия данных. Для доступа на чтение и запись подойдёт btrfs с архивацией zlib, но нам не нужна запись и squashfs вполне устраивает.

Чтобы из initramfs можно было подключиться к NBD-серверу при загрузке понадобится скачать из AUR пакет mkinitcpio-nbd (нужно скачивать и собирать с правами обычного пользователя):

curl -o mkinitcpio-nbd.tar.gz https://aur.archlinux.org/packages/mk/mkinitcpio-nbd/mkinitcpio-nbd.tar.gz tar -xvvzf mkinitcpio-nbd.tar.gz cd mkinitcpio-nbd makepkg -s sudo pacman --root $root --dbpath $root/var/lib/pacman -U mkinitcpio-nbd-0.4.2–1-any.pkg.tar.xz Добавляем в конец файла $root/boot/grub/grub.cfg новый пункт меню:

cat $root/boot/grub/grub.cfg

menuentry «NBD» { 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 \ nbd_host=»$net_default_server» nbd_name=habrahabr root=/dev/nbd0 echo «Загружается виртуальный диск…» initrd initramfs-linux.img } Как видите, поменялась только одна строчка: nbd_host=»$net_default_server» nbd_name=habrahabr root=/dev/nbd0 После подключения к NBD серверу в клиенте появляется блочное устройство с именем /dev/nbd0, поэтому поступаем с ним как с обычным диском: nano $root/etc/fstab

#

/dev/nbd0 / squashfs ro, loop, compress=xz 0 0 В последних версиях NBD сервера появилась непрятная особенность (скорее всего это баг). Когда клиент NBD устанавливает соединение с сервером, а потом внезапно выключается не завершая соединение корректно, и оно продолжает «болтаться» на сервере в виде незавершенного процесса. Если клиент во время загрузки попробует подключиться к NBD заново, то есть вероятность, что сервер не станет создавать новое соедиенение считая старое активным. Предлагаю непосредственно перед подключением к NBD отправлять свой IP адрес через netcat на сервер, чтобы тот закрыл старые подключения, связанные с этим IP адресом:

cp $root/{usr/lib, etc}/initcpio/install/nbd cp $root/{usr/lib, etc}/initcpio/hooks/nbd Нужно отредактировать только один файл. Вставьте между строками следующий фрагмент: nano $root/etc/initcpio/hooks/nbd

modprobe nbd # вставляете после этой строки msg «closing old connections…» echo ${ip} | nc ${nbd_host} 45678 local ready=$(nc -l -p 45678) [ »$ready» -ne 1 ] && reboot msg «connecting…» # и перед этой строкой В initramfs сетью по-прежнему заведует наш модифицированный net_nfs4, после которого вставляем nbd: nano $root/etc/mkinitcpio.conf

MODULES=«loop squashfs» HOOKS=«base udev net_nfs4 keyboard nbd live» Генерируем initramfs: arch-chroot $root mkinitcpio -p habr Перед выполнением следующей команды удалите или переместите файл $root/srv/source_usr.sfs за пределы $root — не имеет смысла помещать архив /usr внутрь архива, содержащего оригинал /usr: mksquashfs $root/* /srv/new_root.sfs -b 4096 -comp xz Переходим к настройке сервераУстанавливаем пакет:

pacman -S nbd Настраиваем NBD сервер: mv /etc/nbd-server/{config, config.old} && nano /etc/nbd-server/config

[generic] user = nbd group = nbd [habrahabr] exportname = /srv/new_root.sfs timeout = 30 readonly = true multifile = false copyonwrite = false Всё достаточно просто. Мы создаём шару с именем habrahabr, ссылаемся на наш файл, устанавливаем таймаут соединения, раздаём в режиме «только для чтения», отдаём только один файл и функция copyonwrite нам не нужна. Copyonwrite позволяет использовать одну и ту же раздачу несколькими клиентами одновременно, при этом каждому клиенту создаётся отдельный файл, куда будут записываться все произведённые им изменения оригинального файла. После отключения клиента файлы с изменениями удаляются автоматически. Использование этой функции замедляет сервер. Информации по NBD в интернете не так много, но man’ы решают.Проверять и завершать процессы, связанные с незакрытыми соединениями будет вот этот файл:

nano /etc/default/close_passive_NBD_connections.sh

#!/bin/sh # завершает все процессы с полученными PID _kill (){ local PID for PID in $* do kill $PID done } main (){ local rIP PIDs # нам передают с клиента значение переменной ip из параметров ядра в grub.cfg rIP=$(netcat -l -p 45678 | cut -d: -f1) # фильтруем пакеты с полученного IP адреса и узнаём их PID PIDs=$(netstat -np | grep $rIP | awk '/^tcp.*nbd-server/{split ($NF, a,»/»); print a[1]}') _kill $PIDs && echo »1» | netcat -z $rIP 45678 } # повторяем в бесконечном цикле while [ 0 ] do main done Файл делаем исполняемым: chmod +x /etc/default/close_passive_NBD_connections.sh Устанавливаем пакеты, в которых находятся утилиты netcat и netstat: pacman -S gnu-netcat net-tools Модифицируем запуск службы NBD: mkdir -p /etc/systemd/system/nbd.service.d && nano /etc/systemd/system/nbd.service.d/close_passive.conf

[Service] Type=oneshot ExecStart=/etc/default/close_passive_NBD_connections.sh Возможно выбрано не самое изящное решение, но оно достаточно понятно и замечательно работает.nbd + squashed live Исходная файловая система: cat $root/etc/fstab #

/dev/nbd0 / squashfs ro, loop, compress=xz 0 0 Состояние файловой системы на загруженном клиенте после выполнения обработчиков live и usr: mount … none on / type ramfs (rw, relatime) /dev/nbd0 on /srv/new_root type squashfs (ro, relatime) /dev/nbd0 on /usr type squashfs (ro, relatime) … Во время загрузки клиента на сервере были получены следующие данные: vnstat -l … eth0 / traffic statistics

rx | tx --------------------------------------±----------------- bytes 1,97 MiB | 198,92 MiB --------------------------------------±----------------- max 2,81 Mbit/s | 138,60 Mbit/s average 575,63 kbit/s | 58,20 Mbit/s min 2 kbit/s | 1 kbit/s --------------------------------------±----------------- packets 32473×100874 --------------------------------------±----------------- max 5991 p/s | 7576 p/s average 1159 p/s | 3602 p/s min 4 p/s | 1 p/s --------------------------------------±----------------- time 28 seconds На этот раз мы сэкономили ещё всего лишь 3% трафика (в пределах погрешности). Разница во времени загрузки объясняется тем, что при использовании NFS перед подключением к серверу делается принудительная пауза в 10 секунд, а в случае сервера NBD такой задержки нет.Педаль в пол Давайте попробуем ускорить загрузку. Самое слабое звено в нашей цепочке загрузки — TFTP сервер. Полностью исключить его мы не сможем, но минимизировать его присутствие можно с помощью загрузчика iPXE, как посоветовал kvaps в комментариях к предыдущей статье.Подключитесь к загрузочному серверу под именем username.Меню с вариантами загрузки мы делать не будем, а автоматически загрузимся в самый быстрый на текущий момент: nano ~/myscript.ipxe

#! ipxe

ifopen net0

set server_ip 192.168.1.100 set http_path http://${server_ip} set kern_name vmlinuz-linux

kernel ${http_path}/${kern_name} || read void initrd ${http_path}/initramfs-linux.img || read void imgargs ${kern_name} add_efi_memmap ip=${net0/ip}:${server_ip}:${net0/gateway}:${net0/netmask}:: eth0: none nbd_host=${server_ip} nbd_name=habrahabr root=/dev/nbd0 || read void boot || read void Мы планируем получать файлы vmlinuz-linux и initramfs по протоколу HTTP. Внедрим наш скрипт в загрузчик: sudo pacman -S git && git clone git://git.ipxe.org/ipxe.git cd ipxe/src/ make bin/undionly.kpxe EMBED=/home/username/myscript.ipxe Возвращаемся в root на сервере и копируем загрузчик: cp {/home/username/ipxe/src/bin,$root/boot}/undionly.kpxe Исправим DHCP сервер таким образом, чтобы он предлагал скачивать новый файл: nano /etc/dhcpd.conf #if option architecture = 7 { # filename »/grub/x86_64-efi/core.efi»; # } else { # filename »/grub/i386-pc/core.0»; #} filename »/undionly.kpxe»; systemctl restart dhcpd4 Устанавливаем HTTP сервер

pacman -S apache привязываем папку с загрузчиком к рабочей папки сервера mount --bind /srv/nfs/diskless/boot/ /srv/http/ можно перемонтировать в режим «только для чтения» mount -o remount, ro, bind /srv/nfs/diskless/boot/ /srv/http/ запускаем сервер systemctl start httpd Смотрим, что происходит на сервере:

vnstat -l … rx | tx --------------------------------------±----------------- bytes 1,50 MiB | 206,73 MiB --------------------------------------±----------------- max 2,96 Mbit/s | 191,95 Mbit/s average 684,08 kbit/s | 94,08 Mbit/s min 5 kbit/s | 1 kbit/s --------------------------------------±----------------- packets 22762×90737 --------------------------------------±----------------- max 5735 p/s | 9871 p/s average 1264 p/s | 5040 p/s min 3 p/s | 1 p/s --------------------------------------±----------------- time 18 seconds Выигрыш в скорости загрузки от замены TFTP на HTTP заметен невооружённым глазом и это не единственный примечательный момент iPXE. Например, здесь показано, как можно прямо во время загрузки выбрать сервер с официальным образом установочной флешки и загрузиться в него прямо через Интернет без необходимости предварительного скачивания. Уверен, что теперь вы сможете повторить то же самое и со своим образом.

Возвращаемся на сервер Попробуйте добавить обработчик live в наш загрузочный сервер. Сейчас правила rsync пропускают копирование содержимого /srv, где у нас находятся файлы клиента. Мы можем поменять правила или примонтировать директорию с помощью systemd: nano /etc/fstab

LABEL=HABR / ext4 rw, relatime, data=ordered 0 1 /srv/new_root/srv /srv none bind 0 1 В данном случае папки /srv/new_root/srv и /srv связываются в режиме полного доступа на чтение и запись, но мы знаем решения.Тот факт, что загрузочный сервер может работать в режиме «только для чтения», будет весьма полезен для систем, установленных на недорогую USB флешку. С такого накопителя лучше побольше читать, и поменьше на него записывать. Если вы откроете его в интернет, то получите дополнительную степень защиты. Например, роутер открывает защищенный VPN канал, на другом конце которого находится загрузочный сервер…

Чтобы переписать систему на флешку, её нужно вставить в компьютер и подключить к VirtualBox (Меню Устройства > Устройства USB и выбрать нужную из списка). Список доступных блочных устройств проверяется командой lsblk, как в самой первой статье. Разметьте её пометив загрузочной, отформатируйте с той же меткой и примонтируйте к /mnt.

Создадим новый файл с правилами для rsync:

nano /root/clone_filter

+ /boot/* + /etc/* + /home/* + /srv/* + /usr/* + /var/*  — /*/* Дождитесь выполнения команды: rsync -aAX /* /mnt --filter=«merge /root/clone_filter» Остаётся отмонтировать флешку и можно с неё загружаться.PS Решение разрабатывалось для автоматизации компьютерных классов. Система одинаково работает на пожертвованных и новых компьютерах с самыми разнообразными конфигурациями. Восстановление системы на клиенте к первоначальному состоянию производится обычной перезагрузкой. Нужные данные можно сохранять на диске загрузочного сервера или на любом другом сетевом или локальном накопителе.

Поделитесь своими идеями применения.

© Habrahabr.ru