Ай Лайн
Связаться
Отдел продаж +7 (812) 385-74-84
Время работы пн-пт с 9:00 до 18:00

Установка Ubuntu Server по PXE (autoinstall)

Полностью автоматическая установка Ubuntu Server по сети: TFTP + веб-сервер, загрузка по UEFI, конфигурация autoinstall для cloud-init. Проверялось на Ubuntu 20.04; сервер развёртывания — Ubuntu 18.04 и новее. Если у целевой машины всего 2 ГБ ОЗУ, дополнительно см. Ubuntu autoinstall на машине с 2 ГБ ОЗУ.

Все команды выполняются от root.

Сервер TFTP и веб-сервер

Ставим TFTP-сервер (по нему грузится загрузчик) и Apache (по нему отдаются ISO и файлы автоустановки):

apt-get -y install tftpd-hpa apache2

Публикуем каталог TFTP через Apache — так один и тот же каталог доступен и по TFTP, и по HTTP:

cat > /etc/apache2/conf-available/tftp.conf <<EOF
<Directory /var/lib/tftpboot>
        Options +FollowSymLinks +Indexes
        Require all granted
</Directory>
Alias /tftp /var/lib/tftpboot
EOF
a2enconf tftp
systemctl restart apache2

Ядро и initrd из установочного образа

Скачиваем образ live-сервера:

wget http://old-releases.ubuntu.com/releases/20.04/ubuntu-20.04-live-server-amd64.iso -O /var/lib/tftpboot/ubuntu-20.04-live-server-amd64.iso

Извлекаем из него ядро и initramfs — грузиться по сети будут именно они, а сам ISO установщик подтянет по HTTP:

mount /var/lib/tftpboot/ubuntu-20.04-live-server-amd64.iso /mnt/
cp /mnt/casper/vmlinuz /var/lib/tftpboot/
cp /mnt/casper/initrd /var/lib/tftpboot/
umount  /mnt

Загрузчик GRUB для сетевой загрузки

Скачиваем подписанный сетевой GRUB (нужен для UEFI Secure Boot):

wget http://archive.ubuntu.com/ubuntu/dists/focal/main/uefi/grub2-amd64/current/grubnetx64.efi.signed -O /var/lib/tftpboot/pxelinux.0

Конфигурация загрузчика даёт два пункта меню: полностью автоматическая установка и обычная ручная.

mkdir -p /var/lib/tftpboot/grub
cat > /var/lib/tftpboot/grub/grub.cfg <<'EOF'
default=autoinstall
timeout=30
timeout_style=menu
menuentry "Focal Live Installer - automated" --id=autoinstall {
    echo "Loading Kernel..."
    # символ ';' обязательно экранировать или взять аргумент в кавычки
    linux /vmlinuz ip=dhcp url=http://${pxe_default_server}/tftp/ubuntu-20.04-live-server-amd64.iso autoinstall ds="nocloud-net;s=http://${pxe_default_server}/tftp/" root=/dev/ram0 cloud-config-url=/dev/null
    echo "Loading Ram Disk..."
    initrd /initrd
}
menuentry "Focal Live Installer" --id=install {
    echo "Loading Kernel..."
    linux /vmlinuz ip=dhcp url=http://${pxe_default_server}/tftp/ubuntu-20.04-live-server-amd64.iso root=/dev/ram0 cloud-config-url=/dev/null
    echo "Loading Ram Disk..."
    initrd /initrd
}
EOF

Разбор параметров ядра:

Параметр Зачем
ip=dhcp получить адрес по DHCP до старта установщика
url=…iso откуда установщику скачать образ
autoinstall включить автоматическую установку без вопросов
ds="nocloud-net;s=…" где лежат meta-data и user-data для cloud-init (каталог, слэш в конце обязателен)
root=/dev/ram0 корень разворачивается в ОЗУ
cloud-config-url=/dev/null не дать cloud-init второй раз скачать ISO в память — см. статью про 2 ГБ ОЗУ

Конфигурация автоустановки

cloud-init ждёт в каталоге из ds= два файла. Первый — meta-data, он может быть почти пустым:

cat > /var/lib/tftpboot/meta-data <<EOF
instance-id: focal-autoinstall
EOF

Второй — user-data с секцией autoinstall. Удобный способ получить рабочую основу: один раз поставить систему руками и взять сгенерированный файл /var/log/installer/autoinstall-user-data, а затем поправить под себя.

cat > /var/lib/tftpboot/user-data <<'EOF'
#cloud-config
autoinstall:
  version: 1
  # interactive-sections не даёт автоматически перезагрузиться в конце
  #interactive-sections:
  #  - locale
  apt:
    # даже при no/false запрос geoip всё равно выполняется
    #geoip: no
    preserve_sources_list: false
    primary:
    - arches: [amd64, i386]
      uri: http://us.archive.ubuntu.com/ubuntu
    - arches: [default]
      uri: http://ports.ubuntu.com/ubuntu-ports
  identity: {hostname: focal-autoinstall, password: <хеш_пароля>,
    username: ubuntu}
  keyboard: {layout: us, variant: ''}
  locale: en_US.UTF-8
  # имя интерфейса на вашем оборудовании будет другим
  network:
    network:
      version: 2
      ethernets:
        ens192:
          critical: true
          dhcp-identifier: mac
          dhcp4: true
  ssh:
    allow-pw: true
    authorized-keys: []
    install-server: true
  # создаёт раздел EFI, раздел /boot и корень на LVM
  storage:
    grub:
      reorder_uefi: False
    swap:
      size: 0
    config:
    - {ptable: gpt, path: /dev/sda, preserve: false, name: '', grub_device: false,
      type: disk, id: disk-sda}
    - {device: disk-sda, size: 536870912, wipe: superblock, flag: boot, number: 1,
      preserve: false, grub_device: true, type: partition, id: partition-sda1}
    - {fstype: fat32, volume: partition-sda1, preserve: false, type: format, id: format-2}
    - {device: disk-sda, size: 1073741824, wipe: superblock, flag: linux, number: 2,
      preserve: false, grub_device: false, type: partition, id: partition-sda2}
    - {fstype: ext4, volume: partition-sda2, preserve: false, type: format, id: format-0}
    - {device: disk-sda, size: -1, flag: linux, number: 3, preserve: false,
      grub_device: false, type: partition, id: partition-sda3}
    - name: vg-0
      devices: [partition-sda3]
      preserve: false
      type: lvm_volgroup
      id: lvm-volgroup-vg-0
    - {name: lv-root, volgroup: lvm-volgroup-vg-0, size: 100%, preserve: false,
      type: lvm_partition, id: lvm-partition-lv-root}
    - {fstype: ext4, volume: lvm-partition-lv-root, preserve: false, type: format,
      id: format-1}
    - {device: format-1, path: /, type: mount, id: mount-2}
    - {device: format-0, path: /boot, type: mount, id: mount-1}
    - {device: format-2, path: /boot/efi, type: mount, id: mount-3}
write_files:
  # какое ядро ставить
  - path: /run/kernel-meta-package
    content: |
      linux-virtual
    owner: root:root
    permissions: "0644"
EOF

В поле password подставляется хеш, а не пароль открытым текстом. Получить его: mkpasswd -m sha-512 (пакет whois) или openssl passwd -6. Файл user-data отдаётся по HTTP без авторизации — не кладите в него ничего, кроме одноразового установочного пароля, и меняйте его после развёртывания.

Настройка DHCP

В DHCP-сервере задаются опции 66 (адрес TFTP-сервера) и 67 (имя загрузчика — pxelinux.0). Конкретный синтаксис зависит от вашего DHCP-сервера.

Запуск

После этого UEFI-сервер загружается по сети и ставится полностью автоматически.

Грабли, на которые стоит рассчитывать

  • Памяти нужно больше 2 ГБ. Для тестов достаточно ВМ с 3 ГБ. Параметр cloud-config-url=/dev/null не даёт cloud-init лишний раз скачать ISO — это снижает требования к памяти, уменьшает трафик и ускоряет загрузку (подробнее — Ubuntu autoinstall на машине с 2 ГБ ОЗУ).
  • Сгенерированный /var/log/installer/autoinstall-user-data не годится как есть:
    • нет свойства version — конфигурация не проходит валидацию, добавьте его;
    • секции network нужен дополнительный уровень вложенности (network: network:);
    • у каждого элемента storageconfig свойство preserve должно быть false, иначе curtin не станет ставить систему на чистый диск;
    • свойство toggle в секции keyboard приходит пустым и ломает валидацию — его проще удалить.
  • После установки машина снова уходит в PXE. curtin на UEFI-системах ставит текущий вариант загрузки первым в списке, то есть сетевую загрузку. Лечится недокументированным параметром curtin reorder_uefi: False, который subiquity передаёт как есть (см. секцию storagegrub выше).
  • geoip: no в секции apt не работает — запросы geoip всё равно уходят.
  • Размеры разделов задавайте числом в байтах. «Человекочитаемые» значения вида size: 512M сохраняются как float, и расчёт томов LVM в процентах ломается.

Чего в autoinstall не хватает

Часть задач штатно не решается и требует early-commands, late-commands или доработки через cloud-init:

  • задание часового пояса;
  • задание пароля root;
  • отдельный прокси только для apt (установщик считает любой заданный прокси общесистемным);
  • пауза в конце вместо автоматической перезагрузки — обходится через interactive-sections, но тогда пауз становится три;
  • прямая передача конфигурации curtin: приходится писать YAML для cloud-init, который отдаёт YAML в subiquity, а тот генерирует YAML для curtin;
  • выбор пакета ядра: имя берётся из /run/kernel-meta-package, в initramfs оно жёстко задано как linux-generic. Для виртуальных машин удобнее linux-virtual — переопределяется через write_files, как в примере выше.

Связанные статьи

Оставить заявку