We all are tired of repeating the same steps for creating the image and instance in AWS. But no longer! After reading this blog you will make an image using Packer and an instance through Terraform with help a shell script. You should have prior knowledge of Packer and Terraform. Below, I have briefed them for your reference.
Packer is an open source tool for creating identical machine images for multiple platforms from a single source configuration. You can find more about packer at the official website – Packer doc
Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions. A good primer on Terraform can be found here – Terraform Doc
Amazon EC2’s simple web service interface allows you to obtain and configure capacity with minimal friction. It provides you with complete control of your computing resources and lets you run on Amazon’s proven computing environment.
Before we start make sure that your system ssh public key should be added in the AWS account. To find your system SSH key.
For Ubuntu system:
- Go to your home directory.
- Press ctrl+H to see the hidden file.
- There would be a folder named .ssh.
- open the file id_rsa.pub.
- This file contents the public SSH Key.
If that folder does not exist then you can run the following command. This will generate the ssh key.
Command: ssh-keygen
To generate ssh-key for ubuntu system you can refer the following link – DigitalOcean
To generate ssh-key for windows system you can refer the following link – Joyent
To Add SSH key in AWS account
- Click the username dropDown menu.
- Select the My Security Credentials. A new window will be open.
- Select the User item from the menubar on the left side.
- Select the Security Credentials tag.
- If score down the page you will find the upload SSH public key button click on the button.
- Paste your SSH key there.
Project Directory Structure
Let’s get started by creating our project directory structure. In the main project folder, create two folders – packer and terraform. Both folders will have the respective configuration file. The main project folder will three shell scripts which we will create.
-project_folder --packer ---packer.json ---docker_download1.sh --terraform ---demo_terraform.tf ---vars.tf ---provider.tf ---terraform.tfvar --build.sh --packer.sh --terraform.sh
In the packer folder, we will create a configuration file in JSON format which will be used to define how and what image we want to build. In packer terminology, it is called a template.
Below packer template will create an ec2 image with Docker installed in it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"variables": { | |
"aws_access_key": "", | |
"aws_secret_key": "", | |
"aws_region": "us-east-1", | |
"version": "1.7.1", | |
"revision": "0", | |
"instance_type": "t2.micro", | |
"image_name" : "{{ env `IMAGE_NAME` }}" | |
}, | |
"builders": [{ | |
"type": "amazon-ebs", | |
"access_key": "{{user `aws_access_key`}}", | |
"secret_key": "{{user `aws_secret_key`}}", | |
"region": "{{user `aws_region`}}", | |
"instance_type": "{{user `instance_type`}}", | |
"ssh_username": "ubuntu", | |
"ami_name": "{{user `image_name`}}", | |
"ami_regions": "{{user `aws_region`}}", | |
"source_ami_filter": { | |
"filters": { | |
"virtualization-type": "hvm", | |
"name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*", | |
"root-device-type": "ebs" | |
}, | |
"owners": ["099720109477"], | |
"most_recent": true | |
}, | |
"launch_block_device_mappings": [ | |
{ | |
"device_name": "/dev/sda1", | |
"volume_size": 30, | |
"volume_type": "gp2", | |
"delete_on_termination": true | |
} | |
], | |
"ssh_pty": "true", | |
"tags": { | |
"Name": "bootstrap-{{user `owner`}}-{{user `environment`}}-{{timestamp}}", | |
"Role": "bootstrap", | |
"BuildDate": "{{isotime}}" | |
} | |
}], | |
"provisioners": [{ | |
"type": "shell", | |
"environment_vars": [ | |
"version={{user `version`}}" | |
], | |
"scripts": ["packer/docker_download1.sh"] | |
}] | |
} |
Explanation
The above template will do the following task.
- create an image in AWS in us-east-1 region with the image name as defined by the environment variable.
- Builder will build the ubuntu machine with some external memory.
- Through provisioner key in JSON, we will install docker in AWS image.
- docker_download1.sh script is the script through which docker is installed in the image.
Terraform
Now its time to create the instance of the image through terraform. The set of files used to describe infrastructure in terraform is simply known as a terraform configuration.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data "aws_ami" "ubuntu" { | |
most_recent = true | |
filter { | |
name = "name" | |
values = ["${var.image_name}"] | |
} | |
filter { | |
name = "virtualization-type" | |
values = ["hvm"] | |
} | |
owners = [981997154569] | |
} | |
resource "aws_key_pair" "mykey" { | |
key_name = "mykey" | |
public_key = "${file("${var.PATH_TO_PUBLIC_KEY}")}" | |
} | |
resource "aws_instance" "bootstrap" { | |
ami = "${data.aws_ami.ubuntu.id}" | |
instance_type = "t2.micro" | |
key_name = "${aws_key_pair.mykey.key_name}" | |
count = "1" | |
provisioner "file" { | |
source = "program/hello-world.sh" | |
destination = "/tmp/hello-world.sh" | |
} | |
provisioner "remote-exec" { | |
inline = [ | |
"chmod +x /tmp/hello-world.sh", | |
"/tmp/hello-world.sh", | |
] | |
} | |
connection { | |
user = "${var.INSTANCE_USERNAME}" | |
private_key = "${file("${var.PATH_TO_PRIVATE_KEY}")}" | |
} | |
} | |
output "ip" { | |
value = "${aws_instance.bootstrap.ami}" | |
} | |
output "name" { | |
value = "${var.image_name}" | |
} |
Explanation
The above configuration will do the following task.
- Data key of the terraform script will help us to find the ami_id of the image. In data key, there is a filter attribute that will filter the ami on the bases of the ami_name.
- Resource “aws_instance bootstrap” will create the instance of the image that is filtered by the data key. Ami attribute value is given by the data key.
- The connection attribute will make a connection with the new instance that is created through ssh key in it we mention the private key file path and the username of the remote system.
- Resource “aws_key_pair mykey” mention the key pair name and public key file path which is used to make a secure connection.
To make it work we have to do one little work. As we have made the AWS instance through packer it acts as a normal machine. So if we want to install the docker in the AWS instance writing in the script is not enough. We have to make a connection to the instance then only we can install docker on the machine. To make the connection we have to add the security group in AWS.
To Add Security Group
- Login to your AWS account.
- Go to Services drop down to select the EC2.
- Left menuBar Under the heading NETWORK & SECURITY selects the Security Group.
- Go to Inbound tag click the edit button a pop box will open.
- Select Add Rules.
- In Type tag, select the All TCP item.
- In Source tag, selects My IP.
- Click the save button.
In vars.tf file we mention the variable’s value for terraform configuration file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
variable "AWS_ACCESS_KEY" {} | |
variable "AWS_SECRET_KEY" {} | |
variable "region" { | |
default="us-east-1" | |
} | |
variable "image_name" { | |
default = "ami_image_name" | |
} | |
variable "PATH_TO_PRIVATE_KEY" { | |
default = "mykey" | |
} | |
variable "PATH_TO_PUBLIC_KEY" { | |
default= "mykey.pub" | |
} | |
variable "INSTANCE_USERNAME" { | |
default = "ubuntu" | |
} |
Now is the time for the build script.
We will make one script called build.sh that will call two other scripts that will run the packer and terraform command. This script will run through two command line arguments – access key and secret key.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
echo "*** Deployment started" | |
usage() { | |
echo "(AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY) to be set" | |
exit 1 | |
} | |
if [ ${#} -ne 2 ]; then | |
usage | |
fi | |
ACCESS_KEY=$1 | |
SECRET_KEY=$2 | |
export SSH_KEY_NAME | |
SSH_KEY_NAME="mykey" | |
export IMAGE_NAME | |
IMAGE_NAME="ubuntu-dockers" | |
sed -i -e "s/ami_image_name/${IMAGE_NAME}/g" terraform/vars.tf | |
echo "*** starting packer" | |
sh packer.sh $ACCESS_KEY $SECRET_KEY; | |
echo "*** starting terraform" | |
sh terraform.sh; | |
sed -i -e "s/${IMAGE_NAME}/ami_image_name/g" terraform/vars.tf | |
echo "*** Deployment complete" | |
In line number 18 we are replacing default value of image name that is given vars.tf file. This is the name of which we have made the AWS image.
In line number 24 we again replacing the value of image_name with ami_image_name so that line number 18 can run again.
In line number 20 packer script is called with two command line parameter that is received by the build script.
packer script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -e | |
usage() { | |
echo "environment variable AWS_PROFILE or access keys (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY) to be set" | |
exit 1 | |
} | |
if [ ${#} -ne 2 ]; then | |
usage | |
fi | |
AWS_ACCESS_KEY=$1 | |
AWS_SECRET_KEY=$2 | |
run_packer() { | |
set -x | |
echo "enter into run packer" | |
packer build \ | |
-var aws_access_key=$1\ | |
-var aws_secret_key=$2\ | |
packer/demo-packer.json | |
set +x | |
} | |
run_packer $AWS_ACCESS_KEY $AWS_SECRET_KEY |
The above script will run successfully if two command line arguments are given, otherwise, it will get terminated with a message “environment variable AWS_PROFILE or access keys (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY) to be set“. From line number 20 to 23 packer build command is written that will call our above packer template.
Finally, the terraform script.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cd terraform | |
ssh-keygen -f mykey | |
terraform init | |
terraform plan\ | |
-out out.terraform | |
terraform apply out.terraform | |
rm out.terraform KEY |
In line number 2, the command is for ssh key generation. This will create the ssh public key and private key to make the remote connection. The path for these keys has to be mentioned in vars.tf file.
Our work is done! We made the image and its instance using packer and terraform.
This is how we dynamically create our image and instance using the script. To run the build script again we have to change the image_name in the build.sh file as AWS won’t create an image with the same name.
This method has an advantage over creating an image using amazon interface as it requires repetitive manual tasks. However, with help of these scripts, we can create multiple instances easily.
To see the full code you can refer to the git link – Packer-terraform-project
For this blog, I referred to the following sites.
Excellant stuff Priyanka – steps are explained very well with clear notes ! Kudos 🙂