Terraform – New vSphere Deployment

Keď sme si v článku Terraform 101 – Začíname stručne vysvetlili základy automatizácie Terraformom, mohli by sme sa vrhnúť na praktickú ukážku toho, v čom nám vie pomôcť.

V predchádzajúcom článku, sme si ukázali, ako nainštalovať vCenter server pomocou Terraformu s využitím CLI vcsa-deploy. Dnes si k inštalácii doplníme vytvorenie datacentra, clustra, adresára pre VMky, pridanie ESXi hosta do clustra a vytvorenie distribuovaného switcha.

Skončíme tak, že vmnic0 bude v štandardnom switchi a vmnic1 v distribuovanom. Čo už je solídny základ pre následnú konfiguráciu.

Úvod

V článku Terraform 101 sme si povedali, že kvôli prehľadnosti kódu, si vytvárame súborovú štruktúru ao súbormi ako providers.tf, main.tf, vars.tf, … Tieto súbory si dnes “zabalíme” ešte do adresárov a vytvoríme tak moduly.

  • Modul na vytvorenie vCenter servera
  • Modul na vytvorenie základných objektov
  • Modul na vytvorenie vDS.

Budeme tak možnosť v prípade potreby, volať jednotlivé moduly samostatne. Pri novom deploymente, to možno využijeme iba pri prípadnom “ladení” konfigurácie vDS, ale pri následných konfiguráciách, sa nám tento návyk rozdeľovania kódu do modulov určite zíjde.

Príklad adresárovej štruktúry:

terraform/
├── vsphere-bootstrap/
│   ├── providers.tf
│   ├── main.tf
│   ├── vars.tf
│   ├── terraform.tfvars
│   ├── secret.auto.tfvars
│   └── Modules/
│       ├── basic-infra-objects/
│           ├── main.tf
│           ├── vars.tf
│           ├── outputs.tf
│       ├── vcsa/
│           ├── Template/
│               ├── embedded_vCSA_on_ESXi.json
│           ├── main.tf
│           ├── vars.tf
│       └── vds/
│           ├── main.tf
│           ├── vars.tf

Moduly

Základné informácie k modulom:

  • premenné (variables), ktoré majú byť použité v module, musia byť vždy deklarované
  • ak chceme posunúť hodnotu premennej z rootovského main.tf (pri volaní modulu), musíme mať premennú deklarovanú ako v rootovskom vars.tf tak aj v (child)modulovom vars.tf
  • terraform.tfvars (hodnoty premenných) sa automaticky načítava iba z rootovského adresára. Neodporúča sa teda mať terraform.tfvars v modulovom adresári a volať ho explicitne. Default hodnoty však môžu byť zadefinované vo vars.tf modulu, prípadne hodnoty premenných môžu byť zadefinované pri volaní samotného modulu.
  • použitie premenej a jej hodnoty z príkazu output v jednom module, je možné použiť ako vstup do druhého modulu. Aby bola splnená 1. podmienka, táto premenná musí byť deklarovaná vo vars.tf modulu, kde bude použitá (nemusí byť deklarovaná aj v rootovskom vars.tf)

Root Module

Poďme si teda ukázať jednottlivé časti kódu.

Deklarácia premenných – vars.tf

variable "template_file_path" {
  description = "JSON template file path"
  type = string
}

variable "config_file_path" {
  description = "vcsa configuration JSON file path"
  type = string
}

variable "vcsadeploy_file_path" {
  description = "command line file path"
  type = string
}

variable "vcsadeploy_log_file_path" {
  description = "log file path"
  type = string
}

variable "esxi_IP" {
  description = "IP address of ESXi"
  type = string
}

variable "esxi_pass" {
  description = "ESXi password"
  type = string
}

variable "esxi_portgroup" {
  description = "ESXi portgroup"
  type = string
}

variable "esxi_datastore" {
  description = "ESXi datastore"
  type = string
}

variable "vc_deploy_option" {
  description = "vcsa deployment option"
  type = string
  default = "tiny"
}

variable "vcsa_fqdn" {
  description = "vcsa fqdn"
  type = string
}

variable "vcsa_IP" {
  description = "vcsa network IP"
  type = string
}

variable "vcsa_prefix" {
  description = "vcsa network refix"
  type = number
}

variable "vcsa_gw" {
  description = "vcsa network GW"
  type = string
}

variable "dns" {
  description = "dns server"
  type = string
}

variable "vcsa_root_pass" {
  description = "vcsa OS pass"
  type = string
}

variable "vcsa_sso_pass" {
  description = "administrator@vsphre.local pass"
  type = string
}

variable "datacenter" {
  description = "Name of new Datacenter"
  type = string
  }
  
variable "cluster" {
  description = "Name of new Cluster"
  type = string
  }

  
variable "hosts" {
  description = "List of ESXi hosts"
  type        = list(string)
}

variable "folder" {
  description = "Name of new VM folder"
  type = string
}

variable "esxi_root" {
  description = "Root account"
  type = string
  default = "root"
}

variable "network_interfaces" {
  description = "List of ESXi network interfaces"
  type        = list(string)
}

variable "vds_name" {
  description = "Name of new vDS"
  type = string
}

variable "vds_uplinks" {
  description = "List of vDS uplinks"
  type        = list(string)
}

variable "vds_active_uplinks" {
  description = "List of Active vDS uplinks"
  type        = list(string)
}

variable "management_vlan" {
  description = "VLAN ID for Management port group"
  type        = number

Hodnoty premenných – terraform.tfvars

template_file_path = "C:\\Automation\\Terraform\\vsphere-bootstrap\\Modules\\vcsa\\Template\\embedded_vCSA_on_ESXi.json"
config_file_path = "C:\\Automation\\Terraform\\vsphere-bootstrap\\Modules\\vcsa\\Template\\vcsa_embedded_vCSA_on_ESXi.json"
vcsadeploy_log_file_path = "C:\\Automation\\Terraform\\vsphere-bootstrap\\Modules\\vcsa\\Template\\vcsa_embedded_vCSA_on_ESXi.log"
vcsadeploy_file_path = "E:\\vcsa-cli-installer\\win32"

esxi_IP = "10.10.100.50"
esxi_portgroup = "sPG-100"
esxi_datastore = "ds-nesx01-local"

vc_deploy_option = "tiny"
vcsa_fqdn = "nvcsa01.domain.local"

vcsa_IP = "10.10.100.51"
vcsa_prefix = "24"
vcsa_gw = "10.10.100.1"
dns = "10.10.100.10"

datacenter = "DC-TF"
cluster = "CL-TF"
folder = "VM-TF"
hosts = [
  "nesx01.domain.local",
]

network_interfaces = [
  "vmnic0",
  "vmnic1"
  ]

vds_name = "vDS-TF"

vds_uplinks = [
  "uplink2",
  "uplink1"
  ]

vds_active_uplinks = [
  "uplink2",
  "uplink1"
  ]
  
management_vlan = 74

Hodnoty premenných – secret.auto.tfvars – by default terraform automaticky načítava iba hodnoty premenných zo súboru terraform.tfvars. Ak chceme, aby načítaval aj iný .tfvars súbor, potrebujeme do jeho mena zapísať “auto”. V našom prípade som si vyrobil špeciálny súbor, kvôli heslám. Ako načítať heslá z OpenBao alebo Hashicorp Vaultu, si ukážeme v samostatnom článku.

esxi_pass = "Password1"
vcsa_root_pass = "Password2"
vcsa_sso_pass = "Password3"

Definícia providerov – providers.tf

terraform {
  required_providers {
    vsphere = {
      source = "hashicorp/vsphere"
    }
  }
}

provider "vsphere" {
  user                 = "administrator@vsphere.local"
  password             = var.vcsa_sso_pass
  vsphere_server       = var.vcsa_fqdn
  allow_unverified_ssl = true
  api_timeout          = 10
}

To, ako správne zapísať providera sa dozviete v jeho dokumentácii pod tlačidlom “Use Provider“. vSphere providera udržuje samotná spoločnosť Hashicorp a je dostupný na strákach https://registry.terraform.io/providers/vmware/vsphere/latest/docs.

Volanie modulov a “posúvanie premenných” – main.tf

module "Module_VCSA" {
  source 			= "./Modules/vcsa"
  template_file_path 		= var.template_file_path
  config_file_path 		= var.config_file_path
  vcsadeploy_log_file_path 	= var.vcsadeploy_log_file_path
  vcsadeploy_file_path 		= var.vcsadeploy_file_path
  
  esxi_IP 			= var.esxi_IP
  esxi_pass 			= var.esxi_pass
  esxi_portgroup 		= var.esxi_portgroup
  esxi_datastore 		= var.esxi_datastore
  vc_deploy_option 		= var.vc_deploy_option
  vcsa_fqdn 			= var.vcsa_fqdn
  vcsa_IP 			= var.vcsa_IP
  vcsa_prefix 			= var.vcsa_prefix
  vcsa_gw 			= var.vcsa_gw
  dns 				= var.dns
  vcsa_root_pass 		= var.vcsa_root_pass
  vcsa_sso_pass 		= var.vcsa_sso_pass
}

module "Module_basic-infra-objects" {
  source 			= "./Modules/basic-infra-objects"
  datacenter 			= var.datacenter
  cluster 			= var.cluster
  folder 			= var.folder
  hosts 			= var.hosts
  esxi_root 			= var.esxi_root
  esxi_pass 			= var.esxi_pass
}

module "Module_vds" {
  source 			= "./Modules/vds"
  vds_name 			= var.vds_name
  network_interfaces 		= var.network_interfaces
  vds_vmnic_devices   		= ["vmnic1"]
  vds_uplinks 			= var.vds_uplinks
  vds_active_uplinks 		= var.vds_active_uplinks
  management_vlan       	= var.management_vlan
  
  datacenter_id 		= module.Module_basic-infra-objects.vsphere_datacenter_datacenter_moid
  host_ids          		= module.Module_basic-infra-objects.host_ids
}

Child Module – VCSA

Inštaláciu vCenter servera sme si prešli v minulom článku, takže teraz už len vypíšeme obsahy súborov potrebné pre správne spustenie kódu.

Súbor main.tf

resource "local_file" "vcsa_json" {
    content = templatefile (
            var.template_file_path, 
            { 
             esxi_IP = var.esxi_IP,
             esxi_pass = var.esxi_pass,
             esxi_portgroup = var.esxi_portgroup,
             esxi_datastore = var.esxi_datastore,
             vc_deploy_option = var.vc_deploy_option,
             vcsa_name = element(split(".", var.vcsa_fqdn),0),
             vcsa_fqdn = var.vcsa_fqdn,
             vcsa_IP = var.vcsa_IP,
             vcsa_prefix = var.vcsa_prefix,
             vcsa_gw = var.vcsa_gw,
             dns = var.dns,
             vcsa_root_pass = var.vcsa_root_pass,
             vcsa_sso_pass = var.vcsa_sso_pass
            }
            )
    filename = var.config_file_path
}

resource "null_resource" "vcsa_install" {
  provisioner "local-exec" {
    command = "${var.vcsadeploy_file_path}\\vcsa-deploy install --accept-eula --acknowledge-ceip --no-esx-ssl-verify --log-dir=${var.vcsadeploy_log_file_path} ${var.config_file_path}"
  }
}

Deklarácia premenných – vars.tf

variable "template_file_path" {
  description = "JSON template file path"
  type = string
}

variable "config_file_path" {
  description = "vcsa configuration JSON file path"
  type = string
}

variable "vcsadeploy_file_path" {
  description = "command line file path"
  type = string
}

variable "vcsadeploy_log_file_path" {
  description = "log file path"
  type = string
}

variable "esxi_IP" {
  description = "IP address of ESXi"
  type = string
}

variable "esxi_pass" {
  description = "ESXi password"
  type = string
}

variable "esxi_portgroup" {
  description = "ESXi portgroup"
  type = string
}

variable "esxi_datastore" {
  description = "ESXi datastore"
  type = string
}

variable "vc_deploy_option" {
  description = "vcsa deployment option"
  type = string
  default = "tiny"
}

variable "vcsa_fqdn" {
  description = "vcsa fqdn"
  type = string
}

variable "vcsa_IP" {
  description = "vcsa network IP"
  type = string
}

variable "vcsa_prefix" {
  description = "vcsa network refix"
  type = number
}

variable "vcsa_gw" {
  description = "vcsa network GW"
  type = string
}

variable "dns" {
  description = "dns server"
  type = string
}

variable "vcsa_root_pass" {
  description = "vcsa OS pass"
  type = string
}

variable "vcsa_sso_pass" {
  description = "administrator@vsphre.local pass"
  type = string
}

Child Module – Basic-Infra-Objects

Tento module vyrobí objekt Datacenter, v ňom objekt Cluster, objekt Folder pre ukladanie VMiek a zaradí do klástra všetky ESXi hosty, ktoré máme zadefinované.

Všeobecná informácia: Všetky nastavenia použitých “Resource” sa nastavia podľa predvolených hodnôt alebo tak, ako to explicitne zadefinujeme. Takže v “Resource” blokoch definujeme iba to, čo chceme zmeniť oproti predvoleným hodnotám (alebo si nie sme istí, aka hodnota je predvolená). Všetky hodnoty, ktoré Terraform bude nastavovať sa zobrazia po spustení príkazu: terraform plan

 

main.tf

resource "vsphere_datacenter" "datacenter" {
  name = var.datacenter
}

resource "vsphere_compute_cluster" "compute_cluster" {
  name            = var.cluster
  datacenter_id   = vsphere_datacenter.datacenter.moid

  drs_enabled          = true
  drs_automation_level = "fullyAutomated"

  ha_enabled = true
}

data "vsphere_host_thumbprint" "thumbprint" {
  for_each = toset(var.hosts)
  address  = each.value
  insecure = true
}

resource "vsphere_host" "host" {
  for_each      = toset(var.hosts)
  hostname      = each.value
  username      = var.esxi_root
  password      = var.esxi_pass
  thumbprint 	= data.vsphere_host_thumbprint.thumbprint[each.value].id
  
  cluster       = vsphere_compute_cluster.compute_cluster.id
}

resource "vsphere_folder" "folder" {
  path          = var.folder
  type          = "vm"
  datacenter_id = vsphere_datacenter.datacenter.moid
}

vars.tf

variable "datacenter" {
  type = string
  }
  
variable "cluster" {
  type = string
  }

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

variable "folder" {
  type = string
}

variable "esxi_root" {
  type = string
}

variable "esxi_pass" {
  type = string
}

Výstupy – outputs.tf – keďže ID Datacentra a ID ESXi hostov, budeme potrebovať pre vytvorenie distribuovaného switcha, ich hodnoty si necháme vygenerovať a uložiť.

output "vsphere_datacenter_datacenter_moid" {
  value = vsphere_datacenter.datacenter.moid
}

output "host_ids" {
  value = {
    for hname, h in vsphere_host.host :
    hname => h.id
  }
}

Vysvetlenie zápisu value v host_ids si vysvetlíme inokedy. Teraz nám stačí vedieť, že si týmto vyrobíme “mapu” vyzerajúcu nasledovne:

{
  "nesx01.psguy.local" = "host-93"
  "nesx02.psguy.local" = "host-94"
}

Child Module – vDS

Tento posledný modul nám

  • vyrobí distribuovaný switch s dvoma uplinkami (obe nastavené ako aktívne)
  • priradí vmnic1 ESXi hostu ako uplink2 vDS switcha
  • vyrobí port groupu s názvom “dPG-MGMT” s aktívnym uplinkom uplink2

main.tf

resource "vsphere_distributed_virtual_switch" "vDS" {
  name                                   = var.vds_name
  datacenter_id                          = var.datacenter_id

  uplinks                                = var.vds_uplinks
  active_uplinks                         = var.vds_active_uplinks

  dynamic "host" {
    for_each = var.host_ids
    content {
      host_system_id                     = host.value
      devices                            = var.vds_vmnic_devices
    }
  }
}

resource "vsphere_distributed_port_group" "management" {
    name 				 = "dPG-MGMT"
    distributed_virtual_switch_uuid      = vsphere_distributed_virtual_switch.vDS.id
    vlan_id 			         = var.management_vlan
    type 				 = "earlyBinding"
    active_uplinks  	                 = ["uplink2"]
    
    security_policy_override_allowed     = false
    block_override_allowed               = false
    live_port_moving_allowed             = true
}

To, aby terraform priradil vmnic1 uplinku uplink2, sme docielili zapísaním poradia uplinkov v premenných vds_uplinks a vds_active_uplinks a zároveň zapísaním hodnoty premennej vds_vmnic_devices pri volaní modulu z main.tf v rootovskom module.

vars.tf

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

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

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

variable "vds_name" {
  type = string
}

variable "datacenter_id" {
  type        = string
}

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

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

variable "management_vlan" {
  type        = number
}

 

outputs.tf

output "vsphere_distributed_virtual_switch_vDS_id" {
  value = vsphere_distributed_virtual_switch.vDS.id
}

Po aplikovaní tohto modulu, by sme mohli presunúť VMkernel port zo štandardného switcha na distribuovaný. Najjednoduchšie je to urobiť ručne. Ako to urobiť terraformom, si ukážeme v niektorom z nasledujúcich článkov.

init, plan, apply

Teraz nám už ostáva iba

  1. zinicializovať adresár s našim projektom – vsphere-boostrap
  2. spustiť plan
  3. spustiť apply

Ak by sme chceli destroynuť iba zmeny, ktoré vykonal konkrétny modul, urobíme to príkazom:

terraform.exe destroy -target="module.Module_vds"

V prípade, že chceme znova spustiť konkrétny modul, vymeníme destroy za apply.

Upozornenie: Destroy modulu, ktorý inštaluje vCenter server, vykoná iba zmazanie podkladového súboru vcsa_embedded_vCSA_on_ESXi.json nie samotného vCenter servera. Terraform totiž iba volá CLI command vcsa-deploy. Po destroy zmien vykonaných v tomto module a ich následnom opätovnom aplikovaní, Terraform zavolá vcsa-deploy a ten sa bude snažiť opäť nainštalovať vCenter server s rovnakými parametrami.

Záver

Pevne verím, že čím viac príkladov použitia Terraformu si ukazejeme, tým viac sa vám začína páčiť. V nasledujúcom článku, si vyrobíme samostatné moduly pre vytvorenie nových distribuovaných port group, nových VMiek a podobne.

Author: Martin

Infrastructure engineer | virtualization & cloud enthusiast | vSphere specialist | blogger | Veeam Vanguard 2021,2022,2023 | VMware vExpert 2017 - 2025 | VMCE | VCP-VCF Architect, VCP-DCV, NV, TKO, VCAP-DCV, CompTIA Security+ | Slovak VMUG Leader | Slovak VUG Leader | husband&father