Recently I got asked to provision a number of identical VMs. Now usually I resort to PowerCLI for this task, but this time I thought it would be nice to get out of my comfort zone and try something new for a change. Since I was experimenting with Packer in the beginning of this year I thought it might be a good idea to give Hashicorp Terraform a try.

Introduction

Like Packer Terraform can be used to provision infrastructure resources using a declarative configuration file. This means that a Terraform configuration describes a desired state. Then it makes sure that all the necessary steps are executed to create, modify or remove objects as needed. In contrast a tool like Powershell works in an imperative style. Using an imperative syntax means that you specify exactly which commands need to be executed to achieve a certain goal. There are both cons and cons to each of these approaches. And there are many resources on the web that go into that in more detail so I don’t have to. ?

Installation

Now to get started with Terraform you first need to install it. You can download the appropriate files from the Terraform download page. Like Packer there are versions available for macOS, FreeBSD, Linux, OpenBSD, Solaris and Windows. The beauty is you can simply download an archive file, extract it and you’re ready to go.

Create a project

Once you installed Terraform you can start creating your first project. In this case I’ll be using the vSphere provider to provision a number of VMs. To make this happen you need to first create a configuration file. When you create a directory for your project Terraform will process all files that have a `.tf` file extension. Since this project contains credentials to connect to a vCenter server I use a file called `terraform.tfvars` which is processed by default if it exists in the project directory. More information on using variables can be found on the getting started page. To make things easy on myself I used the “Cloning and customization example”from the vSphere provider documentation as a baseline.

I did however change a couple of things that I want to highlight.

  • Changed the code so a datastore cluster is used instead of a single datastore
  • Added a count property in the vsphere_virtual_machine resource so multiple resouces are created instead of just one
  • Added a number of variables to control a suffix for the VM and guest OS hostname and to assign each VM with a static IPv4 address

The code contains comments that describe how the configuration is supposed to work. So you can reuse the code in your projects easily. So without any further ado, here is my sample configuration file.

# Variable definitions
variable "vsphere_user" {}
variable "vsphere_password" {}
variable "vsphere_server" {}
# Define the number of resouces to be deployed 
variable "source_vm" {
  default = "vCv-01"
}
# Define a prefix for the VM name and guest hostname 
variable "name_prefix" {
  default = "vCv-"
}
# Define the number of resouces to be deployed 
variable "count" {
  default = 2
}
# Use an offset to start counting from a certain number
# or else the first server will be named server-01 and receive an ip address 192.168.105.51 
variable "offset" {
  default = 1
}
# Start number in last octect of ipv4 address
variable "start_ipv4_address" {
  default = 50
}

# Initialize vSphere provider, variables can be assigned with the var-file terraform parameter
provider "vsphere" {
  user           = "${var.vsphere_user}"
  password       = "${var.vsphere_password}"
  vsphere_server = "${var.vsphere_server}"

  # If you have a self-signed cert
  allow_unverified_ssl = true
}

data "vsphere_datacenter" "dc" {
  name = "DataCenter1"
}

data "vsphere_datastore_cluster" "datastore_cluster" {
  name          = "DatastoreCluster1"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}
data "vsphere_compute_cluster" "cluster" {
  name          = "ComputeCluster1"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "vsphere_network" "network" {
  name          = "portgroup1"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "vsphere_virtual_machine" "template" {
  name          = "${var.source_vm}"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

resource "vsphere_virtual_machine" "vm" {
  count = "${var.count}"
  # Define VM name using the resource count +1 making the first name server-01
  # except when an offset is used. If for example offset=1 the first name is server-02
  name             = "${var.name_prefix}${format("%02d", count.index + 1 + var.offset)}"
  resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
  # Changed from example; datastore cluster instead of datastore
  datastore_cluster_id = "${data.vsphere_datastore_cluster.datastore_cluster.id}"

  num_cpus = 2
  memory   = 1024
  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}"
    eagerly_scrub    = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}"
    thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}"
  }

  clone {
    template_uuid = "${data.vsphere_virtual_machine.template.id}"

    customize {
      windows_options {
        # Define hostname using the resource count +1 making the first name server-01
        # except when an offset is used. If for example offset=1 the first name is server-02
        computer_name = "${var.name_prefix}${format("%02d", count.index + 1 + var.offset)}"
        workgroup    = "vcloudvision.com"
        admin_password = "SuperSecretPassword123!"
      }

      network_interface {
        ipv4_address = "${format("192.168.105.%d", (count.index + 1 + var.offset + var.start_ipv4_address))}"
        ipv4_netmask = 23
      }

      ipv4_gateway = "192.168.104.1"
    }
  }
}

Initializing plugins

Now before you can use the vSphere provider you must first initialize it. This is achieved by using the following command in your project directory. Below the command you can see it’s output.

terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "vsphere" (1.10.0)...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.vsphere: version = "~> 1.10"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Testing and applying the configuration

When you initialized the plugin you can first do a dry-run before deploying the resources for real. Use the following command for a dry-run.

terraform plan

Finally when you are confident that the configuration will create the resources correctly you can apply the configuration using the following command.

terraform apply

And when you are done with the project you can remove it just as easily.

terraform destroy

That’s all there is to it.


Now I am aware that simply provisioning a couple VMs is only part of a solution. You probably also want to install some applications, maybe configure some network resources such as a firewall et cetera. Fortunately there are plenty of providers within the Terraform framework that you can use. But for the purposes of this blog post I limit myself to the vSphere provider.

So I hope this blog post makes sense and maybe get you excited about using Terraform for your projects as well.


Rudolf Kleijwegt

I am an experienced IT professional with over 20 years of hands-on experience designing, deploying, and maintaining IT infrastructure in both enterprise and service provider environments. My skills span across Linux and Windows and a multitude of server applications, allowing me to excel in a wide range of IT roles. Currently, my primary focus is on Software Defined DataCenter and DevOps. I am passionate about staying up to date with the latest trends in the industry to achieve superior outcomes.

3 Comments

rohan · June 14, 2019 at 9:28 am

thank you

Simon · November 22, 2019 at 8:59 am

Hello

Thanks for this instruction.
But what if I want to add another VM. In my test environment every time I wanna apply again, the other VM that already exists is gonna be deleted and created again.
What’s the solution of this? Create every time a new file and then a new VM?

Rudolf Kleijwegt · November 26, 2019 at 1:40 pm

Terraform saves the state of your project. So if you started out with one VM, you can simply increase the count of your resource object and a new one will be created. Check out the Procedural vs Declarative paragraph of this blog post for a more detailed explanation.

Leave a Reply

Avatar placeholder

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