Burricornio Dev

Ingeniería de software sin humo: sistemas, código y aprendizajes en abierto.

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

  1. Docker y Docker Compose:

  2. 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/
    
  3. 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 habilites eks 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.

Nota: Si no usas awslocal, puedes utilizar aws 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, usa terraform validate/plan con valores mock o valida en AWS real en entornos lab/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 job infra_deploy_lab, por ejemplo, solo se ejecutará si se cumplen dos condiciones: el commit está en la rama main y se han modificado ficheros dentro del entorno lab 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:

Ventajas de este enfoque:

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:

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.

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:

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

  1. Provisionar el Clúster (una sola vez):

    make infra-up
    
  2. Desplegar los Servicios Base:

    make deploy-services-local
    
  3. Configurar los Servicios:

    make configure
    
  4. Desplegar la Aplicación:

    make app-deploy
    
  5. 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

Problema: Timeouts en CI/CD

Problema: Diferencias entre LocalStack y AWS real

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.

El script scripts/run-infra-tests.sh ya implementa estas decisiones:

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, use terragrunt validate/plan con valores mock si desea validar wiring, o pruebe el despliegue real en un entorno lab 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:

Ruta del script: scripts/run-infra-tests.sh

Prerrequisitos

Variables de entorno relevantes

Alcance de las pruebas

Ejecución

Salidas

Trazas y robustez

Limitaciones