Automating VM Deploys with Terraform & Ansible - Part 1
Since this is my first official blog post, I thought I'd cover a situation that is very recent in my career, and that is automating server/vm deploys. One of the challenges my team faces is the amount of time it takes to execute a server request into a tangible VM in a timely fashion. By the time a request gets put in, and it gets escalated to the right team there's a turnaround of up to 5 business days, and that's assuming the request gets executed correctly the first time. From an end user's perspective, that's an incredibly long time. It's not flexible, not agile, and not timely. So I decided to solve that problem. The whole solution is a bit extensive, so I will break this post up into two parts. So let's get started with part 1.
As mentioned, Terraform maintains state of the resources it creates. In the same directory the terraform config files remain in, there will be a .tfstate and .tfstate.backup file. The .tfstate file contains all of the configuration options for the resources that have been provisioned. If the config file values differ from the values in the .tfstate file, Terraform assumes the desired state is not achieved, and will correct that when the next apply is run.
The above section is important because now we want to destroy those resources. Since terraform provisioned a resource of "virtual_machine", when we run destroy against that same config file, we will destroy the resources of "virtual_machine". Once we kick off the destroy job, the .tfstate file will be nearly empty.
Run "terraform destroy" to being the destruction.
Boom. Just like that the VMs are destroyed. Terraform will only destroy resources which it created, so in my case since Terraform didn't create any network objects, load balancer objects, etc. if any of that was created post-deploy, then they would have to be manually cleaned up.
This is already a pretty lengthy post, and I think it's a good stopping point for a "Part 1". In my next post I'll dig into running Ansible to configure a VM, and I'll provide my list of future enhancements i wish to dig into. Thank you for reading up until now! Let me know what you think!
Requirements
Phase 1 of the solution I want to implement has to have a few key characteristics:
- It has to be repeatable with expected results
- It has to be easy enough for anyone to execute
- It has to speed up time from execution to delivery
- It has to maintain state and allow for destruction of those resources
- It has to be fun!
Inevitably, that last bullet point will come as long as the 4 above it are satisfied, right? Depends if you like the solution i suppose. So based on that criteria it would seem that we would want to target a solution that fits the whole "Infrastructure as code" model. That is exactly what I did.
The Solution
Anytime I hear the phrase "Infrastructure as Code", I immediately think of Hashicorp. They develop a wide array of infrastructure as code tools which all work together to develop a pipeline for highly available sites and applications.
Terraform is part of their "provisioning" phase, and that's exactly the problem it tackles. Terraform allows you to define resources you wish to provision, providers you wish to provision them on (think of the "where" you want your workload provisioned; cloud providers, on prem, etc), and any variables specific to these operations (credentials, public cloud keys, etc).
Provisioning is only half of the challenge, though. Once a VM is provisioned and deployed, we need to be able to deploy packages, install updates, join to an Active Directory domain (if windows), and overall abide by the parameters that constitute a servers role. Enter Ansible.
Ansible is a configuration management tool developed by RedHat. It does a fantastic job of setting desired configuration of network devices. They have a large amount of modules which consist of API's for various devices you wish to manage (Linux or Windows machines, Cisco ACI devices, NetApp arrays, Pure arrays, etc). The list goes on and on. Check them out at https://docs.ansible.com/ansible/latest/modules/modules_by_category.html. As of this writing, Ansible 2.8 is the latest version.
So I use Terraform to provision and deploy a VM, and afterwards Ansible will set the desired state of the VM.
Time for the nerdy bits!
So if you're a technical person like me, this is the part that will excite you the most. We'll dig into the config. In my situation i have Ansible and Terraform installed on a Red Hat 7 VM. The reason for that is because i want Terraform to kick off the ansible playbook once it gets to the end of provisioning, and the way I did that was with the "local exec" provisioner. We'll dig into that soon.
In this example we have 3 blocks of code that make up the main terraform config file; a provider statement, a module which accepts variables, and an output statement. Provider defines the platform we will provision resources on; in this case, VMware. The module in this example was taken directly from the terraform registry, https://registry.terraform.io/modules/vancluever/virtual-machine/vsphere/1.0.1. Big thank you to the author vancluever, as this module has saved me a ton of time.
If you download the module on the terraform registry, you can see there are a lot of variables that can be set. In my lab I've set only the variables in the code snippet above. The idea of terraform modules is to re-use code where it makes sense. In my case I provision VMware resources, so it makes sense to modular-ize the creation of VM's and simplify my terraform configs.
The output statement is useful since i can gather specific information (in this case, the IP's of the machines that are created), and have that information sent to the requester.
To kick off the build of this particular set of VMs, it's as simple as browsing to the directory containing the terraform config files and running a few commands, as seen below.
The initialize will download all of the modules and providers necessary to make the code work. Next we would typically "plan" the code, which is to run a simple what-if type of scenario showing you all of the things that are about to get built. For the sake of brevity, I'll jump straight to the provisioning piece.
There it is. The resource has been provisioned. What we should have is a 3 VMs named "terra-test0, terra-test1, terra-test2" up and the "output" section of the terraform code will spit out the IP addresses of each VM.Terraform Config
Below is the config for a typical VM deployed via Terraform. Let's review.
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 } module "virtual-machine" { source = "vancluever/virtual-machine/vsphere" version = "1.0.1" # insert the 5 required variables here datacenter = "Thos" datastore = "localdisk1" memory = "2048" network = "VM Network" resource_pool = "192.168.0.150/Resources" vm_count = "3" vm_name_prefix = "terra-test" template_name = "Ansible" template_os_family = "linux" dns_servers = "192.168.0.156" } output "IP_address"{ value = module.virtual-machine.virtual_machine_default_ips description = "test output which should shoot out IP addresses"
In this example we have 3 blocks of code that make up the main terraform config file; a provider statement, a module which accepts variables, and an output statement. Provider defines the platform we will provision resources on; in this case, VMware. The module in this example was taken directly from the terraform registry, https://registry.terraform.io/modules/vancluever/virtual-machine/vsphere/1.0.1. Big thank you to the author vancluever, as this module has saved me a ton of time.
If you download the module on the terraform registry, you can see there are a lot of variables that can be set. In my lab I've set only the variables in the code snippet above. The idea of terraform modules is to re-use code where it makes sense. In my case I provision VMware resources, so it makes sense to modular-ize the creation of VM's and simplify my terraform configs.
The output statement is useful since i can gather specific information (in this case, the IP's of the machines that are created), and have that information sent to the requester.
To kick off the build of this particular set of VMs, it's as simple as browsing to the directory containing the terraform config files and running a few commands, as seen below.
PS C:\Terraform\TestBox> terraform init Initializing modules... Initializing the backend... Initializing provider plugins... 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.13" Terraform has been successfully initialized!
The initialize will download all of the modules and providers necessary to make the code work. Next we would typically "plan" the code, which is to run a simple what-if type of scenario showing you all of the things that are about to get built. For the sake of brevity, I'll jump straight to the provisioning piece.
Plan: 3 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. s Enter a value: yes module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Creating... module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still creating... [10s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still creating... [20s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still creating... [30s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still creating... [40s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still creating... [50s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still creating... [1m0s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still creating... [1m10s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still creating... [1m20s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still creating... [1m30s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0] (local-exec): test module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Creation complete after 1m30s [id=421703c3-6325-6d75-382f-09947dc54e6f] Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
As mentioned, Terraform maintains state of the resources it creates. In the same directory the terraform config files remain in, there will be a .tfstate and .tfstate.backup file. The .tfstate file contains all of the configuration options for the resources that have been provisioned. If the config file values differ from the values in the .tfstate file, Terraform assumes the desired state is not achieved, and will correct that when the next apply is run.
The above section is important because now we want to destroy those resources. Since terraform provisioned a resource of "virtual_machine", when we run destroy against that same config file, we will destroy the resources of "virtual_machine". Once we kick off the destroy job, the .tfstate file will be nearly empty.
Run "terraform destroy" to being the destruction.
Plan: 0 to add, 0 to change, 3 to destroy. Do you really want to destroy all resources? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Destroying... [id=421703c3-6325-6d75-382f-09947dc54e6f] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Still destroying... [id=421703c3-6325-6d75-382f-09947dc54e6f, 10s elapsed] module.virtual-machine.vsphere_virtual_machine.virtual_machine_linux[0]: Destruction complete after 17s Destroy complete! Resources: 3 destroyed.
Boom. Just like that the VMs are destroyed. Terraform will only destroy resources which it created, so in my case since Terraform didn't create any network objects, load balancer objects, etc. if any of that was created post-deploy, then they would have to be manually cleaned up.
This is already a pretty lengthy post, and I think it's a good stopping point for a "Part 1". In my next post I'll dig into running Ansible to configure a VM, and I'll provide my list of future enhancements i wish to dig into. Thank you for reading up until now! Let me know what you think!
Comments
Post a Comment