Como usar o Packer para criar imagens de máquina imutáveis
1. Introdução ao conceito de imagens imutáveis e o papel do Packer
Imagens de máquina imutáveis representam um paradigma fundamental na infraestrutura moderna. Diferentemente do modelo mutável, onde servidores são atualizados e modificados ao longo do tempo (gerando o temido "configuration drift"), a infraestrutura imutável preconiza que uma vez que uma imagem é criada, ela nunca é alterada. Para aplicar uma atualização, uma nova imagem é construída e as instâncias antigas são substituídas.
O HashiCorp Packer é a ferramenta padrão-ouro para automatizar a criação dessas imagens. Ele funciona através de três componentes principais:
- Builders: responsáveis por criar a máquina base em diferentes plataformas (AWS, Azure, GCP, VMware, VirtualBox)
- Provisioners: executam scripts e ferramentas de configuração dentro da imagem durante sua construção
- Post-processors: manipulam o artefato final (compressão, exportação, push para registries)
Os cenários de uso são vastos: desde AMIs para EC2 na AWS, imagens Docker, snapshots de disco no GCP, até templates para ambientes on-premises com VMware.
2. Instalação e configuração inicial do Packer
A instalação do Packer é simples. No Linux/macOS:
# Linux (Ubuntu/Debian)
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install packer
# macOS (Homebrew)
brew tap hashicorp/tap
brew install hashicorp/tap/packer
# Verificar instalação
packer version
A estrutura básica de um template HCL2 inclui variáveis, sources e builds:
# variables.pkr.hcl
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "instance_type" {
type = string
default = "t2.micro"
}
# sources.pkr.hcl
source "amazon-ebs" "ubuntu" {
region = var.aws_region
instance_type = var.instance_type
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
}
# build.pkr.hcl
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
}
}
Para configurar credenciais na AWS, utilize variáveis de ambiente ou o arquivo ~/.aws/credentials:
export AWS_ACCESS_KEY_ID="seu-access-key"
export AWS_SECRET_ACCESS_KEY="seu-secret-key"
export AWS_DEFAULT_REGION="us-east-1"
3. Criando uma imagem base com um builder de nuvem (exemplo AWS)
Vamos criar um template completo para gerar uma AMI com Nginx e hardening básico:
# ubuntu-nginx.pkr.hcl
packer {
required_plugins {
amazon = {
version = ">= 1.0.0"
source = "github.com/hashicorp/amazon"
}
}
}
variable "ami_name" {
type = string
default = "ubuntu-nginx-20.04-{{timestamp}}"
}
source "amazon-ebs" "ubuntu" {
ami_name = var.ami_name
instance_type = "t2.micro"
region = "us-east-1"
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
tags = {
Name = "NginxImage"
Environment = "production"
Version = "1.0.0"
}
}
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
inline = [
"sudo apt-get update -y",
"sudo apt-get upgrade -y",
"sudo apt-get install -y nginx ufw",
"sudo ufw allow 'Nginx HTTP'",
"sudo ufw --force enable",
"sudo systemctl enable nginx",
"sudo rm -rf /var/www/html/index.nginx-debian.html",
"echo '<h1>Imagem Imutável Packer</h1>' | sudo tee /var/www/html/index.html"
]
}
provisioner "shell" {
script = "scripts/hardening.sh"
}
}
O script de hardening pode incluir:
#!/bin/bash
# scripts/hardening.sh
# Desabilitar login root SSH
sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
# Remover pacotes desnecessários
sudo apt-get autoremove -y
sudo apt-get autoclean -y
# Configurar limites de kernel
echo "net.ipv4.tcp_syncookies = 1" | sudo tee -a /etc/sysctl.conf
echo "net.ipv4.ip_forward = 0" | sudo tee -a /etc/sysctl.conf
4. Trabalhando com provisioners avançados
O provisioner file permite copiar artefatos locais para dentro da imagem:
provisioner "file" {
source = "./app/"
destination = "/home/ubuntu/app/"
}
provisioner "file" {
content = templatefile("templates/nginx.conf.tpl", { server_name = "example.com" })
destination = "/tmp/nginx.conf"
}
Para automação complexa, utilize o provisioner ansible:
provisioner "ansible" {
playbook_file = "./playbooks/webserver.yml"
ansible_env_vars = [
"ANSIBLE_HOST_KEY_CHECKING=False"
]
extra_arguments = [
"--extra-vars", "nginx_port=8080"
]
}
Em ambientes Windows, o provisioner powershell é essencial:
provisioner "powershell" {
inline = [
"Install-WindowsFeature -Name Web-Server -IncludeManagementTools",
"New-Item -Path 'C:\\inetpub\\wwwroot\\index.html' -ItemType File -Value '<h1>Windows Image Imutável</h1>'"
]
}
5. Customização com variáveis, templates e funções
Para gerenciar múltiplos ambientes, crie arquivos .pkrvars.hcl:
# dev.pkrvars.hcl
aws_region = "us-east-1"
instance_type = "t2.micro"
ami_prefix = "dev-nginx"
environment = "development"
# prod.pkrvars.hcl
aws_region = "us-west-2"
instance_type = "t3.medium"
ami_prefix = "prod-nginx"
environment = "production"
Utilize funções integradas para personalização:
variable "ami_name" {
type = string
default = "nginx-${var.environment}-${regex_replace(timestamp(), "[-: ]", "")}"
}
source "amazon-ebs" "ubuntu" {
ami_name = var.ami_name
tags = {
BuildID = uuidv4()
CreatedAt = formatdate("YYYY-MM-DD hh:mm:ss", timestamp())
}
}
Iterações com for permitem builds multiplataforma:
locals {
regions = ["us-east-1", "us-west-2", "eu-west-1"]
}
source "amazon-ebs" "multi-region" {
for_each = toset(local.regions)
region = each.key
ami_name = "nginx-${each.key}-${timestamp()}"
# ... outras configurações
}
build {
for_each = source.amazon-ebs.multi-region
sources = [source.amazon-ebs.multi-region[each.key]]
}
6. Post-processors: otimização e distribuição das imagens
O post-processor manifest gera metadados essenciais:
build {
sources = ["source.amazon-ebs.ubuntu"]
post-processor "manifest" {
output = "manifest.json"
strip_path = true
custom_data = {
build_user = "ci-pipeline"
version = "1.0.0"
}
}
}
Para exportar e comprimir artefatos:
post-processor "compress" {
output = "ubuntu-nginx-{{timestamp}}.tar.gz"
}
post-processor "artifice" {
files = ["ubuntu-nginx-*.tar.gz"]
}
Em pipelines de containers:
source "docker" "ubuntu" {
image = "ubuntu:20.04"
commit = true
}
build {
sources = ["source.docker.ubuntu"]
provisioner "shell" {
inline = ["apt-get update && apt-get install -y nginx"]
}
post-processor "docker-tag" {
repository = "myregistry/nginx"
tags = ["latest", "1.0.0"]
}
post-processor "docker-push" {
login = true
login_server = "myregistry.azurecr.io"
login_username = var.docker_username
login_password = var.docker_password
}
}
7. Integração do Packer com pipelines CI/CD
Exemplo de workflow no GitHub Actions:
# .github/workflows/packer-build.yml
name: Build AMI with Packer
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
validate-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Packer
uses: hashicorp/setup-packer@main
with:
version: "latest"
- name: Validate template
run: |
packer fmt -check .
packer validate -var-file=prod.pkrvars.hcl .
- name: Build AMI
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
packer build -var-file=prod.pkrvars.hcl \
-var="ami_version=${{ github.sha }}" \
ubuntu-nginx.pkr.hcl
Para GitLab CI:
# .gitlab-ci.yml
stages:
- validate
- build
packer-validate:
stage: validate
script:
- packer fmt -check .
- packer validate -var-file=prod.pkrvars.hcl .
packer-build:
stage: build
script:
- packer build -var-file=prod.pkrvars.hcl \
-var="ami_version=$CI_COMMIT_SHORT_SHA" \
ubuntu-nginx.pkr.hcl
only:
- main
8. Boas práticas, segurança e manutenção de imagens imutáveis
Princípios fundamentais para imagens seguras e eficientes:
# Minimizar superfície de ataque
provisioner "shell" {
inline = [
"sudo apt-get remove --purge -y vim-tiny nano curl wget",
"sudo apt-get autoremove --purge -y",
"sudo rm -rf /var/log/*.log /var/cache/apt/archives/*.deb",
"sudo passwd -l root"
]
}
# Gerenciamento de segredos com Vault
provisioner "shell" {
environment_vars = [
"VAULT_ADDR=${var.vault_addr}",
"VAULT_TOKEN=${var.vault_token}"
]
script = "scripts/fetch-secrets.sh"
}
Estratégias de rolling update em clusters Kubernetes:
# Atualizar deployment com nova imagem
kubectl set image deployment/nginx-deployment \
nginx=$ECR_REPOSITORY:$AMI_VERSION
# Rollback se necessário
kubectl rollout undo deployment/nginx-deployment
Para ambientes Nomad:
job "nginx" {
group "web" {
task "nginx" {
driver = "docker"
config {
image = "myregistry/nginx:${var.ami_version}"
}
}
}
}
Lembre-se sempre de versionar suas imagens com tags semânticas e manter um registro claro do que mudou entre versões. O Packer, combinado com pipelines CI/CD, permite que equipes entreguem infraestrutura imutável de forma confiável e auditável.
Referências
- Documentação Oficial do HashiCorp Packer — Guia completo de instalação, builders, provisioners e post-processors
- Tutorial: Build an Image with Packer (AWS) — Passo a passo oficial para criar AMIs na AWS usando Packer
- Packer + Ansible: Automating Image Creation — Artigo sobre integração do Packer com Ansible para provisionamento avançado
- GitHub Actions + Packer: CI/CD para AMIs — Action oficial do HashiCorp para integrar Packer em workflows do GitHub
- HashiCorp Learn: Packer with Vault — Tutorial sobre gerenciamento seguro de segredos durante builds do Packer
- Packer Best Practices Guide — Guia de boas práticas para templates HCL2, variáveis e organização de projetos
- Infrastructure as Code: Packer, Terraform, and Beyond — Recurso da HashiCorp sobre como Packer se integra ao ecossistema IaC