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.
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 ] }
Hopefully this was useful. I’m sure there are more efficient ways of doing this. I will update the post if i find them. Keep a look out for the next instalment
3 thoughts on “Terraform Learnings: Deploy an OVA Using the vSphere Provider”