Back to blog
Sep 20, 2025
7 min read

Создание модулей Terraform

Гайд по созданию модулей terraform на примере proxmox провайдера bpg

В предыдущей статье (Terraform + Proxmox) мы переиспользовали готовый модуль для создания виртуальных машин. В этой статье создадим собственный модуль для автоматического скачивания cloud-образов операционных систем.

Cloud-images vs Дистрибутив

Что такое cloud-образ и чем он отличается от обычного образа?

Обычный образ — это ISO-файл с дистрибутивом для установки ОС. При развёртывании на его основе обычно требуется ручная или скриптовая установка и настройка системы.

Cloud-образ — это виртуальный диск, содержащий уже установленную и настроенную операционную систему, снабжённый механизмами автоматической инициализации (в нашем случае через cloud-init). При старте виртуальной машины облачная платформа или гипервизор передаёт этим механизмам параметры (имя хоста, SSH-ключи, сетевые настройки и др.), после чего система автоматически завершает настройку без вмешательства пользователя.

СвойствоCloud-образОбычный образ
Настройка при первом запускеАвтоматическая через cloud-initОтсутствует, требует ручной или скриптовой настройки
РазмерОптимизирован, содержит только базовые компонентыМожет быть больше, включает полный дистрибутив или ПО
Интеграция с платформойТесно интегрирован с облачными сервисами (метаданные, сети, SSH-ключи)Независим от платформы, без автоматической интеграции
МасштабируемостьВысокая: быстрый клонинг и развертывание множества экземпляров с разными параметрамиСредняя: требуется дополнительная настройка для каждого клона
Сценарии использованияАвтоматизация CI/CD, быстрый автоскейлинг, унифицированные VMКастомные шаблоны, специализированные инсталляции, физические серверы

Изучение ресурса

Создание любого модуля начинается с изучения ресурса, который мы хотим описать. В нашем случае это proxmox_virtual_environment_download_file.

Ознакомившись с примерами и схемой, разберём основные поля:

resource "proxmox_virtual_environment_download_file" "release_20231228_debian_12_bookworm_qcow2" {
  content_type       = "import"
  datastore_id       = "local"
  file_name          = "debian-12-generic-amd64-20231228-1609.qcow2"
  node_name          = "pve"
  url                = "https://cloud.debian.org/images/cloud/bookworm/20231228-1609/debian-12-generic-amd64-20231228-1609.qcow2"
  checksum           = "d2fbcf11fb28795842e91364d8c7b69f1870db09ff299eb94e4fbbfa510eb78d141e74c1f4bf6dfa0b7e33d0c3b66e6751886feadb4e9916f778bab1776bdf1b"
  checksum_algorithm = "sha512"
}

resource "proxmox_virtual_environment_download_file" "latest_debian_12_bookworm_qcow2_img" {
  content_type = "iso"
  datastore_id = "local"
  file_name    = "debian-12-generic-amd64.qcow2.img"
  node_name    = "pve"
  url          = "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
}

Основные параметры, которые нас интересуют в первую очередь:

ПараметрТипОписаниеПример значения
content_typeStringТип содержимого образа, определяет папку на датасторе (например, iso или vztmpl).iso
datastore_idStringИдентификатор datastore, куда будет сохранён образ.local-lvm
node_nameStringИмя узла (node) Proxmox, с которого будет выполняться загрузка образа.pve01
urlStringURL для скачивания образа.https://example.com/ubuntu-22.04-cloud.img

Дополнительные параметры, которые могут быть полезны:

ПараметрТипОписание
checksumStringОжидаемая контрольная сумма файла.
checksum_algorithmStringАлгоритм расчёта контрольной суммы. Допустимые: md5sha1sha224sha256sha384sha512.
decompression_algorithmStringАлгоритм распаковки после загрузки. Допустимые: gzlzozstbz2.
file_nameStringИмя файла в datastore. По умолчанию вычисляется из URL.
overwriteBooleanЕсли true (по умолчанию), при изменении размера файла в datastore он будет перезаписан. Если false, проверка не будет выполняться.
overwrite_unmanagedBooleanЕсли true, при наличии файла с тем же именем в datastore старый файл удаляется и загружается новый. Если false, в случае существования выдается ошибка.
upload_timeoutNumberТаймаут на загрузку файла в секундах. По умолчанию 600 (10 мин).
verifyBooleanЕсли true (по умолчанию), проверяются SSL/TLS сертификаты при скачивании. Если false, проверка отключается.

Для нашего модуля, помимо основных, будем использовать следующие параметры (сделаем их необязательными):

checksum, checksum_algorithm, file_name, overwrite, upload_timeout

Создание модуля

Конфигурационный файл

Создадим конфигурационный файл cloud_images.tf в корне проекта, который будет парсить наш YAML-конфиг в модуль:

locals {
  images_config = yamldecode(file("./configs/images.yaml"))
}

module "cloud_images" {
  source = "./modules/cloud_images"

  images_config = local.images_config
}

Модуль

Создадим папку для нашего нового модуля и два файла в ней: Структура папки модуля cloud_images с файлами images.tf и variables.tf

Начнём заполнять наш модуль images.tf:

Описываем провайдер, который будет использоваться модулем:

terraform {
  required_providers {
    proxmox = {
      source = "bpg/proxmox"
    }
  }
}

Поскольку мы будем использовать булевый параметр enable, соберём новую карту (map) только с включёнными образами:

locals {
  enabled_images = {
    for key, image in var.images_config.images : key => image
    if image.enabled
  }
}

Создаём ресурс:

resource "proxmox_virtual_environment_download_file" "cloud_images" {
  for_each = local.enabled_images

  content_type = "iso"
  datastore_id = each.value.datastore_id
  node_name    = each.value.node_name
  url          = each.value.url
  file_name    = each.value.file_name

  checksum            = try(each.value.checksum, null)
  checksum_algorithm  = try(each.value.checksum_algorithm, null)
  compression         = try(each.value.compression, null)
  decompression_algorithm = try(each.value.decompression_algorithm, null)
  overwrite           = try(each.value.overwrite, null)
  overwrite_unmanaged = try(each.value.overwrite_unmanaged, null)
}

for_each - оператор цикла, который пройдет по списку образов, которые мы пометили для скачивания. coalesce -  встроенная функция для работы с несколькими значениями, которая возвращает первый аргумент, не равный null. Если все аргументы равны null, функция возвращает null.

Переменные

Теперь создадим файл variables.tf для описания входных переменных модуля:

Заполним файл переменных для модуля, согласно документации и выбранных нами переменных:

variable "images_config" {
  description = "Configuration object containing images and global settings from YAML file"
  type = object({
    global = object({
      node_name      = string
      datastore_id   = string
      upload_timeout = optional(number, 3600)
      overwrite      = optional(bool, false)
    })
    images = map(object({
      enabled            = bool
      content_type       = string
      url                = string
      file_name          = optional(string)
      checksum           = optional(string)
      checksum_algorithm = optional(string)
      node_name          = optional(string)
      datastore_id       = optional(string)
      upload_timeout     = optional(number)
      overwrite          = optional(bool)
    }))
  })
}

Инвентарный файл с образами

Создадим файл с образами, которые хотим скачатьСтруктура папки configs с файлом images.yaml

И заполним его сперва global переменными, а потом образ-специфичными:

global:
  node_name: "pve1"
  datastore_id: "local"
  upload_timeout: 3600
  overwrite: false

images:
  ubuntu_22_04:
    enabled: true
    content_type: "import"
    url: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
    file_name: "ubuntu-22.04-cloudimg-amd64.img"
    checksum: "sha256:b2175cd98cfb13f0b5493e8c8b0e6d6c8b2e6b6b6b6b6b6b6b6b6b6b6b6b6b6b"
   checksum_algorithm: "sha256"
   # Override global settings for this image
   node_name: "pve2" # Use different node
   upload_timeout: 7200 # Longer timeout for this image
 
  debian_12:
    enabled: true
    content_type: "import"
    url: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
    file_name: "debian-12-generic-amd64.qcow2"
    checksum: "sha256:d2fbcf11fb28795842e91364d8c7b69f1870db09ff299eb94e4fbbfa510eb78d"
    checksum_algorithm: "sha256"
    # Use different datastore for this image
    datastore_id: "fast-ssd"
    
  rocky_linux_9:
    enabled: false
    content_type: "import"
    url: "https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"

Output (для наглядности)

Output - это блок, который позволяет экспортировать значения из модуля или корневой конфигурации и делать их доступными. В нашем случае мы его используем только для того, чтобы посмотреть, какие образа будут у нас скачиваться:

Создадим файл output.tf в папке модуля: Структура папки модуля с добавленным файлом output.tf со следующим содержимым:

output "downloaded_image_files" {
  description = "List of downloaded image file names"
  value = [
    for key, image in proxmox_virtual_environment_download_file.cloud_images : image.file_name
  ]
}

теперь создадим конфигурационный файл в корне output.tf, который будет вызывать этот аутпут:

output "downloaded_image_files" {
  description = "List of downloaded image file names"
  value       = module.cloud_images.downloaded_image_files
}

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

Вывод команды terraform plan: Результат выполнения terraform plan - создание двух ресурсов

Как мы можем видеть, тф создаст нам 2 ресурса, как мы и указали в конфиге.

Если мы попробуем активировать наш 3 образ, в котором не определяем название Активация третьего образа в конфигурации то получим следующее: Результат terraform plan с тремя ресурсами Потому что имя тф “достанет” только после создания ресурса.

Заключение

Мы создали Terraform-модуль для автоматизации загрузки cloud-образов в Proxmox. Модуль позволяет:

  • Управлять множественными образами через YAML-конфигурацию
  • Включать/отключать образы с помощью флага enable
  • Использовать как обязательные, так и опциональные параметры
  • Легко масштабировать и поддерживать инфраструктуру

Такой подход делает управление образами более структурированным и позволяет легко добавлять новые образы без изменения основного кода.

Все примеры, лежат в этом репозитории.