Сдающие серверы в аренду компании сталкиваются с необходимостью автоматизации установки операционных систем. В первые годы мы в HOSTKEY предлагали клиентам лишь небольшое количество вариантов для инсталляции, но со временем усовершенствовали сервис. Рассказываем, как это сделать с минимальными затратами.
Недостатки ручной установки
Изначально наши инженеры разворачивали системы на серверах вручную с помощью дистрибутивов на DVD и USB-приводов, которые весили около килограмма и требовали отдельного питания (от USB-порта сервера они стали работать гораздо позднее).
Затем мы заменили оптические приводы на USB-устройства Zalman с жестким диском внутри и возможностью выбора образа ISO в крохотном меню с помощью рычажка.
После были флешки с автозагрузчиком Ventoy. Это комбинировалось с IPMI и IP KVM, а часто — с монитором и клавиатурой. Такие флешки с библиотекой ISO мы и сейчас ставим в серверы клиентов по запросу.
При относительно небольшом количестве машин такая организация работы была возможна, но основная проблема ручного подхода — отсутствие вариантов масштабирования. При увеличении серверного парка придется расширять штат инженеров и увеличивать стоимость аренды. К тому же рынок не стоял на месте, и не предлагать варианты самообслуживания стало как-то неприлично.
Проблемы автоматизации…
Для начала мы развернули PXE-сервер: это на время решило вопрос масштабирования, но по мере роста возникли новые трудности. Главная из них — необходимость управлять установкой ОС для разных моделей материнских плат. Простой PXE не позволял делать это удобно, поэтому пришлось искать варианты упрощения автоматической инсталляции, не требующие лишних действий от инженеров или специалистов техподдержки.
Выходом из ситуации стало внедрение штатного Foreman для управления процедурой PXE-деплоя и конфигурациями ОС через интерфейс API. Так у нас появилась более продвинутая система автоматизации и были сделаны конфигурации под основные операционные системы. Но обозначились новые проблемы:
- Деплой позволял управлять инсталляцией Linux, но при установке Windows в режиме UEFI возникали проблемы — загрузка образов WIM или ISO в ramdisk iPXE не работала. Мы исправили это, сделав деплой через собственный Live CD с CentOS, который начинал процесс и готовил сервер к установке с Windows PE. Устройство такого деплоя — отдельная история, и мы как-нибудь о ней расскажем. Этот опыт заложил основу и для изменения Linux-инсталляций.
- Как только проблема с Windows была в целом решена, Canonical убрали поддержку Debian-Installer в Ubuntu 20.04. Нам пришлось создавать unattended-инсталляцию для Casper, который на тот момент был слаборазвитым и довольно неудобным.
Решать проблемы по мере поступления оказалось затратно по времени и неэффективно с точки зрения бизнеса, поэтому мы решили использовать комплексный подход и составили перечень требований к будущей системе:
- Отсутствие проблем с поддержкой различных инсталляторов в будущем.
- Упрощение поддержки деплоя Unix-систем, так как конфигурация Casper радикально отличается от Anaconda, а она даже близко не похожа на Debian-Installer. Что уж говорить про RouterOS от Mikrotik, OpenSUSE или какой-нибудь ArchLinux.
- Наличие унифицированной процедуры разбивки дисков и настройки томов, чтобы в будущем управлять ею через Web API нашего хостинга.
…и их решение
Опыт с Windows Server нам очень помог. Для автоматизации мы используем LiveCD на базе CentOS 8, сборка которого происходит через Jenkins и хранится в Git. Мы можем контролировать состав ПО, поддержку оборудования, а также менять поведение образа при загрузке через API Foreman, передавая параметры-триггеры. Это позволяет запускать тестирование и форматирование сервера, сбор информации о компонентах выделенного сервера, установку Windows и установку Unix-систем. Как устроена эта кухня, стоит рассказать в отдельной статье.
При создании Unix-инсталляции мы отталкивались от того, что сложной процедуры установки она не требует. Достаточно разбить диск, записать на него файлы ОС и провести базовые настройки:
- - задать hostname;
- - настроить монтирование файловых систем через fstab;
- - настроить сеть;
- - создать служебного пользователя с заданным паролем и ключами;
- - провести дополнительные настройки (задать локаль и т. д.);
- - провести обновление ОС.
Процедура очень похожа на установку ArchLinux по классическому beginners guide. Первый запуск новых инсталляций планировался на основных популярных дистрибутивах: Debian, Ubuntu, CentOS.
Этапы автоматизации
- Подготовка образа с файлами. Это достаточно простая процедура, для проведения которой необходимо установить ОС, а затем уменьшить образ: удалить из него ядро (через пакетный менеджер), очистить кеши и обнулить конфигурацию сети. Операции над ОС выполняются через chroot на смонтированном разделе с корневой файловой системой, а затем ее содержимое отправляется в архив tar.gz. Последующее обновление или добавление стандартного ПО выполняется точно так же, но в обратном порядке: выгружаем образ с зеркала, добавляем ПО, обновляем, чистим кеши и снова запаковываем в архив. В итоге образ готов и лежит на зеркале.
Подготовка сценария установки ОС. Наш скрипт собирается из нескольких частей. Foreman использует отдельную сущность для разбивок таблиц разделов, которые привязываются к типу ОС. В будущем мы перейдем к единому формату разбивки, управляемому из API.
Так как новая разбивка является универсальным shell-скриптом для CentOS 8, у нас не было потребности в привязке отдельных таблиц разделов диска к конкретным системам. Каждая такая таблица является ссылкой на универсальный скрипт через сниппет и оформляется примерно так:
<%#
kind: ptable
name: Debian_LVM_HBA
oses:
- Debian
- Ubuntu
-%>
<%= snippet 'Linux_Default_part_HBA' %>
Реальный код находится в сниппете Linux_Default_part_HBA, и его дублирования не происходит.
Сам скрипт написан на shell и производит следующие процедуры:
- Анализирует состав блочных устройств и выбирает наименьший для установки ОС.
for device in ${blockdevices[*]};do
if [[ `cat /sys/block/$device/queue/rotational` -eq 1 ]];then
hdd_devs+=($device)
elif [[ $(cut -d: -f1 < /sys/block/$device/dev) -ne 8 ]];then
nvme_devs+=($device)
else
ssd_devs+=($device)
fi
done
# Simply set first device by type and size priority
if [[ ! -z $nvme_devs ]];then
INST_DRIVE=$( GET_SMALLEST_DRIVE ${nvme_devs[@]} )
fi
if [[ ! -z $ssd_devs ]]&&[[ -z $INST_DRIVE ]];then
INST_DRIVE=$( GET_SMALLEST_DRIVE ${ssd_devs[@]} )
fi
if [[ ! -z $fake_r ]]&&[[ -z $INST_DRIVE ]];then
INST_DRIVE=${fake_r[0]}
fi
if [[ ! -z $hdd_devs ]]&&[[ -z $INST_DRIVE ]];then
INST_DRIVE=$( GET_SMALLEST_DRIVE ${hdd_devs[@]} )
fi
<% end -%>
if [[ -z $INST_DRIVE ]];then
ERROR_REPORT partitioning
exit 1
fi
Зачищает имеющиеся диски от следов меток файловых систем, LVM и т. п.
Производит разбивку с помощью parted отдельно для инсталляций в режимах EFI или Legacy:
# Base partitioning
if [ -d /sys/firmware/efi ];then
if [[ $(echo $INST_DRIVE | grep -c nvme) -eq 0 ]];then
ESP_PART=${INST_DRIVE}1
BOOT_PART=${INST_DRIVE}2
ROOT_PART=${INST_DRIVE}3
else
ESP_PART=${INST_DRIVE}p1
BOOT_PART=${INST_DRIVE}p2
ROOT_PART=${INST_DRIVE}p3
fi
parted -s /dev/${INST_DRIVE} mklabel gpt mkpart fat32 1MiB 256MiB set 1 esp on
parted -s /dev/${INST_DRIVE} mkpart $FILESYSTEM 256MiB 1GiB
parted -s /dev/${INST_DRIVE} mkpart $FILESYSTEM 1GiB $ROOT_PART_SIZE
wipefs -a /dev/$ESP_PART
mkfs.vfat -F32 /dev/$ESP_PART
else
if [[ $(echo $INST_DRIVE | grep -c nvme) -eq 0 ]];then
BOOT_PART=${INST_DRIVE}1
ROOT_PART=${INST_DRIVE}2
else
BOOT_PART=${INST_DRIVE}p1
ROOT_PART=${INST_DRIVE}p2
fi
parted -s /dev/${INST_DRIVE} mklabel msdos
parted -s /dev/${INST_DRIVE} mkpart primary $FILESYSTEM 1MiB 1GiB set 1 boot on
parted -s /dev/${INST_DRIVE} mkpart primary $FILESYSTEM 1GiB $ROOT_PART_SIZE
fi
Описанные выше примеры предполагают разбивку без RAID. Если требуется создать авторазбивку для более сложных блочных конфигураций, используются отдельные скрипты, и мы выбираем, с чем именно инсталлируется ОС через API Foreman. В будущем мы планируем перейти к более сложной системе с гибким управлением разбивкой через собственный API и дружественный интерфейс в пользовательской панели управления.
Результатом всех манипуляций над диском является смонтированная структура с корнем для новой инсталляции. Mountpoint всегда один (/mnt), и какой именно в нем состав файловых систем, не имеет значения для следующих блоков скрипта. Таким образом, это точка для контроля ошибок в ходе установки.
Дальнейший ход инсталляции выполняет основной скрипт Linux_Default, в который включен сценарий для разбивки дисков. Он решает общие задачи для инсталляции всех типов ОС:
<%#
kind: provision
name: Linux_Default
model: ProvisioningTemplate
-%>
#!/usr/bin/env bash
STAGE_CALL provisioning_start 5
# Set param manualprovision on OS for starting script manualy
<% if host_param_true?('manualprovision') %>
sleep 5
echo "=============================="
echo -e "\n You can start /provision.sh manualy \n"
echo "=============================="
exit 0
<% end -%>
# Выполняем задачи неспецифичные для ОС, например, задаем константы для сервера времени
<% if host_param('medium_fqdn') == "mirror.hostkey.com" -%>
TZ="Europe/Amsterdam"
NTP_SRV="ntpserver.example.com"
<% elsif host_param('medium_fqdn') == "mirror.hostkey.us" -%>
TZ="America/New_York"
NTP_SRV="ntpserver.example.us"
<% else -%>
TZ="Europe/Moscow"
NTP_SRV="ntpserver.example.ru"
<% end -%>
# Здесь вставляем разбивку
<%= @host.diskLayout %>
# Загружаем с зеркала темплейт ОС и распаковываем файлы в корень новой ОС
cd /mnt
curl -k -L --output - -s <%= @host.os.medium_uri(@host) %>/<%= @host.operatingsystem.name %>.tar.gz | tar xvz
# Подключаем виртуальные ФС к новому корню
mount --bind /dev /mnt/dev
mount --bind /dev/pts /mnt/dev/pts
mount --bind /sys /mnt/sys
mount --bind /proc /mnt/proc
mount --bind /run /mnt/run
<% if host_param_true?('uefi') %>
mkdir /mnt/boot/efi
mount /dev/$ESP_PART /mnt/boot/efi
<% end -%>
STAGE_CALL provisioning_end 5
# Вызываем задачи специфичные для ОС
<%= snippet_if_exists(template_name + "_" + @host.operatingsystem.family) %>
# дополнительные неспецифичные задачи, выполняемые при наличии распакованного корня, например, генерация fstab, имя хоста, задание пароля root и т.п.
STAGE_CALL finish_template_start 5
# Размонтируем корень
<% if host_param_true?('uefi') %>
umount /dev/$ESP_PART
<% end -%>
umount /mnt/dev/pts
umount /mnt/*
umount /mnt
swapoff /dev/$VGNAME/swap
# Отправляем Foreman сообщение о завершении установки
wget --no-proxy --quiet --output-document=/dev/null <%= foreman_url('built') %>
sync
STAGE_CALL finish_template_end 5
reboot
Здесь можно также задать имя хоста, сгенерировать fstab (с этим нам очень помог скрипт genfstab из LiveCD ArchLinux), задать пользователя, локаль и т. п. В общем, провести процедуры, которые одинаковы для современных дистрибутивов Linux.
Специфичными обязательными задачами являются настройка сети, а также обновление ОС и установка ПО. Так как настройка сети завязана на имена адаптеров и другие специфичные параметры, мы используем скрипт firstinstall. Он генерируется на этапе установки и записывается основным сценарием на файловую систему ОС. Скрипт стартует с помощью systemd или rc, в зависимости от ОС.
Приведу пример конфигурации сети для Ubuntu/Debian:
# Setting network up
CONNECTION_NAME=\$(ip l | grep -B1 -i '<%= @host.mac %>' | head -1 | cut -d: -f2)
<% if @host.operatingsystem.name.include?('debian') or @host.operatingsystem.name.include?('ubuntu_bionic') %>
cat << EON > /etc/network/interfaces
#loopback
auto lo
iface lo inet loopback
#
auto \$CONNECTION_NAME
allow-hotplug \$CONNECTION_NAME
iface \$CONNECTION_NAME inet static
address <%= @host.ip %>
gateway <%= @host.subnet.gateway %>
netmask <%= @host.subnet.mask %>
dns-nameservers <%= @host.subnet.dns_primary %>
dns-search <%= @host.domain %>
EON
ifdown \$CONNECTION_NAME
ifup \$CONNECTION_NAME
<% else %>
mkdir -p /etc/netplan
cat << EON > /etc/netplan/01-netcfg.yaml
# This file describes the network interfaces available on your system
# For more information, see netplan(5).
network:
version: 2
renderer: networkd
ethernets:
\$CONNECTION_NAME:
addresses: [ <%= @host.ip %>/<%= @host.subnet.cidr %> ]
gateway4: <%= @host.subnet.gateway %>
nameservers:
search: [ <%= @host.domain %> ]
addresses:
- "<%= @host.subnet.dns_primary %>"
EON
netplan apply
<% end -%>
Экранирование $ здесь используется, поскольку это включенный в тело основного сценария скрипт firstinstall. Он вставляется в файл в корне ОС через “cat <EOF>”.
Чтобы мы могли видеть ход установки, каждый этап процесса контролируется вызовами STAGE_CALL к нашему API, а если что-то идет не так, это отражается в журналах. Инсталляция представляет собой единый скрипт, который несложно отладить: достаточно выставить параметр manualinstall для Foreman, чтобы получить LiveCD с собранным скриптом, но без старта инсталляции.
Основной минус подхода — так как инсталляция осуществляется на отдельной ОС, до этапа перезагрузки невозможно увидеть проблемы с совместимостью оборудования. С другой стороны, добавление поддержки нового оборудования даже упрощается, поскольку нет необходимости, например, поддерживать udeb для Debian/Ubuntu.
Заключение
Перейдя на новую схему, мы унифицировали процесс развертывания ОС и обслуживания серверов разных поколений: систем без UEFI (на базе сокетов 1366 и подобных), блэйд-систем HP и IBM, серверов Supermicro поколений от X8 до X12, Gigabyte, Asus и Asrock, кастомных BIOS Т-платформ и OCP Winterfell, а также современных серверов Dell и материнских плат для EPYC и Ryzen, на которых режим Legacy фактически не поддерживается.
Сейчас мы автоматически сдаем клиентам почти 90% машин: если заказать стандартный или стоковый сервер через API или сайт, он будет полностью готов через 5–15 минут. Описанное в статье решение позволило практически полностью автоматизировать самостоятельную переустановку ОС клиентом через личный кабинет или API. Инженерам приходится подключаться только в редких случаях на машинах без удаленного управления, с его нестандартными вариациями, а также в случае сложной конфигурации дисковой подсистемы.
Теперь выход новой ветви ОС и очередной системы инсталляции не вызывает желания уйти из профессии — это, скорее, рутинное событие из тех, что случаются раз в квартал. Мы продолжаем работать над совершенствованием системы деплоя каждый день, разбирая логи неудачных установок, а также тестируя в различных режимах новые материнские платы и серверные платформы. Добавляем новые возможности разбивки дисков, передачи ключей SSH, настройки ОС и сетевых адресов, оповещения клиентов об установках и т. п. Система полностью готова к развитию и расширению.