Меня зовут Владислав, я системный инженер в «Инферит Облако» — российском провайдере облачной инфраструктуры для бизнеса. В работе с большими парками серверов я часто сталкивался с проблемами классической автоустановки Ubuntu Server, и в статье расскажу, как удалось упростить этот процесс.

Проблема и мотивация

Работая с большим парком серверов, я не раз сталкивался с проблемами классической автоустановки операционных систем. PXE, DHCP, TFTP/HTTP-серверы, таблицы MAC-адресов и ручная правка preseed-файлов превращали процесс в настоящий кошмар. В этой статье расскажу, как мы упростили установку Ubuntu Server, используя самодостаточный ISO-образ с autoinstall, который автоматически определяет оборудование, настраивает сеть и запрашивает конфигурацию через API, минимизируя ручную работу.

Проблема: почему PXE — это боль

Классическая установка через PXE требует сложной инфраструктуры: DHCP-сервер с кастомными настройками, TFTP или HTTP-сервер для загрузки образов, а также таблицы MAC-адресов, которые нужно заранее собирать. Процесс выглядит так:

  1. Загружаешься с live-образа, чтобы собрать MAC-адреса и информацию о дисках.

  2. Вносишь данные в таблицу или базу.

  3. Генерируешь preseed-файл и надеешься, что он корректен.

  4. Запускаешь установку — если повезет.

Каждый новый сервер — это ручная сверка, правка конфигов и потерянное время. Хотелось решения, где инженер вносит данные один раз в одном месте, а установка проходит автоматически.

Решение: самодостаточный ISO с autoinstall

Был разработан подход, который устраняет необходимость в PXE и сложной инфраструктуре. Вместо этого используется ISO-образ Ubuntu Server, который:

  • Содержит минимальный autoinstall.yaml для поднятия сети.

  • Автоматически определяет сетевые интерфейсы и диски.

  • Запрашивает полный конфигурационный файл через API.

  • Выполняет установку без участия человека.

Преимущества подхода

  • Простота: нет необходимости в DHCP, TFTP или таблицах MAC-адресов.

  • Гибкость: ISO сам определяет оборудование, что снижает риск ошибок.

  • Автоматизация: инженер вносит данные только в API, остальное происходит автоматически.

Как это работает: общая схема

  1. Загрузка ISO: сервер загружается с модифицированного ISO-образа Ubuntu Live Server.

  2. Поднятие сети: autoinstall.yaml настраивает сетевые интерфейсы (bonding, VLAN) и получает IP через DHCP.

  3. Сбор данных: скрипты собирают информацию о сетевых интерфейсах, дисках и IPMI.

  4. Запрос к API: данные отправляются на сервер API, который возвращает полный autoinstall.yaml.

  5. Установка: Cloud-init применяет полученный YAML для завершения установки.

Шаг 1. Подготовка ISO-образа:

Для создания самодостаточного ISO-образа мы используем Ubuntu Live Server как основу.

Процесс включает следующие шаги:

  1. Распаковка ISO:

    7z -y x jammy-live-server-amd64.iso -osource-files

  2. Редактирование GRUB. 

    Открываем boot/grub/grub.cfg и добавляем запись для автоматической установки:

    menuentry "Autoinstall Ubuntu Server" {
    set gfxpayload=keep
    linux /casper/vmlinuz autoinstall ---
    initrd /casper/initrd
    }

  3. Добавление autoinstall.yaml.

    Копируем файл autoinstall.yaml в папку source-files. Этот файл содержит начальную конфигурацию для поднятия сети и запроса финального YAML через API.

  4. Сборка 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

Если установка не начинается, проверьте:

  1. Доступность API-сервера (curl https://service_ip:5111).

  2. Корректность VLAN и bonding через cat /tmp/network-status.log.

  3. Наличие дисков в /tmp/disk.

Преимущества и сравнение с PXE

Аспект 

PXE

Наш подход

Инфраструктура

DHCP, TFTP/HTTP, таблицы MAC

Только ISO и API

Ручная работа

Сбор MAC, правка preseed

Ввод данных в API один раз

Гибкость

Жесткая привязка к сети

Автоматическое определение

оборудования

Отладка

Сложная, много точек отказа

Логи и debug-режим в autoinstall

Заключение

Описанный выше подход к автоматической установке Ubuntu Server устраняет сложности PXE, значительно упрощая весь процесс. Самодостаточный ISO-образ настраивает сеть, собирает данные об оборудовании и запрашивает конфигурацию через API, минимизируя ручную работу. Это решение идеально для дата-центров, CI/CD-систем и любых сценариев, где нужна быстрая и шаблонная установка серверов.

Комментарии (0)


  1. vmcore
    25.09.2025 12:30

    насколько я понимаю, подключать Virtual Media все равно приходится вручную?


    1. KotPoliglot Автор
      25.09.2025 12:30

      у нас автоматизировано через инструмент от производителя материнской платы, но загружаться с него нужно вручную, хотя некоторые производители серверов и это позволяют автоматизировать


  1. kvazimoda24
    25.09.2025 12:30

    Так нужен DHCP или не нужен? Вы уж определитесь :)

    Ну а так, можно было просто клонзиллой раскатывать образ с CloudInit'ом. Ну и выбор между ISO'ошником и PXE... У каждого варианта свои плюсы и свои минусы. Мне в этом плане больше понравилось собрать микро ISO'шник с iPXE (или грузить по PXE), куда можно прописать скрипт, который уже хоть по HTTP загрузит ядро и initramfs. А дальше уже все то, что вы там насобирали в ISO'нике.