CZ 
09.03.2026 Františka VÍTEJTE V MÉM SVĚTĚ

An English translation is available for this article. Pro tento článek je dostupný anglický překlad.
Terraform and VMware Cloud Director - Creating Multiple VMs

Terraform a VMware Cloud Director - vytvoření více VM

| Petr Bouška - Samuraj |
Pokud chceme pomocí Terraform spravovat virtuální infrastrukturu, tak v praxi máme některých prostředků stejného typu desítky, stovky nebo i více. Typicky jde o virtuální stroje (VM). Jejich konfiguraci (popis) chceme mít přehlednou a dobře škálovatelnou. V článku se podíváme na nějaké základní možnosti. Využijeme proměnné, datové typy map a object a meta-argument for_each v rámci resource.
zobrazeno: 427x (272 CZ, 155 EN) | Komentáře [0]

Pozn.: Popis v článku vychází z Terraform verze 1.14.4, VMware Cloud Director Provider verze 3.14 a VMware Cloud Director 10.6.

Terraform možnosti pro správu několika podobných objektů

Úvod

V dokumentaci nebo článcích na internetu se vesměs uvádí základní příklady, kdy vytváříme jeden či dva objekty stejného typu a využijeme samostatné bloky resource. Nenalezl jsem moc oficiálních informací, jak řešit praktické situace. Kdy máme stovky objektů, které postupně přidáváme či rušíme (nebo upravujeme). Užitečné rady jsem nalezl v řadě diskusí, kdy se lidé ptají právě na tento problém.

V diskusích se také řeší, kolik VM udržovat v rámci jedné konfigurace (projektu/workspace). Pokud to budou tisíce, tak bude konfigurace rozsáhlá, stavový (State) soubor hodně velký a každé zpracování bude trvat. Různé věci mají různá řešení. Extrém je pro každé VM samostatná konfigurace. Ale rozumné může být nějakým způsobem dělit konfiguraci do skupin.

Jeden blok resource pro podobné objekty

Asi nechceme mít v konfiguraci pro každé VM samotný blok resource, kde se mnoho informací opakuje. Lepší bude mít oddělené proměnné, kde pouze nastavíme unikátní hodnoty pro každé VM. Standardně blok resource konfiguruje jeden reálný objekt, ale máme možnost využít meta-argument for_each nebo count.

Datové typy

Terraform podporuje mnoho různých datových typů. Primitivní typy obsahují jednu datovou hodnotu, jde třeba o string, number nebo bool. Kolekce obsahují více hodnot stejného typu, můžeme využít list, map a set. Typ objekt object není kolekce, ale definuje konkrétní strukturu (přiřazení názvů atributů k typům hodnot).

List

Seřazená kolekce prvků stejného typu. Prvky v seznamu jsou identifikovány po sobě jdoucími celými čísly od nuly.

variable "list" {
  type = list(string)
}

list = ["value1", "value2"]

var.list[0]

Map

Mapování libovolných názvů klíčů na hodnoty stejného typu. Libovolný počet položek. Hodnoty jsou identifikovány názvem klíče.

variable "map" {
  type = map(string)
}

map = {
  key1 = "value1"
  key2 = "value2"
}

val.map.key1

Object

Mapování explicitních názvů klíčů na hodnoty definovaného (různého) typu. Máme tedy přesně danou strukturu objektu (schéma). Hodnoty jsou identifikovány definovaným názvem klíče.

variable "object" {
  type = object({
    key1 = number
    key2 = string
  })
}

object = {
  key1 = 1
  key2 = "value2"
}

val.object.key1

Jak na konfiguraci více VM

Datové typy pro VM

Můžeme definovat proměnnou, která bude obsahovat specifické údaje pro vytvoření VM. Vhodný typ je object. Příklad objektu a určitých atributů:

variable "vm" {
  type = object({
    vm_name = string,
    vm_ip   = string,
    vm_ram  = number
  })
} 

Protože chceme definovat/vytvářet více VM, tak můžeme využít list nebo map a uvnitř náš popsaný objekt.

variable "vm_list" {
  type = list(object({
    vm_name = string,
    vm_ip   = string,
    vm_ram  = number
    })
  )
} 

Využití for_each v resource

Pokud chceme pomocí jednoho bloku resource vytvořit a spravovat více podobných objektů, tak můžeme využít meta-argument for_each (případně count). Vstupem pro for_each je kolekce (map nebo set of strings). Vytvoří instanci pro každou položku. Opakovaně se prochází datová struktura a konfigurují zdroje pro každou položku ve struktuře.

V řadě příkladů se používá proměnná typu list (jako jsme uvedli výše). Ta nemůže být vstupem pro for_each, proto je potřeba ji převést. K tomu se používá výraz for.

for_each = { for vm in var.vm_list : vm.vm_name => vm }

For projde seznam VM a pro každý VM objekt vytvoří mapování, kde je klíč jméno a hodnota je celý objekt. Výsledek je objekt (díky použitým závorkám { }), který se automaticky převede na map pro použití ve for_each.

V bloku, kde použijeme for_each, můžeme odkazovat na hodnoty instance pomocí each.key a each.value. V našem případě je klíč jméno VM a jednotlivé atributy objektu můžeme použít pomocí each.value.vm_ip.

Příklad konfigurace více VM

Do souboru variables.tf přidáme definici nových proměnných.

# VM parameters unique
variable "vm_list" {
  type = list(object({
    vm_name = string,    
    vm_ip   = string
    })
  )
} 

V souboru terraform.tfvars nastavíme jejich hodnoty.

# VM list, unique parameters
vm_list = [ 
  {
    vm_name = "Demo-VM"
    vm_ip   = "172.30.21.100"
  }, {
    vm_name = "Next-VM"
    vm_ip   = "172.30.21.101"
  }
]

V prostředku vcd_vapp_vm využijeme for_each, abychom vytvořili každé VM ze seznamu.

# create VMs from template wtih manual IP assignment
resource "vcd_vapp_vm" "VMs" {
  for_each         = { for vm in var.vm_list : vm.vm_name => vm }
  vapp_name        = var.vapp_name
  name             = each.value.vm_name
  computer_name    = each.value.vm_name
  vapp_template_id = data.vcd_catalog_vapp_template.my-vapp-template.id

  network {
    type               = "org"
    name               = var.org_net
    ip_allocation_mode = "MANUAL"
    ip                 = each.value.vm_ip
    is_primary         = true
  }
}

Proměnná pro VM s map místo list

V řadě příkladů se využívá typ list a následně při použití ve for_each ještě for. Patrně je funkčně stejné, když použijeme map, pak již nepotřebujeme for. Nevím, zda nemůže být problém, když bychom chtěli VM přejmenovat. Níže je příklad, kde jméno VM používáme jako klíč, takže je již nadbytečný atribut vm_name (ale v příkladu je).

Využijeme také předefinovanou hodnotu pro atribut velikosti paměti pomocí optional. Pokud není v proměnné zadaná hodnota, tak se použije výchozí 4096.

variable "vm_list" {
  type = map(object({
    vm_name = string,    
    vm_ip   = string,
    vm_ram  = optional(number, 4096)
    })
  )
}

vm_list = { 
  "Demo-VM" = {
    vm_name = "Demo-VM"
    vm_ip   = "172.30.21.100"
  }
  "Next-VM" = {
    vm_name = "Next-VM"
    vm_ip   = "172.30.21.101"
  }
}

resource "vcd_vapp_vm" "VMs" {
  for_each         = var.vm_list
  vapp_name        = var.vapp_name
  name             = each.key
  computer_name    = each.key
  memory           = each.value.vm_ram 
  vapp_template_id = data.vcd_catalog_vapp_template.my-vapp-template.id

  network {
    type               = "org"
    name               = var.org_net
    ip_allocation_mode = "MANUAL"
    ip                 = each.value.vm_ip
    is_primary         = true
  }
}
Visual Studio Code - Terraform project - multiple VMs for_each

Kompletní příklad vytvoření více VM

Jde pouze o příklad, který ukazuje kostru řešení. Jedná se o úpravu konfigurace popsané v předchozích článcích. Tam jsme navíc vytvářeli vApp, zde vytváříme pouze dvě VM. Uvedeny jsou obsahy jednotlivých souborů.

terraform.tf

# Version and providers requirement
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    vcd = {
      source  = "vmware/vcd"
      version = "~> 3.14"
    }
  }
}

# Configure the VMware Cloud Director Provider
provider "vcd" {
  user                 = var.vcd_user
  password             = var.vcd_pass
  auth_type            = "integrated"
  org                  = var.vcd_org
  vdc                  = var.vcd_vdc
  url                  = var.vcd_url
  max_retry_timeout    = var.vcd_max_retry_timeout
  allow_unverified_ssl = var.vcd_allow_unverified_ssl
  logging              = var.vcd_log
  logging_file         = var.vcd_log_file
}

variables.tf

# Logging
variable "vcd_log" {
  description = "Enable Logging"
  type = bool
}
variable "vcd_log_file" {
  description = "Logging File"
  type = string
}

# VMware Cloud Director variables
variable "vcd_user" {
  description = "VCD username"
  type = string
}
variable "vcd_pass" {
  description = "VCD user password"
  type = string
  sensitive = true
}
variable "vcd_org" {
  description = "VMware Cloud Director Organization"
  type = string
}
variable "vcd_vdc" {
  description = "Virtual Data Center"
  type = string
}
variable "vcd_url" {
  description = "VCD API URL"
  type = string
}
variable "vcd_max_retry_timeout" {
  type = number
}
variable "vcd_allow_unverified_ssl" {
  description = "Allow unverified SSL"
  type = bool
}
variable "vcd_catalog_name" { 
  description = "VCD Catalog"
  type = string
}
variable "vcd_template_name" {
  description = "Catalog Template"
  type = string
}

# VM parameters common
variable "vapp_name" {
  description = "vApp name for VM"
  type = string
}
variable "org_net" {
  description = "vApp Network name"
  type = string
}
variable "vm_ip_mask" {
  description = "vApp Network Subnet Mask"
  type = string
}
variable "vm_gw" {
  description = "vApp Network Gateway"
  type = string
}
variable "vm_dns" {
  description = "VM DNS server"
  type = string
}

# VM parameters unique
variable "vm_list" {
  type = map(object({
    ip            = string
    ram           = optional(number, 4096)
    cpus          = optional(number, 2)
    cpu_cores     = optional(number, 2)
    customization = bool
    })
  )
}

terraform.tfvars

# Logging
vcd_log      = false
vcd_log_file = "vcd-debug.log"

# VMware Cloud Director Connection Variables
vcd_org  = "Firma"
vcd_vdc  = "Firma vDC"
vcd_url  = "https://server.domain.com/api"
vcd_user = "test_user"
vcd_pass = "xxx" 
vcd_max_retry_timeout    = "240"
vcd_allow_unverified_ssl = false

# Catalog
vcd_catalog_name  = "Firma_catalog2"
vcd_template_name = "template-Alma"

vm.auto.tfvars

Konfiguraci (přiřazení hodnot proměnným) jednotlivých VM vložíme do samostatného souboru.

# VM parameters common
vapp_name  = "Demo-vApp"
org_net    = "Demo_net"
vm_ip_mask = "22"
vm_gw      = "172.30.20.1"
vm_dns     = "172.30.20.1"

# VM list, unique parameters
vm_list = { 
  "Demo-VM" = {
    ip            = "172.30.21.100"
    ram           = 2048
    customization = false
  }
  "Next-VM" = {
    ip            = "172.30.21.101"
    customization = true
  }
}

main.tf

# read Catalog
data "vcd_catalog" "my-catalog" {
  name = var.vcd_catalog_name
}

# read Catalog Template
data "vcd_catalog_vapp_template" "my-vapp-template" {
  catalog_id = data.vcd_catalog.my-catalog.id
  name       = var.vcd_template_name
}

# create VMs from template
resource "vcd_vapp_vm" "VMs" {
  for_each         = var.vm_list
  vapp_name        = var.vapp_name
  name             = each.key
  computer_name    = each.key
  memory           = each.value.ram 
  cpus             = each.value.cpus
  cpu_cores        = each.value.cpu_cores
  vapp_template_id = data.vcd_catalog_vapp_template.my-vapp-template.id
  
  network {
    type               = "org"
    name               = var.org_net
    ip_allocation_mode = "MANUAL"
    ip                 = each.value.ip
    is_primary         = true
  }

  customization {
    enabled    = each.value.customization
    initscript = templatefile("initscript.tftpl", { vm_name = each.key, vm_ip_mask = "${each.value.ip}/${var.vm_ip_mask}", vm_gw = var.vm_gw, vm_dns = var.vm_dns })    
  } 
}

Provisioning VM

Pomocí Terraform CLI provedeme inicializaci, zobrazíme změny a aplikací změn vytvoříme nová VM.

terraform init
terraform plan
terraform apply

Pokud potřebujeme vytvořit další VM, tak pouze doplníme jeho konfiguraci do proměnné vm_list v souboru vm.auto.tfvars. Následně spustíme terraform apply.

Guest OS Customization

V minulém díle jsme si popisovali spuštění skriptu pomocí Guest OS Customization. Museli jsme to provést ve dvou krocích. První krok provedl vytvoření VM a nastavení OS (spuštění skriptu). Pak bylo potřeba upravit nastavení a přizpůsobení vypnout.

Zde jsme v konfiguraci vm.auto.tfvars pro objekt VM přidali atribut customization, který určuje, zda je přizpůsobení povoleno nebo ne. Takže když vytváříme VM, nastavíme jej na true. Pro druhý krok stačí upravit tuto hodnotu na false a znovu spustit terraform apply.

Závěr

Popsaný příklad je základní, když budeme chtít spravovat komplexní prostředí, tak patrně bude potřeba použít různé pokročilejší metody. Využívají se moduly a locals. Konfigurace jednotlivých VM může být v samostatných souborech (třeba Yaml), které se načítají z určité složky. Můžeme mít defaultní konfiguraci VM, kterou spojujeme s konfigurací pro určité VM (přepíše nastavené společné hodnoty). Také je varianta mít konfiguraci v CSV souboru.

Související články:

Infrastruktura jako kód - Terraform

Nástroje typu Infrastructure as Code (IaC) umožňují definovat, nasazovat a spravovat infrastrukturu deklarativním (nebo imperativním) způsobem pomocí konfiguračních souborů. Prostředky (servery, sítě, úložiště apod.) popíšeme v textovém souboru, který definuje požadovaný stav. Nástroj zajistí, že reálné prostředí odpovídá definici. Zatím se budeme věnovat nástroji Terraform.

Virtualizace

Články z populárních témat o virtualizace serverů a stanic.

Pokud se chcete vyjádřit k tomuto článku, využijte komentáře níže.

Komentáře

Zatím zde nejsou žádné komentáře.

Přidat komentář

Vložit tag: strong em link

Nápověda:
  • maximální délka komentáře je 2000 znaků
  • HTML tagy nejsou povoleny (budou odstraněny), použít se mohou pouze speciální tagy (jsou uvedeny nad vstupním polem)
  • nový řádek (ENTER) ukončí odstavec a začne nový
  • pokud odpovídáte na jiný komentář, vložte na začátek odstavce (řádku) číslo komentáře v hranatých závorkách