Terraform Basics (with VMware)

VMware

Hashicorp’s Terraform is an Infrastructure as Code (IaC) declarative language that allows you to define the existence and state of objects within your infrastructure and then publish this new state to your infrastructure. Terraform is agnostic of platform, you can use a wide range of “providers” to manage a number of different infrastructure components from a single Terraform script.

In this simple example, i’ll show how to create a template and then deploy two virtual machines on VMware using this template using the Terraform tool. The example assumes that you have an Ubuntu 20.04 client machine that you’ll install Terraform onto, then a small VMware environment which has VMware vCenter and at least one ESXi host.

Install Terraform

  1. Make sure the system is up-to-date and install these packages which are needed for further steps:
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common curl
  1. Next add the Hashicorp GPG key needed by the repository:
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
  1. Then, add the official repository for HashiCorp for Linux:
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
  1. Now that we have added the repo to the list, let’s update to add the relevant repository content:
sudo apt update
  1. Once update, run this command to install Terraform:
sudo apt install terraform
  1. Run terraform to ensure its been installed correctly.
terraform

You should see the help guide, showing the available commands, if that is the case Terraform has been successfully installed.

Prepare VMware vCenter Permissions

The next step is to create an account within VMware vCenter that you’ll use to make changes to your VMware environment. If you are testing this on a production environment is recommended to restrict the permissions this account has to change your VMware environment.

Create “variables.tf” Terraform File

The first file we need to create is the “variables.tf” file, this defines some variables, in this case a file that defines the username, password and vCenter Server address.

variable "username" {
  default       = "terraform-admin@domain.local"
}

variable "password" {
  default       = "mypassword"
}

variable "vcenter" {
  default       = "vcenter.domain.com"
}

These variables are important because we’ll refer to them from the main.tf file shortly.

Create Ubuntu VMware Template

I’m assuming you know how to create a VMware VM template, the below gives a great simple guide of how to do this
https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/manage/hybrid/server/best-practices/vmware-ubuntu-template

A very brief procedure is to install Ubuntu (20.04) onto a VM, so you have a bootable Ubuntu machine, with whatever extra packages you require. You then run the following commands to prepare the machine for being turned into a template.

sudo apt-get update
sudo apt-get upgrade -y

sudo sed -i 's/preserve_hostname: false/preserve_hostname: true/g' /etc/cloud/cloud.cfg
sudo truncate -s0 /etc/hostname
sudo hostnamectl set-hostname localhost

sudo rm /etc/netplan/00-installer-config.yaml

cat /dev/null > ~/.bash_history && history -c
sudo shutdown now

Within VMware vCenter now find the VM you’ve just created, right click and select “Convert to Template”, once converted we’re ready to build our configuration and attempt to deploy some VMs (using the template) by declaring them within Terraform.

Create “main.tf” Terraform File

Create the “main.tf” file in the same directory as your variables.tf you created earlier. You notice that this file refers to the variables you declared in the variables.tf file earlier.

You should change the Datacenter name, datastore names etc. to what

provider "vsphere" {
  user                  = var.username
  password              = var.password
  vsphere_server        = var.vcenter
  allow_unverified_ssl  = true
}

# A "data" resource is a readonly resource, something Terraform knows about but won't be changing.
data "vsphere_datacenter" "datacenter" {
  name = "MyDatacenter"
}

data "vsphere_datastore" "datastore" {
  name          = "datastore1"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_compute_cluster" "cluster" {
  name          = "My Cluster"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_network" "network" {
  name          = "my-network-pg"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_virtual_machine" "template" {
  name          = "ubuntu2204"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}


resource "vsphere_virtual_machine" "bill" {
  name             = "bill"
  folder           = "Production"
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id
  num_cpus         = 2
  memory           = 2048
  guest_id         = data.vsphere_virtual_machine.template.guest_id
  scsi_type        = data.vsphere_virtual_machine.template.scsi_type
  network_interface {
    network_id = data.vsphere_network.network.id
    adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0]
  }
  disk {
    label            = "disk0"
    size             = data.vsphere_virtual_machine.template.disks.0.size
    thin_provisioned = data.vsphere_virtual_machine.template.disks.0.thin_provisioned
  }
  clone {
    template_uuid    = data.vsphere_virtual_machine.template.id
    customize {
        linux_options {
          host_name = "bill"
          domain   = "domain.com"
        }
        network_interface {
          ipv4_address = "172.16.0.10"
          ipv4_netmask = 24
        }
        ipv4_gateway   = "172.16.0.1"
        dns_server_list = ["172.16.200.10","172.16.200.11"]
        dns_suffix_list = ["internal.domain.com","domain.com"]
    }
  }
}

resource "vsphere_virtual_machine" "ben" {
  name             = "ben"
  folder           = "Production"
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id
  num_cpus         = 2
  memory           = 2048
  guest_id         = data.vsphere_virtual_machine.template.guest_id
  scsi_type        = data.vsphere_virtual_machine.template.scsi_type
  network_interface {
    network_id = data.vsphere_network.network.id
    adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0]
  }
  disk {
    label            = "disk0"
    size             = data.vsphere_virtual_machine.template.disks.0.size
    thin_provisioned = data.vsphere_virtual_machine.template.disks.0.thin_provisioned
  }
  clone {
    template_uuid    = data.vsphere_virtual_machine.template.id
    customize {
        linux_options {
          host_name = "ben"
          domain   = "internal.sanger.ac.uk"
        }
        network_interface {
          ipv4_address = "172.16.0.11"
          ipv4_netmask = 24
        }
        ipv4_gateway   = "172.16.0.1"
        dns_server_list = ["172.16.200.10","172.16.200.11"]
        dns_suffix_list = ["internal.domain.com","domain.com"]
    }
  }
}

Before running Terraform operations, download plugins using the terraform init command.

terraform init

This will then download the “provider” packages and intialise the Terraform environment ready for use with in this case vSphere (VMware). You can use various other providers the same way so its there ready for use.

Running Terraform and Observing the Results

Now we are ready to run Terraform, we’re going to run a plan first just to see what Terraform thinks it needs to do.

terraform plan

Check the output, you should see it says it will create two new VMs. If you are happy run the following to apply the configuration.

terraform apply

After a few seconds you should see the VMs have been deployed, then powered on, they’ll boot up, which means they run the “cloud-init” scripts, this will set the hostname, apply the IP network configurations.

After a few minutes you should have two working VMs deployed, in this example: Bill and Ben.

Destroy it All

If you want to remove what you’ve deployed, you can run:

terraform destroy

The thing to remember is that Terraform only manages (changes) what it knows about (in it’s state). Our script only declared two VMs, so this destroy command will only destroy these two VMs.

Conclusion

Hopefully this gives you a very simple introduction to what you can do with Terraform. Its very extensible, you can add various providers and drive different parts of your infrastructure all from one script.

For example, you could have Terraform, create DNS records within your DNS server for the VMs automatically, then deploy the VMs into a ready state, all in one fell swoop.

Additional Information

https://garyflynn.com/post/create-your-first-vsphere-terraform-configuration/

https://blogs.vmware.com/cloud/2019/11/19/infrastructure-code-terraform-vmware-vmware-cloud-aws/

https://github.com/diodonfrost/terraform-vsphere-examples

https://thenewstack.io/install-terraform-and-the-gaia-web-ui-on-ubuntu-server-22-04/

Image Attribution

Leave a Reply

Your email address will not be published. Required fields are marked *