Меня зовут Владислав, я системный инженер в «Инферит Облако» — российском провайдере облачной инфраструктуры для бизнеса. В работе с большими парками серверов я часто сталкивался с проблемами классической автоустановки Ubuntu Server, и в статье расскажу, как удалось упростить этот процесс.
Проблема и мотивация
Работая с большим парком серверов, я не раз сталкивался с проблемами классической автоустановки операционных систем. PXE, DHCP, TFTP/HTTP-серверы, таблицы MAC-адресов и ручная правка preseed-файлов превращали процесс в настоящий кошмар. В этой статье расскажу, как мы упростили установку Ubuntu Server, используя самодостаточный ISO-образ с autoinstall, который автоматически определяет оборудование, настраивает сеть и запрашивает конфигурацию через API, минимизируя ручную работу.
Проблема: почему PXE — это боль
Классическая установка через PXE требует сложной инфраструктуры: DHCP-сервер с кастомными настройками, TFTP или HTTP-сервер для загрузки образов, а также таблицы MAC-адресов, которые нужно заранее собирать. Процесс выглядит так:
Загружаешься с live-образа, чтобы собрать MAC-адреса и информацию о дисках.
Вносишь данные в таблицу или базу.
Генерируешь preseed-файл и надеешься, что он корректен.
Запускаешь установку — если повезет.
Каждый новый сервер — это ручная сверка, правка конфигов и потерянное время. Хотелось решения, где инженер вносит данные один раз в одном месте, а установка проходит автоматически.
Решение: самодостаточный ISO с autoinstall
Был разработан подход, который устраняет необходимость в PXE и сложной инфраструктуре. Вместо этого используется ISO-образ Ubuntu Server, который:
Содержит минимальный autoinstall.yaml для поднятия сети.
Автоматически определяет сетевые интерфейсы и диски.
Запрашивает полный конфигурационный файл через API.
Выполняет установку без участия человека.
Преимущества подхода
Простота: нет необходимости в DHCP, TFTP или таблицах MAC-адресов.
Гибкость: ISO сам определяет оборудование, что снижает риск ошибок.
Автоматизация: инженер вносит данные только в API, остальное происходит автоматически.
Как это работает: общая схема
Загрузка ISO: сервер загружается с модифицированного ISO-образа Ubuntu Live Server.
Поднятие сети: autoinstall.yaml настраивает сетевые интерфейсы (bonding, VLAN) и получает IP через DHCP.
Сбор данных: скрипты собирают информацию о сетевых интерфейсах, дисках и IPMI.
Запрос к API: данные отправляются на сервер API, который возвращает полный autoinstall.yaml.
Установка: Cloud-init применяет полученный YAML для завершения установки.

Шаг 1. Подготовка ISO-образа:
Для создания самодостаточного ISO-образа мы используем Ubuntu Live Server как основу.
Процесс включает следующие шаги:
Распаковка ISO:
7z -y x jammy-live-server-amd64.iso -osource-files
Редактирование GRUB.
Открываем boot/grub/grub.cfg и добавляем запись для автоматической установки:
menuentry "Autoinstall Ubuntu Server" {
set gfxpayload=keep
linux /casper/vmlinuz autoinstall ---
initrd /casper/initrd
}Добавление autoinstall.yaml.
Копируем файл autoinstall.yaml в папку source-files. Этот файл содержит начальную конфигурацию для поднятия сети и запроса финального YAML через API.
Сборка ISO: используем xorriso для создания нового ISO:
xorriso -as mkisofs -r \
-V 'Ubuntu 22.04 LTS AUTO (EFIBIOS)' \
-o ../ubuntu-22.04-autoinstall.iso \
--grub2-mbr ../BOOT/1-Boot-NoEmul.img \
-partition_offset 16 \
--mbr-force-bootable \
-append_partition 2 28732ac11ff8d211ba4b00a0c93ec93b ../BOOT/2-Boot-NoEmul.img \
-appended_part_as_gpt \
-iso_mbr_part_type a2a0d0ebe5b9334487c068b6b72699c7 \
-c '/boot.catalog' \
-b '/boot/grub/i386-pc/eltorito.img' \
-no-emul-boot -boot-load-size 4 -boot-info-table --grub2-boot-info \
-eltorito-alt-boot \
-e '--interval:appended_partition_2:::' \
-no-emul-boot \
.
Этот процесс создает ISO, который автоматически запускает установку без необходимости PXE-инфраструктуры.
Шаг 2: Настройка autoinstall.yaml
Файл autoinstall.yaml — сердце решения. Он выполняет начальную настройку и
подготавливает сервер к полноценной установке. Основные задачи:
Настройка сети (bonding, VLAN, DHCP).
Сбор информации о дисках и IPMI.
Запрос финального YAML через API.
Пример начального autoinstall.yaml:
#cloud-config autoinstall: version: 1 debug: true early-commands: # Загрузка модуля VLAN - modprobe 8021q || true # Поиск сетевых интерфейсов Mellanox - lshw -c network -json 2>/dev/null | awk -v RS='{' -F'"' '/vendor/ && /Mellanox Technologies/ {for(i=1;i<=NF;i++){if($i=="logicalname"){print $(i+2)}}}' > /tmp/mellanox_interfaces # Настройка bonding - ip link add bond0 type bond - ip link set bond0 type bond miimon 100 mode 802.3ad - | first_card=$(sed -n '1p' /tmp/mellanox_interfaces) second_card=$(sed -n '2p' /tmp/mellanox_interfaces) ip link set $first_card down ip link set $second_card down ip link set $first_card master bond0 ip link set $second_card master bond0 - ip link set bond0 up - sleep 10 # Настройка VLAN - ip link add link bond0 name bond0.160 type vlan id 160 - ip link set bond0.160 up - sleep 10 - dhclient bond0.160 || true - ip a > /tmp/network-status.log # Обнаружение дисков - | lsblk -dno NAME,TRAN,MODEL | awk '$2 == "sata" || $2 == "nvme" {print $1 ":" $3}' > /tmp/disks if [ -s /tmp/disks ]; then awk -F: ' { model = $2 gsub(/^[[:space:]]+|[[:space:]]+$/, "", model); if (model != "") { count[model]++ disks[model] = disks[model] $1 "\n" } } END { for (m in count) { if (count[m] >= 1) { printf "%s", disks[m] } } }' /tmp/disks > /tmp/disk fi # Получение IPMI IP - apt update - apt install ipmitool -y - ipmitool lan print 1 | grep 'IP Address' | grep -v Source | awk -F: '{print $2}' | awk '{print $1}' > /tmp/lan # Запрос финального YAML через API - | first_disk=$(sed -n '1p' /tmp/disk 2>/dev/null | tr -d '\n' || echo "sda") second_disk=$(sed -n '2p' /tmp/disk 2>/dev/null | tr -d '\n' || echo "$first_disk") ipmi_ip=$(sed -n '1p' /tmp/lan 2>/dev/null | tr -d '\n') bond0_iface0=$(sed -n '1p' /tmp/mellanox_interfaces) bond0_iface1=$(sed -n '2p' /tmp/mellanox_interfaces) curl --retry 3 --retry-delay 5 -X POST https://service_ip:5111/api/update_host \ -H "Content-Type: application/json" \ -H "Accept: application/x-yaml" \ -d "{\"ipmi_ip\": \"$ipmi_ip\", \"bond0_iface0\": \"$bond0_iface0\", \"bond0_iface1\": \"$bond0_iface1\", \"first_disk\": \"$first_disk\", \"second_disk\": \"$second_disk\"}" \ --output /autoinstall.yaml # Проверка успешности запроса - | if [ ! -s /autoinstall.yaml ]; then echo "Failed to fetch autoinstall.yaml from API" > /tmp/autoinstall-error.log exit 1 fi locale: en_US.UTF-8 keyboard: layout: us storage: config: - type: disk id: disk-sda ptable: gpt path: /dev/$first_disk wipe: superblock grub_device: true - type: partition id: efi-part-sda device: disk-sda size: 512M flag: boot - type: format id: efi-format-sda volume: efi-part-sda fstype: fat32 label: ESP - type: mount id: efi-mount-sda device: efi-format-sda path: /boot/efi identity: realname: testuser hostname: testserver01 username: testuser password: “.…” timezone: Europe/Moscow late-commands: - curtin in-target -- apt-get update - curtin in-target -- apt-get install -y curl vim ssh openssh-server
Объяснение ключевых моментов:
Сеть: создается bonding-интерфейс bond0 в режиме LACP (802.3ad) для повышения отказоустойчивости. На него настраивается VLAN 160, и IP-адрес запрашивается через DHCP.
Диски: скрипт определяет SATA и NVMe-диски через lsblk, сохраняя их имена для использования в разметке.
API: данные об оборудовании отправляются на сервер API, который возвращает финальный autoinstall.yaml с полной конфигурацией (диски, пользователи, пакеты).
Шаг 3: Работа с API
Для обработки запросов мы используем самописный сервис на Flask. Он принимает JSON с данными о сервере (IPMI IP, сетевые интерфейсы, диски) и возвращает YAML с полной конфигурацией. Пример запроса:
curl -X POST http://service_ip:5111/api/update_host \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "ipmi_ip": “ipmi_ip", "bond0_iface0": "enp94s0f0np0", "bond0_iface1": "enp94s0f1np1", "bond1_iface0": "enp95s0f0np0", "bond1_iface1": "enp95s0f1np1", "first_disk": "sda", "second_disk": "sdb" }'
Ответ:
#cloud-config autoinstall: version: 1 debug: true bootcmd: - ["systemctl", "enable", "debug-shell.service"] error-handling: continue-on-error: true network: version: 2 ethernets: enp94s0f0np0: dhcp4: false enp94s0f1np1: dhcp4: false enp95s0f0np0: dhcp4: false enp95s0f1np1: dhcp4: false bonds: bond0: interfaces: [enp94s0f0np0, enp94s0f1np1] mtu: 9100 parameters: mode: 802.3ad transmit-hash-policy: layer2+3 lacp-rate: slow dhcp4: false bond1: interfaces: [enp95s0f0np0, enp95s0f1np1] mtu: 9100 parameters: mode: 802.3ad transmit-hash-policy: layer2+3 lacp-rate: slow dhcp4: false vlans: bond0.60: id: 60 link: bond0 mtu: 9100 addresses: [bond0.60_ip] gateway4: gateway nameservers: addresses: [dns1_ip, dns2-ip] search: [search.domain] bond1.20: id: 20 link: bond1 mtu: 9100 addresses: [bond1.20_ip] storage: config: - type: disk id: disk-sda ptable: gpt path: /dev/sda wipe: superblock grub_device: true - type: disk id: disk-sdb ptable: gpt path: /dev/sdb wipe: superblock grub_device: true - type: partition id: efi-part-sda device: disk-sda size: 512M flag: boot grub_device: true - type: partition id: efi-part-sdb device: disk-sdb size: 512M flag: boot - type: partition id: raid-part-sda device: disk-sda size: -1 - type: partition id: raid-part-sdb device: disk-sdb size: -1 - type: raid id: md0 raidlevel: 1 devices: [raid-part-sda, raid-part-sdb] name: ubuntu-raid preserve: false wipe: superblock - type: format id: efi-format-sda volume: efi-part-sda fstype: fat32 label: ESP - type: format id: efi-format-sdb volume: efi-part-sdb fstype: fat32 - type: format id: root-format volume: md0 fstype: ext4 - type: mount id: efi-mount-sda device: efi-format-sda path: /boot/efi - type: mount id: root-mount device: root-format path: / timezone: Europe/Moscow identity: hostname: testserver01 username: testuser password: “…" late-commands: - curtin in-target -- apt-get update - curtin in-target -- apt-get install -y curl vim ssh openssh-server
Сервис подставляет данные в шаблон autoinstall.yaml, который затем используется для завершения установки.
Для сервиса написана простая админка, где добавляем сервера. Страница добавления сервера выглядит так:

Основная страница:

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



Пошла установка, начали выполняться early-commands:

После их выполнения мы видим заполненную данными с сервера табличку:

И установка пошла с необходимыми параметрами:

ОС установилась, сервер перезагрузился и поднялся с необходимой нам разметкой, пользователем и сетью.

Отладка и устранение ошибок
Для упрощения диагностики мы сохраняем логи:
Сетевой статус: /tmp/network-status.log
Список дисков: /tmp/disks
Ошибки API: /tmp/autoinstall-error.log
Если установка не начинается, проверьте:
Доступность API-сервера (curl https://service_ip:5111).
Корректность VLAN и bonding через cat /tmp/network-status.log.
Наличие дисков в /tmp/disk.
Преимущества и сравнение с PXE
Аспект | PXE | Наш подход |
Инфраструктура | DHCP, TFTP/HTTP, таблицы MAC | Только ISO и API |
Ручная работа | Сбор MAC, правка preseed | Ввод данных в API один раз |
Гибкость | Жесткая привязка к сети | Автоматическое определение оборудования |
Отладка | Сложная, много точек отказа | Логи и debug-режим в autoinstall |
Заключение
Описанный выше подход к автоматической установке Ubuntu Server устраняет сложности PXE, значительно упрощая весь процесс. Самодостаточный ISO-образ настраивает сеть, собирает данные об оборудовании и запрашивает конфигурацию через API, минимизируя ручную работу. Это решение идеально для дата-центров, CI/CD-систем и любых сценариев, где нужна быстрая и шаблонная установка серверов.
