Image for post
Image for post
Cover photo by Bill Oxford on Unsplash

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.

Inspiration

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.

I’ve experience using Terraform by Hashicorp, and I understand using Terraform and Ansible together. I’m also aware of AWS CloudFormation, and also how I could snapshot an image and create an AMI.

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.

Dependencies

On your machine, have the following installed.

  • Ansible
  • 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

  1. 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.
  2. Determine information about the default VPC and its subnets. Randomly select a subnet from the list to host our EC2 instance.
  3. Determine our public IP address and create a security group allowing ssh access from our IP address (only).
  4. Create an EC2 instance in the selected subnet and associated with the security group, and we’ll update our inventory with the new host.
  5. Install git and php to the jumpbox.
  6. 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/
roles/
keys/

Navigate into inventory/ and create a file called local as shown below.

[local]
127.0.0.1 ansible_connection=local

Create another file called ec2 with just the following contents. The playbooks will append host information into here later.

[jumpbox]

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.

region_name: 'us-east-1'
key_name: 'my_keypair'
ami_id: 'ami-0323c3dd2da7fb37d'
instance_type: 't2.micro'
instance_name: 'jumpbox'

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.

  1. Default VPC
  2. 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.

  1. Firstly, we filter the VPC list on whether it has the isDefault flag 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_id that we are interested in.
  2. Secondly, we query the subnets to extract all those associated with the default VPC using the vpc_id as a filter. We register this as subnet_info
  3. Finally, we use jinja to extract all the subnet id values from subnet_info into 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 exact_count=1 with instance_tags and 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.

Image for post
Image for post

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
- pause:
seconds: 30

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
block:
- import_tasks: key-pair.yml
- import_tasks: network-information.yml
- import_tasks: security-group.yml
- import_tasks: ec2.yml
- meta: refresh_inventory
- pause:
seconds: 30

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
gather_facts: False
roles:
- 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 keys/ directory.

Image for post
Image for post

All completed — the server is created and accessible. A quick check and we have git and 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.

You can follow me on Twitter and connect on LinkedIn.

Written by

DevOps | SRE | AWS | GCP https://twitter.com/davelms

Get the Medium app