Ручное создание виртуальных машин (ВМ) в Proxmox — процесс длительный и склонный к ошибкам: нужно вручную клонировать образы, настраивать сеть, прописывать параметры, следить за согласованностью конфигураций… Всё это может занять час и больше. Но этот процесс можно ускорить и сделать более воспроизводимым. 

Всем привет. Меня зовут Юрий Шахов, я DevOps-инженер в компании «Флант». И сегодня хочу рассказать о том, как автоматизировать создание ВМ в Proxmox, используя Terraform. Мы рассмотрим, как создать единый образ и настроить Terraform, и разберём некоторые особенности Proxmox.

В этой статье будут использоваться следующие инструменты:

  • Proxmox Virtual Environment или просто Proxmox — система виртуализации;

  • Terraform — инструмент для управления инфраструктурой как кодом;

  • GitLab Terraform State — часть функциональности GitLab для хранения состояния инфраструктуры после применения Terraform;

  • Ceph — распределённое хранилище. В рамках этой статьи используется только для хранения единого образа для ВМ. 

Предполагается, что читатель имеет базовое представление об этих технологиях. Также предполагается, что Proxmox-кластер уже настроен и доступен по какому-то домену, например https://proxmox.tutorial.com.

Общий план действий следующий:

Готовим образ

Создание образа и cloud-init

Для запуска ВМ нам понадобится образ, который далее будет использоваться в Terraform шаблонах. Для полной автоматизации и удобства скачиваем выбранный образ (здесь будет рассмотрено на примере Ubuntu 24.04) и наполняем его нужными пакетами. Сначала создаём шаблон из виртуальной машины:

sudo apt update -y
sudo apt install libguestfs-tools -y

wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
sudo virt-customize -a noble-server-cloudimg-amd64.img --install qemu-guest-agent
sudo qm create 9999 --name "ubuntu-2404-template" --memory 2048 --cores 2 --net0 virtio,bridge=vmbr1
sudo qm set 9999 --scsi0 local:0,import-from=/path/to/noble-server-cloudimg-amd64.img
sudo qm set 9999 --boot c --bootdisk scsi0
sudo qm set 9999 --ide2 local:cloudinit
sudo qm set 9999 --serial0 socket --vga serial0
sudo qm set 9999 --agent enabled=1
sudo qm template 9999

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

Клонирование шаблона

Мы получили готовый образ, однако он находится на конкретном сервере, где мы выполняли команды. Чтобы создавать ВМ на каждом сервере Proxmox, необходимо скопировать образ на все серверы или иметь централизованное хранилище образов. Для начала рассмотрим первый вариант.

  1. Клонируем шаблон с опцией Full Clone на этот же сервер. Для этого в интерфейсе Proxmox кликаем правой кнопкой на существующем шаблоне.

  2. Для нового шаблона изменяем VM ID, поскольку это считается отдельной ВМ.

  3. Конвертируем новую ВМ в шаблон.

  4. Мигрируем полученный шаблон на нужный сервер. 

Стоит проговорить, что мы используем Full Clone, поскольку это полная копия шаблона, в то время как Linked Clone — шаблон, которому необходим доступ к оригинальной версии. В текущей конфигурации рекомендуется использовать именно Full Clone для корректной работы ВМ на каждом сервере.

Централизованное хранение образов

Поскольку мы создаём ВМ на своих серверах, нам может потребоваться какое-то распределённое хранилище, например Ceph. Оно также может пригодиться для хранения образа для ВМ. Сделав это, мы получим единую точку хранения образа, который не придётся копировать на каждый новый сервер. 

Создадим новый пул в Ceph и подключим его к Proxmox. Если используется Ceph на внешних серверах (не установленный с помощью Proxmox), то выполняем:

ceph osd pool create proxmox
ceph osd pool application enable proxmox rbd
ceph auth get-or-create client.proxmox mon 'allow r, allow command "osd blocklist"' osd 'allow rwx pool=proxmox'

После этого необходимо добавить Ceph внутри Proxmox. Это можно сделать через UI: в Datacenter выбираем Storage -> Add -> RBD и туда вставляем полученные ранее авторизационные данные. После этого можем скопировать туда наш образ. Таким образом мы получаем образ, хранимый в Ceph и доступный на каждом сервере.

Настраиваем Terraform для Proxmox

Инициализация провайдера proxmox

Для взаимодействия Terraform и Proxmox будет использоваться провайдер terraform-provider-proxmox. В блоке required providers указываем его название и версию. Больше информации о нём можно найти в документации.

Чтобы обращаться в Proxmox по API, требуется создать пользователя, назначить ему необходимые права и создать API-токен. Это можно сделать с помощью следующих команд:

pveum role add TerraformProv -privs "Datastore.AllocateSpace Datastore.AllocateTemplate Datastore.Audit Pool.Allocate Sys.Audit Sys.Console Sys.Modify VM.Allocate VM.Audit VM.Clone VM.Config.CDROM VM.Config.Cloudinit VM.Config.CPU VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Migrate VM.Monitor VM.PowerMgmt SDN.Use"
pveum user add terraform@pve
pveum aclmod / -user terraform@pve -role TerraformProv
  
pveum user token add terraform@pve terraform -privsep 0

Управление Terraform State в GitLab

Поскольку мы описыванием инфраструктуру как код, то и состояние инфраструктуры после изменений стоит хранить не на локальном диске, а в удалённой системе. Если у вас уже используется GitLab, то можно воспользоваться его встроенной функциональностью.

Предположим, что мы создаём репозиторий для хранения Terraform файлов и в нём же будем хранить Terraform State (посмотреть информацию о нём можно в Operate -> Terraform States). Рекомендуется создать отдельного пользователя, а для него — токен с доступом к API. 

Отмечу, что также состояние можно хранить в S3. Рассматривать сейчас мы это не будем, подробнее можно ознакомиться в документации.

Финализируем main.tf

Итак, основу для main.tf мы подготовили: подключили провайдер, использовали GitLab для хранения tfstate и создали необходимые токены. 

Посмотреть пример данного файла.
terraform {
  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = "3.0.1-rc3"
    }
  }
  backend "http" {
    address        = "https://gitlab.tutorial.com/api/v4/projects/<PROJECT_ID>/terraform/state/tfstate"
    lock_address   = "https://gitlab.tutorial.com/api/v4/projects/<PROJECT_ID>/terraform/state/tfstate/lock"
    unlock_address = "https://gitlab.tutorial.com/api/v4/projects/<PROJECT_ID>/terraform/state/tfstate/lock"
    lock_method    = "POST"
    unlock_method  = "DELETE"
    retry_wait_min = 5
    username       = "terraform"
  }
}
variable "proxmox_api_token_secret" {
  type      = string
  sensitive = true
}
provider "proxmox" {
  pm_api_url          = "https://proxmox.tutorial.com/api2/json"
  pm_api_token_id     = "terraform@pve!terraform"
  pm_api_token_secret = var.proxmox_api_token_secret
}

API-токены, которые мы создали ранее, необходимо будет объявить в командной строке перед использованием команд Terraform. Для примера:

export TF_VAR_proxmox_api_token_secret=<PROXMOX_API_TOKEN>
export TF_HTTP_PASSWORD=<GITLAB_PROJECT_OR_PERSONAL_TOKEN>

Описываем ВМ как код

В документации провайдера есть примеры описания ВМ, поэтому здесь мы не будем останавливаться на каждом параметре, а лишь отметим некоторые моменты ресурса proxmox_vm_qemu:

  • В параметре clone указывается название образа, который мы подготовили ранее.

  • При необходимости игнорирования изменений в определённых параметрах можно использовать ignore_changes.

  • При необходимости закрепления ВМ на конкретном сервере Proxmox можно использовать параметр target_node.

  • Для описания множества ВМ одного типа можно воспользоваться параметром for_each и переменными locals. Таким образом, нам не придётся описывать отдельный ресурс для каждой машины. Пример:

locals {
virtual_machines_worker = {
   "worker-1" = { target_node = "server-1", ip = "10.10.10.1" },
   "worker-2" = { target_node = "server-1", ip = "10.10.10.2" },
...
}
}
 
resource "proxmox_vm_qemu" "worker" {
  for_each = local.virtual_machines_worker
  name             = each.key
  target_node      = each.value.target_node
...
}

Применяем изменения

Кажется, мы всё сделали и готовы к запуску ВМ! Представим, что мы выполнили terraform apply и ресурсы создались, мы начали их использовать и поняли, что нужно внести изменения. Что стоит знать?

Terraform по умолчанию обновляет 10 ресурсов параллельно. Если вы не хотите, чтобы все ВМ одновременно стали недоступны, то можно добавить флаг -parallelism и указать количество ресурсов, которые будут обновляться параллельно.

terraform apply -parallelism=2 tf.plan

Важно отметить, что Terraform не следит за тем, поднялась ли ВМ после применения изменений или нет (если в процессе был её рестарт), и сразу переходит к следующей.

Также можно явно указывать один или несколько конкретных ресурсов с использованием флага -target:

terraform plan -target='proxmox_vm_qemu.worker["worker-1"]' -
target='proxmox_vm_qemu.worker["worker-2"]'

Применение некоторых изменений проходит с простоем, а некоторых — без него. Вот описание процесса наиболее популярных изменений:

  • Количество CPU и RAM применяется сразу для всех изменённых ВМ, после чего ВМ сразу перезагружаются.

  • Увеличение дискового пространства происходит без перезагрузки ВМ.

  • В случае миграции ВМ на другой сервер, отличный от указанного в Terraform, с ней ничего не произойдет, изменения к ней будут применяться, но мигрирована обратно она не будет.

Важное замечание — влияние изменений по RAM, CPU, диску и другим параметрам на необходимость перезагрузки будет зависеть от настроек Hotplug для ВМ в Proxmox и от поддержки Hotplug гостевой ОС.

Заключение

Вот мы и автоматизировали создание ВМ в Proxmox. Теперь добавление новой ВМ будет занимать несколько минут вместо получаса или часа. В этой статье мы: 

  • подготовили единый образ для ВМ и рассмотрели, как хранить его в едином хранилище, а не на каждом сервере;

  • настроили Terraform для работы с Proxmox и сохранили Terraform State в GitLab;

  • узнали о полезных параметрах;

  • рассмотрели, как применять изменения на определённых ВМ, а также какие из них будут проходить с простоем, а какие — без него.

Спасибо, что дочитали до конца. Надеюсь, эта статья была вам интересна и полезна.

P. S.

Читайте также в нашем блоге: