Install Packer
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install packer
Create 3 packer files.
1. The main packer HCL code (amzn_linux.pkr.hcl)
2. Variable defined (variables.pkr.hcl)
3. Variables assigned (var_set.auto.pkrvars.hcl)
In the main packer HCL code define the block that will be the source for your custom AMI
packer {
required_plugins {
docker = {
version = ">= 0.0.7"
source = "github.com/hashicorp/docker"
}
}
}
source "amazon-ebs" "this" {
ami_name = "amzn-linux-silver-{{timestamp}}"
ami_users = var.share_with
instance_type = var.inst_type
region = var.region
#security_group_id = "sg-0f4abe34913599a2e" #Use this if you don't want to use the Packer created temp SG
#iam_instance_profile = var.iam_profile
associate_public_ip_address = true
ssh_username = var.ssh_user
aws_polling {
delay_seconds = 30
max_attempts = 200
}
launch_block_device_mappings {
device_name = "/dev/xvda"
volume_size = var.vol_size
volume_type = var.vol_type
delete_on_termination = true
}
# To use a specific subnet uncomment the parameter below and comment out the subnet_filter block
# subnet_id = "subnet-d9205ebc"
subnet_filter {
filters = {
"tag:Name": "public"
}
most_free = true
random = false
}
source_ami = var.image_id #uncomment to use and comment the filter below
# If you want to use a predefined AMI or existing AMI and you want to filter off specifics, use the below and comment out the above source_ami parameter
# source_ami_filter {
# filters = {
# name = "amzn2-goldimage-*"
# root-device-type = "ebs"
# virtualization-type = "hvm"
# }
# most_recent = true
# owners = ["1234567890"] #accountID sharing the GI
# }
tags = {
Release = "Latest"
Name = "amzn-linux-si-{{timestamp}}"
Base_AMI_Name = "{{ .SourceAMIName }}"
}
}
Below the source block define the build block. This will tell Packer what to bake into your AMI using provisioners. There are a variety of provisioners and the below has multiple used for example purposes.
build {
sources = [
"source.amazon-ebs.this"
]
provisioner "shell-local" {
inline = ["sleep 60"]
}
provisioner "shell" {
environment_vars = [
"FOO=BAR",
]
inline = [
"sudo yum update -y",
"sudo yum install wget -y",
"sudo yum install -y yum-utils",
"sudo amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2",
"sleep 5",
"sudo yum install -y httpd",
"sleep 5",
"sudo systemctl start httpd",
"sudo systemctl enable httpd",
"sudo amazon-linux-extras install ansible2",
"sleep 5",
"echo foo equals $FOO"
]
}
provisioner "ansible-local" {
playbook_file = "./scripts/playbook.yml"
}
provisioner "shell" {
script = "scripts/install_bins.sh"
}
provisioner "breakpoint" {
disable = true
}
post-processor "checksum" {
checksum_types = ["sha1", "sha256"]
output = "packer_{{.BuildName}}_{{.ChecksumType}}.checksum"
}
}
The second file is the variable definitions HCL. Define the variables and their type. In the main HCL code other parameters could be made into variables but for example purposes I didn’t go through that process.
# ssh_username could be set in the main HCL like the below:
ssh_username = "ec2_user"
# or you could set it as such
ssh_username = var.ssh_user
Example above, shows you can set the variable with its expected value, or you can set it to a variable. If you assign the parameter to a variable you have to either define it in the variable files, or when you run Packer to create the build set the variable at the command line.
I prefer to first create my definitions file for variables like the below:
// variables.pkr.hcl
// For those variables that you don't provide a default for, you must
// set them from the command line, a var-file, or the environment.
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
# regex(...) fails if it cannot find a match
condition = can(regex("^ami-", var.image_id))
error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
}
}
variable "share_with" {
type = list(string)
}
variable "region" {
type = string
default = "us-east-1"
}
variable "ssh_user" {
type = string
}
variable "inst_type" {
type = string
}
variable "vol_size" {
type = number
}
variable "vol_type" {
type = string
}
variable "iam_profile" {
type = string
}
Lastly set the variables in the third file
image_id = "ami-090fa75af13c156b4"
share_with = [] #account ids of who you share with
inst_type = "t3.large"
ssh_user = "ec2-user"
vol_size = 40
vol_type = "gp3"
iam_profile = "demo-base-ec2-profile"
After you have your files, keep in mind if you have any scripts that will run and are called via provisioners that those exist within the proper directories to be run or the Packer build will fail.
Begin the build
packer init .
packer build .
After initiating the Packer build, a helper EC2 instance will spin up and be assigned a security group if defined in the main HCL, otherwise one will be created temporarily, as well as keypairs to facilitate the communication to the instance. The provisioners will run (some screenshots of the process are shown below but full output is not).
The code can be found on my github and some small modifications would need to be made to the example to run in other AWS environments.
Leave a Reply