Terraform, Terragrunt y LocalStack Implementación
Introducción
Este documento detalla nuestra implementación de infraestructura como código (IaC) usando Terraform, Terragrunt para gestión de entornos y LocalStack para pruebas locales.
flowchart TD A[Terragrunt] --> B[Modules Terraform] B --> C[Dev Environment] B --> D[Prod Environment] C --> E[Minikube] D --> F[AWS Real] E --> G[Todo App] E --> H[RabbitMQ] E --> I[PostgreSQL] F --> J[Todo App] F --> K[AWS MQ] F --> L[RDS PostgreSQL]
Prerequisitos
Instalación de Dependencias
-
Docker y Docker Compose:
-
Terraform y Terragrunt:
# Terraform 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 update && sudo apt install terraform # Terragrunt curl -L https://github.com/gruntwork-io/terragrunt/releases/download/v0.36.0/terragrunt_linux_amd64 -o terragrunt chmod +x terragrunt sudo mv terragrunt /usr/local/bin/
-
AWS CLI (para LocalStack):
pip install awscli-local
Configuración de LocalStack
Actualizamos docker-compose.localstack.yml
para incluir LocalStack:
version: '3.8'
services:
# Servicios existentes...
localstack:
image: localstack/localstack:latest
ports:
- "4566:4566"
- "4510-4559:4510-4559"
environment:
- SERVICES=s3,cloudformation,mq,ec2,iam,rds
- DEFAULT_REGION=eu-central-1
- DEBUG=1
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "./localstack_data:/tmp/localstack"
Nota Importante: La variable de entorno
SERVICES
es crucial. LocalStack solo inicia los servicios de AWS que se enumeran explícitamente en esta línea para ahorrar recursos. Si en el futuro añades un nuevo módulo de Terraform que utilice un servicio de AWS no listado aquí (ej.lambda
,sqs
), debes añadirlo a esta lista. De lo contrario, tus pruebas de Terraform fallarán. En LocalStack Community, no habiliteseks
porque no está soportado; las pruebas de EKS se hacen en AWS real.
Desarrollo Iterativo de Terraform con LocalStack
Este flujo de trabajo permite a los desarrolladores probar cambios en los módulos de Terraform para los entornos de nube (lab
, prod
) de forma local, rápida y sin costos, utilizando LocalStack como un clon de AWS.
El objetivo es redirigir temporalmente las llamadas de Terragrunt de un entorno (ej. lab
) a nuestra instancia local de LocalStack.
Flujo de Trabajo Detallado
Paso 1: Iniciar LocalStack
Asegúrate de que LocalStack esté corriendo.
docker compose -f docker-compose.localstack.yml up -d
Paso 2: Crear un Archivo de Sobrescritura del Proveedor
Para “engañar” a Terragrunt, crea un archivo llamado _provider_override.tf
en el directorio del componente que quieres probar (ej. infra/terraform/environments/lab/network
).
Contenido para _provider_override.tf
:
# Este archivo redirige las llamadas de AWS a LocalStack
provider "aws" {
access_key = "test"
secret_key = "test"
region = "eu-central-1"
s3_force_path_style = true
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
endpoints {
ec2 = "http://localhost:4566"
eks = "http://localhost:4566"
iam = "http://localhost:4566"
rds = "http://localhost:4566"
s3 = "http://localhost:4566"
mq = "http://localhost:4566"
# Añadir más servicios según sea necesario
}
}
Importante: Este archivo no debe ser añadido a Git. Puedes añadir
**/*_provider_override.tf
a tu archivo.gitignore
global.
Paso 3: Desplegar en LocalStack
Navega al directorio donde creaste el archivo de sobreescritura y ejecuta apply
.
cd infra/terraform/environments/lab/network
terragrunt apply -auto-approve
Terragrunt ahora aplicará la configuración del módulo de red en tu instancia local de LocalStack.
Paso 4: Inspeccionar los Recursos
Usa awslocal
para verificar que los recursos se han creado correctamente.
# Listar VPCs creadas en LocalStack
awslocal ec2 describe-vpcs
# Listar subredes
awslocal ec2 describe-subnets
¿Qué es awscli-local
(awslocal)?
awscli-local
es un wrapper del AWS CLI que antepone automáticamente --endpoint-url http://localhost:4566
a los comandos del CLI, de modo que todas las peticiones se enrutan a LocalStack en lugar de AWS. Proporciona el binario awslocal
que simplifica el uso del CLI con LocalStack.
- Instalación (Python/pip):
pip install awscli-local # Verificar awslocal --version
- Alternativa sin instalación (contenedor con CLI de LocalStack):
docker run --rm -it -e AWS_ACCESS_KEY_ID=test -e AWS_SECRET_ACCESS_KEY=test \ -e AWS_DEFAULT_REGION=eu-central-1 --network host localstack/aws-cli \ awslocal ec2 describe-vpcs
Nota: Si no usas
awslocal
, puedes utilizaraws
añadiendo--endpoint-url http://localhost:4566
en cada comando.
Comandos útiles para validar el módulo network
# VPCs (filtrar campos con jq)
awslocal ec2 describe-vpcs | jq '.Vpcs[] | {VpcId, CidrBlock, State}'
# Subredes
awslocal ec2 describe-subnets | jq '.Subnets[] | {SubnetId, VpcId, CidrBlock, AvailabilityZone, MapPublicIpOnLaunch}'
# Tablas de rutas
awslocal ec2 describe-route-tables | jq '.RouteTables[] | {RouteTableId, VpcId, Routes}'
# Internet Gateways
awslocal ec2 describe-internet-gateways | jq '.InternetGateways[] | {InternetGatewayId, Attachments}'
Si ejecutaste make test-infra
, el script habrá generado infra-test-outputs.json
en la raíz del repo con los outputs del módulo network
(por ejemplo, vpc_id
, public_subnet_ids
, private_subnet_ids
). Puedes cruzar estos valores con awslocal
para validar:
# Extraer VpcId del output de Terraform
jq -r '.vpc_id.value' infra-test-outputs.json
# Verificar que exista esa VPC en LocalStack
VPC_ID=$(jq -r '.vpc_id.value' infra-test-outputs.json)
awslocal ec2 describe-vpcs --filters Name=vpc-id,Values="$VPC_ID" | jq '.Vpcs[] | {VpcId, State}'
# Verificar subredes públicas y privadas
jq -r '.public_subnet_ids.value[]' infra-test-outputs.json | xargs -I {} awslocal ec2 describe-subnets --subnet-ids {} | jq '.Subnets[] | {SubnetId, MapPublicIpOnLaunch}'
jq -r '.private_subnet_ids.value[]' infra-test-outputs.json | xargs -I {} awslocal ec2 describe-subnets --subnet-ids {} | jq '.Subnets[] | {SubnetId, MapPublicIpOnLaunch}'
Importante: En LocalStack Community, la API de EKS no está soportada (retorna 501). Comandos como
awslocal eks list-clusters
fallarán. Para validar el módulo EKS, usaterraform validate/plan
con valores mock o valida en AWS real en entornoslab/prod
.
Paso 5: Limpiar
Una vez que hayas terminado de probar, destruye los recursos y elimina el archivo de sobreescritura.
# Destruir los recursos en LocalStack
terragrunt destroy -auto-approve
# Eliminar el archivo de sobreescritura
rm _provider_override.tf
Redirección a LocalStack: Dos Métodos para Dos Contextos
Es importante entender que existen dos maneras de redirigir las llamadas de Terraform a LocalStack, cada una adaptada a su contexto:
Flujo de Trabajo | Método de Redirección | ¿Por qué? |
---|---|---|
Desarrollo Local | Fichero _provider_override.tf |
Es un “truco” manual y explícito para que un desarrollador redirija el tráfico a su localhost . |
Pipeline CI/CD | Variable de Entorno AWS_ENDPOINT_URL |
Es el método estándar y automático para configurar el proveedor en un entorno desatendido como el CI/CD. |
1. Desarrollo Local (_provider_override.tf
)
En tu máquina local, el fichero _provider_override.tf
es detectado automáticamente por Terraform y sus configuraciones tienen prioridad sobre cualquier otra. Es ideal para el desarrollo porque es explícito, temporal y no requiere modificar variables de entorno globales.
2. Pipeline de CI/CD (AWS_ENDPOINT_URL
)
En un entorno automatizado como GitLab CI, el proveedor de AWS de Terraform está diseñado para detectar la variable de entorno AWS_ENDPOINT_URL
. Cuando esta variable está presente, el proveedor redirige todas las llamadas a la API de AWS a esa URL. Este es el método preferido para la automatización, ya que no requiere modificar ningún fichero de código y se integra de forma nativa con los sistemas de CI/CD.
Diagrama de Flujos de Trabajo de Infraestructura
El siguiente diagrama visualiza los dos flujos principales para el desarrollo y despliegue de la infraestructura.
graph TD subgraph "Flujo 1: Desarrollo Iterativo de Terraform (Local)" A[Desarrollador modifica un módulo, ej. 'lab/network'] --> B[Crea '_provider_override.tf'] B --> C[terragrunt apply] C --> D{Recursos creados en LocalStack} D --> E[awslocal ec2 describe-vpcs] E --> F[terragrunt destroy] F --> G[Elimina '_provider_override.tf'] end subgraph "Flujo 2: Pipeline de CI/CD (Automatizado)" H[Commit & Push a GitLab] --> I[Job 'infra_test' se ejecuta con LocalStack] I -- Falla --> M[Pipeline falla] I -- Éxito --> L[Merge a 'main'] L --> N[Job 'infra_deploy_lab' se ejecuta] N --> O{Terragrunt aplica en AWS Lab} O -- Éxito --> P[Creación de Git Tag] P --> Q[Job 'infra_deploy_prod' se ejecuta] Q --> R{Terragrunt aplica en AWS Prod} end
CI/CD Integration (GitLab)
Ejemplo de .gitlab-ci.yml
para pruebas de infraestructura:
stages:
- test
- deploy
infra_test:
stage: test
image: hashicorp/terraform:latest
services:
- name: localstack/localstack:latest
alias: localstack
variables:
AWS_ACCESS_KEY_ID: "test"
AWS_SECRET_ACCESS_KEY: "test"
AWS_DEFAULT_REGION: "eu-central-1"
# Redirigir las llamadas de AWS al servicio de LocalStack
AWS_ENDPOINT_URL: "http://localstack:4566"
script:
- ./scripts/run-infra-tests.sh
infra_deploy_lab:
stage: deploy
image: hashicorp/terraform:latest
environment: lab
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- infra/terraform/environments/lab/**/*
- infra/terraform/modules/**/*
script:
- cd infra/terraform/environments/lab
- terragrunt run-all apply -auto-approve
infra_deploy_prod:
stage: deploy
image: hashicorp/terraform:latest
environment: prod
rules:
- if: '$CI_COMMIT_TAG'
changes:
- infra/terraform/environments/prod/**/*
- infra/terraform/modules/**/*
script:
- cd infra/terraform/environments/prod
- terragrunt run-all apply -auto-approve
Nota sobre la Eficiencia del Pipeline: La directiva
rules:changes
hace que nuestros jobs de despliegue sean mucho más inteligentes. El jobinfra_deploy_lab
, por ejemplo, solo se ejecutará si se cumplen dos condiciones: el commit está en la ramamain
y se han modificado ficheros dentro del entornolab
o de los módulos compartidos. Esto evita despliegues innecesarios, ahorrando tiempo y reduciendo el riesgo de errores.
Gestión del Estado Remoto y Bootstrapping
La gestión del estado se ha simplificado y ahora sigue estas reglas:
- El archivo raíz
infra/terraform/terragrunt.hcl
define elremote_state
con backends3
(bucket, región, lock table, etc.) para entornos reales (lab
/prod
). - En pruebas locales (LocalStack/CI), los componentes del entorno
lab
sobreescriben este backend alocal
mediante:- Un bloque
remote_state { backend = "local" }
eninfra/terraform/environments/lab/*/terragrunt.hcl
. - Un bloque
generate "backend"
que createrragrunt_backend.tf
conbackend "local" {}
evitando colisiones con archivos del módulo.
- Un bloque
Ventajas de este enfoque:
- No hace falta un componente
backend
separado ni gestión de dependencias para el bootstrapping. - Evita la condición de huevo y gallina en CI/LocalStack.
- Los módulos de Terraform no declaran backends; Terragrunt los gestiona por entorno.
Recursos por Entorno
Recurso | LocalStack (Dev) | Lab (Staging) | Prod (AWS) |
---|---|---|---|
S3 Buckets | test-bucket | app-staging | app-prod |
PostgreSQL | (in Minikube) | todo-staging-db | todo-prod-db |
RabbitMQ | (in Minikube) | todo-staging-queue | todo-prod-queue |
EKS | - | todo-lab-cluster | todo-prod-cluster |
Flujo de Trabajo de Desarrollo Local
El entorno de desarrollo local se centra en proveer un clúster de Kubernetes (kind
) para desplegar y probar la aplicación. La responsabilidad está claramente separada:
- Terraform/Terragrunt: Se encarga únicamente de provisionar y destruir el clúster de
kind
(make infra-up
ymake infra-down
). - Ansible/Makefile: Se encarga de desplegar y configurar las dependencias de la aplicación (PostgreSQL, RabbitMQ) sobre el clúster ya existente (
make deploy-services-local
ymake configure
). - Helm/Makefile: Se encarga de desplegar la aplicación en sí (
make app-deploy
).
Alternativa actual: Clúster local con kind (solo Docker)
Hemos habilitado una alternativa más reproducible para el entorno local usando kind
(Kubernetes in Docker) mediante un proveedor de Terraform. Esto evita depender de binarios locales externos y funciona en CI/DC siempre que haya Docker.
- Proveedor:
tehcyx/kind
- Módulo:
infra/terraform/modules/local-k8s
- Entorno:
infra/terraform/environments/dev/local-k8s
Provisionar el clúster
make infra-up
Este objetivo ejecuta Terragrunt para crear el recurso kind_cluster
. El kubeconfig se genera en:
infra/terraform/modules/local-k8s/kubeconfig
Para usar kubectl
con este clúster:
export KUBECONFIG=infra/terraform/modules/local-k8s/kubeconfig
kubectl cluster-info
kubectl get nodes
Notas tras aplicar (make infra-up
)
Al finalizar make infra-up
, verás una guía rápida en consola. Resumen:
# Kubeconfig local generado por Terraform
export KUBECONFIG=infra/terraform/modules/local-k8s/kubeconfig
# Validación rápida
kubectl cluster-info
kubectl get nodes
Destruir y limpiar completamente
El objetivo infra-down
ha sido reforzado para limpiar residuos de kind y k3d incluso si las CLIs no están instaladas:
make infra-down
Este objetivo ejecuta:
terragrunt destroy
del componente local-k8s.- Eliminación del clúster kind por CLI (si está disponible) o directamente vía Docker por label
io.x-k8s.kind.cluster
y la redkind
. - Eliminación del kubeconfig generado en
infra/terraform/modules/local-k8s/kubeconfig
. - Eliminación de la red
k3d-<cluster>
si hubiese sido creada previamente.
Con esto, evitarás conflictos al cambiar entre motores locales o al reprovisionar el clúster con el mismo nombre.
Pasos del Flujo de Trabajo
-
Provisionar el Clúster (una sola vez):
make infra-up
-
Desplegar los Servicios Base:
make deploy-services-local
-
Configurar los Servicios:
make configure
-
Desplegar la Aplicación:
make app-deploy
-
Destruir el Clúster: Cuando termines, puedes eliminar el clúster de Minikube.
make infra-down
Testing de Recursos Kubernetes
Pruebas Locales
# Verificar despliegue
kubectl get deployment todo-backend
# Verificar logs
kubectl logs deployment/todo-backend
# Ejecutar prueba de integración
kubectl run test --image=curlimages/curl --rm -it -- \
curl http://todo-backend:8000/health
Pruebas en Pipeline
kubernetes_test:
stage: test
image: bitnami/kubectl:latest
script:
- kubectl apply -f kubernetes/manifests/test-pod.yaml
- kubectl wait --for=condition=complete job/test-job --timeout=60s
- kubectl logs job/test-job
Convenciones de Nombrado
Seguimos estas convenciones para consistencia:
Tipo Recurso | Convención | Ejemplo |
---|---|---|
S3 | <app>-<env>-<purpose> |
todo-app-dev-uploads |
K8s | <app>-<component> |
todo-backend |
PostgreSQL | <app>-<env>-<db> |
todo-dev-db |
Troubleshooting Ampliado
Problema: Cambios no se reflejan en LocalStack
- Solución:
- Reiniciar contenedor:
docker compose restart localstack
- Limpiar volumenes:
docker compose down -v
- Volver a aplicar:
terragrunt apply -auto-approve
- Reiniciar contenedor:
Problema: Timeouts en CI/CD
- Solución:
- Aumentar timeout en pipeline
- Verificar disponibilidad de servicios
- Usar caché de dependencias
Problema: Diferencias entre LocalStack y AWS real
- Solución:
- Usar versión más reciente de LocalStack
- Verificar soporte de servicios
- Implementar pruebas de integración reales en entorno lab
Limitaciones de LocalStack y Alcance de Pruebas
LocalStack Community no emula todos los servicios de AWS. En particular, el API de EKS
devuelve 501 Not Implemented
en la edición Community. Referencia: LocalStack Coverage.
- Durante las pruebas automáticas locales y de CI, ejecutamos únicamente los componentes soportados por LocalStack. En este repositorio, eso significa:
lab/network
: Sí se valida, planifica y aplica contra LocalStack.lab/eks
: Se excluye de las pruebas en LocalStack (o se usan entradas mock solo para validación). El despliegue real de EKS se prueba en entornoslab
/prod
con AWS real.
El script scripts/run-infra-tests.sh
ya implementa estas decisiones:
- Trazas detalladas:
set -Eeuo pipefail
,set -x
con timestamps ytrap
de errores muestran la línea exacta donde se detiene. - Diagnóstico rápido: imprime versiones de
terragrunt
,terraform
,docker
y si existeaws
CLI. - Ejecución acotada: ejecuta
init
,validate
,apply
youtput
exclusivamente dentro deinfra/terraform/environments/lab/network
. - Backend local para pruebas: se fuerza backend local mediante bloques
generate
yremote_state
enlab
para evitar dependencias de S3/Dynamo.
Ejemplo de CI actualizado (solo módulos soportados)
infra_test:
stage: test
image: hashicorp/terraform:latest
services:
- name: localstack/localstack:latest
alias: localstack
variables:
AWS_ACCESS_KEY_ID: "test"
AWS_SECRET_ACCESS_KEY: "test"
AWS_DEFAULT_REGION: "eu-central-1"
AWS_ENDPOINT_URL: "http://localstack:4566"
script:
# Ejecuta pruebas solo para 'lab/network' (LocalStack Community soporta EC2/VPC/Subnets)
- cd infra/terraform/environments/lab/network
- terragrunt init -input=false
- terragrunt validate -input=false
- terragrunt apply -auto-approve -input=false
- terragrunt output -json > ../../../../infra-test-outputs.json
artifacts:
when: always
paths:
- infra-test-outputs.json
expire_in: 1 week
Nota: Para
lab/eks
, useterragrunt validate
/plan
con valores mock si desea validar wiring, o pruebe el despliegue real en un entornolab
con AWS.
Prueba rápida con scripts/run-infra-tests.sh
(Pipeline & Local)
Este script ejecuta pruebas rápidas de infraestructura contra LocalStack, pensado para:
- Validación en CI/CD de los módulos soportados por LocalStack (network).
- Reproducir el mismo flujo en local con un solo comando.
Ruta del script: scripts/run-infra-tests.sh
Prerrequisitos
- Docker y Docker Compose (para levantar LocalStack).
- Terraform y Terragrunt en el PATH.
- Opcional:
awscli-local
(binarioawslocal
) para inspección manual de recursos (ver sección de inspección con awslocal).
Variables de entorno relevantes
AWS_ENDPOINT_URL
(por defectohttp://localhost:4566
).AWS_ACCESS_KEY_ID
yAWS_SECRET_ACCESS_KEY
(se usan valores dummytest
/test
).AWS_DEFAULT_REGION
(nos basamos eneu-central-1
).
Alcance de las pruebas
- Solo se ejecuta el componente
infra/terraform/environments/lab/network
. - LocalStack Community no soporta EKS; por tanto, EKS no se aplica en este flujo.
Ejecución
-
En local (recomendado):
make test-infra
Esto levantará LocalStack (vía docker compose), esperará a que esté listo y ejecutará
init
,validate
,apply
youtput
enlab/network
. -
En CI/CD (GitLab), el job
infra_test
de ejemplo llama a este script directamente:script: - ./scripts/run-infra-tests.sh
Salidas
- El script genera
infra-test-outputs.json
en la raíz del repo con los outputs del módulonetwork
(vpc_id
,public_subnet_ids
,private_subnet_ids
, …). Puedes usarawslocal
yjq
para cruzar estos valores con los recursos en LocalStack.
Trazas y robustez
- El script activa trazas detalladas con timestamps (
set -Eeuo pipefail
,set -x
,trap
de errores) y limpia recursivamente cachés.terragrunt-cache
antes de ejecutar.
Limitaciones
- EKS no está soportado en LocalStack Community (retorna 501). Para EKS, limitarse a
validate/plan
con valores mock en LocalStack o validar con AWS real en los entornoslab
/prod
.