Skip to content

Packer

Example of an AMI build with Packer and provisioned with Ansible

The name of your Packer configuration file must be <file_name>.pkr.hcl or <file_name>.pkr.json.

config.pkr.hcl
packer {
required_plugins {
amazon = {
version = ">= 1.2.8"
source = "github.com/hashicorp/amazon"
}
ansible = {
version = "~> 1"
source = "github.com/hashicorp/ansible"
}
}
}
source "amazon-ebs" "ubuntu" {
ami_name = "ansible-ami"
instance_type = "t2.micro"
region = "us-east-2"
ssh_username = "ubuntu"
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
}
build {
name = "test-packer"
sources = [
"source.amazon-ebs.ubuntu"
]
provisioner "ansible" {
user = "ubuntu"
playbook_file = "playbook.yml"
extra_arguments = ["--scp-extra-args", "'-O'"]
}
}

Export your AWS credentials

Terminal window
$ export access_key = "<aws_access_key>"
$ export secret_key = "<aws_secret_key>"

Create your Ansible playbook to provision the AMI

playbook.yml
---
- name: "Apache configuration"
hosts: all
remote_user: ubuntu
tasks:
- name: Ensure Apache is at the latest version
apt:
name: apache2
state: latest
update_cache: yes
become: true
- name: enabled mod_rewrite
become: true
apache2_module: name=rewrite state=present
- name: apache2 listen on port 443
become: true
lineinfile: dest=/etc/apache2/ports.conf regexp="^Listen 80" line="Listen 443" state=present
- name: apache2 virtualhost on port 443
become: true
lineinfile: dest=/etc/apache2/sites-available/000-default.conf regexp="^<VirtualHost \*:80>" line="<VirtualHost *:443>" state=present
- name: Remove default /var/www/html/
become: true
file:
path: /var/www/html
state: absent
- name: Git checkout website
become: true
git:
repo: https://github.com/cloudacademy/static-website-example
dest: /var/www/html
notify:
- start apache2
handlers:
- name: start apache2
service: name=apache2 state=started

Run Packer

Check your Packer version

Terminal window
$ packer version
> Packer v1.7.2

Initialize your Packer configuration

This command will install the required plugins.

Terminal window
$ packer init .

Format the Packer configuration

Terminal window
$ packer fmt config.pkr.hcl
> config.pkr.hcl

Validate your Packer configuration

Terminal window
$ packer validate config.pkr.hcl
> The configuration is valid.

Build your AMI

Terminal window
$ packer build config.pkr.hcl
test-packer.amazon-ebs.ubuntu: output will be in this color.
==> test-packer.amazon-ebs.ubuntu: Prevalidating any provided VPC information
==> test-packer.amazon-ebs.ubuntu: Prevalidating AMI Name: ansible-ami
test-packer.amazon-ebs.ubuntu: Found Image ID: ami-07e4e8101a24d7fb5
==> test-packer.amazon-ebs.ubuntu: Creating temporary keypair: packer_65b91c67-9340-c65b-6326-50779d27cd81
==> test-packer.amazon-ebs.ubuntu: Creating temporary security group for this instance: packer_65b91c69-bfaa-0870-164b-017b74dc3ee7
==> test-packer.amazon-ebs.ubuntu: Authorizing access to port 22 from [0.0.0.0/0] in the temporary security groups...
==> test-packer.amazon-ebs.ubuntu: Launching a source AWS instance...
test-packer.amazon-ebs.ubuntu: Instance ID: i-0f720d4a051b98d80
==> test-packer.amazon-ebs.ubuntu: Waiting for instance (i-0f720d4a051b98d80) to become ready...
==> test-packer.amazon-ebs.ubuntu: Using SSH communicator to connect: 18.118.130.55
==> test-packer.amazon-ebs.ubuntu: Waiting for SSH to become available...
==> test-packer.amazon-ebs.ubuntu: Connected to SSH!
==> test-packer.amazon-ebs.ubuntu: Provisioning with Ansible...
test-packer.amazon-ebs.ubuntu: Setting up proxy adapter for Ansible....
==> test-packer.amazon-ebs.ubuntu: Executing Ansible: ansible-playbook -e packer_build_name="ubuntu" -e packer_builder_type=amazon-ebs --ssh-extra-args '-o IdentitiesOnly=yes' --scp-extra-args '-O' -e ansible_ssh_private_key_file=/var/folders/rp/hnntscqx44n4gc658zqbwg1r0000gn/T/ansible-key249877241 -i /var/folders/rp/hnntscqx44n4gc658zqbwg1r0000gn/T/packer-provisioner-ansible2320871080 /Users/badr/Documents/Personnal_Project/Portfolio/packer_test/ansible/playbook.yml
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: PLAY [Apache configuration] ****************************************************
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: TASK [Gathering Facts] *********************************************************
test-packer.amazon-ebs.ubuntu: ok: [default]
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: TASK [Ensure Apache is at the latest version] **********************************
test-packer.amazon-ebs.ubuntu: changed: [default]
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: TASK [enabled mod_rewrite] *****************************************************
test-packer.amazon-ebs.ubuntu: changed: [default]
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: TASK [apache2 listen on port 443] **********************************************
test-packer.amazon-ebs.ubuntu: changed: [default]
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: TASK [apache2 virtualhost on port 443] *****************************************
test-packer.amazon-ebs.ubuntu: changed: [default]
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: TASK [Remove default /var/www/html/] *******************************************
test-packer.amazon-ebs.ubuntu: changed: [default]
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: TASK [Git checkout website] ****************************************************
test-packer.amazon-ebs.ubuntu: changed: [default]
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: RUNNING HANDLER [started apache2] **********************************************
test-packer.amazon-ebs.ubuntu: ok: [default]
test-packer.amazon-ebs.ubuntu:
test-packer.amazon-ebs.ubuntu: PLAY RECAP *********************************************************************
test-packer.amazon-ebs.ubuntu: default : ok=8 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test-packer.amazon-ebs.ubuntu:
==> test-packer.amazon-ebs.ubuntu: Stopping the source instance...
test-packer.amazon-ebs.ubuntu: Stopping instance
==> test-packer.amazon-ebs.ubuntu: Waiting for the instance to stop...
==> test-packer.amazon-ebs.ubuntu: Creating AMI ansible-ami from instance i-0f720d4a051b98d80
test-packer.amazon-ebs.ubuntu: AMI: ami-0daea26f520e2cfce
==> test-packer.amazon-ebs.ubuntu: Waiting for AMI to become ready...
==> test-packer.amazon-ebs.ubuntu: Skipping Enable AMI deprecation...
==> test-packer.amazon-ebs.ubuntu: Terminating the source AWS instance...
==> test-packer.amazon-ebs.ubuntu: Cleaning up any extra volumes...
==> test-packer.amazon-ebs.ubuntu: No volumes to clean up, skipping
==> test-packer.amazon-ebs.ubuntu: Deleting temporary security group...
==> test-packer.amazon-ebs.ubuntu: Deleting temporary keypair...
Build 'test-packer.amazon-ebs.ubuntu' finished after 5 minutes 27 seconds.
==> Wait completed after 5 minutes 27 seconds
==> Builds finished. The artifacts of successful builds are:
--> test-packer.amazon-ebs.ubuntu: AMIs were created:
us-east-2: ami-0daea26f520e2cfce

Your AMI is now available in your AWS account ami_id: ami-0daea26f520e2cfce, in the region us-east-2.

Automate your AMI build with Terraform

ec2.tf
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
for_each = toset(["dev", "staging", "prod"])
name = "instance-${each.key}"
ami = "ami-0daea26f520e2cfce"
instance_type = "t2.micro"
key_name = "user1"
monitoring = true
vpc_security_group_ids = [aws_security_group.nat_instance_sg.id]
subnet_id = module.vpc.public_subnets[0]
tags = {
Terraform = "true"
Environment = "${each.key}"
}
depends_on = [
aws_security_group.nat_instance_sg
]
}

Execute Terraform

Method 1: Using CLI

Terminal window
$ terraform init
$ terraform apply

Method 2: Using Github Actions / Gitlab CI / Jenkins

Here is an example of a Gitlab CI/CD to automate the AMI build.

.gitlab-ci.yml
# Define the stages
stages:
- init
- format
- validate
- plan
- apply
# If the commit starts with "deploy/", then run the pipeline
.rules_deploy: &rules_deploy
- if: '$CI_COMMIT_TITLE =~ /^deploy/'
# Export AWS credentials
.script_export_key: &script_export_key
- |
export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} # AWS_ACCESS_KEY_ID is a Gitlab CI/CD variable
export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} # AWS_SECRET_ACCESS_KEY is a Gitlab CI/CD variable
# Define the jobs
default:
image:
name: hashicorp/terraform:latest
entrypoint: [""]
before_script:
- *script_export_key
init:
stage: init
rules:
- *rules_deploy
script:
- terraform init
artifacts: # artifacts define, which files will be stored in Gitlab, to download any artifacts you need to use instead 'dependencies: []' because all artifacts are downloaded by default
paths:
- ".terraform"
- ".terraform.lock.hcl"
expire_in: 1 day # expire_in define, how long the artifacts will be stored in Gitlab
format:
stage: format
rules:
- *rules_deploy
script:
- terraform fmt
validate:
stage: validate
rules:
- *rules_deploy
script:
- terraform validate
plan:
stage: plan
rules:
- *rules_deploy
script:
- terraform init
- terraform plan -out "plan.json" # Save the plan in a file
artifacts:
paths:
- "plan.json"
expire_in: 1 day
apply:
stage: apply
rules:
- *rules_deploy
script:
- terraform init
- terraform apply -auto-approve "plan.json"
dependencies:
- plan
when: manual # This job will be executed manually in order to avoid any mistake