Привет, Хабр! Меня зовут Алексей Зернов. В этой статье я расскажу об автоматизации развертывания отечественной операционной системы РедОС. На решение было потрачено огромное количество сил и времени, поэтому я буду рад, если этот материал с описанием процесса сэкономит кому-то пару недель боли. Вся информация под катом.
Шел 2024 год, мы русифицировались как могли. На повестке стоял вопрос об автоматизации развертывания отечественных операционных систем. По нашей задумке решение должно давать возможность устанавливать операционные системы на железо в пару кликов, быть простым в реализации и поддержке.

Среди существующих решений был выбран MAAS; после небольшого RND он подходил под нашу задачу лучше конкурентов. Учитывая, что в таких глубинах я еще не плавал, практически все для меня было в новинку. Поэтому в статье охватим следующие вопросы:

  • Установка MAAS с тестовой базой данных.

  • Установка на тестовую машину операционной системы из списка доступных.

  • Установка кастомных операционных систем.

  • Packer для сборки кастомных операционных систем.

  • Создание отдельного проекта под сборку операционной системы РедОС через Packer.

  • Добавление РедОС как кастомного образа и установка в MAAS.

  • Патч Curtin под РедОС и пересборка MAAS.

  • Про сети и контроллеры стойки.

Я не буду углубляться в описание, что такое MAAS и некоторых других вещей, мы здесь ни за этим.
Учитывая, что делал практически наощупь, если у кого-то есть замечания или предложения по улучшению, пишите, буду благодарен за обратную связь.

Краткое введение.

MAAS (Metal as a Service) — это система управления серверами, которая позволяет автоматизировать развертывание и управление для физических серверов и виртуальных машин. Она предоставляет интерфейс для легкого добавления новых узлов, установки операционных систем и управления конфигурациями. MAAS поддерживает различные архитектуры и операционные системы.
Как и у всех тех, кто столкнулся с этой задачей, выбор был между несколькими инструментами: OpenStack Ironic, MaaS, xCat, foreman, Cobler.

Как ранее и писал, мук выбора не будет: потрогав немного каждый, поняли, что самым интересным и подходящим является MAAS. Вот о нем и поговорим.

Установка MAAS с тестовой базой данных.

Canonical рекомендует устанавливать MAAS на Ubuntu, воспользуемся Ubuntu 22.04.5 LTS.
Установим MAAS с тестовой базой данных:

sudo apt-add-repository ppa:maas/3.5
sudo snap install --channel=3.5 maas
sudo snap install maas-test-db

Инициализируем регион и стойку:

sudo maas init region+rack --maas-url http://192.168.1.65:5240/MAAS --database-uri maas-test-db:///

Создадим администратора MAAS и импортируем ssh ключи из Github:

sudo maas createadmin --username admin --password ubuntu --email example.example@example.ru --ssh-import gh:User

После этого можно перейти в браузере по http://192.168.1.65:5240/MAAS и настроить основные параметры, отвечая на вопросы интерфейса.

Не забываем настроить DHCP. Для этого в UI перейдем в раздел Subnets > VLAN > Настроить DHCP.

Выберем соответствующие параметры DHCP (Managed or Relay). Следует учитывать, что от версии к версии у MAAS меняются кнопочки в интерфейсе.

Теперь MAAS может найти некую машинку, у которой включен pxe и загрузка по сети. Я осознанно не заостряю на этом внимания, так как в интернетах эти процессы описаны достаточно подробно, даже видео на *tube есть.

Установка на тестовую машину операционной системы из списка доступных.

После того, как машинка попала в MAAS и имеет статус "New", нам нужно зайти в configuration машины и в Power configuration, указать настройки управления питанием. В MAAS есть много вариантов управления питанием. После этого power status обновится и станет актуальным.

На следующем этапе для каждой машины нужно пройти шаги из Action: Commission, Allocate и Deploy.

Более подробно об этих шагах:
Commission - этот процесс включает в себя сбор информации о машине такой, как аппаратные характеристики, сетевые интерфейсы и диски.
Allocate - это действие выделяет машину для конкретного пользователя или проекта.
Deploy - это действие устанавливает операционную систему на машину.
Release - это действие освобождает машину, сделав ее доступной для других пользователей или проектов.
Abort - это действие прерывает текущую операцию, выполняемую на машине.
Clone - это действие создает копию текущей машины.
Первым шагом проходим Commission, следом – Allocate, за этим – Deploy. Шаг Release используется на уже развернутой машине, когда нам нужно ее освободить перед новым развертыванием.
Немного слайдов:

Рис.1 Новая машина.
Рис.1 Новая машина.

Как мы видим, присвоен адрес из DHCP пула, который был настроем ранее, когда включали DHCP.

Рис.2 Выбор типа управления питанием.
Рис.2 Выбор типа управления питанием.
Рис.3 Актуальный статус питания.
Рис.3 Актуальный статус питания.
Рис.4 Запуск действия Commission.
Рис.4 Запуск действия Commission.
Рис.5 Немного логов после Commission.
Рис.5 Немного логов после Commission.

Теперь мы можем выполнить Allocate, а за ним Deploy.

Рис.6 Развертывание операционной системы из списка существующих.
Рис.6 Развертывание операционной системы из списка существующих.

После развертывания нам доступна машина с операционной системой Ubuntu 20.04.
По ssh можно добраться до нее по ssh ключам, которые мы добавили при установке MAAS и пользователю ubuntu.

Установка кастомных операционных систем. Packer для сборки кастомных операционных систем.

Установка кастомных операционных систем в MAAS подразумевает, что мы берем некую операционную систему, собираем ее под себя со своими значениями и устанавливаем через MAAS. Есть два варианта работы с кастомными операционными системами maas-image-builder и packer.

Maas-image-builder – проприетарный, и мы в силу некоторых причин его не рассматриваем.
Packer – это инструмент с открытым исходным кодом, который используется для создания образов машин для различных платформ.
Packer мы и будем использовать. Для MAAS есть специальный проект packer-maas, доступен в github.
Прежде чем начать ковырять репу, нам нужно подготовить рабочую станцию, на которой будет производиться сборка. Использовать для сборки сам сервер MAAS не считаю верным, пусть каждый решает свою задачу. Опытным путем выяснено, что как это ни странно, лучше всего для сборки подходит Ubuntu. Поэтому, используем Ubuntu 24.04 LTS.

Есть несколько вариантов установки Packer. Был использован самый простой метод, стандартная установка через VPN
Команды:

wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $( lsb_release -cs ) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install packer

Небольшое уточнение: можно поставить packer так, как написано, например, у Яндекса и других, но там используется dpkg, и без некоторых плясок make при проверке наличия пакетов при сборке образа выдаст ошибку.
Это решаемо, но зачем?
Далее добавим пакеты необходимые для работы packer.

sudo apt install -y qemu-utils

Пакет qemu-utils содержит утилиты для работы с QEMU, эмулятором и виртуализатором.

sudo apt install -y libnbd-bin

Пакет libnbd-bin содержит утилиты для работы с Network Block Device (NBD), который позволяет экспортировать блочные устройства через сеть.

sudo apt install -y nbdkit

Пакет nbdkit предоставляет инструменты для создания и управления NBD-серверами.

sudo apt install -y fuse2fs

Пакет fuse2fs предоставляет файловую систему FUSE (Filesystem in Userspace), которая позволяет создавать файловые системы в пользовательском пространстве.

sudo apt install -y qemu-system

Пакет qemu-system содержит основные компоненты QEMU для эмуляции и виртуализации различных архитектур.

sudo apt install -y ovmf

Пакет ovmf предоставляет Open Virtual Machine Firmware (OVMF), который является реализацией UEFI для виртуальных машин.

sudo apt install -y cloud-image-utils

Пакет cloud-image-utils содержит утилиты для работы с облачными образами, такими как образы для OpenStack, AWS и других облачных платформ.

sudo apt install -y parted 

Пакет parted предоставляет утилиту для управления разделами дисков.

sudo apt install -y make

Пакет make предоставляет утилиту для автоматизации сборки программного обеспечения.

sudo apt install -y curtain

Пакет curtain предоставляет утилиты для работы с конфиденциальностью и безопасностью виртуальных машин.

sudo apt install -y git

Пакет git предоставляет систему контроля версий.

ПЫ/СЫ, кому просто поставить:

sudo apt install -y make curtain git qemu-utils libnbd-bin nbdkit fuse2fs qemu-system ovmf cloud-image-utils parted

ПЫ/СЫ_new:
У каждого пользователя могут возникать рандомные проблемы с зависимостями пакетов, ваша операционная система подскажет путь, если что.

Создание отдельного проекта под сборку операционной системы РедОС через Packer.

Вот теперь можно развернуть бурную деятельность по созданию проекта для сборки РедОС.

Сразу хочу сказать, что не буду перечислять свои ошибки, косяки и прочие нюансы исследования, иначе статья превратиться в раздутую книгу о тяжелой доле. Также здесь я рассказываю о концепции, которая завелась у меня. По собственному желанию можно перенести сборку в ci/cd, можно написать helm и закатить MAAS в кубер (зачем правда? но да ладно) и т.д.

Первым делом клонируем репозиторий packer-maas:

git clone https://github.com/canonical/packer-maas.git

Теперь в директории packer-maas создадим директорию redos и в ней такую структуру:

.
├── http
│ └── redos-7.ks.pkrtpl.hcl
├── Makefile
├── README.md
└── redos-7.pkr.hcl
mkdir -p http && touch http/redos-7.ks.pkrtpl.hcl Makefile README.md redos-7.pkr.hcl

Краткое описание.
redos-7.pkr.hcl – это основной файл шаблона Packer, который описывает процесс создания образа операционной системы.
README.md – ну это README.md
Makefile – Makefile используется для автоматизации процесса сборки образа.
http/redos-7.ks.pkrtpl.hcl – файл шаблона операционной системы, если проще, то обычный kikstart файл для автоматической установки операционной системы.

redos-7.pkr.hcl файл:

packer {
  required_version = ">= 1.7.0"
  required_plugins {
    qemu = {
      version = "~> 1.0"
      source  = "github.com/hashicorp/qemu"
    }
  }
}
 
variable "filename" {
  type        = string
  default     = "redos-7.tar.gz"
  description = "The filename of the tarball to produce"
}
 
# variable "redos_7_iso_url" {
#   type    = string
#   default = "http://127.0.0.1:8080/redos-MUROM-7.3.4-20231220.0-Everything-x86_64-DVD1.iso"
# }
 
variable "redos_7_sha256sum_url" {
  type    = string
  default = "http://127.0.0.1:8080/redos-MUROM-7.3.4-20231220.0-Everything-x86_64-DVD1.iso.MD5SUM"
}
 
variable "redos_iso_path" {
  type    = string
  default = "/home/alex/redos-MUROM-7.3.4-20231220.0-Everything-x86_64-DVD1.iso"
}
 
variable "ssh_keys_file_user_n" {
  type        = string
  default     = "/home/alex/creds/alexkey.txt"
  description = "Path to the file containing SSH keys"
}
 
variable "password_user_n" {
  type        = string
  default     = "/home/alex/creds/alexpass.txt"
}
 
variable ks_proxy {
  type    = string
  default = "${env("KS_PROXY")}"
}
 
variable "ks_os_repos" {
  type    = string
  default = "--url='https://repo1.red-soft.ru/redos/7.3/x86_64/os/'"
}
 
variable "ks_updates_repos" {
  type    = string
  default = "--baseurl='https://repo1.red-soft.ru/redos/7.3/x86_64/updates/'"
}
 
variable "ks_extras_repos" {
  type    = string
  default = "--baseurl='https://repo1.red-soft.ru/redos/7.3/x86_64/extras/'"
}
 
variable "ks_extras_repos_kernels6" {
  type    = string
  default = "--baseurl=https://repo1.red-soft.ru/redos/7.3/x86_64/extras/kernels6-73"
}
 
variable ks_mirror {
  type    = string
  default = "${env("KS_MIRROR")}"
}
 
variable "timeout" {
  type        = string
  default     = "1h"
  description = "Timeout for building the image"
}
 
locals {
  ks_proxy         = var.ks_proxy != "" ? "--proxy=${var.ks_proxy}" : ""
  ks_os_repos      = var.ks_mirror != "" ? "--url=${var.ks_mirror}/os/x86_64" : var.ks_os_repos
  ks_updates_repos = var.ks_mirror != "" ? "--baseurl=${var.ks_mirror}/updates/x86_64" : var.ks_updates_repos
  ks_extras_repos  = var.ks_mirror != "" ? "--baseurl=${var.ks_mirror}/extras/x86_64" : var.ks_extras_repos
  ks_extras_repos_kernels6 = var.ks_mirror != "" ? "--baseurl=${var.ks_mirror}/extras/x86_64" : var.ks_extras_repos_kernels6
}
 
source "qemu" "redos-7" {
  boot_command     = ["<up><tab> ", "inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/redos-7.ks ", "console=ttyS0 inst.cmdline", "<enter>"]
  boot_wait        = "10s"
  communicator     = "none"
  disk_size        = "50G"
  disk_interface = "ide"
  headless         = true
  iso_checksum     = "file:${var.redos_7_sha256sum_url}"
  # iso_url          = var.redos_7_iso_url
  iso_url          = var.redos_iso_path
  memory           = 2048
  qemuargs         = [["-serial", "stdio"]]
  shutdown_timeout = var.timeout
  http_content = {
    "/redos-7.ks" = templatefile("${path.root}/http/redos-7.ks.pkrtpl.hcl",
      {
        KS_PROXY         = local.ks_proxy,
        KS_OS_REPOS      = local.ks_os_repos,
        KS_UPDATES_REPOS = local.ks_updates_repos,
        KS_EXTRAS_REPOS  = local.ks_extras_repos,
        KS_EXTRAS_REPOS_KERNELS6  = local.ks_extras_repos_kernels6,
        SSH_KEYS_USER_N         = file(var.ssh_keys_file_user_n),
        PASSWORD_USER_N         = file(var.password_user_n)
      }
    )
  }
}
 
build {
  sources = ["source.qemu.redos-7"]
  post-processor "shell-local" {
    inline = [
      "SOURCE=${source.name}",
      "OUTPUT=${var.filename}",
      "source ../scripts/fuse-nbd",
      "source ../scripts/fuse-tar-root",
      "rm -rf output-${source.name}",
    ]
    inline_shebang = "/bin/bash -e"
  }
}

Описание:

1. Packer блок.

packer {
  required_version = ">= 1.7.0"
  required_plugins {
    qemu = {
      version = "~> 1.0"
      source  = "github.com/hashicorp/qemu"
    }
  }
}

Этот блок задает требования к версии Packer и необходимым плагинам. Он указывает, что Packer должен быть версии 1.7.0 или выше и использовать плагин QEMU версии 1.0 для виртуализации.

2. Переменные.

filename – имя файла, который будет сгенерирован после сборки.

redos_7_iso_url – (закомментирован) возможно использовать redos_7_iso_url или redos_iso_path. Работают оба варианта, нужно только менять источник в kikstart файле. Ниже, в описании kikstart файла будет описано.
Предоставить локальный доступ по http для разработки можно командой  python3 -m http.server 8080 в директории с файлами.

redos_iso_path – локальный путь к ISO-образу RedOS, который будет использоваться для сборки.

redos_7_sha256sum_url – URL для получения контрольной суммы SHA256 для ISO-образа RedOS. Можно использовать файл с сайта РедОС("https://repo1.red-soft.ru/redos/7.3/x86_64/oem/redos-MUROM-7.3.4-20231220.0-Everything-x86_64-DVD1-kickstart.md5")

ssh_keys_file_user_n – путь к файлу SSH ключа, для конфигурации пользователя. Можно использовать ключи, которые добавлены в MAAS при установке, он передаст их в cloud-init, но некоторые ключи так добавлять удобнее.

password_user_n — путь к файлу с паролем пользователя. Пароль в файле в зашифрованном виде. Для шифрования можно использовать python3 -c 'import crypt; print(crypt.crypt("password", crypt.METHOD_SHA512))'

ks_proxy, ks_os_repos, ks_updates_repos, ks_extras_repos — ссылки на прокси и репозитории, которые будут использоваться в kickstart (ks) файле для настройки системы.

timeout — время, через которое процесс сборки прекратится, если не завершится.

3. locals.

Локальные переменные используются для создания значений, которые зависят от других переменных. Например, прокси и репозитории могут быть переопределены значениями из переменных среды KS_MIRROR и KS_PROXY.

4. source "qemu" "redos-7".

Этот блок определяет источник сборки, который использует QEMU для создания виртуальной машины. Основные параметры:

boot_command – последовательность команд, вводимая при загрузке для установки системы с использованием kickstart.

disk_size – размер виртуального диска.

iso_checksum – контрольная сумма для проверки загружаемого ISO-файла.

iso_url – путь к ISO-образу.

memory – объем оперативной памяти для виртуальной машины.

headless – тихий режим сборки. Не будет задано никаких вопросов. Если поставить на false, можно понаблюдать как РедОС при установке кушает kikstart файл, не особо нужно, но прикольно. Этот блок также использует файл redos-7.ks (kickstart-файл), который передается через встроенный HTTP-сервер и настраивает параметры установки ОС, включая репозитории, SSH-ключи и пароль.

5. build блок.

Этот блок определяет сборку и постпроцесс, который выполняет дополнительные команды после создания образа. В данном случае используется скрипт для работы с nbd и сжатия образа в tar.gz файл.

Мы в рамках данной статьи не полезем ковырять этот блок, там все работает из коробки от packer-maas.

Следующий файл это Makefile:

#!/usr/bin/make -f

include ../scripts/check.mk

PACKER ?= packer
PACKER_LOG ?= 0
TIMEOUT ?= 1h

export PACKER_LOG

.PHONY: all clean

all: redos-7.tar.gz

$(eval $(call check_packages_deps))

redos-7.tar.gz: check-deps clean
    ${PACKER} init redos-7.pkr.hcl && ${PACKER} build -var timeout=${TIMEOUT} redos-7.pkr.hcl

clean:
    ${RM} -rf output-redos-7 redos-7.tar.gz

1. Переменные:

PACKER – путь к утилите Packer (по умолчанию packer).

PACKER_LOG – уровень логирования для Packer (по умолчанию отключен).

TIMEOUT – время ожидания завершения сборки (по умолчанию 1 час).

2. Экспорт:

Переменная PACKER_LOG экспортируется, чтобы Packer мог использовать ее для логирования.

3. Основные цели:

all – основная цель сборки. Генерирует файл redos-7.tar.gz (сборка выполняется с использованием Packer).

clean – очищает предыдущие результаты сборки, удаляя директорию output-redos-7 и файл redos-7.tar.gz.

4. Важные команды:

check-deps — выполняется проверка зависимостей через подключаемый файл check.mk (использует вызов check_packages_deps).

Packer команды:

packer init — инициализация Packer с файлом конфигурации redos-7.pkr.hcl.

packer build — сборка образа с использованием указанного тайм-аута.

Вот здесь и проверяется наличие пакета packer и прочего в подключаемом include ../scripts/check.mk
Здесь более подробно останавливаться не на чем, файл взят от кого-то из redhat-based.

http/redos-7.ks.pkrtpl.hcl:

# Generated by Anaconda 33.25.4
# Generated by pykickstart v3.30
#version=F33
# url ${KS_OS_REPOS} ${KS_PROXY}
# repo --name="Updates" ${KS_UPDATES_REPOS} ${KS_PROXY}
# repo --name="Extras" ${KS_UPDATES_REPOS} ${KS_PROXY}
# repo --name="kernels6" ${KS_EXTRAS_REPOS_KERNELS6} ${KS_PROXY}
cdrom
poweroff
firewall --enabled --service=ssh
firstboot --disable
lang en_US.UTF-8
keyboard us
network --device=link --bootproto=dhcp
selinux --enforcing
timezone UTC --isUtc
bootloader --location=mbr --timeout=1
rootpw --plaintext password
zerombr
clearpart --all --initlabel
part / --size=1 --grow --asprimary --fstype=ext4
user --groups=wheel --name=alex --password=${PASSWORD_USER_N} --iscrypted --gecos="alex"

%post --interpreter=/bin/bash --erroronfail
# workaround anaconda requirements and clear root password
passwd -d root
passwd -l root

dnf update -y
dnf install cloud-utils-growpart -y
dnf install python2-oauthlib -y
dnf install e2fsprogs -y
dnf install cloud-init -y
dnf install kernel-lt -y
dnf install efibootmgr -y
dnf install -y rsync

# Clean up install config not applicable to deployed environments.
for f in resolv.conf fstab; do
    rm -f /etc/$f
    touch /etc/$f
    chown root:root /etc/$f
    chmod 644 /etc/$f
done

rm -f /etc/sysconfig/network-scripts/ifcfg-[^lo]*

# Kickstart copies install boot options. Serial is turned on for logging with
# Packer which disables console output. Disable it so console output is shown
# during deployments
sed -i 's/^GRUB_TERMINAL=.*/GRUB_TERMINAL_OUTPUT="console"/g' /etc/default/grub
sed -i '/GRUB_SERIAL_COMMAND="serial"/d' /etc/default/grub
sed -ri 's/(GRUB_CMDLINE_LINUX=".*)\s+console=ttyS0(.*")/\1\2/' /etc/default/grub
sed -i 's/GRUB_ENABLE_BLSCFG=.*/GRUB_ENABLE_BLSCFG=false/g' /etc/default/grub
ssh-keygen -A
systemctl restart sshd

dracut --force /boot/initramfs-$(uname -r).img $(uname -r)

#----Install SSH key ----
mkdir -m0700 /home/alex/.ssh/
cat <<EOF >/home/alex/.ssh/authorized_keys
${SSH_KEYS_USER_N}
EOF
chmod 0600 /home/alex/.ssh/authorized_keys
chown -R alex:alex /home/alex/.ssh
restorecon -R /home/alex/.ssh/

dnf clean all
%end

%packages --ignoremissing
@core
bash-completion
rsync
tar
yum-utils
bridge-utils
grub2-efi-x64
shim-x64
grub2-efi-x64-modules
dosfstools
lvm2
mdadm
device-mapper-multipath
iscsi-initiator-utils

-plymouth
# Remove ALSA firmware
-a*-firmware
# Remove Intel wireless firmware
-i*-firmware
dracut-live

%end

%post --interpreter=/bin/bash --erroronfail
cloud-init clean --logs
cloud-init init
cloud-init modules --mode config
cloud-init modules --mode final

systemctl enable cloud-init
systemctl restart cloud-init
%end

Как можно заметить, это – обычный kikstart файл. Здесь есть несколько моментов, на которых заострим внимание, но в целом он обычный.

1. Указание источника установки и репозиториев.

url — указывает репозиторий с основным дистрибутивом для операционной системы.

repo — определяет дополнительные репозитории: обновления, пакеты Extras и ядра (kernels6).

cdrom — также указывает на использование CD-ROM в качестве источника установки.

Чтобы установка в секции %packages была только из cdrom, url и repo сейчас закомментированы.

** 2. Конфигурация базовых параметров системы.**

firewall — включен с разрешенным доступом по SSH.

firstboot --disable — отключает запуск первичной настройки при первом входе в систему.

lang, keyboard, timezone — конфигурация языка, раскладки клавиатуры и часового пояса.

network — настройка сети по DHCP для автоматического получения IP.

Здесь этот процесс отдан на использование активного интерфейса, т.к. Qemu при сборке пакером использует один интерфейс, потом при установке на железо или ВМ интерфейс будет другим, и это может вызвать сложности. Можно хардкодить, если имеется непреодолимое желание, я предупредил.

selinux --enforcing — включен режим SELinux.

bootloader — установка загрузчика в MBR с таймаутом 1 секунда.

rootpw — пароль для пользователя root.

clearpart, part — очистка всех существующих разделов и создание нового раздела для корневой файловой системы с расширением по мере необходимости.

По той же причине, что и сеть, а точнее для переиспользования, применяется автоматическое определение, т.к. у Qemu точно vda, у остальных – по настроению.
user — создание пользователя "alex" с предоставлением прав администратора (в группе wheel), зашифрованных паролем.

3. %post — действия после установки

passwd -d root, passwd -l root — удаление и блокировка пароля root.

dnf * Установка обновлений и ряда пакетов.

for f in resolv.conf fstab; do
    rm -f /etc/$f
    touch /etc/$f
    chown root:root /etc/$f
    chmod 644 /etc/$f
done
rm -f /etc/sysconfig/network-scripts/ifcfg-[^lo]*

Очистка конфигурационных файлов (например, сетевых интерфейсов), которые могут не потребоваться в развертываемой среде.

sed -i 's/^GRUB_TERMINAL=.*/GRUB_TERMINAL_OUTPUT="console"/g' /etc/default/grub
sed -i '/GRUB_SERIAL_COMMAND="serial"/d' /etc/default/grub
sed -ri 's/(GRUB_CMDLINE_LINUX=".*)\s+console=ttyS0(.*")/\1\2/' /etc/default/grub
sed -i 's/GRUB_ENABLE_BLSCFG=.*/GRUB_ENABLE_BLSCFG=false/g' /etc/default/grub
ssh-keygen -A
systemctl restart sshd

Изменение конфигурации загрузчика GRUB для отключения использования последовательной консоли, что необходимо для правильного отображения вывода при развертывании.

Перегенерация SSH-ключей системы и перезапуск SSH-сервиса для подключения по SSH.
РедОС не хочет делать это автоматически, просто не запускает sshd, не стал разбираться, перегенерировал.

dracut --force /boot/initramfs-$(uname -r).img $(uname -r)

Пересборка initramfs для текущего ядра.

mkdir -m0700 /home/alex/.ssh/
cat <<EOF >/home/alex/.ssh/authorized_keys
${SSH_KEYS_USER_N}
EOF
chmod 0600 /home/alex/.ssh/authorized_keys
chown -R alex:alex /home/alex/.ssh
restorecon -R /home/alex/.ssh/

Настройка SSH-доступа для пользователя "alex"

dnf clean all
%end

Очистка кэша DNF после завершения установки.

4. %packages — установка пакетов.

Определяет набор пакетов для установки, включая минимальные серверные компоненты.
--ignoremissing — пропускает недостающие пакеты.

-plymouth, -a-firmware, -i-firmware** — исключение пакетов для Plymouth (графическая загрузка), а также драйверов и прошивок для беспроводных устройств.

5. %post — действия после установки (cloud-init).

%post --interpreter=/bin/bash --erroronfail
cloud-init clean --logs
cloud-init init
cloud-init modules --mode config
cloud-init modules --mode final
systemctl enable cloud-init
systemctl restart cloud-init
%end

Настройка cloud-init для автоматической инициализации и запуска после развертывания.

Этот пункт добавлен для работы с cloud-init в MAAS. Ковырял в Curtin: почему без этого не срабатывает, если все проходит успешно. Нормального решения не нашел.
Поэтому пока оставим это здесь, если с таким подходом это работает.

Дополнительные файлы.
В контексте решения добавлял некоторые файлы, например:
/home/alex/creds/alexkey.txt – в этот файл закинул публичный ключ для пользователя, почему, писал ранее.

/home/alex/creds/alexpass.txt - зашифрованный пароль для пользователя.

/home/alex/redos-MUROM-7.3.4-20231220.0-Everything-x86_64-DVD1.iso - образ, чтобы не выкачивать, можно использовать url и добавить ссылку. Локально, или на сайт РедОС.

http://127.0.0.1:8080/redos-MUROM-7.3.4-20231220.0-Everything-x86_64-DVD1.iso.MD5SUM - файл checksumm, как расшарить, писал ранее.

Сделано это было чтобы просто отправить код в гит, а необходимые кредсы были не в переменных файла redos-7.pkr.hcl, а в отдельных файлах, которые в гит точно не поедут. Пока разработка локальная, это нормально, потом можно прикручивать Vault, S3 и т.д.

Перед сборкой выполняем инициализацию Packer для текущей директории:

packer init .

Теперь, когда это все сделано, можно попробовать собрать образ. Опять же, есть пара вариантов сборки, находясь в директории packer можно запустить:

packer init redos-7.pkr.hcl && packer build -var timeout=1h redos-7.pkr.hcl

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

sudo PACKER_LOG=1 make > output.log 2>&1

PACKER_LOG=1 – выведет лог отладки. В начале работы с этим помогает.

> output.log 2>&1 – направляет лог в файл, учитывая что при сборке ~2-2.5 тысяч строк лога, удобно. Лог сборки приводить не вижу смысла, он либо соберется, либо нет. Если нет, то причина опять же будет указана.
После окончания сборки видим, что файл redos-7.tar.gz и файл лога output.log на месте.
В случае успешной сборки файл весит ~1.2GB

Рис.7 
Рис.7 

Добавление РедОС как кастомного образа и установка в MAAS.

Теперь отправим файл на сервер MAAS любым удобным способом. Далее подготовим apikey администратора, чтобы авторизоваться на MAAS сервере и создать boot-resource из нашего файла redos-7.tar.gz.

Команда:

sudo maas apikey --username=admin > ~/api-key-file

Авторизация в MAAS под admin:

maas login admin http://localhost:5240/MAAS/api/2.0/ $(cat ~/api-key-file)

Создание boot-resources из образа:

maas admin boot-resources create name='custom/Redos-7-3' title='Redos-7-3' architecture='amd64/generic' base_image='rhel/9' filetype='tgz' content@=redos-7.tar.gz

На этой команде следует остановиться, она важна. Постараюсь объяснить.

name='custom/Redos-7-3' - должен быть именно custom.
MAAS и Curtin (речь о нем пойдет ниже), имеют множество условий для работы с разными образами. В том числе условия, которые срабатывают на custom, у которого есть разные варианты, как пример ubuntu, centos, rhel и другие.

Если мы здесь поставим, например, name='centos/Redos-7-3', то мы получим ошибки по разным причинам, а custom позволяет использовать минимально доступный preseed из MAAS. Возможные значения ограничены и написать, допустим, name='redos/Redos-7-3' не выйдет.

Значения, которые я нашел (ubuntu, centos, rhel, debian, sles, windows, custom).

base_image='rhel/9' – должен быть чем-то редхатовским, centos тоже работал, но base_image='rhel/9'* не вызывает ошибок никогда.
Нужен он затем, что при использовании в name='custom/ MAAS начинает пытаться использовать netplan для настройки сети, а РедОС его, соответственно, не использует.
Использование base_image='rhel/9' позволяет MAAS определить, что это точно не Ubuntu и позволить использовать стандартные redhat-based настройки.

Патч Curtin под РедОС и пересборка MAAS.

Curtin - инструмент для автоматизации установки операционных систем. В документации написано проще и умнее (This is ‘curtin’, the curt installer. It is blunt, brief, snappish, snippety and unceremonious. Its goal is to install an operating system as quick as possible).

В MAAS используется для раскатки операционных систем. К моему сожалению, Curtin немного не в курсе, что существует наша отечественная операционная система РедОС, но, как показывают практика, ему и не надо, сами придут, сами pull request предложат)))

Так вот, это вызывает некоторые проблемы, которые сейчас и будут решены.
Для того, чтобы влезть в код Curtin, потребуется загрузить, распаковать, изменить, пересобрать и установить snap-пакет MAAS.

1. Загрузка пакета:

snap download maas

Эта команда загружает snap-пакет MAAS с сервера Snap Store на локальную машину, но не устанавливает его.

2. Распаковка:

unsquashfs maas_XXXXX.snap, где XXXXX номер сборки, например maas_36889

Эта команда извлекает содержимое snap-пакета maas_36889.snap в директорию squashfs-root/ для дальнейшей модификации.

3. Изменение.

На этом шаге будут внесены изменения в код Curtin. Описание процесса будет ниже.

4. Пересборка snap-пакета:

snap pack ./squashfs-root

Команда упакует модифицированную директорию ./squashfs-root в snap-пакет. На выходе получится snap-пакет maas_3.5.1-XXX_amd64.snap

5. Установка пересобранного snap-пакета:

sudo snap install --dangerous maas_3.5.1-XXX_amd64.snap

(* snap под sudo плохо, но ради теста можно.)

Команда устанавливает новый snap-пакет MAAS, который был пересобран. Ключ --dangerous используется, потому что этот snap-пакет не подписан официальными ключами Snap Store и может содержать непроверенные изменения.

6. Настройка соединений:

snap connections maas | awk '$1 != "content" && $3 == "-" {print $2}' | xargs -r -n1 sudo snap connect

snap connections maas показывает текущие интерфейсы, используемые пакетом MAAS, и их состояния (подключены ли они или нет).
Утилита awk фильтрует вывод, выбирая строки, где тип интерфейса не является content и где третья колонка содержит символ "-", что означает отсутствие соединения.
А xargs передает интерфейсы, которые требуют подключения, в команду snap connect., которая далее подключает интерфейсы для работы MAAS.

7. Перезапуск MAAS:

sudo snap restart maas

После установки и настройки соединений требуется перезапустить snap-сервис MAAS, чтобы все изменения вступили в силу.
Шаги описаны, теперь прежде чем перейти к изменениям в коде Curtin, хочу сразу предупредить: решено костыльным образом, так, например, изменения которые мы сейчас внесем, работать будут для всех RedHat-based систем, но Ubuntu больше не устанавливается.

Для меня важным было запустить именно РедОС, и работоспособность установки Ubuntu не важна, поэтому вы можете самостоятельно поковырять, чтобы вернуть ей работоспособность, я покажу где, либо когда-то найду время и исправлю.

Итак, распаковали и первый файл, который мы исправим /squashfs-root/usr/lib/python3/dist-packages/curtin/commands/curthooks.py
Первая функция, которая нас интересует install_kernel(cfg, target)

Выглядит так:

def install_kernel(cfg, target):
    def install(pkg):
        env = os.environ.copy()
        # recent flash_kernel has checks to prevent it running in cases like
        # containers or chroots, but we actually want that as curtin
        # is mostly or always doing chroot installs.  LP: #1992990
        env["FK_FORCE"] = "yes"
        env["FK_FORCE_CONTAINER"] = "yes"
        distro.install_packages([pkg], target=target, env=env)

    kernel_cfg = cfg.get('kernel', {'package': None,
                                    'fallback-package': "linux-generic",
                                    'mapping': {}})
    if kernel_cfg is not None:
        kernel_package = kernel_cfg.get('package')
        kernel_fallback = kernel_cfg.get('fallback-package')
    else:
        kernel_package = None
        kernel_fallback = None

    mapping = copy.deepcopy(KERNEL_MAPPING)
    config.merge_config(mapping, kernel_cfg.get('mapping', {}))

    # Machines using flash-kernel may need additional dependencies installed
    # before running. Run those checks in the ephemeral environment so the
    # target only has required packages installed.  See LP: #1640519
    fk_packages = get_flash_kernel_pkgs()
    if fk_packages:
        distro.install_packages(fk_packages.split(), target=target)

    if kernel_package:
        install(kernel_package)
        return

    # uname[2] is kernel name (ie: 3.16.0-7-generic)
    # version gets X.Y.Z, flavor gets anything after second '-'.
    kernel = os.uname()[2]
    codename, _ = util.subp(['lsb_release', '--codename', '--short'],
                            capture=True, target=target)
    codename = codename.strip()
    version, abi, flavor = kernel.split('-', 2)

    try:
        map_suffix = mapping[codename][version]
    except KeyError:
        LOG.warn("Couldn't detect kernel package to install for %s."
                 % kernel)
        if kernel_fallback is not None:
            install(kernel_fallback)
        return

    package = "linux-{flavor}{map_suffix}".format(
        flavor=flavor, map_suffix=map_suffix)

    if distro.has_pkg_available(package, target):
        if distro.has_pkg_installed(package, target):
            LOG.debug("Kernel package '%s' already installed", package)
        else:
            LOG.debug("installing kernel package '%s'", package)
            install(package)
    else:
        if kernel_fallback is not None:
            LOG.info("Kernel package '%s' not available.  "
                     "Installing fallback package '%s'.",
                     package, kernel_fallback)
            install(kernel_fallback)
        else:
            LOG.warn("Kernel package '%s' not available and no fallback."
                     " System may not boot.", package)

Эта функция install_kernel(cfg, target) предназначена для установки ядра (kernel) в целевой системе. Она делает это с учетом конфигурации (параметров ядра), проверяет доступные пакеты ядра и устанавливает их либо по явной конфигурации, либо автоматически выбирает подходящее ядро и fallback-пакеты в случае необходимости.
Меняем строку:

kernel_cfg = cfg.get('kernel', {'package': None, 'fallback-package': "linux-generic", 'mapping': {}})

На строку:

kernel_cfg = cfg.get('kernel-lt', {'package': 'kernel-lt', 'fallback-packages': ["kernel", "linux-generic"], 'mapping': {}})

Теперь функция использует ключ kernel-lt (вместо kernel), и указаны два fallback-пакета: kernel и linux-generic. То есть, в случае неудачи при установке основного пакета kernel-lt функция попытается установить один из fallback-пакетов.

Это изменение учитывает, что в РедОС пакет ядра = kernel-lt, а не просто kernel. Ведь мы знаем, что kernel-lt, правда?

Следующая функция, которая нас интересует, redhat_update_initramfs(target, cfg)
Вот такая она:

def redhat_update_initramfs(target, cfg):
    if not redhat_update_dracut_config(target, cfg):
        LOG.debug('Skipping redhat initramfs update, no custom storage config')
        return
    kver_cmd = ['rpm', '-q', '--queryformat',
                '%{VERSION}-%{RELEASE}.%{ARCH}', 'kernel']
    with util.ChrootableTarget(target) as in_chroot:
        LOG.debug('Finding redhat kernel version: %s', kver_cmd)
        kver, _err = in_chroot.subp(kver_cmd, capture=True)
        LOG.debug('Found kver=%s' % kver)
        initramfs = '/boot/initramfs-%s.img' % kver
        dracut_cmd = ['dracut', '-f', initramfs, kver]
        LOG.debug('Rebuilding initramfs with: %s', dracut_cmd)
        in_chroot.subp(dracut_cmd, capture=True)

Функция redhat_update_initramfs(target, cfg) отвечает за обновление образа initramfs на системах, использующих Red Hat (и её производные) для управления ядром и инициализацией системы при загрузке.

Меняем эту функцию на:

def redhat_update_initramfs(target, cfg):
    if not redhat_update_dracut_config(target, cfg):
        LOG.debug('Skipping redhat initramfs update, no custom storage config')
        return

    kver_cmds = [
        ['rpm', '-q', '--queryformat', '%{VERSION}-%{RELEASE}.%{ARCH}', 'kernel-lt'],
        ['rpm', '-q', '--queryformat', '%{VERSION}-%{RELEASE}.%{ARCH}', 'kernel']
    ]

    kernel_found = False
    kver = None

    with util.ChrootableTarget(target) as in_chroot:
        for kver_cmd in kver_cmds:
            LOG.debug('Finding redhat kernel version: %s', kver_cmd)
            try:
                kver, _err = in_chroot.subp(kver_cmd, capture=True)
                LOG.debug('Found kver=%s' % kver)
                kernel_found = True
                break
            except util.ProcessExecutionError:
                LOG.debug('Kernel not found with command: %s' % kver_cmd)

        if not kernel_found:
            LOG.error('No compatible kernel found (kernel-lt or kernel)')
            return

        initramfs = f'/boot/initramfs-{kver}.img'
        dracut_cmd = ['dracut', '-f', initramfs, kver]
        LOG.debug('Rebuilding initramfs with: %s', dracut_cmd)

        in_chroot.subp(dracut_cmd, capture=True)

В нее добавлены:

  1. Поиск нескольких версий ядра (kernel-lt и kernel)

  2. Обработка ошибок при поиске ядра (добавлена проверка, которая пытается найти ядро kernel-lt и, если эта попытка не удалась (например, пакет не установлен), переходит к поиску обычного ядра. Используется блок try-except для обработки возможной ошибки)

  3. Проверка на наличие ядра.

С этим файлом закончили, идем дальше . /squashfs-root_backup/usr/lib/python3/dist-packages/curtin/distro.py
Я добавил сюда информацию о дистрибутиве РедОС и РедОС в DISTROS.redhat, потому что полностью переписывать Curtin, чтобы РедОС был сам по себе, отдельным дистрибутивом – задача слишком сложная, да и на текущем этапе не особо целесообразная.

DistroInfo = namedtuple('DistroInfo', ('variant', 'family'))
DISTRO_NAMES = ['redos', 'arch', 'centos', 'debian', 'fedora', 'freebsd', 'gentoo',
                'ol', 'opensuse', 'redhat', 'rhel', 'sles', 'suse', 'ubuntu',
                'rocky', 'almalinux']


# python2.7 lacks  PEP 435, so we must make use an alternative for py2.7/3.x
# https://stackoverflow.com/questions/36932/how-can-i-represent-an-enum-in-python
def distro_enum(*distros):
    return namedtuple('Distros', distros)(*distros)


DISTROS = distro_enum(*DISTRO_NAMES)

OS_FAMILIES = {
    DISTROS.debian: [DISTROS.debian, DISTROS.ubuntu],
    DISTROS.redhat: [DISTROS.centos, DISTROS.fedora, DISTROS.ol,
                     DISTROS.redhat, DISTROS.rhel, DISTROS.rocky,
                     DISTROS.almalinux, DISTROS.redos],
    DISTROS.gentoo: [DISTROS.gentoo],
    DISTROS.freebsd: [DISTROS.freebsd],
    DISTROS.suse: [DISTROS.opensuse, DISTROS.sles, DISTROS.suse],
    DISTROS.arch: [DISTROS.arch],
}

Тут стоит сказать, что можно еще много и долго нырять в глубину, но сейчас это работает так. Как я ранее писал, если у кого-то есть замечания, я допустил ошибки, пишите, буду исправлять и проверять.

Собираем это все и переустанавливаем. Теперь можно устанавливать РедОС на машину.

Рис.8
Рис.8
Рис.9
Рис.9
Рис.10
Рис.10

Система установлена, попробуем подключиться, чтобы проверить, что все прошло так, как мы ожидали:

Рис.11
Рис.11

Про сети и контроллеры стойки.

Эта часть исключительно теоретическая.

MAAS по умолчанию не управляет машинами из других подсетей. Например, есть подсеть 192.168.1.0/25, условно с VLAN 11., и есть подсеть 192.168.1.128/25 с VLAN 12. Наш MAAS Server проживает по адресу 192.168.1.65 и соответственно в 11 VLAN.
Без вмешательства MAAS не будет играть с машинами из 12 VLAN. Даже если мы добавим по MAC адресу, он будет доступен, и управление питанием будет работать, он все равно не пройдет Commission.

Для решения есть два варианта: первый – это добавить в сервер MAAS сетевые интерфейсы для всех необходимых VLAN, и тогда машины из других подсетей будут доступны в рамках одного rack-контроллера.

Второй – собрать несколько rack-контроллеров из разных подсетей и добавить их к серверу MAAS, тогда остальные подсети, в которых эти контроллеры, станут доступны.

Рис.12
Рис.12

Вывод:
MAAS является хорошим решением для команд, у которых нет потребности в полноценном OpenStack + Ironic.

Из коробки есть хорошие варианты работы с разными операционными системами, а также возможность создавать кастомные образы. Также имеется хорошая возможность кастомизации всего, на что навыков хватит.

У меня на этом все.

P.S.
В данном примере все работает на операционных системах Ubuntu.
Чтобы не возникло вопросов, сам сервер maas вполне можно установить на РедОС через snap, занялся этим уже после статьи, это работает.
Сборка. Честно пытался перевезти сборку образа на РедОС, успехов не достиг. Видимо это стоит большего времени, чем я могу себе позволить. Вы всегда можете разобраться и прислать, как это получилось, буду рад.

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


  1. THEOILMAN
    21.11.2024 15:41

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