Use Ansible to create and configure EC2 instances on AWS
Quick how to tutorial to using Ansible to stand up EC2 instances on AWS. Over the next 5 minutes, we’ll create an initial jumpbox in our default VPC and install some basic developer tools onto it.
I’ve been using the awesome A Cloud Guru “Cloud Playground” sandboxes to progress through some of their training content. These environments are fully-functional AWS accounts and allow the user to follow along the Cloud certification tutorials and training — a great new feature of A Cloud Guru.
A sandbox lasts 5 hours, which is usually ample enough time to follow along, but every so often I find that I come back the next day and want to recreate some baseline infrastructure and pick up from where I left off.
That inspired me to look at Infrastructure as Code and build out a repeatable platform from code blueprints.
However, since Ansible is also a cloud infrastructure provisioning tool, my use-case looked a good challenge to demonstrate using the Ansible modules for AWS. Let’s get stuck in.
On your machine, have the following installed.
- Python ≥ 2.6, with boto, boto3, and botocore.
I also have AWS CLI, and have configured the “Cloud Sandbox” AWS Access Key and AWS Secret Id using
aws configure. You don’t have to do this way, but I tend to find having the AWS CLI to hand as well is easier all round — follow the boto installation instructions for other options.
What we’re going to create
Our goal is to create an EC2 instance in the default VPC — located in North Virginia (us-east-1) in the case of the Cloud Sandboxes.
We’re going to make that EC2 instance accessible over ssh from our IP only. For that we will need to create an EC2 key pair.
We’re going to ensure that the instance has a few tools — for the purpose of demonstration, we’ll let Ansible install packages onto the EC2 instance.
Steps to follow
- Create an EC2 key pair (if one does not already exist — Ansible has built-in idempotency, one of is many plus points) and save the private key to file.
- Determine information about the default VPC and its subnets. Randomly select a subnet from the list to host our EC2 instance.
- Determine our public IP address and create a security group allowing ssh access from our IP address (only).
- Create an EC2 instance in the selected subnet and associated with the security group, and we’ll update our inventory with the new host.
- Install git and php to the jumpbox.
- Check out our new instance.
Initialize project and set up our variables
I’ve started off the exercise with a brand new, empty project directory and in there created three empty directories:
inventory/ and create a file called
local as shown below.
Create another file called
ec2 with just the following contents. The playbooks will append host information into here later.
When we run our commands, we can specify this inventory directory with the option
-i inventory and Ansible will pick up the contents from here.
I created a
roles/ directory and in there ran
ansible-galaxy init create-ec2-instances to create a basic roles outline structure to manage the tasks.
For the purposes of these tutorial, I’ve referenced the following variables which helps avoid some hard-coding in the tasks. The AMI is that of a standard Amazon Linux 2 in us-east-1.
Step 1. Create a new EC2 key pair.
Our first step is to let Ansible create a new EC2 key pair. We register the output and then we can write the
private_key contents into a local pem file in the
keys/ directory. Don’t forget the file permissions.
Step 2. Obtain networking information from AWS.
There are two pieces of AWS network information we want to know, and fortunately Ansible provides a way to query for both of these.
- Default VPC
- Subnets in that default VPC
If we were building out a larger piece of infrastructure — i.e. in our own AWS Account — we would probably want to create a brand new VPC and subnets, which is also feasible with Ansible. Here though, finding and using the default is sufficient.
In the yaml below you will see three tasks.
- Firstly, we filter the VPC list on whether it has the
isDefaultflag set. You can filter on all kinds of attributes and these match against the AWS CLI. We save the resultant response into
default_vpc— you will see from the next step, it is an array (in our case, of 1 entry) and it is the value of
vpc_idthat we are interested in.
- Secondly, we query the subnets to extract all those associated with the default VPC using the
vpc_idas a filter. We register this as
- Finally, we use jinja to extract all the subnet id values from
subnet_infointo a list, and then select one at random — that’ll be the subnet we’ll create our instance into.
Step 3. Secure our instance.
We want to ensure that only we can access our new server.
First of all, we ask Ansible what our public IP is. We can do this by using the
ipify_facts module (top tip: you can run this straight from a command line using
ansible -m ipify_facts to quickly get at this information too).
Once we have our public IP address, we can create a new Security Group that allows SSH access only from that IP.
Step 4. Create the EC2 instances.
We’re now ready for the final step and can create an EC2 instance. Most of the values we’ve set up as variables or picked up along the way (e.g. the
vpc_subnet_id) so it’s just filling in the blanks at this stage.
You’ll see we can combine
count_tag to ensure that if we re-run the playbook, we will not create more instances. I’ve noticed through experimentation that this is applied within the same subnet — my random subnet selector means I create a few more instances than I wanted, but we could hard-code the subnet and ensure we do only get one.
Once the instance has been created, we append the new host in our
inventory/ec2 file that we created at the start. You should find you get something like this.
Because we are dynamically updating the inventory, we can optionally include
refresh_inventory and a pause for 30 seconds — gives AWS just enough time to start the VM up and ensure sshd is running. These are important if you’re heading straight into the configuration.
- meta: refresh_inventory
Bringing Steps 1–4 together
At this stage, we’ve got all our tasks set up inside the
create-ec2-instances role and our
roles/create-ec2-instances/tasks/main.yml looks like this:
- name: Create jumpbox in default VPC
- import_tasks: key-pair.yml
- import_tasks: network-information.yml
- import_tasks: security-group.yml
- import_tasks: ec2.yml
- meta: refresh_inventory
We can create a playbook in the root project directory (call it what you like, I called mine
create-ec2.yml). Note that we specify
hosts: local for the AWS infrastructure tasks.
# Create jumpbox on an EC2 instance- hosts: local
- role: create-ec2-instances
Simply run the command
ansible-playbook create-ec2.yml -i inventory to run through the playbook to create our key, security group, and instance.
Step 5. Configure the jumpbox.
In this playbook, we’re going to update yum so that the server is up to date and install a couple of packages — selected at random to demonstrate a step to set up of our jumpbox. I opted for git and php — it’s only a demo.
Note our new playbook references
hosts: jumpbox which picks out the hosts from the
inventory/ec2 file that we dynamically appended to in Step 4.
Step 6. Check it all out.
At this stage;
- we should have a jumpbox created in the default VPC in our AWS Account.
- our instance should be accessible over ssh only to our IP address.
- the jumpbox should have our packages pre-installed and ready to use.
Let’s check it out, just to be sure and ssh into it — remember the auto-generated private key has been saved into the
All completed — the server is created and accessible. A quick check and we have
php installed as expected. Job done.
A note from the author
Thank you for reading this article — I hope you found it useful. As mentioned in the introduction, there are many ways to accomplish this task and I look forward to your comments and feedback.
All source code for this demonstration can be downloaded at GitHub.