Skip to content

Terraform and AnsibleΒ€

PreparationΒ€

Ansible and TF client installed, then:

$ export D="$DT_PROJECT_ROOT/tmp/clusters/DO/ansible"
$ export TF_VAR_do_token="$(pass show DO/pat)"
$ alias tf=terraform
$ mkdir -p "$D"
$ cd "$D"

$ export D="$DT_PROJECT_ROOT/tmp/clusters/DO/ansible"
$ export TF_VAR_do_token="$(pass show DO/pat)"
$ alias tf=terraform
$ mkdir -p "$D"
$ cd "$D"

ConfigΒ€

Provider:Β€

$ cat provider.tf
terraform { 
 required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

data "digitalocean_ssh_key" "terraform" { name = "terraform" }
provider "digitalocean" { token = var.do_token }
variable "do_token" { }
variable "pvt_key" { default = "~/.ssh/id_rsa_terraform" }

Worker:Β€

$ cat worker.tf
resource "digitalocean_droplet" "worker" {
  image = "ubuntu-20-04-x64"
  name = "worker"
  region = "fra1"
  size = "s-1vcpu-1gb"
  private_networking = true
  ssh_keys = [ data.digitalocean_ssh_key.terraform.id ]
  connection {
    host = self.ipv4_address
    user = "root"
    type = "ssh"
    private_key = file(var.pvt_key)
    timeout = "2m"
  }

  provisioner "remote-exec" {
    inline = ["echo 'Hello World'"]
  }
    provisioner "local-exec" {
      command = "ansible-playbook -i '${digitalocean_droplet.worker.ipv4_address},' playbook.yml"
    }
}

Ansible:Β€

$ cat playbook.yml
---
- hosts: all
  become: yes
  become_user: root
  become_method: sudo
  tasks:
    - name: Install nginx
      apt:
        name: nginx
        state: latest
    - name: Restart Nginx
      service: name=nginx state=restarted
      become: yes
and

$ cat ansible.cfg
[defaults]
host_key_checking = False
remote_user = root

DeployΒ€

$ tf init
$ tf apply -auto-approve

$ tf init

Initializing the backend...

Initializing provider plugins...        
- Reusing previous version of digitalocean/digitalocean from the dependency lock file               
- Using previously-installed digitalocean/digitalocean v2.10.1

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.
$ tf apply -auto-approve                              
digitalocean_droplet.worker: Refreshing state... [id=257761846]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and  
found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

And we have the playbook deployed:

$ ip=$(tf show | grep ipv4 | head -n 1 | cut -d = -f 2 | xargs)
$ wget "http://$ip/" -O -  | grep -i welcome

$ ip=$(tf show | grep ipv4 | head -n 1 | cut -d = -f 2 | xargs)
$ wget "http://$ip/" -O -  | grep -i welcome          
--2021-08-04 01:23:55--  http://46.101.159.33/                                  
Connecting to 46.101.159.33:80... connected.                                    
HTTP request sent, awaiting response... 200 OK                                  
Length: 612 [text/html]                 
Saving to: β€˜STDOUT’

-                   100%[===================>]     612  --.-KB/s    in 0s

2021-08-04 01:23:55 (22.1 MB/s) - written to stdout [612/612]

<title>Welcome to nginx!</title>        
<h1>Welcome to nginx!</h1>

DiscussionΒ€

  • The remote exec is ugly. We have to give TF our private key, otherwise not required.
  • We had to do it to make sure the worker is up when ansible takes over.
  • A yet more "ugly" alternative would be a sleep.

  • Cleaner for sure would be to use dedicated ansible plugins for TF:

Lets' focus on the dynamic inventory based approach:

This script https://github.com/nbering/terraform-inventory/blob/master/terraform.py simply walks the TF state file after a successfull apply, pulling out ansible specific information configured directly within the TF resource configs, e.g.:

resource "ansible_host" "example" {
    inventory_hostname = "example.com"
    groups = ["web"]
    vars = {
        ansible_user = "admin"
    }
}

resource "ansible_group" "web" {
  inventory_group_name = "web"
  children = ["foo", "bar", "baz"]
  vars = {
    foo = "bar"
    bar = 2
  }
}

So in essence you do sth like this at (re-)provisioning time:

$ tf apply && ansible-playbook -i /etc/ansible/terraform.py playbook.yml
Back to top