El Principio Central
Infraestructura inmutable significa que los servers nunca se modifican después del deployment. El workflow es:
Cambio de Código → Construir Nueva Imagen → Deployar Nuevas Instancias → Destruir Instancias Viejas
No:
Cambio de Código → SSH al server → apt-get install → rezar
Construcción de Golden Images con Packer
# packer/app-server.pkr.hcl
variable "app_version" { type = string }
variable "git_sha" { type = string }
source "amazon-ebs" "app_server" {
ami_name = "app-server-${var.app_version}-${formatdate("YYYYMMDD-hhmm", timestamp())}"
instance_type = "t3.medium"
region = "us-east-1"
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-arm64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
owners = ["099720109477"]
most_recent = true
}
ssh_username = "ubuntu"
tags = {
Name = "app-server-${var.app_version}"
GitSHA = var.git_sha
BuildTime = timestamp()
}
}
build {
sources = ["source.amazon-ebs.app_server"]
provisioner "shell" {
inline = [
"sudo apt-get update -y",
"sudo apt-get upgrade -y",
"sudo apt-get install -y curl unzip jq",
"curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -",
"sudo apt-get install -y nodejs",
]
}
provisioner "file" {
source = "../dist/"
destination = "/tmp/app/"
}
provisioner "shell" {
inline = [
"sudo mkdir -p /opt/app",
"sudo cp -r /tmp/app/* /opt/app/",
"cd /opt/app && sudo npm ci --production",
"sudo systemctl daemon-reload",
"sudo systemctl enable app.service",
]
}
}
El Pipeline de AMI
# .github/workflows/ami-pipeline.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- run: npm test
- name: Build AMI
id: build
run: |
AMI_ID=$(packer build -machine-readable \
-var "app_version=${{ github.ref_name }}" \
-var "git_sha=${{ github.sha }}" \
packer/app-server.pkr.hcl | \
grep 'artifact,0,id' | cut -d: -f2)
echo "ami_id=$AMI_ID" >> $GITHUB_OUTPUT
- name: Validate AMI
run: |
INSTANCE_ID=$(aws ec2 run-instances \
--image-id ${{ steps.build.outputs.ami_id }} \
--instance-type t3.small \
--query 'Instances[0].InstanceId' --output text)
aws ec2 wait instance-status-ok --instance-ids $INSTANCE_ID
IP=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID \
--query 'Reservations[0].Instances[0].PublicIpAddress' --output text)
curl -f http://$IP:3000/health || exit 1
aws ec2 terminate-instances --instance-ids $INSTANCE_ID
Deployments Blue-Green
#!/bin/bash
NEW_AMI=$1
CURRENT_ENV=$(get_active_environment)
TARGET_ENV=$([[ "$CURRENT_ENV" == "blue" ]] && echo "green" || echo "blue")
# 1. Lanzar nuevas instancias en el environment target
update_asg_ami "$TARGET_ENV" "$NEW_AMI"
wait_for_healthy "$TARGET_ENV" 120
# 2. Canary: mandar 10% del tráfico
set_traffic_weights "$CURRENT_ENV" 90 "$TARGET_ENV" 10
sleep 300
# 3. Verificar error rates
ERROR_RATE=$(get_error_rate "$TARGET_ENV" "5m")
if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
echo "Error rate muy alto ($ERROR_RATE), haciendo rollback"
set_traffic_weights "$CURRENT_ENV" 100 "$TARGET_ENV" 0
exit 1
fi
# 4. Shift a 50/50
set_traffic_weights "$CURRENT_ENV" 50 "$TARGET_ENV" 50
sleep 300
# 5. Cutover completo
set_traffic_weights "$CURRENT_ENV" 0 "$TARGET_ENV" 100
La Historia de la Migración
Migrar de mutable a inmutable nos tomó tres meses. Las partes más difíciles:
1. Descubrir todos los cambios manuales. Corrimos debsums y find / -newer /var/log/installer en cada server de producción. Encontramos 23 paquetes instalados manualmente, 8 archivos de config modificados, 3 cron jobs agregados a mano, y un script Python que alguien había escrito directamente en un server de producción que era crítico para el pipeline de facturación.
2. Configuración de la aplicación. La app leía config de archivos locales editados a mano. Movimos todo a variables de ambiente inyectadas vía user data del EC2 y AWS Systems Manager Parameter Store.
3. Manejo de logs. Servers que se destruyen pierden sus logs. Configuramos el agente de CloudWatch Logs en la golden image para shippear logs antes de que el server muera.
4. Hábitos del equipo. El cambio más difícil fue cultural. Los ingenieros tuvieron que dejar de pensar "voy a hacer SSH y arreglarlo" y empezar a pensar "voy a arreglar el template y reconstruir." Literalmente deshabilitamos el acceso SSH a instancias de producción para forzar el cambio.
Para Cerrar
La infraestructura inmutable no es sobre herramientas, Packer es solo el mecanismo. Es sobre una disciplina: los servers son ganado, no mascotas. Nacen de una imagen, sirven requests, y son reemplazados. Sin parches, sin hotfixes, sin "voy a cambiar esta cosita nomás."
La migración fue dolorosa. Tres meses de arqueología en servers que venían acumulando cambios por más de un año. Pero el resultado vale la pena: cada deployment es un estado conocido-bueno, cada rollback es instantáneo (solo apuntar a la AMI anterior), y el configuration drift es imposible por diseño.
La mejor infraestructura es la que no te sorprende.