In the first part of this series we went over Terraform at a very high level and discussed a basic example of using it to provision a single Windows VM or a single Linux VM. While this is a helpful baseline, it doesn’t realistically help us when defining our application structure in code. For example, if you wanted to set up a three tier application you’ll need to have three separate instances of the project we used. At that point we’re not really gaining much efficiency over deploying a template in vCenter. In this post I’ll discuss Terraform Modules, give an example of how to store your Terraform Module in GitHub, and provide a real-life example of how to deploy a three tier application using multiple source images.
Introduction to Terraform Modules
In Part 1 of this series I compared traditional programming languages to Terraform. We can do the same thing with Modules. For example, with Powershell or Python, you can download a module that provides functionality and consume that functionality in your code without having to author it. The same thing happens in Terraform, and just like with other languages, you can create your own, or download one from a third party.
Terraform Registry
Thankfully, Hashicorp provides us with a registry that we can use to find and consume Terraform Modules. At the time this post is published, the Terraform Registry contains support for 63 providers and has 3454 Modules. So if you are looking for specific functionality, check there first!
Roll Your Own Module
That said, you may have a need to create your own module, or if you’re like me, want to make sure you understand how modules work before you use one that someone else created.
Best Practices
First and foremost, you’ll want to take a look at the official documentation from Hashicorp on the standard module structure. It gets pretty in-depth, but I’ll hit on a few high level important items.
- Store your module in github
- At the very least, make sure you split your variables and outputs into different TF files
- Include an examples directory and show every variation of how to call your module. For example, if you can create a linux image or a windows image, but you need to call them differently, show an example for each.
- Include a README.md file that has a description of the module, but also references your examples
Why store your module in GitHub?
Aside from the wide ranging benefits of using version control, using GitHub will allow you to call a specific version of your module.
File Structure
The following files should be in your GitHub reposity. When you run terraform init
, it will be downloaded to the .terraform directory on the machine running the project.
main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# Author: Jon Howe # Blog: https://www.virtjunkie.com/vmware-provisioning-using-hashicorp-terraform-part-2/ # GitHub: https://github.com/jonhowe/Terraform-vSphere-VirtualMachine/blob/master/main.tf # Vcenter connection parameters provider "vsphere" { user = var.vsphere_user password = var.vsphere_password vsphere_server = var.vsphere_server allow_unverified_ssl = true } data "vsphere_datacenter" "dc" { name = var.datacenter } data "vsphere_datastore" "datastore" { name = var.datastore datacenter_id = data.vsphere_datacenter.dc.id } data "vsphere_compute_cluster" "cluster" { name = var.cluster datacenter_id = data.vsphere_datacenter.dc.id } data "vsphere_network" "network" { name = var.portgroup datacenter_id = data.vsphere_datacenter.dc.id } data "vsphere_virtual_machine" "template" { name = var.template_name datacenter_id = data.vsphere_datacenter.dc.id } resource "vsphere_virtual_machine" "linux" { count = var.is_windows_image ? 0 : 1 name = var.vm_name resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id datastore_id = data.vsphere_datastore.datastore.id num_cpus = var.vcpu_count memory = var.memory 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 linked_clone = "true" customize { #https://www.terraform.io/docs/providers/vsphere/r/virtual_machine.html#linux-customization-options linux_options { host_name = var.vm_name domain = var.domain_name } network_interface {} /* network_interface { ipv4_address = var.vm_ip ipv4_netmask = var.vm_cidr } ipv4_gateway = var.default_gw dns_server_list = ["1.2.3.4"] */ } } } resource "vsphere_virtual_machine" "windows" { count = var.is_windows_image ? 1 : 0 name = var.vm_name resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id datastore_id = data.vsphere_datastore.datastore.id num_cpus = var.vcpu_count memory = var.memory 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 linked_clone = "true" customize { #https://www.terraform.io/docs/providers/vsphere/r/virtual_machine.html#windows-customization-options windows_options { computer_name = var.vm_name admin_password = var.adminpassword /* join_domain = "cloud.local" domain_admin_user = "administrator@cloud.local" domain_admin_password = "password" run_once_command_list = [ ] */ } network_interface {} /* network_interface { ipv4_address = var.vm_ip ipv4_netmask = var.vm_cidr } ipv4_gateway = var.default_gw dns_server_list = ["1.2.3.4"] */ } } } |
variables.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# Author: Jon Howe # Blog: https://www.virtjunkie.com/vmware-provisioning-using-hashicorp-terraform-part-2/ # GitHub: https://github.com/jonhowe/Terraform-vSphere-VirtualMachine/blob/master/variables.tf variable "vsphere_server" { description = "vsphere server for the environment - EXAMPLE: vcenter01.hosted.local" } variable "vsphere_user" { description = "vsphere server for the environment - EXAMPLE: vsphereuser" } variable "vsphere_password" { description = "vsphere server password for the environment" } /* variable "virtual_machine_dns_servers" { type = list default = ["192.168.1.60", "172.16.1.1"] } */ variable "adminpassword" { description = "Administrator password for windows builds" } variable "datacenter" { description = "Datacenter name in vCenter" } variable "datastore" { description = "datastore name in vCenter" } variable "cluster" { description = "Cluster name in vCenter" } variable "portgroup" { description = "Port Group new VM(s) will use" } variable "domain_name" { description = "Domain Search name" } variable "default_gw" { description = "Default Gateway" } variable "template_name" { description = "VMware Template Name" } variable "vm_name" { description = "New VM Name" } variable "vm_ip" { description = "IP Address to assign to VM" } variable "vm_cidr" { description = "CIDR Block for VM" } variable "vcpu_count" { description = "How many vCPUs do you want?" } variable "memory" { description = "RAM in MB" } variable "is_windows_image" { description = "Boolean flag to notify when the custom image is windows based." default = false } |
output.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# Author: Jon Howe # Blog: https://www.virtjunkie.com/vmware-provisioning-using-hashicorp-terraform-part-2/ # GitHub: https://github.com/jonhowe/Terraform-vSphere-VirtualMachine/blob/master/output.tf output "DC_ID" { description = "id of vSphere Datacenter" value = data.vsphere_datacenter.dc.id } output "Windows-VM" { description = "VM Names" value = vsphere_virtual_machine.windows.*.name } output "Windows-ip" { description = "default ip address of the deployed VM" value = vsphere_virtual_machine.windows.*.default_ip_address } output "Windows-guest-ip" { description = "all the registered ip address of the VM" value = vsphere_virtual_machine.windows.*.guest_ip_addresses } output "Windows-uuid" { description = "UUID of the VM in vSphere" value = vsphere_virtual_machine.windows.*.uuid } output "Linux-VM" { description = "VM Names" value = vsphere_virtual_machine.linux.*.name } output "Linux-ip" { description = "default ip address of the deployed VM" value = vsphere_virtual_machine.linux.*.default_ip_address } output "Linux-guest-ip" { description = "all the registered ip address of the VM" value = vsphere_virtual_machine.linux.*.guest_ip_addresses } output "Linux-uuid" { description = "UUID of the VM in vSphere" value = vsphere_virtual_machine.linux.*.uuid } |
How to use this module?
Now that we’ve created our module and have it in Github, all we need to do in order to complete our initial goal of having a three tier application is to create a single main.tf file, and call the module 3 times. Below is an example that allows us to set separate information for each of the new VMs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# Author: Jon Howe # Blog: https://www.virtjunkie.com/vmware-provisioning-using-hashicorp-terraform-part-2/ # GitHub: https://github.com/jonhowe/Terraform-vSphere-VirtualMachine/blob/master/examples/multi-vm/main.tf module "DC1" { is_windows_image = "1" source = "github.com/jonhowe/Terraform-vSphere-VirtualMachine/" vsphere_server = "vcenter.home.lab" vsphere_user = "administrator@vsphere.local" vsphere_password = "VMware1!" adminpassword = "terraform" datacenter = "VirtJunkie-DC" datastore = "vsanDatastore" cluster = "VirtJunkie" portgroup = "100-LabNetwork" domain_name = "home.lab" default_gw = "172.16.1.1" template_name = "WS16-CORE" vm_name = "DC1" vm_ip = "172.16.1.150" vm_cidr = 24 vcpu_count = 1 memory = 1024 } module "MGMT" { is_windows_image = "1" source = "github.com/jonhowe/Terraform-vSphere-VirtualMachine/" vsphere_server = "vcenter.home.lab" vsphere_user = "administrator@vsphere.local" vsphere_password = "VMware1!" adminpassword = "terraform" datacenter = "VirtJunkie-DC" datastore = "vsanDatastore" cluster = "VirtJunkie" portgroup = "100-LabNetwork" domain_name = "home.lab" default_gw = "172.16.1.1" template_name = "WS16-GUI" vm_name = "Mgmt" vm_ip = "172.16.1.151" vm_cidr = 24 vcpu_count = 2 memory = 4096 } module "IAAS" { is_windows_image = "1" source = "github.com/jonhowe/Terraform-vSphere-VirtualMachine/" vsphere_server = "vcenter.home.lab" vsphere_user = "administrator@vsphere.local" vsphere_password = "VMware1!" adminpassword = "terraform" datacenter = "VirtJunkie-DC" datastore = "vsanDatastore" cluster = "VirtJunkie" portgroup = "100-LabNetwork" domain_name = "home.lab" default_gw = "172.16.1.1" template_name = "WS16-GUI" vm_name = "IAAS" vm_ip = "172.16.1.159" vm_cidr = 24 vcpu_count = 2 memory = 4096 } |
References and what’s next?
What’s Next?
At this point we have shown how to create and manage images using packer, a couple of parts on how to use Terraform to deploy infrastructure, so all we have left is doing configuration of our newly created VMs.
- Use Ansible to configure our newly provisioned VMs
- Wrap the Terraform VM Deployment process, as well as the Ansible VM configuration process together into a single process you execute once
- Use Ansible to do a greenfield deployment of a vSphere environment