Ansible, an open-source automation tool, simplifies cloud infrastructure management, making it easier to deploy, manage, and scale applications. When it comes to creating a Virtual Private Cloud (VPC) on AWS and setting up servers, Ansible stands out with its straightforward playbooks and powerful automation capabilities.
This guide will walk you through using Ansible to efficiently create a VPC on AWS and provision servers, ensuring a streamlined and error-free deployment process. As you leverage Ansible’s declarative language, you can achieve consistent and repeatable infrastructure setups, enhancing your cloud operations and reducing manual effort.
Prerequisite
- Have an AWS account
- GitHub account, git
- Will we be creating multi-subnet VPC on AWS as shown in the diagram below
We will use Ohio throughout this project
Here are the steps;
STEP 1 : Create Ansible Server
Create an EC2 instance with the following details;
AMI: Ubuntu 22.04
Instance Type: t2.micro //free tier eligible
SecGrp: allow SSH on port 22
Create a new keypair
Add the code below to your user data
#!/bin/bash
apt update
apt install ansible -y
Create an IAM Role, give the instance these details for the role
Service: EC2
Policy: AdministratorAccess
Name: ansible-admin-role
Check out this post to learn more about the IAM role and ec2 instance
Connect to your instance using SSH using the key pair you created before
Check out this post to learn how to connect to your ec2 instance
Confirm Ansible is installed on the instance
sudo apt install awscli -y
aws sts get-caller-identity
STEP 2 : Try Out PlayBook Code
The code below creates a new directory and a test playbook
mkdir vpc-stack-vprofile
cd vpc-stack-vprofile
vim test-aws.yml
Most people prefer Vim for accessing files on Linux but nano has always worked well for me so you can also use nano to create and edit the test-aws.yml file
Copy and paste this line into the file
- hosts: localhost
connection: local
gather_facts: False
tasks:
- name: sample ec2 key
ec2_key:
name: my_keypair
region: us-east-2
This Ansible playbook is designed to create an EC2 key pair on AWS. It operates locally on the ‘localhost’ and does not gather system facts. The playbook includes a single task named “sample ec2 key” that utilizes the ec2_key module. This task generates a key pair named “my_keypair” in the specified AWS region “us-east-2”. The key pair is essential for securely accessing and managing EC2 instances. This simple yet effective playbook ensures that your key pairs are consistently created and managed through automation.
To run the playbook we need to install either pip(python package manager) or apt. Since we use AWS, Boto is Python SDK in AWS. search boto in apt.
Run the code below to search and install Boto
ansible-playbook test-aws.yml
sudo apt search boto
sudo apt install python3-boto3 -y
We need to modify the code so we can download the key pair to our local machine
we will be able to see our file but we have to add another task to store the key in our local machine as it wasn’t downloaded in our machine. From the previous run, we know the exact path to get the private_key from the JSON file.
- hosts: localhost
connection: local
gather_facts: False
tasks:
- name: sample ec2 key
ec2_key:
name: my_keypair
region: us-east-2
register: keyout
- debug:
var: keyout
- name: store login key
copy:
content: "{{keyout.key.private_key}}"
dest: ./sample-key.pem
when: keyout.changed
Our key pair is stored
STEP 3 : Creating PlayBook Variables
Create a GitHub repository to store our VPC playbook or you can use any other Git tool
We will create two variable files, one for our VPC and another for our bastion host
Copy the content below and post on vpc_setup with no file extension
vpc_name: "Vprofile-vpc"
#VPC Range
vpcCidr: '172.20.0.0./16'
#Subnets Range
PubSub1Cidr: 172.20.1.0/24
PubSub2Cidr: 172.20.2.0/24
PubSub3Cidr: 172.20.3.0/24
PrivSub1Cidr: 172.20.4.0/24
PrivSub2Cidr: 172.20.5.0/24
PrivSub3Cidr: 172.20.6.0/24
#Region Name
region: "us-east-2"
#Zone Names
zone1: us-east-2a
zone2: us-east-2b
zone3: us-east-2c
state: present
Copy the content below and paste it on the bastion_host file with no extension.
bastion_ami: ami-0beaa649c482330f7 # Amazon Linux-2 AMI-ID from us-east-2 region
region: us-east-2
MYIP: IP_address_of_your_laptop/32
keyName: vprofile-key
instanceType: t2.micro
When the files are ready commit/push to them GitHub, clone this repository in our Ansible server.
Git clone <your github repo>
STEP 4 : Create a VPC PlayBook
Create a VPC playbook ‘vpc_setup’ in your main directory, and commit the changes to GitHub
- hosts: localhost
connection: local
gather_facts: False
tasks:
- name: Import VPC Variables
include_vars: vars/vpc_setup
- name: Create Vprofile VPC
ec2_vpc_net:
name: "{{vpc_name}}"
cidr_block: "{{vpcCidr}}"
region: "{{region}}"
dns_support: yes
dns_hostnames: yes
tenancy: default
state: "{{state}}"
register: vpcout
Pull the changes to your ansible server, I will show you the code to do this just this once after which you check this step again because we will be doing this a lot after now.
git pull origin main //you already have the repo in your directory
Change the Directory to your repo
Run the vpc_setup playbook
ansible-playbook vpc_setup.yml
Check your console to confirm the changes in your VPC
STEP 5 : Subnet PlayBook
Add the extra config to your vpc_setup.yml, I mean you should append it to the already ensure the indentation is consistent
# - debug:
# var: vpcout
- name: create Public Subnet 1 in Zone1
ec2_vpc_subnet:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
az: "{{zone1}}"
state: "{{state}}"
cidr: "{{PubSub1Cidr}}"
map_public: yes
tags:
Name: vprofile-pubsub1
register: pubsub1_out
- name: create Public Subnet 2 in Zone2
ec2_vpc_subnet:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
az: "{{zone2}}"
state: "{{state}}"
cidr: "{{PubSub2Cidr}}"
map_public: yes
tags:
Name: vprofile-pubsub2
register: pubsub2_out
- name: create Public Subnet 3 in Zone3
ec2_vpc_subnet:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
az: "{{zone3}}"
state: "{{state}}"
cidr: "{{PubSub3Cidr}}"
map_public: yes
tags:
Name: vprofile-pubsub3
register: pubsub3_out
- name: create Private Subnet 1 in Zone1
ec2_vpc_subnet:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
az: "{{zone1}}"
state: "{{state}}"
cidr: "{{PrivSub1Cidr}}"
map_public: yes
tags:
Name: vprofile-privsub1
register: privsub1_out
- name: create Private Subnet 2 in Zone2
ec2_vpc_subnet:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
az: "{{zone2}}"
state: "{{state}}"
cidr: "{{PrivSub2Cidr}}"
map_public: yes
tags:
Name: vprofile-privsub2
register: privsub2_out
- name: create Private Subnet 3 in Zone3
ec2_vpc_subnet:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
az: "{{zone3}}"
state: "{{state}}"
cidr: "{{PrivSub3Cidr}}"
map_public: yes
tags:
Name: vprofile-privsub3
register: privsub3_out
Commit your changes to your GitHub and poll the changes to your server
Check your subnet management console, confirm the changes
STEP 6 : Internet Gateway and Public Route Table
Add or append the config to your vpc_setup file
Commit the changes to your playbook
- name: Internet Gateway Setup
ec2_vpc_igw:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
state: "{{state}}"
tags:
Name: vprofile-igw
register: igw_out
- name: Setup Public Subnet Route Table
ec2_vpc_route_table:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
tags:
Name: Vprofile-PubRT
subnets:
- "{{ pubsub1_out.subnet.id }}"
- "{{ pubsub2_out.subnet.id }}"
- "{{ pubsub3_out.subnet.id }}"
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ igw_out.gateway_id }}"
register: pubRT_out
Run the playbook, Check the AWS console
STEP 7 : NAT Gateway Private Route Table
Add the info below and commit the changes to your GitHub
- name: NAT Gateway1 Setup and allocate new EIP if NATGW does not exist yet in the subnet
ec2_vpc_nat_gateway:
subnet_id: "{{ pubsub1_out.subnet.id }}"
region: "{{region}}"
state: "{{state}}"
wait: yes
if_exist_do_not_create: yes
register: natgw1_out
- name: Setup Private Subnet Route Table
ec2_vpc_route_table:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
tags:
Name: Vprofile-PrivRT1
subnets:
- "{{ privsub1_out.subnet.id }}"
- "{{ privsub2_out.subnet.id }}"
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ natgw1_out.nat_gateway_id }}"
register: privRT1_out
- name: NAT Gateway3 Setup and allocate new EIP if NATGW does not exist yet in the subnet
ec2_vpc_nat_gateway:
subnet_id: "{{ pubsub3_out.subnet.id }}"
region: "{{region}}"
state: "{{state}}"
wait: yes
if_exist_do_not_create: yes
register: natgw3_out
- name: Setup Private Subnet Route Table2
ec2_vpc_route_table:
vpc_id: "{{vpcout.vpc.id}}"
region: "{{region}}"
tags:
Name: Vprofile-PrivRT3
subnets:
- "{{ privsub3_out.subnet.id }}"
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ natgw3_out.nat_gateway_id }}"
register: privRT3_out
- debug:
var: "{{ item }}"
loop:
- vpcout.vpc.id
- pubsub1_out.subnet.id
- pubsub2_out.subnet.id
- pubsub3_out.subnet.id
- privsub1_out.subnet.id
- privsub2_out.subnet.id
- privsub3_out.subnet.id
- igw_out.gateway_id
- pubRT_out.route_table.id
- natgw1_out.nat_gateway_id
- privRT1_out.route_table.id
- natgw3_out.nat_gateway_id
- privRT3_out.route_table.id
- set_fact:
vpcid: "{{ vpcout.vpc.id }}"
pubsub1id: "{{ pubsub1_out.subnet.id }}"
pubsub2id: "{{ pubsub2_out.subnet.id }}"
pubsub3id: "{{ pubsub3_out.subnet.id }}"
privsub1id: "{{ privsub1_out.subnet.id }}"
privsub2id: "{{ privsub2_out.subnet.id }}"
privsub3id: "{{ privsub3_out.subnet.id }}"
igwid: "{{ igw_out.gateway_id }}"
pubRTid: "{{ pubRT_out.route_table.id }}"
natgw1id: "{{ natgw1_out.nat_gateway_id }}"
privRT1id: "{{ privRT1_out.route_table.id }}"
natgw3id: "{{ natgw3_out.nat_gateway_id }}"
privRT3id: "{{ privRT3_out.route_table.id }}"
cacheable: yes
- name: Create variables file for vpc Output
copy:
content: "vpcid: {{ vpcout.vpc.id }}\npubsub1id: {{ pubsub1_out.subnet.id }}\npubsub2id: {{ pubsub2_out.subnet.id }}\npubsub3id: {{ pubsub3_out.subnet.id }}\nprivsub1id: {{ privsub1_out.subnet.id }}\nprivsub2id: {{ privsub2_out.subnet.id }}\nprivsub3id: {{ privsub3_out.subnet.id }}\nigwid: {{ igw_out.gateway_id }}\npubRTid: {{ pubRT_out.route_table.id }}\nnatgw1id: {{ natgw1_out.nat_gateway_id }}\nprivRT1id: {{ privRT1_out.route_table.id }}\nnatgw3id: {{ natgw3_out.nat_gateway_id }}\nprivRT3id: {{ privRT3_out.route_table.id }}"
dest: vars/output_vars
Pull your changes to your Ansible servers and run the vpc_setup.yml, confirm that the resources have been created
The result should be that Ansible generates a file output_vars on your vars folder. Run ls ./vars to confirm this
Commit the new file or changes to your GitHub repo using your GitHub credentials and merge it with the main directory
git add .
git commit -m "your commit message"
git config --global user.name "your username"
git config --global user.email "your email"
git push origin main
git pull origin main
You should see your output_vars files in your vars folder. Alternatively, you can copy the output_vars file content and paste it into a new file in the same location on your local git CLI commit it, and push it to your GitHub, ensure you also commit it on your Ansible server before you pull from GitHub else you will merge issues
After your Ansible server repo and your GitHub repo have become consistent (No distinct branch) can now carry out the final step to create your Bastion host
STEP 8 : Bastion Host
Create your bastion host playbook “bastion_host.yml’ and put in the content below
Commit your changes to GitHub and pull it on your Ansible server run the playbook
Ansible-playbook bastion_host.yml
Check through all your resources created; EC2, VPC, subnets, NAT Gateway, Internet Gateway, Elastic IP, etc confirm you can see them and use it
STEP 9 : Delete the Resources
Delete all the resources created to reduce the cost you will accumulate
Don’t fail to delete; EC2 instances, NAT Gateway, Elastic IP, etc. These are resources that cost lots of money.
You can check out the ansible AWS documentation here for more details https://docs.ansible.com/ansible/2.9/modules/list_of_cloud_modules.html
Check the GitHub repository for the full code https://github.com/willie191998/ansible-vpc-setup/tree/main
Conclusion
We have been able to configure infrastructure on AWS using Ansible, let’s understand how Ansible differs from Terraform, Terraform is an open-source Infrastructure as Code (IaC) tool that focuses on provisioning and managing infrastructure on various cloud providers, including AWS.
It uses your AWS credentials to create and manage AWS services through its API.
Ansible, on the other hand, primarily focuses on configuration management, application deployment, and task automation. It executes its infrastructure configuration tasks internally on AWS instances or other remote systems using SSH.
Check out my post on How to create and upload object to an S3 bucket using Terraform