Automate AWS deployments with Ansible + Terraform

This installation is made from a bastion server already available with the proper network permissions.
For different deployment types, you should adapt it to your need.

Install requirements

Ansible installation:

sudo apt update
sudo apt install -y software-properties-common
sudo apt-add-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible

ref: https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#installing-ansible-on-ubuntu

Terraform installation:

wget https://releases.hashicorp.com/terraform/0.12.24/terraform_0.12.24_linux_amd64.zip
sudo unzip -d /usr/local/bin/ ./terraform_0.12.24_linux_amd64.zip

ref: https://www.techrepublic.com/article/how-to-install-terraform-on-ubuntu-server/

AWS Client installation:

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

ref: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html

Connect your environment to your AWS cloud

$ aws configure
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: eu-central-a
Default output format [None]: json

ref: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration

Create a new Ansible project

That is my favorite Ansible layout. The one I’ve seen as the best so far:

mkdir -p ~/my_aws_project/inventory
mkdir -p ~/my_aws_project/playbooks
mkdir -p ~/my_aws_project/roles

Add the EC2 dynamic inventory

Ansible can work with a different kind of inventory called the dynamic inventory.
Instead of having a static declaration in a static file of your inventory, you can generate it from a source.
This source can be a database, an active directory, etc. A dynamic inventory is a scrip that outputs a JSON in a structure that Ansible can handle. We could then develop a script that discovers our EC2 infrastructure that would take some time. Or we can use the one already provide with Ansible:

Install prerequisites:

sudo apt install -y python3-pip
sudo pip3 install boto
sudo pip3 install ansible
sudo rm /usr/bin/python
sudo ln -s /usr/bin/python3 /usr/bin/python

Get the EC2 dynamic inventory:

wget -O ~/my_aws_project/inventory/ec2.py \
https://raw.githubusercontent.com/ansible/ansible/stable-2.9/contrib/inventory/ec2.py
wget -O ~/my_aws_project/inventory/ec2.ini \
https://raw.githubusercontent.com/ansible/ansible/stable-2.9/contrib/inventory/ec2.ini
chmod +x ~/my_aws_project/inventory/ec2.py

There are multiple configuration options you can do with the ini file. For this blog I’ll change those vars:

regions = eu-central-1
vpc_destination_variable = private_ip_address

Test the inventory script:

~/my_aws_project/inventory/ec2.py ## ---> return JSON description of your AWS infrastructure

Because I want to work on one AWS region in the private network only. Since my bastion is already in
the AWS infrastructure.

Add a role for our deployment

I’ll create a role with the only purpose to deploy my infrastructure into AWS.

ansible-galaxy init --init-path ~/my_aws_project/roles ec2_instances_dep

Cable the components (Ansible configuration)

To have that layout working fine and the simpliest way, I use that configuration:

## file ~/my_aws_project/ansible.cfg
[defaults]
roles_path = ./roles
inventory  = ./inventory/ec2.py

Test the ansible inventory:

cd ~/my_aws_project
ansible-inventory --graph ## ---> return the Ansible interpreted inventory

Terraform with Ansible

When I need to do something with Ansible, I first check in the list of modules is the work is already done.
And, nicely, there is a module for Terraform.

So I can add this module in my task main file of my role:

## ~/my_aws_project/roles/ec2_instances_dep/tasks/main.yml
---
- name: I create a directory to store my Terraform config
  file:
    path: "~/aws_terraform"
    state: directory
    recurse: yes

- name: I copy my Terraform template into the working directory create above
  template:
    src: "my_ec2_infra.tf"
    dest: "~/aws_terraform/my_ec2_infra.tf"

- name: I deploy my configuration into AWS from Ansible
  terraform:
    project_path: "~/aws_terraform"
    force_init: true
    state: "present"
  register: r_aws

- name: I do whatever I need to do in my EC2 infrastructure
  debug: msg="update, install, create user, start services, etc"

- name: I destroy my AWS infrastructure 
  terraform:
    project_path: "~/aws_terraform"
    state: "absent"

Terraform content

Add this file into the template directory: ~/my_aws_project/roles/ec2_instances_dep

## file ~/my_aws_project/roles/ec2_instances_dep/my_ec2_infra.tf
provider "aws" {
  region = "eu-central-1"
}

resource "aws_instance" "dba-essential" {
  count                       = "5"
  ami                         = "ami-0e342d72b12109f91"
  availability_zone           = "eu-central-1a"
  instance_type               = "t2.micro"
  associate_public_ip_address = false
  security_groups             = ["my_sg_01"]
  vpc_security_group_ids      = ["sg-602eff2724d52a0b7"]
  key_name                    = "my_key_01"

  root_block_device {
    delete_on_termination = true
    encrypted             = false
    volume_size           = 15
    volume_type           = "gp2"
  }

  tags = {
    Owner           = "Nicolas"
    Name            = "crash-test-${count.index + 1}"
  }

}

Create a playbook to call the role

## file ~/my_aws_project/playbooks/deploy.yml
---
- name: Deploy my infrastructure
  hosts: localhost
  roles:

    - ec2_instances_dep

Run the playbook

cd my_aws_project
ansible-playbook playbooks/deploy.yml

Boom! Here it is. Now imagine that you can generate a unique key and unique directory for each deployment and you can deploy as much infrastructure as your credit card will accept it.

I hope this helps, and please comment below for any questions.