nos_
ENRU

Установка nos на выделенный сервер

Разверните системный образ nos на свободный раздел Linux-сервера, загрузите его один раз через GRUB самого сервера и войдите по SSH.

13 мин чтенияОбновлено

В итоге на физическом сервере x86-64 будет работать nos со своего собственного раздела: ядро и все модули из системного образа релиза, загруженные через GRUB самого сервера, и SSH-сервер nos, который пускает по выданному вами ключу. Linux на сервере остаётся на месте и остаётся загрузкой по умолчанию: nos загружается один раз, когда вы об этом просите, а следующая загрузка — снова Linux.

Понадобится:

  • сервер x86-64 с Linux и GRUB 2, root-доступ к нему и оборудование, для которого у nos есть драйверы (см. раздел «Сначала проверьте сервер»);
  • свободный обычный раздел на нём, не меньше 64 МиБ;
  • ключ ssh-ed25519: sshd в nos других типов не принимает;
  • возможность перезагрузить сервер снаружи (кнопка сброса или API хостинг-провайдера) — на случай, если nos не поднимется.

Так nos работает на собственных серверах проекта в Hetzner: со своего раздела, загружаясь по одному разу рядом с Linux сервера. Полный справочник — страница Install: nos on a server.

Почему это безопасно

Установка устроена так, что неудачная загрузка nos стоит вам одной перезагрузки, а не сервера:

  • У nos свой раздел. На нём файловая система ext2, которую nos монтирует как корневую. В Linux на сервере nos-install добавляет один файл, /etc/grub.d/42_nos, и пересобирает grub.cfg. Больше ничего не трогается.
  • Пункт загрузки по умолчанию не меняется. Загрузку nos «взводит» флаг nos_next=nos в блоке окружения GRUB на разделе nos. GRUB читает флаг, один раз загружает nos и сбрасывает флаг до того, как что-либо загрузить. Первый пункт самого сервера остаётся запасным.
  • reboot внутри nos возвращает в Linux, потому что флага уже нет.
  • От nos, который так и не поднялся, до Linux — одна перезагрузка. После паники, зависания или ядра, которое GRUB не смог загрузить, откатывать нечего.
  • После паники nos перезагружается сам. Пункты GRUB из образа передают ядру panic=10: через десять секунд после отчёта о панике машина перезагружается, и после однократной загрузки это означает Linux.

Сначала проверьте сервер

nos работает только на оборудовании, для которого у него есть драйверы, а единственная консоль сервера — его сеть. nos-install проверяет каждый пункт ниже и сообщает, что нашёл: неподходящий раздел он отвергает, об остальном предупреждает. Исправить что-либо из этого он не может.

Что нужно Почему
Диск NVMe или virtio под раздел nos (virtio-scsi тоже подходит) nos читает только такие диски. С диска SATA или из-за RAID-контроллера GRUB всё равно загрузит ядро через прошивку, но nos не найдёт на нём своего корня: ни модулей, ни /etc/rc, ни sshd.
Сетевая карта с драйвером в nos: Realtek RTL8168 или RTL8125, Intel 82576, I210 или I211 (драйвер igb) либо virtio-net С любой другой картой nos загрузится, но достучаться до него будет нельзя.
DHCP в этой сети Статической настройки IP в nos нет.
Обычный раздел не меньше 64 МиБ (с 256 МиБ свободнее; дискам гостевых систем нужно больше) Он не может входить в md RAID, быть томом LVM или быть зашифрован: ничего из этого nos не читает. На сервере, где диски целиком зеркалированы, такого раздела нет, поэтому оставьте под него место при установке сервера (installimage в Hetzner принимает список разделов) или создайте его из свободного места.
Linux, загружаемый через GRUB 2, BIOS или UEFI, с выключенным Secure Boot nos — ядро Multiboot2, а GRUB в режиме блокировки Secure Boot такое ядро загружать отказывается.
e2fsprogs, util-linux, утилиты GRUB (grub-editenv, а также update-grub или grub-mkconfig) и поддержка squashfs в ядре или unsquashfs Всё это запускает nos-install. В стандартных Ubuntu и Debian всё есть.

Скачайте системный образ

Устанавливается системный образ nos-x86_64.squashfs. Он собирается для каждого тега релиза. На сервере:

wget https://github.com/irqlevel/nos/releases/latest/download/nos-x86_64.squashfs
wget https://github.com/irqlevel/nos/releases/latest/download/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS

Что внутри:

Путь Что это
/boot/kernel64.elf ядро
/boot/grub/nos.cfg его пункты GRUB, nos и nos (next), с той командной строкой, под которую собрано ядро
/lib/modules/*.ko все модули, собранные вместе с этим ядром, поэтому каждый из них в него загружается
/etc/rc скрипт загрузки по умолчанию: insmod /lib/modules/sshd.ko, затем sshd start
/nos-install установщик, соответствующий этому образу
/VERSION version=, revision=, arch=, module_abi=

В образе нет ничего, что относилось бы к конкретной машине: ни ключа хоста, ни разрешённых ключей, ни адреса. Владельцы, времена и права файлов зафиксированы, поэтому сборка каждый раз даёт одни и те же байты, и sha256 образа однозначно определяет его содержимое.

Чтобы собрать образ самому, выполните make image в Linux с установленным тулчейном или scripts/build-image.sh везде, где работает Docker (--clean собирает с нуля, как при релизе). В обоих случаях nos-x86_64.squashfs появится в корне дерева. См. Сборка nos.

Разверните образ на раздел

Под root на сервере смонтируйте образ и запустите лежащий в нём nos-install. Замените /dev/nvme0n1p4 на свой свободный раздел:

mount -o loop,ro nos-x86_64.squashfs /mnt
/mnt/nos-install install /dev/nvme0n1p4 --ssh-key /root/.ssh/authorized_keys
umount /mnt

--ssh-key принимает файл .pub, файл authorized_keys или строку ключа и может повторяться. Если передать /root/.ssh/authorized_keys самого сервера, в nos будут пускать те же ключи, что уже пускают в Linux. Из файла nos-install берёт строки ssh-ed25519 и сообщает, какие пропустил: ключи других типов и строки с опциями перед ключом. Отдельно переданная строка ключа другого типа, как и файл без единого ключа ssh-ed25519, отвергаются. Сама опция необязательна, но без ключа sshd не пустит никого, а если ещё и UDP-шелл выключен, в загруженный nos не попасть вообще.

install проходит эти шаги и останавливается на первой же проблеме:

  1. Проверяет образ и раздел. В образе должны быть /VERSION, ядро и модули, и он должен быть для архитектуры этой машины. Раздел должен быть блочным устройством, не может быть целым диском с разделами, не может быть занят md, LVM или device mapper и не может быть одной из файловых систем работающей системы. install предупреждает, если диск не NVMe и не virtio, если нет сетевой карты с драйвером в nos и если включён Secure Boot.
  2. Готовит файловую систему. Пустой раздел получает файловую систему ext2 с меткой nos в том формате, который пишет nos. Раздел с ext2, в которую nos умеет писать, сохраняет всё своё содержимое. Всё остальное отвергается, если не передан --format, который создаёт новую файловую систему, и прежнее содержимое теряется.
  3. Копирует систему. Монтирует раздел и кладёт туда ядро, модули, nos.cfg и /VERSION. Если на разделе они уже были, старые три переезжают в .prev вместе, для последующего rollback. /etc принадлежит машине: /etc/rc из образа кладётся, только если своего нет, а ваши ключи дописываются в /etc/ssh/authorized_keys. Если процессор Intel и у сервера есть для него файл микрокода в /lib/firmware/intel-ucode, этот файл копируется в /boot/ucode, и GRUB загружает его рядом с ядром. Если файла нет (процессор AMD или не установлен пакет intel-microcode), nos работает с микрокодом, загруженным прошивкой.
  4. Пишет /grubenv на раздел. Там параметры ядра для этой машины и флаг однократной загрузки. e2fsck проверяет файловую систему до записи и после неё.
  5. Добавляет пункт в GRUB. Пишет /etc/grub.d/42_nos, который находит раздел по UUID, запускает update-grub (или grub-mkconfig) и проверяет, что в grub.cfg он появился.

Опции install и update:

Опция Что делает
--ssh-key KEY Добавляет ключ, по которому пускает sshd.
--udpshell[=PORT] Включает UDP-шелл на порту 9000 или PORT. Выключен, если не задан: он выполняет любую команду для любого, кто может отправить машине датаграмму (UDP shell).
--netconsole IP:PORT Шлёт лог ядра по UDP на IP:PORT (Netconsole). Это единственный способ увидеть загрузку, которая умирает уже после подъёма сети.
--cmdline "ARGS" Добавляет параметры ядра (Kernel parameters).
--format Создаёт на разделе новую файловую систему, даже поверх существующей.
--boot-once, --reboot Взводит одну загрузку nos на следующую загрузку; --reboot вдобавок сразу перезагружает.
--image FILE Указывает образ, если запускается не тот nos-install, что лежит внутри него.

Загрузите nos один раз

Взведите одну загрузку и перезагрузитесь. Вместо этого можно передать --reboot в install или update:

mount -o loop,ro nos-x86_64.squashfs /mnt
/mnt/nos-install boot --reboot

boot записывает nos_next=nos в /grubenv на разделе. При следующей загрузке GRUB читает его через 42_nos, делает пункт nos загрузкой по умолчанию на этот один раз, оставляя запасным первый пункт сервера, и сбрасывает флаг, прежде чем что-либо загрузить.

Командная строка ядра — root=UUID=<partition-uuid> dhcp=auto dns=on panic=10 (UUID раздела nos) плюс то, что задано для этой машины через --udpshell, --netconsole и --cmdline. nos монтирует раздел как корень на чтение и запись, получает адрес по DHCP и выполняет /etc/rc. /etc/rc из образа загружает и запускает sshd. При первом запуске sshd создаёт ключ хоста из пула энтропии ядра и хранит его в /etc/ssh/ssh_host_ed25519_key для всех следующих загрузок. Пускает он по ключам из /etc/ssh/authorized_keys.

Примерно через минуту после перезагрузки, со своей машины:

ssh -o UserKnownHostsFile=~/.ssh/known_hosts.nos root@<server> version

Команда выведет nos <version> (<revision>). Отдельный файл known_hosts нужен потому, что у nos собственный ключ хоста, а не тот, что ваш known_hosts уже помнит для Linux по этому адресу. Чтобы у обеих систем был один ключ хоста, до первой загрузки скопируйте /etc/ssh/ssh_host_ed25519_key из Linux по тому же пути на раздел nos: sshd в nos читает формат OpenSSH.

Когда закончите, reboot в nos вернёт сервер в его Linux.

Или разверните всё со своей машины

scripts/nos-deploy.py делает всё описанное выше с машины, где лежит образ, по SSH в Linux сервера под root:

scripts/nos-deploy.py root@<server> install /dev/nvme0n1p4 --ssh-key ~/.ssh/id_ed25519.pub
scripts/nos-deploy.py root@<server>

Первая команда устанавливает. Вторая, без команды, означает update — её и запускают каждый следующий раз. Скрипт:

  1. копирует образ на сервер, проверяет там его sha256 и монтирует;
  2. запускает лежащий в образе nos-install с --boot-once и перезагружает сервер;
  3. ждёт, пока на порту 22 ответит собственный sshd nos с баннером SSH-2.0-nos_sshd вместо прежнего OpenSSH;
  4. входит и проверяет, что работает ядро из образа.

Его опции:

Опция Что делает
--image FILE Берёт этот образ вместо образа по умолчанию — nos-x86_64.squashfs в корне дерева (от make image или scripts/build-image.sh).
--no-boot Кладёт образ на сервер и останавливается перед перезагрузкой.
--wait SECONDS Задаёт, сколько ждать подъёма nos (по умолчанию 600).
--ssh-key, --udpshell[=PORT], --netconsole IP:PORT, --cmdline ARGS, --format Передаются в nos-install.

Проверьте, что установлено

status запускается из смонтированного образа и опций не принимает:

mount -o loop,ro nos-x86_64.squashfs /mnt
/mnt/nos-install status

Он называет пункт GRUB и раздел, что установлено сейчас и что было до этого, параметры ядра, взведена ли загрузка, сколько ключей SSH и ревизию микрокода. На сервере с AMD сразу после первой установки это выглядит так:

GRUB entry:  /etc/grub.d/42_nos, UUID <uuid>
partition:   /dev/nvme0n1p4
installed:   nos <version> (<revision>)
previous:    none
parameters:  none
next boot:   this machine's default
ssh keys:    1
microcode:   none; nos runs firmware's

Обновление, откат, удаление

Чтобы поставить более новый образ поверх текущего, скачайте его, как в разделе «Скачайте системный образ», и выполните:

mount -o loop,ro nos-x86_64.squashfs /mnt
/mnt/nos-install update --reboot

update находит раздел через 42_nos, так что указывать его снова не нужно. Новые ядро, модули и nos.cfg кладутся на место. Старые ядро, модули и /VERSION вместе уезжают в .prev, а микрокод заново копируется из /lib/firmware сервера. /etc (rc, ключи, ключ хоста) и всё остальное на разделе остаются. Параметры ядра тоже остаются, если не передан --udpshell, --netconsole или --cmdline. Любая из этих опций заменяет все параметры целиком, а --cmdline "" их очищает.

Ядро и его модули всегда заменяются только вместе: ядро отвергает модуль, собранный под другой интерфейс ядра. /lib/modules принадлежит образу, поэтому собственные модули держите в другом месте.

Остальные команды тоже запускаются из смонтированного образа:

  • /mnt/nos-install rollback меняет местами текущие ядро, модули и /VERSION с версиями из .prev.
  • /mnt/nos-install uninstall удаляет 42_nos, пересобирает конфигурацию GRUB, чтобы nos пропал из меню, и не трогает раздел.

Как опробовать ядро изнутри nos

Работающий nos может опробовать ядро из релиза, прежде чем оно заменит установленное. Выполните это в шелле nos:

wget https://github.com/irqlevel/nos/releases/download/<tag>/kernel-x86_64.elf /boot/kernel64.elf.next
sha256 /boot/kernel64.elf.next
grubenv /grubenv nos_next=nos-next
reboot

Сверьте вывод sha256 с SHA256SUMS релиза. После этого один раз загрузится пункт nos (next). Модулей он не приносит: модули в /lib/modules принадлежат работающему ядру, и новое ядро их отвергнет, если у них другой интерфейс ядра. Образ целиком обновляйте из Linux.

Как вернуться в Linux

  • Из работающего nos: выполните reboot. Флага однократной загрузки уже нет, поэтому GRUB загрузит пункт сервера по умолчанию.
  • Если nos завис или не отвечает: перезагрузите сервер снаружи. GRUB сбросил флаг ещё до загрузки nos, так что поднимется Linux.
  • После паники: подождите. panic=10 перезагрузит машину через десять секунд после отчёта.
  • Чтобы убрать nos из меню: выполните nos-install uninstall из Linux, как описано выше.

Если вы хотите оставаться в nos между перезагрузками, сделайте его загрузкой сервера по умолчанию: задайте GRUB_DEFAULT=nos в /etc/default/grub и выполните update-grub.

Если nos — загрузка по умолчанию, то nos, который не поднимается, будет загружаться снова после каждого сброса, и выбраться можно будет только через rescue-систему хостинг-провайдера.

Если что-то пошло не так

nos-deploy: <server> came back with <banner>, not nos -- GRUB did not boot it. Сервер вернулся в Linux, а не в nos. Проверьте, что Secure Boot выключен. Ещё поищите собственный пункт сервера с id nos или nos-next, например созданный вручную в /etc/grub.d/40_custom: он стоит в grub.cfg первым, и GRUB загружает именно его. nos-install предупреждает, если находит такой пункт.

no nos on <address>:22 after 600 s. nos либо погиб, либо поднялся, но недоступен. Перезагрузите сервер, чтобы вернуть Linux, и выясните причину:

  • Переустановите с --netconsole IP:PORT, указав машину под вашим контролем, и запустите там python3 scripts/netconsole.py -p PORT. Лог ядра, включая отчёт о панике, придёт по UDP (Netconsole).
  • Для загрузки, которая умирает до подъёма сети, используйте disklog=on. Ему нужна область на диске, подготовленная scripts/disklog.py format (Kernel parameters).

ssh отвергает ключ хоста или предупреждает, что он изменился. Ключ хоста nos не совпадает с ключом Linux на сервере. Используйте для nos отдельный файл known_hosts, как в разделе «Загрузите nos один раз», или скопируйте ключ хоста Linux на раздел.

sshd в nos отвергает ваш ключ. Он принимает только ключи ssh-ed25519. Добавьте такой через --ssh-key при следующем update или командой sshd allow изнутри nos (sshd).

WARNING: ... is not on an NVMe or a virtio disk. GRUB загрузит с него ядро, но nos не найдёт там своего корня, а значит, не будет ни sshd, ни способа войти. Разместите раздел на диске NVMe или virtio.

... is in use by ... (RAID, LVM, device mapper). nos нужен обычный раздел: ничего из перечисленного он не читает.

... has a <type> filesystem on it, which nos does not take. Передавайте --format, только если готовы потерять содержимое этого раздела.

/etc/rc on it runs 'insmod ...': that module is not the image's. После обновления модуль, загружаемый не из /lib/modules, оказывается от другой сборки, и новое ядро его отвергает. Пересоберите его под новое ядро.

e2fsck finds ... damaged. Выполните из Linux e2fsck -f <partition>, чтобы починить файловую систему, и запустите nos-install снова.

Что дальше