Packer automatisiert die Erstellung von Machine Images für verschiedene Plattformen. Es erstellt identische Images für Cloud, VM und Container aus einer einzigen Konfiguration.

Installation

Debian/Ubuntu

wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list
apt update && apt install packer

# Version prüfen
packer version

Grundbefehle

packer init .        # Plugins initialisieren
packer fmt .         # Formatieren
packer validate .    # Validieren
packer build .       # Image bauen

Grundstruktur (HCL2)

Einfaches Template

# ubuntu.pkr.hcl

packer {
  required_plugins {
    amazon = {
      source  = "github.com/hashicorp/amazon"
      version = "~> 1"
    }
  }
}

source "amazon-ebs" "ubuntu" {
  ami_name      = "ubuntu-base-{{timestamp}}"
  instance_type = "t3.micro"
  region        = "eu-central-1"

  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"
}

build {
  sources = ["source.amazon-ebs.ubuntu"]

  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx"
    ]
  }
}

Builder

AWS AMI

source "amazon-ebs" "webserver" {
  ami_name        = "webserver-{{timestamp}}"
  ami_description = "Web Server Image"
  instance_type   = "t3.micro"
  region          = "eu-central-1"

  source_ami_filter {
    filters = {
      name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
    }
    most_recent = true
    owners      = ["099720109477"]
  }

  ssh_username = "ubuntu"

  tags = {
    Name    = "webserver"
    Version = "1.0"
  }

  # AMI in mehrere Regionen kopieren
  ami_regions = ["eu-west-1", "us-east-1"]
}

Hetzner Cloud

packer {
  required_plugins {
    hcloud = {
      source  = "github.com/hetznercloud/hcloud"
      version = "~> 1"
    }
  }
}

source "hcloud" "ubuntu" {
  image       = "ubuntu-22.04"
  location    = "nbg1"
  server_type = "cx11"
  ssh_username = "root"

  snapshot_name   = "ubuntu-base-{{timestamp}}"
  snapshot_labels = {
    type = "base"
  }
}

VirtualBox

source "virtualbox-iso" "ubuntu" {
  iso_url          = "https://releases.ubuntu.com/22.04/ubuntu-22.04.3-live-server-amd64.iso"
  iso_checksum     = "sha256:..."
  ssh_username     = "vagrant"
  ssh_password     = "vagrant"
  shutdown_command = "sudo shutdown -P now"

  guest_os_type = "Ubuntu_64"
  disk_size     = 20000
  memory        = 2048
  cpus          = 2

  boot_command = [
    "<esc><wait>",
    "linux /casper/vmlinuz --- autoinstall",
    "<enter>"
  ]

  http_directory = "http"
}

Docker

source "docker" "ubuntu" {
  image  = "ubuntu:22.04"
  commit = true
}

build {
  sources = ["source.docker.ubuntu"]

  provisioner "shell" {
    inline = [
      "apt-get update",
      "apt-get install -y nginx"
    ]
  }

  post-processor "docker-tag" {
    repository = "myrepo/nginx"
    tags       = ["latest", "1.0"]
  }
}

Provisioner

Shell

build {
  sources = ["source.amazon-ebs.ubuntu"]

  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx"
    ]
  }

  provisioner "shell" {
    script = "scripts/setup.sh"
  }

  provisioner "shell" {
    scripts = [
      "scripts/install.sh",
      "scripts/configure.sh",
      "scripts/cleanup.sh"
    ]
  }

  # Mit Umgebungsvariablen
  provisioner "shell" {
    environment_vars = [
      "DEBIAN_FRONTEND=noninteractive",
      "APP_ENV=production"
    ]
    script = "scripts/install.sh"
  }
}

File

provisioner "file" {
  source      = "files/nginx.conf"
  destination = "/tmp/nginx.conf"
}

provisioner "shell" {
  inline = ["sudo mv /tmp/nginx.conf /etc/nginx/nginx.conf"]
}

Ansible

provisioner "ansible" {
  playbook_file = "ansible/playbook.yml"
  extra_arguments = [
    "--extra-vars", "env=production"
  ]
}

# Oder Ansible Local (in der VM)
provisioner "ansible-local" {
  playbook_file = "ansible/playbook.yml"
}

Chef/Puppet

provisioner "chef-solo" {
  cookbook_paths = ["cookbooks"]
  run_list       = ["recipe[webserver]"]
}

provisioner "puppet-masterless" {
  manifest_file = "manifests/default.pp"
}

Variablen

Definition

# variables.pkr.hcl

variable "aws_region" {
  type    = string
  default = "eu-central-1"
}

variable "instance_type" {
  type    = string
  default = "t3.micro"
}

variable "ami_name" {
  type = string
}

variable "tags" {
  type = map(string)
  default = {
    Environment = "production"
  }
}

variable "ssh_password" {
  type      = string
  sensitive = true
}

Verwendung

source "amazon-ebs" "webserver" {
  region        = var.aws_region
  instance_type = var.instance_type
  ami_name      = var.ami_name

  tags = var.tags
}

Werte zuweisen

# Kommandozeile
packer build -var 'ami_name=webserver-v1' .

# Variablen-Datei
packer build -var-file=production.pkrvars.hcl .

# Umgebungsvariable
export PKR_VAR_aws_region=eu-west-1
packer build .
# production.pkrvars.hcl
aws_region    = "eu-central-1"
instance_type = "t3.small"
ami_name      = "webserver-production"

Post-Processors

Manifest

build {
  sources = ["source.amazon-ebs.webserver"]

  post-processor "manifest" {
    output     = "manifest.json"
    strip_path = true
  }
}

Vagrant

post-processor "vagrant" {
  output = "builds/{{.BuildName}}.box"
}

Compress

post-processor "compress" {
  output = "builds/image.tar.gz"
}

Shell-Local

post-processor "shell-local" {
  inline = [
    "echo 'Image ID: {{.ArtifactId}}'",
    "aws sns publish --message 'Image built: {{.ArtifactId}}'"
  ]
}

Multiple Builds

Parallele Builds

source "amazon-ebs" "aws" {
  ami_name      = "webserver-aws-{{timestamp}}"
  region        = "eu-central-1"
  instance_type = "t3.micro"
  # ...
}

source "hcloud" "hetzner" {
  snapshot_name = "webserver-hetzner-{{timestamp}}"
  server_type   = "cx11"
  # ...
}

build {
  sources = [
    "source.amazon-ebs.aws",
    "source.hcloud.hetzner"
  ]

  provisioner "shell" {
    inline = ["sudo apt-get update", "sudo apt-get install -y nginx"]
  }
}

Bedingte Builds

build {
  sources = ["source.amazon-ebs.webserver"]

  # Nur für bestimmten Builder
  provisioner "shell" {
    only   = ["amazon-ebs.webserver"]
    inline = ["echo 'AWS specific'"]
  }

  # Ausschließen
  provisioner "shell" {
    except = ["docker.ubuntu"]
    inline = ["systemctl enable nginx"]
  }
}

Praktische Templates

Base Image

# base-image.pkr.hcl

source "amazon-ebs" "base" {
  ami_name      = "base-ubuntu-{{timestamp}}"
  instance_type = "t3.micro"
  region        = "eu-central-1"

  source_ami_filter {
    filters = {
      name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
    }
    most_recent = true
    owners      = ["099720109477"]
  }

  ssh_username = "ubuntu"

  tags = {
    Name = "Base Image"
    OS   = "Ubuntu 22.04"
  }
}

build {
  sources = ["source.amazon-ebs.base"]

  # System-Updates
  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y"
    ]
  }

  # Basis-Pakete
  provisioner "shell" {
    inline = [
      "sudo apt-get install -y curl wget vim htop"
    ]
  }

  # Cleanup
  provisioner "shell" {
    inline = [
      "sudo apt-get clean",
      "sudo rm -rf /var/lib/apt/lists/*",
      "sudo rm -rf /tmp/*"
    ]
  }
}

Webserver Image

# webserver.pkr.hcl

variable "base_ami" {
  type = string
}

source "amazon-ebs" "webserver" {
  ami_name        = "webserver-{{timestamp}}"
  instance_type   = "t3.micro"
  region          = "eu-central-1"
  source_ami      = var.base_ami
  ssh_username    = "ubuntu"

  tags = {
    Name = "Webserver"
    Role = "web"
  }
}

build {
  sources = ["source.amazon-ebs.webserver"]

  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx"
    ]
  }

  provisioner "file" {
    source      = "files/nginx.conf"
    destination = "/tmp/nginx.conf"
  }

  provisioner "shell" {
    inline = [
      "sudo mv /tmp/nginx.conf /etc/nginx/nginx.conf",
      "sudo systemctl enable nginx"
    ]
  }
}

Golden Image Pipeline

# golden-image.pkr.hcl

locals {
  timestamp = formatdate("YYYYMMDD-hhmmss", timestamp())
}

source "amazon-ebs" "golden" {
  ami_name      = "golden-${local.timestamp}"
  instance_type = "t3.small"
  region        = "eu-central-1"

  source_ami_filter {
    filters = { name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*" }
    most_recent = true
    owners      = ["099720109477"]
  }

  ssh_username = "ubuntu"

  ami_regions = ["eu-west-1", "us-east-1"]

  tags = {
    Name      = "Golden Image"
    Version   = local.timestamp
    ManagedBy = "Packer"
  }
}

build {
  sources = ["source.amazon-ebs.golden"]

  # CIS Hardening
  provisioner "ansible" {
    playbook_file = "ansible/hardening.yml"
  }

  # Monitoring Agent
  provisioner "shell" {
    script = "scripts/install-monitoring.sh"
  }

  # Security Patches
  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y"
    ]
  }

  # Cleanup
  provisioner "shell" {
    script = "scripts/cleanup.sh"
  }

  post-processor "manifest" {
    output = "manifest.json"
  }
}

CI/CD Integration

GitHub Actions

name: Build Image

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Packer
        uses: hashicorp/setup-packer@main

      - name: Init
        run: packer init .

      - name: Validate
        run: packer validate .

      - name: Build
        run: packer build .
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Zusammenfassung

| Befehl | Funktion | |--------|----------| | packer init | Plugins laden | | packer validate | Validieren | | packer build | Image erstellen | | packer fmt | Formatieren |

| Builder | Plattform | |---------|-----------| | amazon-ebs | AWS AMI | | hcloud | Hetzner Cloud | | virtualbox-iso | VirtualBox | | docker | Docker Image | | azure-arm | Azure | | googlecompute | GCP |

| Provisioner | Verwendung | |-------------|------------| | shell | Shell-Skripte | | file | Dateien kopieren | | ansible | Ansible Playbooks | | chef-solo | Chef Cookbooks |

Fazit

Packer ermöglicht reproduzierbare Machine Images für beliebige Plattformen. Die einheitliche Konfiguration spart Zeit bei Multi-Cloud-Deployments. Mit Provisioning-Integration (Shell, Ansible) werden Images vollständig automatisiert erstellt. In CI/CD-Pipelines integriert, bildet Packer die Grundlage für Immutable Infrastructure. Die Kombination mit Terraform ist besonders mächtig.