Terraform is an awesome tool - It writes, plans and creates infrastructure as code.
An Amazon Machine Image (AMI) provides the information required to launch an instance. You must specify an AMI when you launch an instance.
With Terraform AWS provider you can create multiple EC2 instances from a specific AMI.
Packer is an open source tool for creating identical machine images for multiple platforms from a single source configuration. In fact, you can create multiples machine images contains different software.
In this post, I will share the way I use Packer to create 4 kinds of machine images:
Ubuntu server
Ubuntu server + Docker
Ubuntu server + Nginx
Ubuntu server + Ruby on Rails framework + Elixir-Phoenix framework
Getting started
Install Packer and json CLI tool. I’m using MacOS btw
1
brew install packer
1
npm install -g json
The configuration file used to define what image we want to be built and how is called a template in Packer terminology. The format of a template is simple JSON.
From this template, we can easily create the first machine image: latest Ubuntu server.
The rest machine images are latest Ubuntu server + dependencies, we can setup by using Provisioner. Provisioners are configured as part of the Packer JSON template.
Regarding the AWS credential, we can save it as a simple json file and load it by -var-file option.
Finally, make a simple CLI application to ask AWS credential variables, image information before using json CLI to render these variables to the Packer JSON template.
Demo example
Clone the source code, run make to see all available commands
1 2 3 4 5
╰─$ make build build the AMI from json generate generate json-credential file init init base validate validate AMI json
Run the make init and choose one of kind of AMI you want to create including the image information such as instance type, image name, description and region. In this example, I choose base
1 2 3 4 5 6 7 8 9 10 11 12
╰─$ make init ./init.sh Please enter machine image type (e.g: base, base-docker, base-nginx, base-full-stack): > base Enter the instance type: (https://aws.amazon.com/ec2/instance-types/) (e.g: t2.micro): > t2.micro Enter the image name: > Base Image with latest Ubuntu Enter the image description: > Ubuntu Server 18.04 Enter the ami region: (https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) (e.g: ap-southeast-1): > ap-southeast-1
Now, run make generate to generate the AWS credential file
1 2 3 4 5 6 7
╰─$ make generate ./generate.sh Generate AWS Credential configuration Enter AWS Access Key: > AKIAJABCDEF29340929320 Enter AWS Secret Key: > pXULnB209Jkdlwkkew/V20930dk+8wdalEKelE
Run make validate to check whether the JSON configuration is valid or new_post_name
1 2 3 4 5
╰─$ make validate ./validate.sh Please enter machine image type (e.g: base, base-docker, base-nginx, base-full-stack): > base Template validated successfully.
Finally, run the make build, and waiting for the machine image creating process finished then you will have a new AMI.
./build.sh Please enter machine image type (e.g: base, base-docker, base-nginx, base-full-stack): > base amazon-ebs output will be in this color.
==> amazon-ebs: Prevalidating AMI Name: Base Image with latest Ubuntu-1562084759 amazon-ebs: Found Image ID: ami-026c8acd92718196b ==> amazon-ebs: Creating temporary keypair: packer_5d1b8597-427d-7d4a-148d-5cf043c14b95 ==> amazon-ebs: Creating temporary security group for this instance: packer_5d1b859c-f16d-81cc-c437-4a431ed0dd1e ==> amazon-ebs: Authorizing access to port 22 from 0.0.0.0/0 in the temporary security group... ==> amazon-ebs: Launching a source AWS instance... ==> amazon-ebs: Adding tags to source instance amazon-ebs: Adding tag: "Name": "Packer Builder" amazon-ebs: Instance ID: i-0a903cb50cee6bbe4 ==> amazon-ebs: Waiting for instance (i-0a903cb50cee6bbe4) to become ready... ==> amazon-ebs: Using ssh communicator to connect: 3.81.131.159 ==> amazon-ebs: Waiting for SSH to become available... ==> amazon-ebs: Connected to SSH! ==> amazon-ebs: Stopping the source instance... amazon-ebs: Stopping instance, attempt 1 ==> amazon-ebs: Waiting for the instance to stop... ==> amazon-ebs: Creating unencrypted AMI Base Image with latest Ubuntu-1562084759 from instance i-0a903cb50cee6bbe4 amazon-ebs: AMI: ami-0a72e23df7c8078cc ==> amazon-ebs: Waiting for AMI to become ready... ==> amazon-ebs: Copying AMI (ami-0a72e23df7c8078cc) to other regions... amazon-ebs: Copying to: ap-southeast-1 amazon-ebs: Waiting for all copies to complete... ==> amazon-ebs: Adding tags to AMI (ami-0a72e23df7c8078cc)... ==> amazon-ebs: Tagging snapshot: snap-0e49336b3550fb9be ==> amazon-ebs: Creating AMI tags amazon-ebs: Adding tag: "Name": "Base Image with latest Ubuntu" amazon-ebs: Adding tag: "Description": "Ubuntu Server 18.04" ==> amazon-ebs: Creating snapshot tags ==> amazon-ebs: Terminating the source AWS instance... ==> amazon-ebs: Cleaning up any extra volumes... ==> amazon-ebs: No volumes to clean up, skipping ==> amazon-ebs: Deleting temporary security group... ==> amazon-ebs: Deleting temporary keypair... Build 'amazon-ebs' finished.
==> Builds finished. The artifacts of successful builds are: --> amazon-ebs: AMIs were created: ap-southeast-1: ami-09bf72a56cbc77bd2 us-east-1: ami-0a72e23df7c8078cc
NOTE:
Packer wasn’t able to assign an instance profile to an EC2 machine that it provisions. This is because the instance profile itself could have permissions different from Packer. AWS intends it to be this way because this could easily become an attack vector — Packer can be used to create machines that are assigned sensitive permissions and hence, to escalate privileges indirectly. To allow Packer to be able to assign the profile to the instance, you must give it 3 additional permissions: