Packer
Chapter 42: Packer - Machine Image Building
Section titled “Chapter 42: Packer - Machine Image Building”Automated Machine Image Creation with Packer
Section titled “Automated Machine Image Creation with Packer”42.1 Overview
Section titled “42.1 Overview”Packer is an open-source tool by HashiCorp for creating identical machine images for multiple platforms from a single source configuration.
Packer Overview+------------------------------------------------------------------+| || +------------------------+ || | Packer | || +------------------------+ || | || +---------------------+---------------------+ || | | | | || v v v v || +----------+ +----------+ +----------+ +----------+ || | Builders | | Provisio | | Post- | | Templates| || | | | -ners | | Processors| | | || | - AWS | | - Shell | | - Upload | | - JSON | || | - Azure | | - Ansible| | - Scan | | - HCL2 | || | - GCP | | - Puppet | | - Tag | | - Var | || | - Docker | | - Chef | | - Test | | - Packer| || +----------+ +----------+ +----------+ +----------+ || |+------------------------------------------------------------------+Key Features
Section titled “Key Features”| Feature | Description |
|---|---|
| Builders | Create images for different platforms |
| Provisioners | Configure the image |
| Post-Processors | Process images after creation |
| Templates | Define image configuration |
42.2 Packer Architecture
Section titled “42.2 Packer Architecture”Image Building Workflow
Section titled “Image Building Workflow” Packer Workflow+------------------------------------------------------------------+| || +----------+ +----------+ +----------+ +----------+ || | Template | -> | Builder | -> | Provision| -> | Post- | || | (JSON/ | | (Create | | -er | | Processor| || | HCL2) | | Instance| | (Config) | | (Export) | || +----------+ +----------+ +----------+ +----------+ || | | | | || v v v v || +----------------------------------------------------------+ || | State Management | || +----------------------------------------------------------+ || || Output: Machine Image (AMI, VHD, Docker Image, etc.) || |+------------------------------------------------------------------+AWS Builder Types
Section titled “AWS Builder Types” AWS Builders+------------------------------------------------------------------+| || +------------------+ +------------------+ +------------------+ || | AMI Builder | | EC2 Image Builder| | EBS Builder | || | | | | | | || | - Source AMI | | - Pipeline | | - Volume-based | || | - Instance Store | | - Components | | - Snapshot | || | - Launch | | - Distribution | | - Registration | || +------------------+ +------------------+ +------------------+ || || +------------------+ +------------------+ || | Instance Builder | | Chroot Builder | || | | | | || | - EC2 Instance | | - No running | || | - SSH/WinRM | | instance | || | - Full support | | - Fast build | || +------------------+ +------------------+ || |+------------------------------------------------------------------+42.3 Packer Templates
Section titled “42.3 Packer Templates”HCL2 Template Structure
Section titled “HCL2 Template Structure”# Required pluginspacker { required_plugins { amazon = { version = ">= 1.0.0" source = "github.com/hashicorp/amazon" } }}
# Variablesvariable "aws_region" { type = string default = "us-east-1"}
variable "ami_name" { type = string default = "web-server-{{timestamp}}"}
# Data sourcesdata "amazon-ami" "ubuntu" { filters = { name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*" root-device-type = "ebs" virtualization-type = "hvm" } most_recent = true owners = ["099720109477"]}
# Source definitionsource "amazon-ebs" "ubuntu" { region = var.aws_region source_ami = data.amazon-ami.ubuntu.id instance_type = "t3.medium" ssh_username = "ubuntu"
ami_name = var.ami_name ami_description = "Web server AMI built with Packer"
tags = { Name = "web-server" BuiltBy = "Packer" }}
# Build definitionbuild { name = "web-server" sources = [ "source.amazon-ebs.ubuntu" ]
provisioner "shell" { inline = [ "sudo apt-get update", "sudo apt-get install -y nginx", "sudo systemctl enable nginx" ] }
provisioner "file" { source = "./configs/nginx.conf" destination = "/tmp/nginx.conf" }
provisioner "shell" { inline = [ "sudo cp /tmp/nginx.conf /etc/nginx/nginx.conf", "sudo systemctl restart nginx" ] }
post-processor "manifest" { output = "manifest.json" }}JSON Template (Legacy)
Section titled “JSON Template (Legacy)”{ "variables": { "aws_region": "us-east-1", "ami_name": "web-server-{{timestamp}}" }, "builders": [ { "type": "amazon-ebs", "region": "{{user `aws_region`}}", "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"] }, "instance_type": "t3.medium", "ssh_username": "ubuntu", "ami_name": "{{user `ami_name`}}", "tags": { "Name": "web-server", "BuiltBy": "Packer" } } ], "provisioners": [ { "type": "shell", "inline": [ "sudo apt-get update", "sudo apt-get install -y nginx" ] } ], "post-processors": [ { "type": "manifest", "output": "manifest.json" } ]}42.4 Provisioners
Section titled “42.4 Provisioners”Provisioner Types
Section titled “Provisioner Types” Packer Provisioners+------------------------------------------------------------------+| || +------------------+ +------------------+ +------------------+ || | Shell | | Ansible | | Chef | || | | | | | | || | - inline | | - playbook | | - cookbook | || | - script | | - galaxy | | - role | || | - execute_command| | - inventory | | - attributes | || +------------------+ +------------------+ +------------------+ || || +------------------+ +------------------+ +------------------+ || | Puppet | | File | | PowerShell | || | | | | | | || | - manifest | | - upload files | | - scripts | || | - module | | - directories | | - inline | || | - facter | | - permissions | | - elevated | || +------------------+ +------------------+ +------------------+ || |+------------------------------------------------------------------+Shell Provisioner Examples
Section titled “Shell Provisioner Examples”# Inline commandsprovisioner "shell" { inline = [ "sudo apt-get update", "sudo apt-get install -y nginx curl wget", "sudo systemctl enable nginx" ]}
# Script fileprovisioner "shell" { script = "./scripts/setup.sh"}
# Script with argumentsprovisioner "shell" { script = "./scripts/configure.sh" execute_command = "sudo -S -E bash -s '{{ .Path }}'" environment_vars = [ "APP_ENV=production", "DB_HOST=${var.db_host}" ]}
# Expect disconnect (for reboot)provisioner "shell" { inline = ["sudo reboot"] expect_disconnect = true}
# Pause before next provisionerprovisioner "shell" { inline = ["echo 'Waiting for reboot...'"] pause_before = "30s" start_retry_timeout = "10m"}Ansible Provisioner
Section titled “Ansible Provisioner”# Local Ansibleprovisioner "ansible" { playbook_file = "./ansible/site.yml" extra_arguments = [ "--extra-vars", "app_env=production" ]}
# Ansible Galaxyprovisioner "ansible" { playbook_file = "./ansible/site.yml" galaxy_file = "./ansible/requirements.yml" galaxy_command = "ansible-galaxy install -r {{.GalaxyFile}}"}
# Remote Ansible (Ansible installed on instance)provisioner "ansible-remote" { playbook_file = "./ansible/site.yml" ansible_ssh_user = "ubuntu"}File Provisioner
Section titled “File Provisioner”# Upload single fileprovisioner "file" { source = "./configs/app.conf" destination = "/tmp/app.conf"}
# Upload directoryprovisioner "file" { source = "./configs/" destination = "/tmp/configs/"}
# Upload with direction (for Windows)provisioner "file" { source = "./configs/app.conf" destination = "C:/Temp/app.conf" direction = "upload"}42.5 AWS-Specific Configuration
Section titled “42.5 AWS-Specific Configuration”AMI Builder Configuration
Section titled “AMI Builder Configuration”source "amazon-ebs" "web-server" { # Region and source AMI region = "us-east-1" source_ami = "ami-0abcdef1234567890"
# Instance configuration instance_type = "t3.medium" spot_instance_types = ["t3.medium", "t3.large"] spot_price = "0.05"
# Network configuration vpc_id = "vpc-12345678" subnet_id = "subnet-12345678" security_group_ids = ["sg-12345678"]
# SSH configuration ssh_username = "ubuntu" ssh_keypair_name = "my-keypair" ssh_private_key_file = "./keys/my-keypair.pem"
# AMI configuration ami_name = "web-server-{{timestamp}}" ami_description = "Web server AMI" ami_users = ["123456789012"] # Share with accounts ami_groups = ["all"] # Make public (or specific groups)
# AMI tags tags = { Name = "web-server" Environment = "production" BuiltBy = "Packer" Project = "web-app" }
# Snapshot tags snapshot_tags = { Name = "web-server-snapshot" }
# Launch template launch_template = { id = "lt-1234567890abcdef" version = "$Latest" }}
# Windows AMI Buildersource "amazon-ebs" "windows" { region = "us-east-1" source_ami = "ami-0abcdef1234567890" instance_type = "t3.medium"
# Windows-specific user_data_file = "./scripts/windows-userdata.ps1" communicator = "winrm" winrm_username = "Administrator" winrm_password = "PackerPassword123!" winrm_use_ssl = true winrm_insecure = true
# AMI configuration ami_name = "windows-server-{{timestamp}}"}Spot Instances for Cost Savings
Section titled “Spot Instances for Cost Savings”source "amazon-ebs" "spot-instance" { region = "us-east-1" source_ami = "ami-0abcdef1234567890"
# Use spot instances spot_price = "auto" spot_instance_types = [ "t3.medium", "t3.large", "m5.medium" ]
# Fallback to on-demand if spot unavailable spot_price_auto_product = "Linux/Unix (Amazon VPC)"
instance_type = "t3.medium" # Fallback ssh_username = "ubuntu"
ami_name = "spot-built-ami-{{timestamp}}"}42.6 Post-Processors
Section titled “42.6 Post-Processors”Post-Processor Types
Section titled “Post-Processor Types” Post-Processors+------------------------------------------------------------------+| || +------------------+ +------------------+ +------------------+ || | Manifest | | Amazon Import | | Vagrant | || | | | | | | || | - Output JSON | | - Import to EC2 | | - Vagrant box | || | - Image IDs | | - VM Import | | - VirtualBox | || | - Metadata | | - Conversion | | - AWS provider | || +------------------+ +------------------+ +------------------+ || || +------------------+ +------------------+ +------------------+ || | Docker | | Artifactory | | Checksum | || | | | | | | || | - Docker image | | - Upload | | - SHA256 | || | - Push to repo | | - Repository | | - MD5 | || | - Tag | | - Authentication | | - Verification | || +------------------+ +------------------+ +------------------+ || |+------------------------------------------------------------------+Manifest Post-Processor
Section titled “Manifest Post-Processor”build { post-processor "manifest" { output = "manifest.json" strip_path = true custom_data = { git_commit = "${var.git_commit}" build_number = "${var.build_number}" } }}Docker Post-Processor
Section titled “Docker Post-Processor”# Build Docker image from AMIbuild { sources = ["source.amazon-ebs.ubuntu"]
post-processor "docker-import" { repository = "my-app" tag = "latest" }
post-processor "docker-push" { login = true login_username = var.docker_username login_password = var.docker_password login_server = "docker.io" }}42.7 Multi-Region AMI Distribution
Section titled “42.7 Multi-Region AMI Distribution”Copy AMI to Multiple Regions
Section titled “Copy AMI to Multiple Regions”source "amazon-ebs" "base" { region = "us-east-1" source_ami = "ami-0abcdef1234567890" instance_type = "t3.medium" ssh_username = "ubuntu" ami_name = "base-ami-{{timestamp}}"
# Copy to multiple regions ami_regions = [ "us-west-1", "us-west-2", "eu-west-1", "eu-central-1", "ap-southeast-1" ]
# Region-specific AMI names ami_region_kms_key_ids = { us-west-1 = "alias/aws/ebs" us-west-2 = "alias/aws/ebs" eu-west-1 = "alias/aws/ebs" eu-central-1 = "alias/aws/ebs" }}Multi-Region Build Pipeline
Section titled “Multi-Region Build Pipeline”# Build in multiple regions simultaneouslyvariable "regions" { type = list(string) default = ["us-east-1", "eu-west-1", "ap-southeast-1"]}
build { name = "multi-region"
dynamic "sources" { for_each = var.regions iterator = region content { source = "source.amazon-ebs.base"
# Override region-specific settings region = region.value ami_name = "app-${region.value}-{{timestamp}}" } }
provisioner "shell" { inline = ["sudo apt-get update && sudo apt-get install -y nginx"] }}42.8 Golden Image Pipeline
Section titled “42.8 Golden Image Pipeline”Golden Image Architecture
Section titled “Golden Image Architecture” Golden Image Pipeline+------------------------------------------------------------------+| || Stage 1: Base Image || +----------------------------------------------------------+ || | - OS updates and patches | || | - Common packages | || | - Security hardening | || | - Base configuration | || +----------------------------------------------------------+ || | || v || Stage 2: Application Image || +----------------------------------------------------------+ || | - Application runtime | || | - Application dependencies | || | - Application configuration | || | - Application-specific hardening | || +----------------------------------------------------------+ || | || v || Stage 3: Deployment Image || +----------------------------------------------------------+ || | - Environment-specific config | || | - Secrets and credentials | || | - Monitoring agents | || | - Final validation | || +----------------------------------------------------------+ || |+------------------------------------------------------------------+Golden Image Template
Section titled “Golden Image Template”# Base image templatesource "amazon-ebs" "base" { region = "us-east-1" source_ami = data.amazon-ami.ubuntu.id instance_type = "t3.medium" ssh_username = "ubuntu"
ami_name = "base-image-{{timestamp}}" ami_users = var.account_ids}
build { name = "base-image" sources = ["source.amazon-ebs.base"]
# System updates provisioner "shell" { inline = [ "sudo apt-get update", "sudo apt-get upgrade -y", "sudo apt-get install -y curl wget jq unzip" ] }
# Security hardening provisioner "shell" { script = "./scripts/harden.sh" }
# Install common tools provisioner "shell" { script = "./scripts/install-tools.sh" }
# Output manifest post-processor "manifest" { output = "base-manifest.json" }}
# Application image (uses base image)variable "base_ami" { type = string}
source "amazon-ebs" "app" { region = "us-east-1" source_ami = var.base_ami instance_type = "t3.medium" ssh_username = "ubuntu"
ami_name = "app-image-{{timestamp}}"}
build { name = "app-image" sources = ["source.amazon-ebs.app"]
# Install application provisioner "ansible" { playbook_file = "./ansible/app.yml" }
# Configure application provisioner "file" { source = "./configs/" destination = "/opt/app/configs/" }
post-processor "manifest" { output = "app-manifest.json" }}42.9 CI/CD Integration
Section titled “42.9 CI/CD Integration”GitHub Actions Integration
Section titled “GitHub Actions Integration”name: Build AMI with Packer
on: push: branches: [main] paths: - 'packer/**' pull_request: branches: [main]
env: AWS_REGION: us-east-1
jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3
- name: Setup Packer uses: hashicorp/setup-packer@main with: version: "1.9.0"
- name: Validate Template run: | cd packer packer init . packer validate .
build: needs: validate runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }}
- name: Setup Packer uses: hashicorp/setup-packer@main with: version: "1.9.0"
- name: Build AMI run: | cd packer packer init . packer build -machine-readable . env: PKR_VAR_ami_name: "app-${{ github.sha }}"
- name: Upload manifest uses: actions/upload-artifact@v3 with: name: packer-manifest path: packer/manifest.jsonJenkins Pipeline
Section titled “Jenkins Pipeline”// Jenkinsfilepipeline { agent any
environment { AWS_REGION = 'us-east-1' PACKER_VERSION = '1.9.0' }
stages { stage('Validate') { steps { sh ''' packer init packer/ packer validate packer/ ''' } }
stage('Build AMI') { steps { withAWS(credentials: 'aws-credentials', region: "${AWS_REGION}") { sh ''' cd packer packer build \ -var "ami_name=app-${BUILD_NUMBER}" \ -var "git_commit=${GIT_COMMIT}" \ . ''' } } post { success { archiveArtifacts artifacts: 'packer/manifest.json', fingerprint: true } } }
stage('Update Terraform') { steps { script { def manifest = readJSON file: 'packer/manifest.json' def amiId = manifest.builds[0].artifact_id
sh """ cd terraform terraform apply -auto-approve \ -var "ami_id=${amiId}" """ } } } }
post { always { cleanWs() } }}42.10 Best Practices
Section titled “42.10 Best Practices”Security Best Practices
Section titled “Security Best Practices” Packer Security Best Practices+------------------------------------------------------------------+| || 1. Credential Management || +--------------------------------------------------------+ || | - Use IAM roles for EC2 instances | || | - Never hardcode credentials | || | - Use temporary credentials (STS) | || | - Rotate keys regularly | || +--------------------------------------------------------+ || || 2. Image Security || +--------------------------------------------------------+ || | - Start from trusted source AMIs | || | - Apply latest security patches | || | - Remove sensitive data before AMI creation | || | - Use encrypted AMIs | || +--------------------------------------------------------+ || || 3. Network Security || +--------------------------------------------------------+ || | | Use isolated VPC for building | || | | Restrict security group rules | || | | Use VPC endpoints for AWS services | || +--------------------------------------------------------+ || |+------------------------------------------------------------------+Performance Optimization
Section titled “Performance Optimization”# Use spot instances for cost savingssource "amazon-ebs" "optimized" { # ... other config ...
# Spot instances spot_price = "auto" spot_instance_types = ["t3.medium", "t3.large", "m5.medium"]
# Faster EBS volumes volume_type = "gp3" volume_size = 20 encrypted = true
# Parallel provisioning skip_create_ami = false}
# Parallel buildsbuild { sources = [ "source.amazon-ebs.region1", "source.amazon-ebs.region2", "source.amazon-ebs.region3" ]
# Run provisioners in parallel provisioner "shell" { inline = ["echo 'Parallel provisioning'"] }}Template Organization
Section titled “Template Organization”packer/+------------------------------------------------------------------+| || packer.pkr.hcl # Main template || variables.pkr.hcl # Variable definitions || sources.pkr.hcl # Source definitions || builds.pkr.hcl # Build definitions || || scripts/ # Shell scripts || +-- setup.sh || +-- harden.sh || +-- install-tools.sh || || configs/ # Configuration files || +-- nginx.conf || +-- app.conf || || ansible/ # Ansible playbooks || +-- site.yml || +-- roles/ || +-- web-server/ || || manifests/ # Output manifests || +-- base-manifest.json || +-- app-manifest.json || |+------------------------------------------------------------------+42.11 Troubleshooting
Section titled “42.11 Troubleshooting”Common Issues
Section titled “Common Issues” Packer Troubleshooting+------------------------------------------------------------------+| || Issue: SSH Connection Timeout || +--------------------------------------------------------+ || | Solutions: | || | - Check security group allows SSH (port 22) | || | - Verify ssh_username matches AMI | || | - Increase ssh_timeout value | || | - Check subnet route table | || +--------------------------------------------------------+ || || Issue: AMI Creation Failed || +--------------------------------------------------------+ || | Solutions: | || | - Check IAM permissions | || | - Verify AMI name is unique | || | - Check EBS volume limits | || | - Review instance logs | || +--------------------------------------------------------+ || || Issue: Provisioner Failures || +--------------------------------------------------------+ || | Solutions: | || | - Enable debug logging: PACKER_LOG=1 | || | - Check script exit codes | || | - Verify file paths exist | || | - Test provisioners manually | || +--------------------------------------------------------+ || |+------------------------------------------------------------------+Debug Mode
Section titled “Debug Mode”# Enable debug loggingPACKER_LOG=1 packer build template.pkr.hcl
# Save log to filePACKER_LOG=1 PACKER_LOG_PATH=packer.log packer build template.pkr.hcl
# Debug mode (keeps instance running on failure)packer build -debug template.pkr.hcl
# On-error behaviorpacker build -on-error=ask template.pkr.hcl# Options: cleanup, abort, ask, run-cleanup-provisioner42.12 Key Takeaways
Section titled “42.12 Key Takeaways”| Topic | Key Points |
|---|---|
| Builders | Use appropriate builder for use case (EBS, instance, chroot) |
| Provisioners | Shell for simple, Ansible for complex configurations |
| Security | Use IAM roles, encrypt AMIs, isolate build environment |
| CI/CD | Integrate with pipelines for automated image building |
| Multi-Region | Use ami_regions for distribution, parallel builds for speed |
| Golden Images | Build layered images for maintainability |
42.13 References
Section titled “42.13 References”Next Chapter: Chapter 43 - Ansible on AWS