This is a very common workflow when using Ansible for provisioning an AWS EC2 instance. This post assumes a basic understand of Ansible and most importantly, assumes you've properly configured it to connect to AWS.
As Ansible official documentation insists, we are going to use four roles:
1- ami_find to get the ami id based on which we will launch our EC2 instance.
2- ec2_ami_creation to effectively launch the EC2 instance.
3- code_deploy for modifying the instance; this could be anything so we will simply transfer a file to the target machine.
4- build_ami to build our new image based on the running ec2 instance.
This post assumes you are at the top level of your Ansible project: my_ansible_project
The first role: ami_find
cd my_ansible_project/roles && ansible-galaxy init ami_find
In this role we are going to use the ec2_ami_find module and as an example, we will search for the an Ubuntu machine and get its ami_id ( ami-xxxxxxxx ). Now edit my_ansible_project/roles/ami_find/tasks/main.yml
file:
---
- ec2_ami_find:
name: "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"
sort: name
sort_order: descending
sort_end: 1
region: "{{ aws_region }}"
register: ami_find
- set_fact: ami_ubuntu="{{ ami_find.results[0].ami_id }}"
The second role: ec2_ami_creation
Here, we will use the ami_id
we got from the first role and then launch our new instance based on it:
cd my_ansible_project/roles && ansible-galaxy init ec2_ami_creation
In this role we are going to use most importantly the ec2_module to launch our instance. Now edit my_ansible_project/roles/ec2_ami_creation/tasks/main.yml
file:
---
- ec2_vpc_subnet_facts:
region: "{{aws_region}}"
register: vpc
- name: creation of security group of the ec2 instance
ec2_group:
name: example
description: an example EC2 group
region: "{{ aws_region }}"
rules:
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: 0.0.0.0/0
state: present
register: ec2_sg
- name: create instance using Ansible
ec2:
key_name: "{{ ansible_key }}"
group: example
vpc_subnet_id: "{{vpc.subnets[0].id}}"
instance_type: "{{ instance_type }}"
ec2_region: "{{ aws_region }}"
image: "{{ base_image }}"
assign_public_ip: yes
wait: yes
register: ec2
- set_fact: id={{ec2.instances[0].id}}
- name: adding the newly created instance to a temporary group in order to access it later from another play
add_host: name={{ item.public_ip }} groups=just_created
with_items: ec2.instances
- name: Wait for SSH to come up
wait_for: host={{ item.public_dns_name }} port=22 delay=10 timeout=640 state=started
with_items: ec2.instances
The third role: code_deploy
Here, we will provision this instance, which was added to a group called just_created
cd my_ansible_project/roles && ansible-galaxy init code_deploy
In this role we are going to use the template_module to transfer a file & write the machine hostname in it. Now edit my_ansible_project/roles/code_deploy/tasks/main.yml
file:
---
- template: src=my_file.txt.j2 dest=/etc/my_file.txt
then move to templates folder inside your role:
cd my_ansible_project/roles/templates
and add a file called my_file.txt.j2
containing:
my name is {{ ansible_hostname }}`
The fourth role: build_ami
We will now create an image of the running instance using the ec2_ami module. Move to you project folder and:
cd my_ansible_project/roles && ansible-galaxy init build_ami
Now edit my_ansible_project/roles/build_ami/tasks/main.yml
file:
---
- ec2_ami:
instance_id: "{{ instance_id }}"
wait: yes
name: Base_Image
Now, I think you have been wondering how to orchestrate all of these roles. Am I right? If so, continue reading.
We will write a playbook, composed of three plays: first play applicable on localhost
will call our first two roles, second play applicable on our just_created group. last role will be applicable on localhost
. Why localhost
? When we want to manage some AWS ressources, we use our local machine, as simple as that. Next, we will use a vars file in which we will put our variables: ansible_key
, aws_region
, etc...
create infrastructure folder at the top of your project and add a file inside it called aws.yml
:
---
aws_region: ap-southeast-2
ansible_key: ansible
instance_type: t2.small
So at the top of your project create build_base_image.yml
and add this:
---
- hosts: localhost
connection: local
gather_facts: False
vars_files:
- infrastructure/aws.yml
roles:
- ami_find
- { role: ec2_creation, base_image: "{{ ami_ubuntu }}"}
- hosts: just_created
connection: ssh
gather_facts: True
become: yes
become_method: sudo
roles:
- code_deploy
- hosts: localhost
connection: local
gather_facts: False
vars_files:
- infrastructure/aws.yml
roles:
- { role: new_image, instance_id: "{{ id }}"}
That's it, Dont forget to delete your ressources after testing this, or why not create a role to delete the running instance :-)