Construcción de Imágenes Packer para Servidores
Packer es una herramienta de código abierto para crear imágenes de máquinas idénticas para múltiples plataformas a partir de una única configuración de origen. Usando Packer, se define infraestructura en código una única vez, se construye de manera consistente y se implementa en diferentes proveedores en la nube e hipervisores. Esta guía cubre sintaxis de plantillas HCL, generadores para Docker y QEMU, aprovisionadores para instalación de software, post-procesadores para optimización de imágenes e integración con CI/CD.
Tabla de Contenidos
- Descripción General de Packer
- Sintaxis de Plantillas Packer
- Configuración del Generador AWS
- Generador Docker
- Generador QEMU
- Aprovisionadores
- Post-Procesadores
- Construcción de Imágenes
- Integración con CI/CD
- Conclusión
Descripción General de Packer
Packer automatiza la creación de imágenes de máquinas para proveedores en la nube y plataformas de virtualización. En lugar de construir imágenes manualmente a través de consola o scripts, Packer define especificaciones de imagen en código, las construye consistentemente y permite el control de versiones de tu infraestructura.
Beneficios clave:
- Consistencia: Construye imágenes idénticas cada vez
- Velocidad: Automatiza la construcción de imágenes e inicia más rápido con imágenes preconfiguradas
- Control de Versiones: Almacena definiciones de imágenes en git
- Multi-Nube: Construye una vez para AWS, Azure, GCP, Docker simultáneamente
- Pruebas: Valida imágenes antes de la implementación
- Reproducibilidad: Reconstruye exactamente cuando sea necesario
Flujo de trabajo de Packer:
1. Definir plantilla (HCL o JSON)
↓
2. Validar plantilla
↓
3. Construir imagen
├── Crear entorno del generador
├── Ejecutar aprovisionadores (instalar software)
├── Ejecutar post-procesadores (optimizar)
└── Guardar imagen
↓
4. Implementar imagen
Sintaxis de Plantillas Packer
Packer utiliza HCL para definiciones de plantillas legibles.
Estructura de plantilla básica:
# variables.pkr.hcl
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "instance_type" {
type = string
default = "t3.medium"
}
variable "ami_prefix" {
type = string
default = "packer-example"
}
variable "environment" {
type = string
default = "production"
}
# main.pkr.hcl
source "amazon-ebs" "ubuntu" {
ami_name = "${var.ami_prefix}-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
instance_type = var.instance_type
region = var.aws_region
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
owners = ["099720109477"] # Canonical
most_recent = true
}
ssh_username = "ubuntu"
tags = {
Name = "${var.ami_prefix}-image"
Environment = var.environment
BuildTime = timestamp()
}
snapshot_tags = {
Name = "${var.ami_prefix}-snapshot"
}
run_tags = {
Name = "Packer Builder"
}
}
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
inline = [
"echo 'Building image...'",
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
}
}
Definición de variables y tipos:
# variables.pkr.hcl
variable "subnet_id" {
type = string
description = "Subnet ID for builder instance"
sensitive = true
}
variable "instance_count" {
type = number
description = "Number of instances to build"
default = 1
}
variable "tags" {
type = map(string)
description = "Tags to apply to AMI"
default = {
ManagedBy = "Packer"
}
}
variable "packages" {
type = list(string)
description = "Packages to install"
default = [
"nginx",
"curl",
"wget"
]
}
# Use variables in template
output "ami_id" {
value = aws_ami.ubuntu.id
}
# In template
tags = merge(var.tags, {
Name = "My-Image"
})
Configuración del Generador AWS
Construye Imágenes de Máquina de Amazon (AMIs) desde Packer.
Generador AMI básico:
source "amazon-ebs" "example" {
ami_name = "my-app-${local.timestamp}"
instance_type = "t3.micro"
region = "us-east-1"
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
}
owners = ["099720109477"]
most_recent = true
}
ssh_username = "ubuntu"
}
build {
sources = ["source.amazon-ebs.example"]
provisioner "shell" {
script = "scripts/setup.sh"
}
}
Configuración AMI avanzada:
source "amazon-ebs" "production" {
ami_name = "prod-app-${formatdate("YYYY-MM-DD", timestamp())}"
instance_type = var.instance_type
region = var.aws_region
availability_zone = var.availability_zone
subnet_id = var.subnet_id
security_group_id = var.security_group_id
associate_public_ip = false
# Source AMI from custom image
source_ami = var.base_ami_id
# or use filter
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
state = "available"
}
owners = ["099720109477"]
most_recent = true
}
ssh_username = "ubuntu"
ssh_timeout = "10m"
# EBS configuration
ebs_optimized = true
root_volume_size = 50
encrypt_boot = true
kms_key_id = var.kms_key_id
# Tagging
ami_description = "Production application image"
tags = {
Name = "prod-app"
Environment = "production"
BuildDate = timestamp()
Version = var.image_version
}
snapshot_tags = {
Name = "prod-app-snapshot"
}
run_tags = {
Name = "Packer Builder Instance"
}
# Permissions
ami_users = var.ami_users
ami_groups = ["default"]
snapshot_users = var.snapshot_users
# Cleanup
force_deregister = true
force_delete_snapshot = true
}
build {
sources = ["source.amazon-ebs.production"]
provisioner "file" {
source = "app/"
destination = "/tmp/app"
}
provisioner "shell" {
script = "scripts/install.sh"
}
}
Generador Docker
Crea imágenes Docker con Packer.
Generador Docker básico:
source "docker" "ubuntu" {
image = "ubuntu:22.04"
commit = true
changes = [
"ENTRYPOINT /usr/bin/myapp",
"EXPOSE 8080",
"ENV APP_ENV=production"
]
}
build {
sources = ["source.docker.ubuntu"]
provisioner "shell" {
inline = [
"apt-get update",
"apt-get install -y nginx"
]
}
}
Configuración Docker avanzada:
source "docker" "application" {
image = "ubuntu:22.04"
commit = true
# Use existing container
docker_name = "my-container"
# Run in Docker
run_command = [
"-d",
"-i",
"-t",
"--",
"{{.Image}}"
]
# Container configuration
container_dir = "/tmp"
# Export to archive
export_path = "image.tar"
# Device access
privileged = false
# Volume mounts
volumes = {
"/tmp" = "/host/tmp"
}
changes = [
"ENV APP_VERSION=${var.app_version}",
"WORKDIR /app",
"EXPOSE 8080 8443",
"ENTRYPOINT [\"/usr/bin/app\"]"
]
}
build {
sources = ["source.docker.application"]
provisioner "shell" {
inline = [
"apt-get update",
"apt-get install -y nginx curl"
]
}
provisioner "file" {
source = "app/"
destination = "/app"
}
post-processor "docker-tag" {
repository = "myrepo/myapp"
tags = ["latest", "v${var.version}"]
}
post-processor "docker-push" {
ecr_login = true
login_server = var.registry_url
aws_access_key = var.aws_access_key
aws_secret_key = var.aws_secret_key
}
}
Generador QEMU
Construye imágenes para hipervisores KVM y QEMU.
Generador QEMU básico:
source "qemu" "ubuntu" {
iso_url = "https://releases.ubuntu.com/22.04/ubuntu-22.04.1-live-server-amd64.iso"
iso_checksum = "file:https://releases.ubuntu.com/22.04/SHA256SUMS"
output_directory = "output-qemu"
vm_name = "ubuntu-22.04.qcow2"
accelerator = "kvm"
machine_type = "pc"
cpus = 2
memory = 2048
disk_size = 10000
disk_compression = true
disk_image = false
format = "qcow2"
http_directory = "http"
boot_command = [
"<tab>",
"ip=dhcp ipv6.disable=1",
" ds=nocloud-net;s=http://{{.HTTPIP}}:{{.HTTPPort}}/",
"<enter>"
]
boot_wait = "2s"
headless = true
shutdown_command = "echo 'packer' | sudo -S shutdown -P now"
ssh_username = "ubuntu"
ssh_password = "ubuntu"
ssh_wait_timeout = "20m"
}
build {
sources = ["source.qemu.ubuntu"]
provisioner "shell" {
inline = [
"echo 'Building QEMU image'",
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
}
}
Aprovisionadores
Los aprovisionadores ejecutan instalación de software y configuración.
Aprovisionador Shell:
provisioner "shell" {
# Inline commands
inline = [
"apt-get update",
"apt-get install -y nginx curl wget",
"systemctl enable nginx"
]
}
# Or script file
provisioner "shell" {
script = "scripts/install-packages.sh"
}
# Multiple scripts
provisioner "shell" {
scripts = [
"scripts/setup.sh",
"scripts/security.sh",
"scripts/cleanup.sh"
]
}
# With environment variables
provisioner "shell" {
environment_vars = [
"APP_VERSION=1.0.0",
"ENVIRONMENT=production"
]
inline = [
"echo Version is $APP_VERSION"
]
}
Aprovisionador de Archivo:
# Copy file
provisioner "file" {
source = "app/config.yml"
destination = "/tmp/config.yml"
}
# Copy directory
provisioner "file" {
source = "app/"
destination = "/opt/app"
}
# Copy to local (from builder)
provisioner "file" {
type = "ssh"
source = "/opt/app/output.txt"
destination = "local/output.txt"
direction = "download"
}
Aprovisionador Ansible:
provisioner "ansible" {
playbook_file = "playbooks/main.yml"
extra_arguments = [
"-e", "environment=production",
"-e", "app_version=${var.app_version}"
]
ansible_env_vars = [
"ANSIBLE_HOST_KEY_CHECKING=False"
]
}
# With inventory
provisioner "ansible" {
playbook_file = "playbooks/main.yml"
user = "ubuntu"
local_port = 22
host_alias = "packer-instance"
}
Aprovisionador Chef:
provisioner "chef-client" {
chef_version = "16.6.14"
run_list = [
"recipe[base::default]",
"recipe[nginx::default]"
]
cookbook_paths = ["cookbooks"]
server_url = var.chef_server_url
user_id = var.chef_user_id
key = file("keys/client.pem")
}
Post-Procesadores
Los post-procesadores optimizan y distribuyen imágenes.
Etiquetado y envío de Docker:
post-processor "docker-tag" {
repository = "myregistry.azurecr.io/myapp"
tags = ["latest", "v${var.version}"]
}
post-processor "docker-push" {
ecr_login = true
login_server = var.registry_url
aws_access_key = var.aws_access_key
aws_secret_key = var.aws_secret_key
}
Manifiesto para seguimiento de artefactos:
post-processor "manifest" {
output = "manifest.json"
strip_path = true
custom_data = {
build_time = timestamp()
build_version = var.image_version
source_ami = data.aws_ami.ubuntu.id
}
}
Caja Vagrant:
post-processor "vagrant" {
output = "output/ubuntu-{{user `version`}}.box"
only = ["source.amazon-ebs.ubuntu"]
}
post-processor "vagrant-cloud" {
access_token = var.vagrant_cloud_token
box_checksum = file("checksums.txt")
box_checksum_type = "sha256"
box_tag = "myorg/ubuntu"
version = var.version
version_description = "Ubuntu 22.04 with Nginx"
}
Manifiesto con información de artefactos:
build {
sources = [
"source.amazon-ebs.ubuntu",
"source.docker.ubuntu"
]
# ... provisioners ...
post-processor "manifest" {
output = "manifest.json"
custom_data = {
image_version = var.image_version
build_date = timestamp()
git_commit = var.git_commit
}
}
}
Construcción de Imágenes
Construye y prueba imágenes de Packer.
Validar plantilla:
# Validate syntax and configuration
packer validate .
# Validate specific file
packer validate main.pkr.hcl
# With variable files
packer validate -var-file=prod.pkrvars.hcl .
Formatear código:
# Format HCL files
packer fmt .
# Check formatting
packer fmt -check .
Inicializar plantilla:
# Download required plugins
packer init .
# Upgrade plugins
packer init -upgrade .
Construir imagen:
# Build using defaults
packer build .
# Build specific sources
packer build -only='amazon-ebs.ubuntu' .
# With variable overrides
packer build \
-var "aws_region=us-west-2" \
-var "instance_type=t3.small" \
.
# With variable file
packer build -var-file=prod.pkrvars.hcl .
# Debug mode (keep builder instance running)
packer build -debug .
# Force build (remove existing)
packer build -force .
# Show build output
packer inspect main.pkr.hcl
Inspeccionar salida:
# After build, check manifest
cat manifest.json | jq .
# Output shows:
# {
# "builds": [
# {
# "name": "amazon-ebs.ubuntu",
# "builder_type": "amazon-ebs",
# "build_time": 1234567890,
# "files": [],
# "artifact_id": "us-east-1:ami-0123456789abcdef0",
# "artifact_file_id": "AMIid"
# }
# ],
# "last_run_uuid": "abc123..."
# }
Integración con CI/CD
Automatiza construcciones de imágenes en pipelines de CI/CD.
GitHub Actions:
name: Build Packer Image
on:
push:
branches: [main]
paths:
- 'packer/**'
- '.github/workflows/packer.yml'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: hashicorp/setup-packer@main
- name: Validate
run: packer validate packer/
- name: Format check
run: packer fmt -check packer/
- name: Build image
run: packer build -var-file=packer/prod.pkrvars.hcl packer/
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
PACKER_LOG: 1
- name: Upload manifest
uses: actions/upload-artifact@v3
with:
name: packer-manifest
path: manifest.json
GitLab CI:
stages:
- validate
- build
variables:
PACKER_VERSION: "1.8.0"
before_script:
- apt-get update && apt-get install -y wget unzip
- wget https://releases.hashicorp.com/packer/${PACKER_VERSION}/packer_${PACKER_VERSION}_linux_amd64.zip
- unzip packer_${PACKER_VERSION}_linux_amd64.zip
validate:
stage: validate
script:
- packer validate packer/
- packer fmt -check packer/
build_ami:
stage: build
script:
- packer build -var-file=packer/prod.pkrvars.hcl packer/
artifacts:
paths:
- manifest.json
only:
- main
Jenkins:
pipeline {
agent any
environment {
AWS_REGION = 'us-east-1'
AWS_CREDENTIALS = credentials('aws-credentials')
}
stages {
stage('Validate') {
steps {
sh 'packer validate packer/'
}
}
stage('Build') {
steps {
sh '''
packer build \
-var-file=packer/prod.pkrvars.hcl \
packer/
'''
}
}
stage('Archive') {
steps {
archiveArtifacts artifacts: 'manifest.json'
}
}
}
}
Conclusión
Packer estandariza la creación de imágenes en múltiples plataformas, permitiendo implementaciones de infraestructura reproducibles. Al definir imágenes como código, validar plantillas e integrar con pipelines de CI/CD, creas una base para despliegues de infraestructura consistentes y rápidamente desplegables. Combina imágenes construidas con Packer con Terraform para despliegues de Infraestructura como Código que son confiables, auditables y escalables.


