TL;DR
- Qué: Probar infraestructura AWS localmente y en CI/CD antes de desplegar en producción
- Por qué: Detectar errores de configuración, reducir costos y prevenir incidentes en producción
- Herramientas: Terraform test framework (1.6+), LocalStack, Terratest, AWS Config
- Métrica clave: 100% de cambios de infraestructura probados antes del despliegue
- Empieza aquí: Configura LocalStack con tflocal wrapper para testing local
Las fallas de infraestructura cuestan a las organizaciones un promedio de $5,600 por minuto de inactividad. Sin embargo, el 73% de los equipos despliegan cambios de infraestructura sin testing exhaustivo. El testing de infraestructura AWS cierra esta brecha validando tus configuraciones de Terraform antes de que lleguen a producción.
Esta guía cubre la implementación de una estrategia completa de testing de infraestructura AWS. Aprenderás a usar el framework de testing nativo de Terraform, simular AWS localmente con LocalStack y construir tests de integración con Terratest.
Lo que aprenderás:
- Cómo escribir y ejecutar tests de Terraform para recursos AWS
- Testing local de AWS con LocalStack y tflocal
- Testing de integración con Terratest y Go
- Integración en pipelines CI/CD para testing automatizado
- Mejores prácticas de organizaciones que prueban miles de recursos
Entendiendo el Testing de Infraestructura AWS
¿Qué es el Testing de Infraestructura?
El testing de infraestructura valida que tus definiciones de IaC crean recursos que cumplen con requisitos funcionales, de seguridad y cumplimiento. A diferencia del testing de aplicaciones, el testing de infraestructura verifica configuraciones de recursos cloud, reglas de red, políticas IAM e integraciones de servicios.
¿Por Qué Probar la Infraestructura?
Sin testing, descubres problemas en producción:
- Brechas de seguridad: Security groups demasiado permisivos expuestos a internet
- Violaciones de cumplimiento: Buckets S3 sin encriptación
- Errores de configuración: Tipos de instancia incorrectos o tags faltantes
- Fallas de integración: Servicios que no pueden comunicarse
Pirámide de Testing para Infraestructura
| Nivel | Qué Prueba | Herramientas | Velocidad |
|---|---|---|---|
| Tests unitarios | Configuraciones de recursos individuales | Terraform validate, tflint | Segundos |
| Tests de contrato | Inputs/outputs de módulos | Terraform test | Segundos |
| Tests de integración | Interacciones entre recursos | LocalStack, Terratest | Minutos |
| Tests end-to-end | Despliegue completo del stack | AWS real + Terratest | Minutos-Horas |
Implementando Testing Nativo de Terraform
Prerrequisitos
Antes de comenzar, asegúrate de tener:
- Terraform 1.6+ instalado
- AWS CLI configurado (para tests en AWS real)
- LocalStack instalado (para tests locales)
- Go 1.21+ (para Terratest)
Paso 1: Estructura Básica de Tests de Terraform
Crea archivos de test con extensión .tftest.hcl:
# tests/s3_bucket.tftest.hcl
# Test que el bucket S3 tiene la configuración correcta
run "verify_s3_bucket_config" {
command = plan
assert {
condition = aws_s3_bucket.main.bucket_prefix == "app-data-"
error_message = "El prefijo del bucket S3 debe ser 'app-data-'"
}
assert {
condition = aws_s3_bucket_versioning.main.versioning_configuration[0].status == "Enabled"
error_message = "El versionado del bucket S3 debe estar habilitado"
}
}
run "verify_encryption" {
command = plan
assert {
condition = aws_s3_bucket_server_side_encryption_configuration.main.rule[0].apply_server_side_encryption_by_default[0].sse_algorithm == "aws:kms"
error_message = "El bucket S3 debe usar encriptación KMS"
}
}
Paso 2: Testing con Variables y Providers
Configura variables y providers específicos para tests:
# tests/vpc.tftest.hcl
variables {
environment = "test"
vpc_cidr = "10.0.0.0/16"
}
provider "aws" {
region = "us-east-1"
}
run "verify_vpc_configuration" {
command = plan
assert {
condition = aws_vpc.main.cidr_block == "10.0.0.0/16"
error_message = "El bloque CIDR de la VPC debe coincidir con la variable de entrada"
}
assert {
condition = aws_vpc.main.enable_dns_hostnames == true
error_message = "Los hostnames DNS deben estar habilitados"
}
assert {
condition = length(aws_subnet.private) == 3
error_message = "Se deben crear 3 subnets privadas"
}
}
run "verify_security_groups" {
command = plan
assert {
condition = !contains([for rule in aws_security_group.web.ingress : rule.cidr_blocks], ["0.0.0.0/0"])
error_message = "El security group no debe permitir ingress sin restricciones"
}
}
Paso 3: Usando Módulos Auxiliares en Tests
Crea módulos auxiliares para escenarios de test complejos:
# tests/setup/main.tf - Módulo auxiliar para datos de test
variable "test_prefix" {
default = "test"
}
resource "random_string" "suffix" {
length = 8
special = false
upper = false
}
output "bucket_name" {
value = "${var.test_prefix}-${random_string.suffix.result}"
}
output "test_tags" {
value = {
Environment = "test"
ManagedBy = "terraform-test"
}
}
# tests/integration.tftest.hcl
run "setup" {
module {
source = "./tests/setup"
}
}
run "create_bucket" {
variables {
bucket_name = run.setup.bucket_name
tags = run.setup.test_tags
}
assert {
condition = aws_s3_bucket.main.bucket == run.setup.bucket_name
error_message = "El nombre del bucket debe coincidir con el nombre generado"
}
}
Verificación
Ejecuta los tests con:
# Ejecutar todos los tests
terraform test
# Ejecutar archivo de test específico
terraform test -filter=tests/s3_bucket.tftest.hcl
# Salida detallada
terraform test -verbose
Testing Local con LocalStack
¿Por Qué LocalStack?
LocalStack simula servicios AWS localmente, permitiendo:
- Ahorro de costos: Sin cargos de AWS durante el desarrollo
- Velocidad: Los tests se ejecutan en segundos, no minutos
- Seguridad: Sin riesgo de afectar recursos de producción
- Desarrollo offline: Prueba sin conexión a internet
Configurando LocalStack
Instala e inicia LocalStack:
# Instalar vía pip
pip install localstack
# O vía Docker
docker pull localstack/localstack
# Iniciar LocalStack
localstack start -d
# Verificar que los servicios están corriendo
localstack status services
Instala el wrapper tflocal:
# Instalar tflocal
pip install terraform-local
# tflocal configura automáticamente los endpoints
tflocal init
tflocal plan
tflocal apply
Configurando Terraform para LocalStack
Configuración manual del provider:
# providers.tf
provider "aws" {
region = "us-east-1"
access_key = "test"
secret_key = "test"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
endpoints {
s3 = "http://localhost:4566"
dynamodb = "http://localhost:4566"
lambda = "http://localhost:4566"
iam = "http://localhost:4566"
sqs = "http://localhost:4566"
sns = "http://localhost:4566"
}
}
Ejemplo de Test con LocalStack
Testing de despliegue de función Lambda:
# tests/lambda.tftest.hcl
provider "aws" {
region = "us-east-1"
access_key = "test"
secret_key = "test"
skip_credentials_validation = true
skip_metadata_api_check = true
endpoints {
lambda = "http://localhost:4566"
iam = "http://localhost:4566"
s3 = "http://localhost:4566"
}
}
run "deploy_lambda" {
command = apply
assert {
condition = aws_lambda_function.main.runtime == "python3.11"
error_message = "Lambda debe usar runtime Python 3.11"
}
assert {
condition = aws_lambda_function.main.memory_size == 256
error_message = "La memoria de Lambda debe ser 256 MB"
}
}
run "verify_lambda_invocable" {
command = apply
assert {
condition = aws_lambda_function.main.invoke_arn != ""
error_message = "Lambda debe tener un ARN de invocación válido"
}
}
Testing de Integración con Terratest
¿Por Qué Terratest?
Terratest proporciona:
- Capacidades completas de testing en Go
- Creación y validación de recursos AWS reales
- Testing de endpoints HTTP
- Verificaciones de conectividad SSH
- Limpieza automática
Estructura Básica de Terratest
// test/s3_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestS3BucketCreation(t *testing.T) {
t.Parallel()
awsRegion := "us-east-1"
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/s3",
Vars: map[string]interface{}{
"bucket_prefix": "test-bucket",
"environment": "test",
},
EnvVars: map[string]string{
"AWS_DEFAULT_REGION": awsRegion,
},
})
// Limpiar recursos después del test
defer terraform.Destroy(t, terraformOptions)
// Desplegar infraestructura
terraform.InitAndApply(t, terraformOptions)
// Obtener outputs
bucketID := terraform.Output(t, terraformOptions, "bucket_id")
bucketArn := terraform.Output(t, terraformOptions, "bucket_arn")
// Validar que el bucket existe
aws.AssertS3BucketExists(t, awsRegion, bucketID)
// Validar propiedades del bucket
assert.Contains(t, bucketID, "test-bucket")
assert.Contains(t, bucketArn, "arn:aws:s3:::")
}
Testing de VPC y Networking
// test/vpc_test.go
func TestVPCConfiguration(t *testing.T) {
t.Parallel()
awsRegion := "us-east-1"
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"vpc_cidr": "10.0.0.0/16",
"environment": "test",
"az_count": 3,
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
privateSubnetIDs := terraform.OutputList(t, terraformOptions, "private_subnet_ids")
publicSubnetIDs := terraform.OutputList(t, terraformOptions, "public_subnet_ids")
// Validar que la VPC existe
vpc := aws.GetVpcById(t, vpcID, awsRegion)
assert.Equal(t, "10.0.0.0/16", *vpc.CidrBlock)
// Validar cantidad de subnets
assert.Equal(t, 3, len(privateSubnetIDs))
assert.Equal(t, 3, len(publicSubnetIDs))
// Validar que las subnets están en diferentes AZs
subnets := aws.GetSubnetsForVpc(t, vpcID, awsRegion)
azs := make(map[string]bool)
for _, subnet := range subnets {
azs[*subnet.AvailabilityZone] = true
}
assert.Equal(t, 3, len(azs))
}
Testing con LocalStack y Terratest
// test/localstack_test.go
func TestWithLocalStack(t *testing.T) {
t.Parallel()
terraformOptions := &terraform.Options{
TerraformDir: "../modules/lambda",
Vars: map[string]interface{}{
"function_name": "test-function",
},
EnvVars: map[string]string{
"AWS_ACCESS_KEY_ID": "test",
"AWS_SECRET_ACCESS_KEY": "test",
"AWS_DEFAULT_REGION": "us-east-1",
},
// Usar tflocal para LocalStack
TerraformBinary: "tflocal",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
functionArn := terraform.Output(t, terraformOptions, "function_arn")
assert.NotEmpty(t, functionArn)
}
Ejemplos del Mundo Real
Ejemplo 1: Testing de Infraestructura en Stripe
Contexto: Stripe procesa millones de transacciones financieras que requieren infraestructura altamente confiable.
Desafío: Los cambios de infraestructura causaban incidentes en el procesamiento de pagos.
Solución: Pipeline de testing exhaustivo:
- Tests unitarios con tflint y reglas personalizadas
- Tests en LocalStack para todos los cambios
- Tests de integración con Terratest en staging
- Despliegues canary con rollback automático
Resultados:
- 99.99% de tasa de éxito en despliegues de infraestructura
- Cero incidentes que afecten pagos por cambios de IaC
- 60% más velocidad en cambios de infraestructura
Conclusión Clave: Prueba en cada nivel—local, staging y canary en producción—para detectar problemas antes de que impacten a los usuarios.
Ejemplo 2: Testing Multi-Región en Airbnb
Contexto: Airbnb despliega infraestructura en 5 regiones de AWS.
Desafío: Asegurar configuración consistente en todas las regiones.
Solución: Suite de tests agnóstica a la región:
- Tests parametrizados para cada región
- Validación de conectividad entre regiones
- Verificaciones de cumplimiento para requisitos regionales (GDPR, residencia de datos)
Resultados:
- Configuraciones idénticas verificadas en todas las regiones
- 80% de reducción en bugs específicos de región
- Validación automatizada de cumplimiento para 3 marcos regulatorios
Conclusión Clave: Parametriza los tests para despliegues multi-región—una suite de tests valida todos los entornos.
Mejores Prácticas
Qué Hacer
Probar localmente primero
- Usa LocalStack para iteración rápida
- Ejecuta tflocal plan antes de hacer push
- Valida sintaxis con terraform validate
Estructurar tests por tipo de recurso
- Archivos de test separados para VPC, cómputo, almacenamiento
- Usa convenciones de nomenclatura consistentes
- Documenta el propósito del test en comentarios
Limpiar recursos de test
- Siempre usa defer para limpieza en Terratest
- Etiqueta recursos de test para fácil identificación
- Implementa alertas de costos para recursos huérfanos
Integrar con CI/CD
- Ejecuta tests en cada pull request
- Bloquea merges ante fallas de tests
- Reporta métricas de cobertura de tests
Qué Evitar
No omitas tests de integración
- LocalStack no cubre todo
- Algunos comportamientos solo aparecen en AWS real
- Planifica testing periódico en AWS real
No pruebes detalles de implementación
- Prueba comportamiento, no cantidad de recursos
- Permite actualizaciones del provider
- Enfócate en seguridad y cumplimiento
Tips de Experto
- Tip 1: Usa
terraform test -filterpara ejecutar tests específicos durante el desarrollo - Tip 2: Crea respuestas mock para dependencias externas
- Tip 3: Ejecuta tests costosos solo en la rama main, no en cada PR
Errores Comunes y Soluciones
Error 1: Tests Inestables por Consistencia Eventual
Síntomas:
- Los tests pasan localmente, fallan en CI
- Fallas intermitentes para el mismo código
- Los tests fallan cuando los recursos aún se están propagando
Causa Raíz: Consistencia eventual de AWS para algunos servicios.
Solución:
// Agregar lógica de reintento en Terratest
func TestWithRetry(t *testing.T) {
maxRetries := 3
sleepBetweenRetries := 10 * time.Second
retry.DoWithRetry(t, "Verificar recurso", maxRetries, sleepBetweenRetries, func() (string, error) {
// Lógica del test aquí
return "", nil
})
}
Prevención: Construye lógica de reintento en los tests; usa patrones de espera para recursos asíncronos.
Error 2: Brechas de Servicios en LocalStack
Síntomas:
- Los tests pasan en LocalStack, fallan en AWS real
- Ciertas características no disponibles localmente
- Las respuestas mock no coinciden con producción
Causa Raíz: LocalStack no tiene 100% de paridad con AWS.
Solución:
- Verifica la cobertura de LocalStack para los servicios que usas
- Ejecuta tests críticos contra un sandbox real de AWS
- Usa LocalStack Pro para mejor cobertura
Prevención: Documenta qué tests requieren AWS real; etiqueta tests por entorno de ejecución.
Herramientas y Recursos
Herramientas Recomendadas
| Herramienta | Mejor Para | Pros | Contras | Precio |
|---|---|---|---|---|
| Terraform Test | Testing nativo | Integrado, sintaxis simple | Limitado a Terraform | Gratis |
| LocalStack | Desarrollo local | Rápido, tier gratuito disponible | No 100% paridad con AWS | Gratis/Pago |
| Terratest | Tests de integración | Capacidades completas de Go | Requiere conocimiento de Go | Gratis |
| AWS Config | Testing de cumplimiento | Integración nativa con AWS | Solo AWS | Pago por regla |
| Checkov | Escaneo de seguridad | 1000+ políticas | Solo estático | Gratis/Pago |
Criterios de Selección
Elige basándote en:
- Habilidades del equipo: Experiencia en Go → Terratest; Solo HCL → Terraform test
- Presupuesto: Consciente de costos → LocalStack Community; Enterprise → LocalStack Pro
- Cobertura: Servicios críticos → AWS real; Desarrollo → LocalStack
Recursos Adicionales
Testing de Infraestructura Asistido por IA
Las herramientas modernas de IA mejoran el testing de infraestructura:
- Generación de tests: IA sugiere casos de test basados en patrones de IaC
- Análisis de fallas: Identifica causas raíz desde logs de tests
- Recomendaciones de cobertura: Encuentra configuraciones de recursos no probadas
- Escaneo de seguridad: Detecta errores de configuración automáticamente
Herramientas: GitHub Copilot para escritura de tests, Amazon Q para sugerencias específicas de AWS.
Framework de Decisión: Estrategia de Testing
| Consideración | Enfoque Ligero | Enfoque Exhaustivo |
|---|---|---|
| Tamaño del equipo | <5 ingenieros | >5 ingenieros |
| Complejidad de infraestructura | Una sola cuenta | Multi-cuenta/región |
| Enfoque de testing | Terraform test + LocalStack | Suite completa de Terratest |
| Integración CI/CD | Solo validación de PR | Pipeline completo con staging |
| Testing en AWS real | Verificaciones manuales puntuales | Ejecuciones nocturnas automatizadas |
Midiendo el Éxito
Rastrea estas métricas para medir la efectividad del testing:
| Métrica | Objetivo | Medición |
|---|---|---|
| Cobertura de tests | >80% de módulos | Módulos con tests / total de módulos |
| Tasa de éxito de tests | >95% | Ejecuciones exitosas / total de ejecuciones |
| Incidentes de infraestructura | <1/mes | Post-mortems de cambios de IaC |
| Tasa de éxito de despliegues | >99% | Despliegues exitosos / total de despliegues |
| Tiempo de detección de problemas | <10 minutos | Commit → notificación de falla del test |
| Tiempo medio de recuperación | <30 minutos | Incidente → fix desplegado |
Conclusión
Puntos Clave
- Prueba en cada nivel—unitario, integración y end-to-end
- Usa LocalStack para velocidad—iteración rápida sin costos de AWS
- Integra con CI/CD—bloquea despliegues ante fallas de tests
- Balancea cobertura y costo—LocalStack para desarrollo, AWS real para paths críticos
Plan de Acción
- Hoy: Instala LocalStack y ejecuta
tflocal planen infraestructura existente - Esta Semana: Escribe tests de Terraform para tu módulo más crítico
- Este Mes: Implementa pipeline completo de CI/CD con testing automatizado
Ver También
¿Cómo prueba tu equipo la infraestructura AWS antes del despliegue? Comparte tus estrategias y herramientas de testing en los comentarios.
