Back to Blog

Immutable Images with Packer: Builders, Provisioners and Pipelines

Complete DevOps tutorial on Packer. Learn HCL2 templates, AMIs/images, provisioners, post-processors, CI integration.

Immutable Images with Packer: Builders, Provisioners and Pipelines

Immutable Images with Packer: Builders, Provisioners, and Pipelines

Introduction

In the realm of DevOps and Site Reliability Engineering (SRE), the concept of immutable infrastructure has gained prominence. This approach ensures that once a server is built, it does not change throughout its lifecycle. Instead, if updates or changes are required, a new version of the server is built and deployed. Packer, an open-source tool developed by HashiCorp, simplifies the creation of immutable machine images. It supports various platforms, including AWS, Azure, DigitalOcean, and more.

Packer uses a configuration file written in HCL2 (HashiCorp Configuration Language) that defines how the image is built, what software is installed, and how it is configured. This tutorial will guide you through using Packer to create immutable images, exploring its core concepts, syntax, and practical applications. Understanding Packer is vital for DevOps engineers as it enhances CI/CD pipelines, ensures consistency across environments, and reduces deployment errors.

Prerequisites

Before diving into Packer, ensure you have the following:

  • Software:
    • Packer installed on your machine.
    • A code editor (e.g., VSCode, Atom).
  • Cloud Subscriptions:
    • An AWS account (or any other supported cloud provider) with appropriate permissions to create AMIs or images.
  • Permissions:
    • IAM permissions for AWS to create, describe, and delete AMIs.
  • Tools:
    • Command Line Interface (CLI) for the cloud provider you use (AWS CLI, Azure CLI, etc.) configured on your machine.

Core Concepts

Definitions

  • Immutable Images: Images that do not change once created. If updates are necessary, a new image is built.
  • Builders: Components in Packer that create images for specific platforms (e.g., AWS, Azure).
  • Provisioners: Tools used to install and configure software on the images (e.g., Shell scripts, Chef, Ansible).
  • Post-processors: Actions that occur after the image is built, such as uploading the image to a registry.

Architecture

Packer operates on a simple yet powerful architecture:

  1. Configuration: Defined in HCL2 templates.
  2. Building Process: Initiated by a command that triggers builders and provisioners to create the image.
  3. Output: The built image is ready for deployment.

When to Use

Packer is ideal for:

  • Creating consistent environments for development, testing, and production.
  • Automating the image creation process in CI/CD pipelines.
  • Reducing configuration drift and ensuring reproducibility.

Limitations

  • Packer does not manage infrastructure directly; it only creates images.
  • The learning curve associated with HCL2 for beginners.

Pricing Notes

Packer is free to use, but the underlying cloud services may incur costs based on usage.

Syntax/Configuration

Basic HCL2 Template Structure

Here's a basic example of a Packer template:

{
  "builders": [
    {
      "type": "amazon-ebs",
      "access_key": "{{user `aws_access_key`}}",
      "secret_key": "{{user `aws_secret_key`}}",
      "region": "us-east-1",
      "source_ami": "ami-12345678",
      "instance_type": "t2.micro",
      "ssh_username": "ubuntu",
      "ami_name": "my-immutable-image-{{timestamp}}"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": ["apt-get update", "apt-get install -y nginx"]
    }
  ]
}

Parameter Tables

Parameter Description
type Type of builder (e.g., amazon-ebs, azure-arm)
access_key AWS access key for authentication
secret_key AWS secret key for authentication
region AWS region for the image
source_ami Base AMI to use as a starting point
instance_type Type of instance to spin up during image creation
ssh_username User for SSH access to the instance
ami_name Name pattern for the created AMI

Practical Examples

Example 1: Basic AMI Creation

{
  "builders": [
    {
      "type": "amazon-ebs",
      "access_key": "YOUR_AWS_ACCESS_KEY",
      "secret_key": "YOUR_AWS_SECRET_KEY",
      "region": "us-east-1",
      "source_ami": "ami-12345678",
      "instance_type": "t2.micro",
      "ssh_username": "ubuntu",
      "ami_name": "basic-ami-{{timestamp}}"
    }
  ]
}

This creates a basic AMI from an existing one.

Example 2: Installing Software with Shell Provisioner

{
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "sudo apt-get update",
        "sudo apt-get install -y apache2"
      ]
    }
  ]
}

This installs Apache on the created image.

Example 3: Using Multiple Provisioners

{
  "provisioners": [
    {
      "type": "shell",
      "inline": ["apt-get update"]
    },
    {
      "type": "ansible",
      "playbook_file": "playbook.yml"
    }
  ]
}

Install packages using both shell scripts and Ansible.

Example 4: Post-Processor to Create a Vagrant Box

{
  "post-processors": [
    {
      "type": "vagrant",
      "output": "my-box-{{timestamp}}.box"
    }
  ]
}

This post-processor converts the image into a Vagrant box.

Example 5: Environment Variable Usage

{
  "variables": {
    "aws_access_key": "",
    "aws_secret_key": ""
  },
  "builders": [
    {
      "type": "amazon-ebs",
      "access_key": "{{user `aws_access_key`}}",
      "secret_key": "{{user `aws_secret_key`}}"
    }
  ]
}

Use variables for sensitive data to avoid hardcoding.

Example 6: Building Images in Parallel

packer build -parallel=true example.json

This command builds images in parallel, speeding up the process.

Example 7: CI Integration with GitHub Actions

name: Build AMI

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v2
    - name: Install Packer
      run: curl -o packer.zip https://releases.hashicorp.com/packer/1.7.0/packer_1.7.0_linux_amd64.zip && unzip packer.zip && sudo mv packer /usr/local/bin/
    - name: Build AMI
      run: packer build example.json

This GitHub Actions workflow builds an AMI on every push to the main branch.

Example 8: Using Packer with Azure

{
  "builders": [
    {
      "type": "azure-arm",
      "client_id": "YOUR_CLIENT_ID",
      "client_secret": "YOUR_CLIENT_SECRET",
      "tenant_id": "YOUR_TENANT_ID",
      "subscription_id": "YOUR_SUBSCRIPTION_ID",
      "managed_image_resource_group": "myResourceGroup",
      "managed_image_name": "myManagedImage",
      "os_type": "Linux",
      "image_publisher": "Canonical",
      "image_offer": "UbuntuServer",
      "image_sku": "18.04-LTS"
    }
  ]
}

Creating an immutable image in Azure using Packer.

Real-World Scenarios

Scenario 1: CI/CD Pipeline Integration

In a CI/CD pipeline, Packer can be integrated to build images automatically. When a new feature is merged into the main branch, a Packer build can be triggered to create an updated AMI or container image, ensuring the latest version is always available for deployment.

Scenario 2: Multi-Environment Consistency

By using Packer, teams can ensure that the same image is used across development, testing, and production environments. This minimizes the "works on my machine" syndrome and provides a consistent experience throughout the software development lifecycle.

Scenario 3: Disaster Recovery

In a disaster recovery scenario, having immutable images readily available allows teams to quickly restore services by spinning up new instances from the latest images without manual intervention, significantly reducing downtime.

Best Practices

  1. Use Version Control: Store Packer templates in a version control system (e.g., Git) to track changes over time.
  2. Keep Templates DRY: Use variables and functions in HCL2 to avoid duplication and keep templates clean.
  3. Automate Builds: Integrate Packer builds into CI/CD pipelines for continuous delivery.
  4. Use Post-Processors: Utilize post-processors to optimize images for different environments (e.g., Vagrant boxes, Docker images).
  5. Security Considerations: Avoid hardcoding sensitive information. Use environment variables or secrets management tools to manage credentials securely.

Common Errors

  1. Error: "Invalid AMI ID"

    • Cause: The specified AMI ID does not exist or is incorrect.
    • Fix: Verify the AMI ID in the AWS console.
  2. Error: "Permission Denied"

    • Cause: Insufficient permissions to create AMIs.
    • Fix: Ensure that the IAM user/role has the necessary permissions.
  3. Error: "Packer build failed"

    • Cause: Provisioner scripts or commands may have failed.
    • Fix: Check Packer logs for detailed error messages and correct the scripts.
  4. Error: "No valid providers found"

    • Cause: Packer is not correctly configured for the specified platform.
    • Fix: Ensure the correct builder type is specified and that all required parameters are provided.

Related Services/Tools

Tool/Service Description Comparison
Terraform Infrastructure as Code tool for provisioning Packer focuses on image creation
Docker Containerization tool for lightweight images Packer creates VM images
Ansible Configuration management tool Packer can use Ansible as a provisioner
Chef Automation for deployment and configuration Packer can use Chef as a provisioner

Automation Script

Here’s a simple bash script to automate Packer builds:

#!/bin/bash

# Check for required commands
command -v packer >/dev/null 2>&1 || { echo >&2 "Packer is not installed. Aborting."; exit 1; }

# Set AWS credentials
export AWS_ACCESS_KEY_ID="YOUR_AWS_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_AWS_SECRET_KEY"

# Build the image
packer build example.json

# Check if the build was successful
if [ $? -eq 0 ]; then
  echo "Image built successfully."
else
  echo "Image build failed."
  exit 1
fi

This script checks for Packer installation, sets AWS credentials, and builds the image.

Conclusion

In this tutorial, we've explored how to create immutable images using Packer, covering builders, provisioners, and post-processors. Packer is a powerful tool that enhances DevOps workflows, enabling teams to create consistent, reliable, and reproducible machine images across multiple platforms. By integrating Packer into CI/CD pipelines, teams can streamline deployments and reduce the risk of errors.

Next Steps

  • Explore the official Packer documentation for advanced features.
  • Experiment with different provisioners and post-processors.
  • Integrate Packer with your existing CI/CD tools to automate image creation.

References