Managing AMIs using CloudFormation
By: Simon-Pierre Gingras
Tags: Cloud Computing, Python, DevOps, Infrastructure, AWS

One of the long-time missing pieces of CloudFormation is AMIs (Amazon Linux Images). Creating custom AMIs allows you to launch new EC2 instances pre-configured to your liking (operating system, installed packages, hardened security, etc).

Thankfully, here at Poka, we have developed a solution that allows you to easily create AMIs as part of a CloudFormation stack. This project (unlike some others that I have come across on the web) supports not only creating and deleting AMIs, but also updating, which are properties that are necessary for a pleasant CloudFormation flow. For the impatient, you can find the code here.

Give me a sample!

First, let’s look at an example of how you would declare the AMI in a CloudFormation stack:

How you can use a Custom::AMI resource

In the snippet above, you can see that we must declare parameters for an Image and TemplateInstance . The Image section of the parameters can be used to specify some parameters (e.g. Name , Description ) for the CreateImage call. The TemplateInstance section allows you to specify parameters for the EC2 instance that will be used to create the AMI using the RunInstances call. Note that any update to the TemplateInstance property will trigger a complete replacement of the AMI.

How it all works

In order to create an AMI, we need to go through the following steps, in order:

Let’s take a closer look at the individual steps:

Steps 1, 2 & 3: We need to have an EC2 instance that has been fully configured to our liking, and then stopped. To prepare the EC2 instance, we leverage User Data. Unfortunately, there is no easy way of knowing exactly when the User Data script is completed. For that matter, I use a EC2 instance tag that is set once the User Data is completed. You can see this hack in the sample.yaml snippet shown previously:

A dirty little hack ends an otherwise harmless User Data script

We’ll wait before the UserDataFinished tag has been added to the EC2 instance before considering the User Data script to be completed. Now that the tag has been added to the instance, we can stop the instance.

Steps 4, 5 & 6: Once the EC2 instance has been stopped, we can create an image of the instance. This can take a few minutes. When the AMI is ready to go, we can safely terminate the EC2 instance. This will ensure we don’t pollute our AWS account with the stopped EC2 instance and its associated EBS volume.

Under the hood

At Poka, our preferred way of creating custom resources in CloudFormation is via Lambda functions. However, the 6 steps mentioned above can take way longer than the 5 minutes that are allowed for a Lambda function to execute.

To circumvent this limitation, we decided to leverage AWS Step Functions. Using a state machine, we can clearly express the different steps and the wait times in between:

The State Machine

When CloudFormation wants to create a new Custom::AMI resource, it will first invoke the Lambda-backed custom resource. In turn, the Custom Resource will start the State Machine. The last state in the State Machine sends back a success signal to CloudFormation:

How CloudFormation interacts with Custom::AMI

Once CloudFormation has received the success signal, the new AMI is now ready to use!

Getting Started

To start using AMIs in CloudFormation, all you need to do is create a CloudFormation Stack that will contain the Lambda-backed Resource, the State Machine and their associated IAM roles. You can find instructions to install the stack in the README.

Wrap up

AMIs are still not present to this day in CloudFormation. However, using Lambda-backed Custom Resources and State Machines, we have a way to create, update, and delete AMIs in a reliable way using infrastructure as code.

Thanks to Caroline Maltais for the awesome graphics!