Packer is a tool to create machine images for multiple platforms from a single template.
Packer uses configuration files to create the images, these are called templates. They are coded in Hashicorp Configuration Language (hcl).
We will follow the documentation official tutorial on how to build an image for this section. So go there for any clarifications.
You will have a packer {}
block. Here you will have the setting
for packer, and you will add the plugins you need, например:
packer {
required_plugins {
amazon = {
version = ">= 1.2.8"
source = "github.com/hashicorp/amazon"
}
}
}
You need to specify the source
of the plugin; which is where
packer will look to download the plugin. version
is optional
but nice to have.
This is the blueprint for the machine we want to build. Here we add all the infrastructure information we need for it.
After the source
keyword, we specify what type builder
plugin we are going to use. A builder plugin is a packer component
that will create the machine and make it into an image.
source "amazon-ebs" "ubuntu" {
ami_name = "learn-packer-linux-aws"
instance_type = "t2.micro"
region = "us-west-2"
source_ami_filter {
filters = {
name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
}
Here we are using the amazon-ebs
builder plugin. This will
basically launch an ec2
instance from a source AMI, provision the
running machine, and then make an AMI out of that. We are naming it
ubuntu
Each builder has its attributes. In this case amazon-ebs
is going
to launch a t2.micro
in us-west-2
using an
ubuntu-jammy
based image. Then it will create a new AMI image
named learn-packer-linux-aws
.
Finally we have the build
block. Here is where later we will add
stuff to provision the image with.
build {
sources = [
"source.amazon-ebs.ubuntu"
]
}
You can see the full .pkr.hcl
file here.
packer
cliAssuming you are inside the directory where you have the packer configuration file
packer init .
: downloads plugins
packer fmt .
: formats the configs
packer validate .
: self-explanatory
packer build aws-ubuntu.pkr.hcl
: build the image
There is no subcommand for deleting an image. You need to deregister (delete) the AMI from AWS directly, when you do it be sure to also delete the snapshot that was created.
In the above example we just repackage an image that already exists. Packer becomes useful when we can configure that image to our liking, installing different packages, setting up files, and then turning that into an image we can reuse across a fleet of servers.
Packer can do this provisioning automatically, so you can setup a pipeline that builds the image every time there is a trigger.
Continuing from the above example where we are using amazon-ebs
,
we can review that we are using the ssh
communicator. Meaning that we will communicate to the ec2
instance via ssh. We are specifying a username (ssh_username =
"ubuntu"
), which means for provisioning packer will use a temporary ssh
credentials.
Inside the build block we can add what we want to configure on the
ec2
instance, by using a provisioner.
We use the shell
type which basically let us do whatever we want
with a shell.
provisioner "shell" {
environment_vars = [
"FOO=hello world",
]
inline = [
"echo Installing Kubeadm",
"sudo apt-get update",
"sudo apt-get install -y kubeadm",
"echo \"FOO is $FOO\" > example.txt",
]
}
You can have multiple of these. So your build block would look something like:
build {
name = "learn-packer"
sources = [
"source.amazon-ebs.ubuntu"
]
provisioner "shell" {
environment_vars = [
"FOO=hello world",
]
inline = [
"echo Installing Git",
"sudo apt-get update",
"sudo apt-get install -y git",
"echo \"FOO is $FOO\" > example.txt",
]
}
provisioner "shell" {
inline = [
"echo \"Hello there my friend\"",
]
}
}
Then you go ahead and packer build
it and you are good to go.
You can add variables to your templates in order to not modify stuff directly on the template every time you run it, say, modify a name, a version or whatever.
There are two types
input variable: this can be added with command line flags, environment variables and so on.
variable "ami_prefix" {
type = string
default = "learn-packer-linux-aws-redis"
}
If you do not modify the variable, it will use the default value stated
there.
local variable: these are useful when you need to format commonly used values
locals {
timestamp = regex_replace(timestamp(), "[- TZ:]", "")
}
What is timestamp
you ask? There are some built-in
functions.
Do notice that the way you define them is different, for the input each has its own block, and the type defined. For the local they are all inside a block.
You call them like you were using some type of fstring
s.
"${var.ami_prefix}-${local.timestamp}"
You just need to create another source
block, and add the name to
the sources
array in your build block.
build {
name = "learn-packer"
sources = [
- "source.amazon-ebs.ubuntu"
+ "source.amazon-ebs.ubuntu",
+ "source.amazon-ebs.ubuntu-focal"
]
## ...
}