Once i got my head around the basics of Terraform I wanted to play with the vSphere provider to see what its was capable of. A basic use case that everyone needs is to deploy a VM. So my first use case is to deploy a VM from an OVA. The vSphere provider documentation for deploying an OVA uses William Lam’s nested ESXi OVA as an example. This is a great example of how to use the provider but seeing as I plan to play with the NSX-T provider also, I decided to use NSX-T Manager OVA as my source to deploy.
So first thing to do is setup your provider. Every provider in the Terraform registry has a Use Provider button on the provider page that pops up a How to use this provider box. This shows you what you need to put in your required_providers & provider block. In my case I will use a providers.tf file and it will look like the below example. Note you can only have one required_providers block in your configuration, but you can have multiple providers. So all required providers go in the same required_providers block and each provider has its own provider block.
# providers.tf terraform { required_providers { vsphere = { source = "hashicorp/vsphere" version = "2.1.1" } } } provider "vsphere" { user = var.vsphere_user password = var.vsphere_password vsphere_server = var.vsphere_server allow_unverified_ssl = true }To authenticate to our chosen provider (in this case vSphere) we need to provide credentials. If you read my initial post on Terraform you would have seen me mention a terraform.tfvars file which can be used for sensitive variables. We will declare these as variables later in the variables.tf file but this is where we assign the values. So my terraform.tfvars file looks like this
# terraform.tfvars # vSphere Provider Credentials vsphere_user = "administrator@vsphere.local" vsphere_password = "VMw@re1!"Next we need variables to enable us to deploy our NSX-T Manager appliance. So we create a variables.tf file and populate it with our variables. Note – variables that have a default value are considered optional and the default value will be used if no value is passed.
# variables.tf # vSphere Infrastructure Details variable "data_center" { default = "sfo-m01-dc01" } variable "cluster" { default = "sfo-m01-cl01" } variable "vds" { default = "sfo-m01-vds01" } variable "workload_datastore" { default = "vsanDatastore" } variable "compute_pool" { default = "sfo-m01-cl01" } variable "compute_host" {default = "sfo01-m01-esx01.sfo.rainpole.io"} variable "vsphere_server" {default = "sfo-m01-vc01.sfo.rainpole.io"} # vCenter Credential Variables variable "vsphere_user" {} variable "vsphere_password" {} # NSX-T Manager Deployment variable "mgmt_pg" { default = "sfo-m01-vds01-pg-mgmt" } variable "vm_name" { default = "sfo-m01-nsx01a" } variable "local_ovf_path" { default = "F:\\OVAs\\nsx-unified-appliance-3.1.3.5.0.19068437.ova" } variable "deployment_option" { default = "extra_small" } # valid deployments are: extra_small, small, medium, large variable "nsx_role" { default = "NSX Manager" } # valid roles are NSX Manager, NSX Global Manager variable "nsx_ip_0" { default = "172.16.225.66" } variable "nsx_netmask_0" { default = "255.255.255.0" } variable "nsx_gateway_0" { default = "172.16.225.1" } variable "nsx_dns1_0" { default = "172.16.225.4" } variable "nsx_domain_0" { default = "sfo.rainpole.io" } variable "nsx_ntp_0" { default = "ntp.sfo.rainpole.io" } variable "nsx_isSSHEnabled" { default = "True" } variable "nsx_allowSSHRootLogin" { default = "True" } variable "nsx_passwd_0" { default = "VMw@re1!VMw@re1!" } variable "nsx_cli_passwd_0" { default = "VMw@re1!VMw@re1!" } variable "nsx_cli_audit_passwd_0" { default = "VMw@re1!VMw@re1!" } variable "nsx_hostname" { default = "sfo-m01-nsx01a.sfo.rainpole.io" }Now that we have our provider & variables in place we need a plan file to deploy the NSX-T Manager OVA, including the data sources we need to pull information from and the resource we are going to create.
# main.tf# Data source for vCenter Datacenter
data "vsphere_datacenter" "datacenter" {
name = var.data_center
}# Data source for vCenter Cluster
data "vsphere_compute_cluster" "cluster" {
name = var.cluster
datacenter_id = data.vsphere_datacenter.datacenter.id
}# Data source for vCenter Datastore
data "vsphere_datastore" "datastore" {
name = var.workload_datastore
datacenter_id = data.vsphere_datacenter.datacenter.id
}# Data source for vCenter Portgroup
data "vsphere_network" "mgmt" {
name = var.mgmt_pg
datacenter_id = data.vsphere_datacenter.datacenter.id
}# Data source for vCenter Resource Pool. In our case we will use the root resource pool
data "vsphere_resource_pool" "pool" {
name = format("%s%s", data.vsphere_compute_cluster.cluster.name, "/Resources")
datacenter_id = data.vsphere_datacenter.datacenter.id
}# Data source for ESXi host to deploy to
data "vsphere_host" "host" {
name = var.compute_host
datacenter_id = data.vsphere_datacenter.datacenter.id
}# Data source for the OVF to read the required OVF Properties
data "vsphere_ovf_vm_template" "ovfLocal" {
name = var.vm_name
resource_pool_id = data.vsphere_resource_pool.pool.id
datastore_id = data.vsphere_datastore.datastore.id
host_system_id = data.vsphere_host.host.id
local_ovf_path = var.local_ovf_path
ovf_network_map = {
"Network 1" = data.vsphere_network.mgmt.id
}
}# Deployment of VM from Local OVA
resource "vsphere_virtual_machine" "nsxt01" {
name = var.vm_name
datacenter_id = data.vsphere_datacenter.datacenter.id
datastore_id = data.vsphere_ovf_vm_template.ovfLocal.datastore_id
host_system_id = data.vsphere_ovf_vm_template.ovfLocal.host_system_id
resource_pool_id = data.vsphere_ovf_vm_template.ovfLocal.resource_pool_id
num_cpus = data.vsphere_ovf_vm_template.ovfLocal.num_cpus
num_cores_per_socket = data.vsphere_ovf_vm_template.ovfLocal.num_cores_per_socket
memory = data.vsphere_ovf_vm_template.ovfLocal.memory
guest_id = data.vsphere_ovf_vm_template.ovfLocal.guest_id
scsi_type = data.vsphere_ovf_vm_template.ovfLocal.scsi_type
dynamic "network_interface" {
for_each = data.vsphere_ovf_vm_template.ovfLocal.ovf_network_map
content {
network_id = network_interface.value
}
}wait_for_guest_net_timeout = 5
ovf_deploy {
allow_unverified_ssl_cert = true
local_ovf_path = var.local_ovf_path
disk_provisioning = "thin"
deployment_option = var.deployment_option}
vapp {
properties = {
"nsx_role" = var.nsx_role,
"nsx_ip_0" = var.nsx_ip_0,
"nsx_netmask_0" = var.nsx_netmask_0,
"nsx_gateway_0" = var.nsx_gateway_0,
"nsx_dns1_0" = var.nsx_dns1_0,
"nsx_domain_0" = var.nsx_domain_0,
"nsx_ntp_0" = var.nsx_ntp_0,
"nsx_isSSHEnabled" = var.nsx_isSSHEnabled,
"nsx_allowSSHRootLogin" = var.nsx_allowSSHRootLogin,
"nsx_passwd_0" = var.nsx_passwd_0,
"nsx_cli_passwd_0" = var.nsx_cli_passwd_0,
"nsx_cli_audit_passwd_0" = var.nsx_cli_audit_passwd_0,
"nsx_hostname" = var.nsx_hostname
}
}
lifecycle {
ignore_changes = [
#vapp # Enable this to ignore all vapp properties if the plan is re-run
vapp[0].properties["nsx_role"], # Avoid unwanted changes to specific vApp properties.
vapp[0].properties["nsx_passwd_0"],
vapp[0].properties["nsx_cli_passwd_0"],
vapp[0].properties["nsx_cli_audit_passwd_0"],
host_system_id # Avoids moving the VM back to the host it was deployed to if DRS has relocated it
]
}
}
Once we have all of the above we can run the following to validate our plan
terraform plan -out=nsxt01
If your plan is successful you should see an output similar to below
Once your plan is successful run the command below to apply the plan
terraform apply nsxt01
If the stars align your NSX-T Manager appliance should deploy successfully. Once its deployed, if you were to re-run the plan you should see a message similar to below
One of the key pieces to this is the lifecycle block in the plan. The lifecycle block enables you to callout things that Terraform should ignore when it is re-applying a plan. Things like tags or other items that may get updated by other systems etc. In our case we want Terraform to ignore the vApp properties as it will try to apply password properties every time, which would entail powering down the VM, making the change, and powering the VM back on.
Published