Using Packer to streamline machine images creation

In this short article I show how to automate machine images creation using Packer.

Packer is a DevOps tool that enables swift creation of identical machine images for multiple platforms, for instance Amazon Machine Images or Google Compute Engine Images from a single configuration file. Packer works remotely and executes tasks in parallel. That is convenient and saves much time.

A machine image is a single static unit that contains a pre-configured operating system and installed software which is used to quickly create new running machines.

Packer has a command-line interface with these commands:

  
  build       build image(s) from template
  fix         fixes templates from old versions of packer
  inspect     see components of a template
  push        push a template and supporting files to a Packer build service
  validate    check that a template is valid
  version     Prints the Packer version
  

When we say a template, what we actually mean is a JSON file that contains definitions of builders, provisioners and optionally some variables or post-processors.

More Packer builders may be specified in a template. We can for instance add a builder to create an AMI, and another to create an identically provisioned VirtualBox image.

The provisioners section contains a list of Packer provisioners sequentially executed within a running machine before it is turned into an image. All provisioners are applied to all builders, unless we override this behavior using only, or except parameters of the provisioner.

Packer offers a wide array of provisioners out of the box. Remote or local shell scripts execution is probably the most common choice, but Ansible, Puppet and Chef provisioners are available too.

Builder template example:

  
  {
    "builders": [
      {
        "type": "amazon-ebs",
        "name": "us-east-1",
        "access_key": "{{user `aws_access_key`}}",
        "secret_key": "{{user `aws_secret_key`}}",
        "region": "us-east-1",
        "source_ami": "ami-6d1c2007",
        "instance_type": "t2.micro",
        "ssh_username": "centos",
        "ssh_pty": true,
        "ami_name": "consul-instance {{timestamp}}"
      }
    ]
  }
  

Provisioner template example:

  
  {
    "provisioners": [
      {
        "type": "shell",
        "script": "scripts/install—container-instance.sh",
        "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E '{{ .Path }}'"
      }
    ]
  }
  

Since Packer uses our cloud provider account(s) to bake the images, we must provide it with credentials. In case of AWS we need aws_access_key and aws_secret_key (note how they are referred to in the builder definition above) and we can get them from our system as follows:

  
  AWS_ACCESS_KEY=`aws configure get ${PROFILE}.aws_access_key_id`
  AWS_SECRET_KEY=`aws configure get ${PROFILE}.aws_secret_access_key`
  

Obviously we don’t want to store our credentials in the Packer template. Instead we pass them to packer build command using -var parameters:

  
  packer build \
    -var "aws_access_key=${AWS_ACCESS_KEY}" \
    -var "aws_secret_key=${AWS_SECRET_KEY}" \
    template.json
  

This command starts remote build(s) and we can watch the progress in console. After completion, Packer displays ID and name of each created image (Artifacts in Packer terminology).

Now when we know what Packer is, let’s be clear about what it is not. Packer is not a machine image manager. Packer’s job is done when it creates an image and cleans after itself. In this sense, Packer is probably the most unixy project in the entire HashiCorp arsenal. If we need to view the created images or remove an image, we use tool or interface of the respective platform.

Packer works with perhaps all major platforms out of the box, and is extensible using plugins (written in Go). It is a very useful tool enabling automated and streamlined machine image creation - an important step in many DevOps workflows.