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
}
}

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.
Zatím zde nejsou žádné komentáře.