Provisioning an AWS Infrastructure with Ansible

One of the main advantages of cloud computing is the ability to provision a complete infrastructure using lines of code.

Infrastructure as a code, or IaC, allows more agility, consistency and security.

There are some tools that are commonly used for provisioning and managing infrastructure via code. One of them is Ansible.

Ansible works by configuring client machines from an computer with Ansible components installed and configured. It communicates over normal SSH channels in order to retrieve information from remote machines, issue commands, and copy files. Because of this, an Ansible system does not require any additional software to be installed on the client computers.

In this article we are going to set up a complete infrastructure on AWS using only codes via Ansible.

Components

We will configure the following components.

Preparing Your Ansible Control Node

To provision the infrastructure we will need a machine, called Control Node, where we will install Ansible and write our codes.

You can set up this machine within your account in AWS or outside.

For this demo we will use the following machine configurations.

Linux CentOS 7
1vCPU
2GB RAM
8GB HD

Installing Ansible on Your Control Node

Log into your Control Node machine.

Ensure that the CentOS 7 EPEL repository is installed.

$ sudo yum -y install epel-release

Install pip and boto3 packages.

$ sudo yum -y install python-pip
$ sudo pip install boto3

Finally, install Ansible.

$ sudo yum -y install ansible

Ansible is a powerful tool and has many forms of use for the most different purposes. Check the official documentation page.

Ansible by default is installed in /etc/ansible directory.

Jump to Ansible directory.

$ cd /etc/ansible

Creating an AWS User, Access Key and Secret Key

To make our code interact with the AWS infrastructure we have to create a user and the keys so that we can make API calls.

In the next step you will need to download the .csv file. This is the only opportunity to save this file. This file contain the Access Key and Secret Key that we are use to interact with AWS.

Configuring the Network Infrastructure

We are ready to start deploying our infrastructure. In this section we will configure a variable file to authenticate in AWS and a playbook that contains the network infrastructure and one EC2 instance.

Creating a Variable File to Authenticate in AWS.

First, we need to create a file that contains the Access Key and Secret Key.

Create a directory and a file to store the variables.

$ cd /etc/ansible
$ mkdir vars
$ touch info.yml

Edit info.yml and insert the following lines.

$ sudo vim info.ymlaws_id: YOUR_AWS_ACCESS_KEY
aws_key: YOUR_AWS_SECRET_KEY
aws_region: YOUR_REGION
ssh_keyname: ansible-user
remote_user: ansibleuser

Save and close.

This file contains a lots of confidential informations. We need to secure this file.

We will use the utility ansible-vault that allow us encrypt the data.

$ sudo ansible-vault encrypt info.ym
New Vault password: <must be a strong password>
Confirm New Vault password: <must be a strong password>

Now, if you try read this document you should see something like this.

$ cat info.yml$ANSIBLE_VAULT;1.1;AES256
31376665316437636663633636646664326639616365313337306563396631623562
3962336130363461393235383533653135323431313530610a643764343237313265
30346565666636643133366366373235383165393362373664323364653561663835
6461383966623538310a343365343131343735643939386531653061653938393039
37323534393237366133616263373436616537313730373731633232386362353835
61366635656439356435343739623231643231353831393738373932373737353136
33663437666630353365333962393164636633306462343839353434663239326339
64323939656531626662383166373666376464333735666638343236343030346336
62626363663461366664343630353764373734393036623663666538313164373661
33663936656566353666306634633734633036396132333263636238376438363565
31316638306434383262373035313934653161323065336262613666643033653933
39636139613765653662

Creating an Ansible Playbook

According to official documentation, a playbook record and execute Ansible’s configuration, deployment, and orchestration functions. They can describe a policy you want your remote systems to enforce, or a set of steps in a general IT process.

It is where we will configure our code.

One important point: playbooks are written in yml format. We need to be careful about the file indentation. Any extra space, the playbook will not run.

To make our work easy and to avoid errors, we need to configure vim to work in an yml way.

Create vimrc file and insert the lines below.

$ sudo vim ~/.vimrcset ts=2 
set sts=2
set sw=2
set expandtab
syntax on
filetype indent plugin on
set ruler

This configurations allows us identation with keytab without errors.

Create a playbook.

$ cd /etc/ansible/
$ sudo touch aws_infrastructure.yml

Part 1 — Start

---
- name: AWS Network Configuration
hosts: localhost
gather_facts: false
vars_files:
- /etc/ansible/vars/info.yml
  • The code below will be the typhical start for our playbook.
  • It runs on localhost because most of the EC2 cloud modules run on a managed host which talks to the EC2 API to make changes.
  • Fact gathering has beed disabled to speed up the play.
  • The vars/info.yml file contains our variables that set the credentials to access AWS.

Part 2 — VPC

tasks:
- name: Creating a VPC
ec2_vpc_net:
aws_access_key: "{{ aws_id }}"
aws_secret_key: "{{ aws_key }}"
region: "{{ aws_region }}"
name: test_vpc_net
cidr_block: 10.0.0.0/16
tags:
module: ec2_vpc_net
tenancy: default
register: ansibleVPC
- name: Displaying the ansibleVPC output
debug:
var: ansibleVPC
  • The Access/Secret Keys and region are loaded from vars/info.yml as a variable.
  • A name for the VPC and the CIDR block.
  • Configure a tag as key-value pairs.
  • If tenancy is default, new instances in this VPC will run on shared hardware by default.
  • The results of the task are stored in the variable ansibleVPC.
  • Finally, to inspect ansibleVPC, we use debug module to display its contents.

Part 3 — Subnet

- name: Creating a subnet
ec2_vpc_subnet:
aws_access_key: "{{ aws_id }}"
aws_secret_key: "{{ aws_key }}"
region: "{{ aws_region }}"
state: present
cidr: 10.0.0.0/16
map_public: yes
tags:
Name: public_subnet
register: public_subnet
- name: Displaying the public_subnet output
debug:
var: public_subnet
  • Use the ec2_vpc_subnet module to add a subnet to a VPC that we created.
  • You must specify the vpc_id of the VPC that the subnet is in.
  • State is present to specify that this subnet should exist.
  • Specify the CIDR block.
  • Set a tag to identify the subnet.
  • Use map_public to assign instance in a public IP address.
  • Use register: public_subnet contains results you will need later.

Part 4 — Internet Gateway

- name: Creating an Internet Gateway
ec2_vpc_igw:
aws_access_keyt: "{{ aws_id }}"
aws_secret_key: "{{ aws_key }}"
region: "{{ aws_region }}"
state: present
vpc_id: "{{ ansibleVPC.vpc.id }}"
tags:
Name: ansibleVPC_IGW
register: ansibleVPC_IGW
- name: Displaying the ansibleVPC_IG output
debug:
var: ansibleVPC_IGW
  • vpc_id is the VPC ID, which is retrieve by reading data in the variable from the Part 2.
  • state control whether the IGW should be present.
  • register: ansibleVPC_igw is an ID that you will need to create the route table.

Part 5—Route Table

name: Creating a route table
ec2_vpc_igw:
aws_access_keyt: "{{ aws_id }}"
aws_secret_key: "{{ aws_key }}"
region: "{{ aws_region }}"
state: present
vpc_id: "{{ ansibleVPC.vpc.id }}"
tags:
Name: rt_ansibleVPC_PublicSubnet
subnets:
- "{{ public_subnet.subnet.id }}"
routes:
- dest: 0.0.0.0
gateway_id: "{{ ansibleVPC_IGW.gateway.id }}"
- name: Displaying the rt_ansibleVPC_PublicSubnet output
debug:
var: rt_ansibleVPC_PublicSubnet
  • State present means that you are creating the new route table.
  • vpc_id must be set to the ID of the VPC
  • subnets is a list of subnet IDS to attach to the route table
  • route is a list of routes
  • dest is the network being routed to.
  • gateway_id is the ID of an IGW

Part 6— Security Group

- name: Creating a Security Group
ec2_group:
aws_access_keyt: "{{ aws_id }}"
aws_secret_key: "{{ aws_key }}"
region: "{{ aws_region }}"
name: "Ansible Security Group"
description: "Ansible Security Group"
vpc_id: "{{ ansibleVPC.vpc.id }}"
tags:
Name: Ansible Security Group
rules:
- proto: "tcp"
ports: "22"
cidr_ip: 0.0.0.0/0
register: my_vpc_sg
- name: Displaying the my_vpc_sg output
debug:
var: my_vpc_sg
  • In order to launch an instance in AWS you need to assign a security group which act as a instance firewall.
  • The security group must be in the same VPC as the resources you want to protect.
  • If you want traffic to a port you need to add a rule specifying what port do you want to open.

Testing our Playbook

Before run our playbook in production, we have to check if there are syntax errors that could affect the runtime.

Check for syntax errors.

$ cd /etc/ansible
$ sudo ansible-playbook --syntax-check aws_infrastructure.yml

You should have no errors and the output looks like:

Now, we will run our playbook with a dry run flag, to see that everything runs properly. In this demo I’ll run with just one little part of the playbook as an example.

$ sudo ansible-playbook -C aws_infrastructure.yml

You should see something like this.

You can see that, in this case, the VPC was created.

Now, put Part 1 to Part 6 in a single playbook, and run.

$ sudo ansible-playbook aws_infrastructure.yml

Wait a little, and them check in your AWS account the components that we create.

Provisioning an EC2 instances

After configure properly the network components in your AWS infrastructure, we will now provisioning our EC2 instance.

The steps required to create an instance are:

In the same playbook that we work in the previous steps, add the following steps to configure EC2 instance.

- name: AWS EC2 Configuration
hosts: localhost
gather_facts: false
vars_files:
- vars/info.yml
tasks:
- name: Find CentOS AMIs
ec2_ami_info:
aws_access_key: "{{ aws_id }}"
aws_secret_key: "{{ aws_key }}"
region: "{{ aws_region }}"
owners: 679593333241 # CentOS owner ID
filters:
architecture: x86_64
name: CentOS-7*HVM-*
register: amis
- name: Display the AMIs output
debug:
var: amis
- name: Store latest version
set_fact:
latest_ami: "{{ amis.images | sort(attribute='creation_date') | last }}"
- name: Display the latest_ami output
debug:
var: latest_ami
- name: Provisioning an EC2 instance
ec2:
aws_access_key: "{{ aws_id }}"
aws_secret_key: "{{ aws_key }}"
region: "{{ aws_region }}"
image: "{{ latest_ami.imagem_id }}"
instance_type: t2.micro
key_name: "{{ ssh_keyname }}"
count: 1
state: present
group_id: "{{ my_vpc_sg.group_id }}"
wait: yes
vpc_subnet_id: "{{ public_subnet.subnet_id }}"
assign_public_ip: yes
instance_tags:
Name: ansible_ec2_template
register: ec2info

When run our playbook again, ansible will skip the network configuration steps that we’ve already configured earlier in this article and go directly to the EC2 deployment.

Run the playbook.

$ sudo ansible-playbook aws_infrastructure.yml

You can see all the steps.

Go to your EC2 instances dashboard and see that EC2 instance created.

Wrap-up

In this post I show you how to deploy a complete infrastructure in AWS, ranging from network devices and one EC2.

Information security for study purpose only and more!