Infrastructure deployment with Terraform and Ansible

Let’s clear up the most important thing about Ansible and Terraform. These two are NOT competing solutions, but are complementary open source Infrastructure-as-Code toolsets.

Terraform can be used to define and create infrastructure for your system, covering the hardware your applications will run on. Classically this includes compute and networking resources that can be managed by APIs from various service providers and systems.

Ansible configures and deploys your enviormnet on the infrastructure provided by Terraform. This can encompass not only operating system configuration, but also application installation and configuration, together with provisioning and managing storage on which the application runs. The storage can be either external or local. In this blog I will be focussing on the external storage management and configuration.

In it’s simplest form, Terraform cretaes the servers and Ansible configures and installs the servers. Ansible can then also be used to provision storage to the server and then install the application on that external storage. Why external storage? That would be the subject of a completely different conversation around resilience, redundancy, performance amonst other things, which I’m not going to get into here.

Back to using Ansible and Terraform together…

Call Ansible from Terraform

There are a few ways that Ansible and Terraform can play together, and these mainly involve using Terraform to call Ansible, either directly or indirectly.

Inline inventory with Instance IP

The most obvious way to call Ansible from Terraform is to use Terraform’s local-exec provisioner, which would look something like this:

provisioner "local-exec" {
    command = "ansible-playbook -i '${self.public_ip},' --private-key ${var.ssh_key_private} provision.yml"
}

This is OK, but there is a problem, in that local-exec starts without waiting for a compute instance to run against, so in most cases this will fail.

As a workaround you can use the remote-exec provisioner that will wait until a connection is established to the instance and then you invoke the local-exec provisioner.

So what you end up with is this section of code that effectively acts as an Ansible provisioner…

  provisioner "remote-exec" {
    inline = ["sudo dnf -y install python"]

    connection {
      type        = "ssh"
      user        = "user1"
      private_key = "${file(var.ssh_key_private)}"
    }
  }

  provisioner "local-exec" {
    command = "ansible-playbook -u user1 -i '${self.public_ip},' --private-key ${var.ssh_key_private} provision.yml" 
  }

Looking at this in a little more detail you can see the remote-exec waits for the instance to be contactable and then installs something, in this case Python. Then the local-exec provsioner calls an Ansible playbook to do all the low level provisioning, installation and configuration off the instance to a known standard.

To make the ansible-playbook command work you need to have the Ansible YAML (provision.yaml) in the same directory as the Terraform code.

Ansible is using the -i switch which is the inline inventory, allowing you to pass the IP address of the instance Terraform has just created. This won’t work if you need to have multiple hosts in your inventory, but for most occasions such as setting users, external storage provisioning and application installation, this will work just fine.

dynamic Inventory after Terraform

Another simple method is to unlink ansible from Terraform, ie run Terraform and then run Ansible.

You would create your infrastructure using Terraform by invoking terraform apply and then call Ansible by using ansibleplaybook -i inventory build.yaml, where inventory is a directory that contains the dynamic inventory scripts.

Using the Terraform state

It is possible to generate an ansible static inventory file from Terraform state.

Terraform maintains the state of the infrastructure it has created, and this state contains everything, including the instances. With the default Teraform local backend the state is stored in a JSON formatted file that can be passed and converted into an Ansible inventory.

Just to give you an idea, here is a link to a GitHub repo to convert a Terrafom state to an Ansible inventory.

Terraform Ansible plugins

Over the past few years a number of GitHub projects have tried to create an Ansible plugin for Terraform.

Give these a try if you would like, but remember they may no longer be maintained, working or even supported by the current Terrafom plugin system. Here are a couple I found by doing a quick Google search:

Conclusion

By using Ansible and Terraform you can create a very flexible workflow for creating servers with the required software and hardware configurations and, if required, you can even provision block of file storage from an external storage appliance.

Leave a Reply

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