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 :-)