Вводная
Проект libvirt4gitlab-ci
предназначен для автоматизации развертывания виртуальных машин посредством ci/cd модуля gitlab. Проект базируется на кодовой базе https://docs.gitlab.com/runner/executors/custom_examples/libvirt.html с некоторыми доработками.
Архитектурно состоит из двух модулей:
- автоматизированная установка шаблона ОС с iso + подготовка к интеграции с gitlab
- генерация и управления машинами производными от шаблона
Структура каталогов:
- backends/ сценарии запуска virt-inst при создании вм для шаблонов
- scipts/ диалоги работы с инсталятором ос при создании шаблонов
- roles/ роли применяемые при настройке шаблонов
- osid/ параметры автоматической идентификации iso и специфические для дистрибутива параметры
- hooks/ диалоги работы с консолю вм при ее загрузке, например кодовая фраза для luks (не доделано)
- examples/ всякая вторичная мура
- tests/ тестовые конвееры для проверенных дистрибутивов
- base.sh сценарий с общими функциями
- os-install/os-template - сценарии автоматизированной установки ос
- driver.conf - общие переменные
- prepare.sh - сценарий развертывания шаблона
- run.sh - сценарий запуска скрипта задачи гитлаба
- cleanup.sh - удаление вм после окончания конвеера
- executor.sh - общие параметры специфичные для раннера custom
- TESTED.md - проверенные дистрибутивы
- TODO.md - задачи на доработку
Установка гипервизора на примере debian 11.4
Установить ОС в варианте с openssh (использовался debian-11.4.0-amd64-DVD-1.iso
). Поставить необходимые пакеты:
apt install -y curl git acpid locales bridge-utils sudo attr libvirt-daemon libvirt-daemon-system apg expect virtinst libarchive-tools whois libosinfo-bin genisoimage
Создать сетевой мост. Сеть в которую подключаются машины должна раздавать параметры по dhcp. Как вариант, можно создать сеть типа NAT в самом libvirt, в таком случае имя интерфейса моста будет что-то типа virbr0
.
# create bridge with interface to network
cat <<EOF>/etc/network/interfaces.d/br0
auto br0
iface br0 inet static
bridge_ports enp1s0
bridge_stp off
bridge_waitport 0
bridge_fd 0
address 192.168.100.168
netmask 255.255.0.0
gateway 192.168.1.1
EOF
Склонировать репу:
git clone ... /opt
cd /opt/libvirt4gitlab-ci/
Отредактировать [driver.conf] указав имя устройства моста по-умолчанию в параметре VM_BRIDGE_DEFAULT
.
Установить пакет gitlab-runner. Добавить пользователя gitlab-runner в passwordless sudo:
cat <<EOF>/etc/sudoers.d/gitlab-runner
gitlab-runner ALL=(ALL) NOPASSWD: ALL
Defaults:gitlab-runner !requiretty
Зарегистрировать в гитлабе два раннера:
- с тегом l4g-shell и типом shell. Раннер используется для подготовки шаблонов и прочих операций требующих контроля над гипервизором;
- с тегом l4g и типом custom. Отвечает за управление производными виртуалками.
Поправить в конфиге /etc/gitlab-runner/config.toml
секции указав параметры builds_dir
,cache_dir
и пути до сценариев prepare
,run
и cleanup
. Например:
[[runners]]
name = "test-arm"
url = "http://mygitlab"
token = "xxxxxxxxxxxx"
executor = "custom"
builds_dir = "/home/gitlab-runner/l4g/builds"
cache_dir = "/home/gitlab-runner/l4g/cache"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.custom]
prepare_exec = "/opt/libvirt4gitlab-ci/prepare.sh"
run_exec = "/opt/libvirt4gitlab-ci/run.sh"
cleanup_exec = "/opt/libvirt4gitlab-ci/cleanup.sh"
[[runners]]
name = "test-arm"
url = "http://mygitlab"
token = "xxxxxxxxxxxxxxx"
executor = "shell"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
Контрольный пример
Для проверки работоспособности произведем автоматизированную установку debian-11.4 и развертывание тестовых сред. В составе проекта идет уже готовый [.gitlab-ci.yml]
Установка ‘нулевого’ экземпляра ОС
На гипервизор выгрузить файл [debian-11.4.0-amd64-DVD-1.iso] (или похожий) и положить его [/var/lib/libvirt/images/]. Аналогично пакет с gitlab-runner положит в [/var/cache/gitlab-runner_amd64.deb].
Выполнить команду запуска установки:
ISO=/var/lib/libvirt/images/debian-11.4.0-amd64-DVD-1.iso VM_ID=d11 VM_BRIDGE=br0 ./os-install
Здесь ISO
- путь до дистрибутива, VM_ID
- имя будущего шаблона, VM_BRIDGE
- имя разделяемого сетевого интерфейса. Задачу так-же можно выполнит запустив [os-install] из .gitlab-ci проекта. При выполнении задачи будут произведены следующие действия:
- создан диск будушей вм в файле [/var/lib/libvirt/images/d11.qcow]
- сгенерированы пароли для
root
и обычного пользователя (их можно задать через переменные принудительно) - определен тип дистрибутива (по имени тома iso через
isoinfo -d -i ..iso
). Если задана переменнаяOS_ID
, автоопределение будет пропущено. Идентификатор будет записан в полеdescription
вм в форматеosid=....
. - загружен набор параметров установки из файла [osid/$OS_ID] (параметры см. в [osid/README.md]
- запущен expect сценарий, в данном случае [scripts/debian-11.4.exp]
- запущен сценарий [backends/debian10]
- запущен процесс консольного установки с указанного ISO командой [virt-install]
- указанная команда создаст вм в которой консольный ввод-вывод дублируется в файл [/var/log/libvirt/qemu/d11.console.log]
- serial вм замкнут на консоль, а инсталятор запущен в диалоговом режиме на этой консоли
- запущен процесс консольного установки с указанного ISO командой [virt-install]
- сценарий expect произведет установку и перегрузит машину
- осуществлен вход с правами
root
- открытый ключ [driver.conf]:
ID_FILE_PUB
будет прописан пользователю root шаблона - ядру прописаны параметры
non-persistent network
(именование интерфейсов в стиле eth0,eth1 итп что бы при клонировании вм не менялось имя интерфейса) - модифицирован [/etc/issue] для вывода на консольное приглашение адреса полученного виртуальной машиной (т.к. механизмы поставляемые libvirt работают не стабильно)
- машина выключена
- открытый ключ [driver.conf]:
- запущен сценарий [backends/debian10]
- переподключен ISO (после установки он отключается инсталятором)
- в конфиг вм добавлен spice-адаптер для работы графической консоли
- создан снапшот
installed
В итоге у нас получается виртуалка обладающая следующими свойствами:
- при клонировании активный интерфейс всегда
eth0
и получает адрес поdhcp
- локальная консоль клонируется в лог-файл
- адрес полученный интерфейсом
eth0
выводится на консоль в форматеIP: x.x.x.x
- c ключом [gitlab-runner]:
.ssh/id_ed25519
на нее можно зайти подroot
Соответственно никто не мешает создать ее в ручную, т.к. операция не так что бы частая.
Финишная подготовка
Для бесшовного использования вм в гитлабе нужно иметь на ней установленный раннер (он не регистрируется как раннер и используется только при доставке реп и некоторых других операций). Для этого выполнить команду VM_ID=d11 ./os-template
или задачу os-template
из .gitlab-ci проекта.
В процессе подготовки будут выполнены следующие операции:
- состояние вм сброшено до снапшота
installed
. Удален если есть снапшотtemplated
. - определен OS_ID т.к. он требуется для выбора применяемой роли (параметр
ROLES
в [osid/name]. Определение либо из поляdescription
заполненного во время [os-install] либо через переменнуюOS_ID
- вм загружена и к ней применена ansible-роль [roles/TemplatesOS]
- установлен пакет
sudo
- установлен пакет
gitlab-runner
- настроена локаль
- ключ
ID_FILE_PUB
прописан пользователюgitlab-runner
- пользователю
gitlab-runner
будет разрешен passwordlesssudo
- машина выключена
- установлен пакет
- сделан снимок
templated
таким образом, на выходе мы получаем машину у которой кроме прочего установлен раннер, разрешен вход не толко под root
но и под gitlab-runner
, а последний умееет sudo
без пароля. Соответственно эти операции тоже можно произвести в ручную.
Локальные роли
Для упрощения кастомизации локальных установок [os-template] использует следующие переменные окружения:
LOCALROLES
- путь в файловой системе гипервизора со структурой аналогичной [roles/];LOCALROLESPRE
- путь с ролями выполняемыми до всех остальных, безотносительно osid;LOCALROLESPOST
- путь с ролями выполняемыми после всех остальных, безотносительно osid.
В конце каждой применяемой роли необходимо производить выключение машины которая будет запущена заново перед выполнением следующей роли.
Работа с производными машинами
Для использования полученных образов необходимо в выбранный проект добавит задачи с тегом l4g
и секцией script
которые будут выполнятся на автосозданных виртуалках. В примере [.gitlab-ci.yml] проекта есть две задачи testvm
и testvm_predeploy
. Для выполнения нужно создать конвеер с VM_SOURCE=d11
и DEPLOY=1
. При запуске происходит следующее:
- выполняется сценарий
prepare_exec
который- клонирует диск машины указанный в
VM_SOURCE
или в переменной[driver.conf]:VM_SOURCE_DEFAULT
. Если не взведена переменнаяVM_FULLCOPY
новый диск создается с backend шаблона (режим copy-on-write). Если переменная задана, то происходит полное копирование. - если задан параметр
VM_DISK_SIZE
то образ ресайзится до указанного размера командойqemu-img resize
(ресайз фс в контексте вм нужно делать самостоятельно) - вычисляются параметры
VM_VCPU/VM_MEM/VM_BRIDGE
(берутся из переменных задачи или дефолтный из [driver.conf] - вычисляется
VM_ID
(берется из переменнойVM_TARGET
задачи или генерится автоматически в видеpipe-X1-runner-X2-project-X3-job-X4
) - запускается
virt-install
в режиме импорта. Ожидается появление ip-адреса интерфейса в консольном логе машины.
- клонирует диск машины указанный в
- выполняется
run_exec
- проверяет наличие
PREDEPLOY_SCRIPT
- если в наличии, выполняет его на целевой машине с правами рута (внимание! обработка ошибок не производится, а переменные окружения gitlab не доступны!)
- если задана переменная
VM_REBOOT
то машина перегружается
- выполняет команды из секции
script
с правамиgitlab-runner
- проверяет наличие
- выполняет
cleanup_exec
- выполняет удаление вм
Отличие задачи c переменной PREDEPLOY_SCRIPT
заключается в том, что на виртуалке предварительно выполняется сценарий указанный в переменной и виртуалка перегружается, и только после этого передается управление секции script
основного блока.
Примеры развертывания применяемые при тестировании проекта доступны в каталоге tests/
.
Описание переменных
- VM_SAVE - состояние машины после окончания задачи. Может быть
online
- оставлена включенной илиoffline
- оставлена выклбченной. Если переменная не указана, машина будет удалена. - VM_SOURCE - имя вм с которой будет осуществлено клонирование для выполнения текущей задачи. Это может быть как вм созданная на постоянной основе (шаблон), так и другая вм созданная на предыдущем этапе и оставленная при обработке переменной
VM_SAVE=poweroff
- VM_TARGET - имя, которое будет назначено создаваемой вм. Используется для создания вм с известным именем, например для использования в качестве шаблона на следующих этапах
- VM_FORCE - перезатировать вм если существует
- VM_DISK_FULLCOPY - клонирование вм по-умолчанию осуществляется в режиме backing store. Указание данной переменной, отключает использование диска машины-источника и создает полную копию диска
- PREDEPLOY_SCRIPT - выполнения командного файла на этапе подготовки вм, до передачи управления секции script. Выполнение сценария осуществляется под root, при этом проект доступен по пути
/home/gitlab-runner/builds/{секция проекта}/{имя проекта}
- VM_REBOOT - машина будет перегружена после выполнения predeploy сценария и до выполнения секции
script
. При этом в журнал задачи будет выгружен вывод загрузки ядра (снимается с файла в которое перенаправлена консоль vm) - VM_VCPU - число виртуальных процессоров (всего на макете их 24)
- VM_MEM - память в мегабайтах, на макете доступно 128Гб
- VM_DISK_SIZE - объем диска (напр. “64G”).
- VM_BRIDGE - сетевой мост на который будет подсажен сетевой интерфейс. По-умолчанию задается в [driver.conf]
- VM_NETWORK - наименование изолированной сети libvirt которая будет добавлена к создаваемой вм. Если сети не существует, она будет создана на базе шаблона
examples/isolated-net.xml
. - DEBUG - отладочный вывод сценариев раннера (добавлен для cleanup, который штатно в журналы гитлаба не выводится. с DEBUG сбрасывается
set -x
в [/tmp/$$.clean.log]). - VM_INTERACT - запуск инсталятора в интерактивном режиме (для подготовки новых expect-сценариев)
- TEMPLATE_DISK_SIZE - размер диска при установке ОС (os-install).
- VM_NOCLEANUP - если задана, то машина не будет удаляться на этапе cleanup конвеера
Безопасность
Следует учитывать, что разработка раннера не предполагала противодействия внутреннему нарушителю, т.к. беспарольный sudo и отсутствие контроля управляющих переменных позволяют разработчикам конвеера получить полный контроль как над производными вм так и над гипервизором. Защита от внешнего нарушителя в рамках базовой установки + автогенерируемые пароли которые в процессе работы не используются и хранятся только в логах соответствующих задач.
Особые указания
AstraLinux 1.7
Для автоматической установки iso перепаковывается и укладывается рядом с исходным и расширением .repack
, установка производится с него. Ядро 5.10. Пароль от grub ‘astralinux’.
AstraLinux 1.6
После установки инсталятор не прописывает в параметры ядра console=serial, как следствие, на консоли ничего не доступно. Нужно дополнять диалог редактированием загрузчика.
Ubuntu
Установка через переупаковку iso с cloud-init и без сети, т.к. иначе долго думает на обновлениях.
AltLinux 10.1
Автоустановка через механизм autoinstall с модификацией iso, т.к. режим text для инсталятора сломан и на него забили.
VM_SAVE
Управление выходным состоянием вм бывает необходимо когда вм преобразуется уже после состояния templated
(например мы создаем вм с VM_TARGET=newtpl,VM_SAVE=offline
, а на каком либо из следующих шагов делаем машины уже с VM_SOURCE=newtpl
. Вариант с VM_SAVE=online
может применятся как для развертывания постоянных машин так и для тестирования сложных комплектов, когда вм взаимодействуют друг с другом.
LOCALSCRIPTSPRE
Сценарий [os-install] кроме прочего проверяет наличие переменной LOCALSCRIPTSPRE
, задающей каталог с исполняемыми файлами выполняемыми до всех операций установки. Это может понадобится для предварительной выгрузки в хранилище самой свежей версии iso или еще каких либо подготовительных действий.