> ## Documentation Index
> Fetch the complete documentation index at: https://ai-kb.automationanywhere.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Guía de Despliegue con Terragrunt

> Step-by-step commands for deploying the complete EKB EKS infrastructure using Terragrunt, including dry run validation and actual deployment.

Esta guía describe el despliegue completo de la infraestructura EKB EKS en AWS usando Terragrunt. Cubre la instalación de herramientas, configuración del entorno y una secuencia de despliegue por fases diseñada para garantizar el orden correcto de dependencias entre todos los componentes de infraestructura.

Los despliegues se organizan en nueve fases:

1. **Gestión de Estado** — Inicializa el bucket S3 usado para almacenar el estado de Terraform del entorno.
2. **Infraestructura EKS** — Provisiona la VPC, subredes, NAT gateways, roles IAM y el cluster EKS con grupos de nodos administrados.
3. **Almacenamiento y Balanceo de Carga** — Despliega el controlador EBS CSI para volúmenes persistentes y el AWS Load Balancer Controller para el ingreso ALB.
4. **Autoescalado con Karpenter** — Configura el provisionamiento dinámico de nodos con soporte para instancias Spot y manejo de interrupciones via SQS y EventBridge.
5. **Autoescalado con KEDA** — Despliega KEDA para el autoescalado a nivel de pod basado en umbrales de CPU y memoria.
6. **Servicios de Datos** — Provisiona Supabase (autoalojado o Cloud), ElastiCache Redis y Amazon MQ RabbitMQ.
7. **Servicios Odin** — Despliega el stack de aplicación EKB (Web, FastAPI, Celery, Automator) vía Helm.
8. **Observabilidad con SigNoz** — Despliega la traza distribuida, métricas y agregación de logs a través de SigNoz y el agente k8s-infra.
9. **Despliegue Final** — Ejecuta un `terragrunt apply` completo para reconciliar los recursos restantes.

Antes de comenzar, complete la lista de prerrequisitos con el cliente y asegúrese de que todos los placeholders `<YOUR_*>` en la plantilla del entorno estén completados. Algunos valores, incluyendo el ID de VPC, el endpoint del cluster EKS y los endpoints de Redis y RabbitMQ, solo están disponibles después de fases específicas, por lo que la guía indica exactamente cuándo capturarlos y aplicarlos.

***

## Prerrequisitos

* AWS CLI configurada con los permisos apropiados
* Terraform (>= 1.0)
* Terragrunt (última versión)
* `kubectl` para la gestión de Kubernetes
* `helm` para la gestión de charts Helm

***

## Guía de Instalación

### Instalando Terragrunt

**macOS (Homebrew)**

```bash theme={null}
brew install terragrunt
```

**Linux (apt)**

```bash theme={null}
# Agregar clave GPG de HashiCorp
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
 
# Agregar repositorio de HashiCorp
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
 
# Actualizar e instalar
sudo apt update
sudo apt install terragrunt
```

**Windows (Chocolatey)**

```bash theme={null}
choco install terragrunt
```

### Instalando kubectl

**macOS (Homebrew)**

```bash theme={null}
brew install kubectl
```

**Linux**

```bash theme={null}
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
```

**Windows (Chocolatey)**

```bash theme={null}
choco install kubernetes-cli
```

### Instalando Helm

**macOS (Homebrew)**

```bash theme={null}
brew install helm
```

**Linux**

```bash theme={null}
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
```

**Windows (Chocolatey)**

```bash theme={null}
choco install kubernetes-helm
```

### Verificando la Instalación

```bash theme={null}
terragrunt --version
terraform --version
kubectl version --client
helm version
```

### Configuración de AWS CLI

```bash theme={null}
# Instalar AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
 
# Configurar credenciales de AWS
aws configure
 
# Verificar configuración
aws sts get-caller-identity
```

***

## Creando un Nuevo Entorno

### Paso 1: Copie la Plantilla del Entorno

La carpeta `env-template-folder` contiene archivos pre-estructurados con placeholders `<YOUR_*>` listos para ser completados. Cópiala completamente para crear su nueva carpeta de entorno.

```bash theme={null}
# Navegar al directorio de entornos de terragrunt
cd terragrunt/environments
 
# Copiar la carpeta completa de la plantilla a un nuevo entorno (reemplazar 'your-env-name')
cp -r env-template-folder your-env-name
 
# La estructura de carpetas está lista:
# your-env-name/
# ├── terragrunt.hcl               # Configuración central del cluster
# ├── state/
# │   └── terragrunt.hcl           # Configuración del bucket de estado S3
# └── values/
#     ├── infrastructure.yaml      # AWS Load Balancer Controller
#     ├── karpenter-values.yaml    # Configuraciones del controlador Karpenter
#     ├── karpenter-nodeclasses.yaml  # Definiciones de EC2NodeClass
#     ├── karpenter.yaml           # Definiciones de Karpenter NodePool
#     ├── keda.yaml                # KEDA autoscaler
#     ├── aws-ebs-csi-driver.yaml  # Controlador EBS CSI
#     ├── odin-services.yaml       # Servicios de aplicación Odin
#     ├── supabase.yaml            # Supabase (si se autoaloja)
#     ├── ha-supabase-db.yaml      # DB HA de Supabase (si se autoaloja)
#     ├── cloudnative-pg.yaml      # Operador CloudNativePG (si se autoaloja)
#     ├── signoz.yaml              # Observabilidad SigNoz (opcional)
#     └── signoz-k8s-infra.yaml    # Métricas k8s de SigNoz (opcional)
```

### Paso 2: Verifique que Todos los Placeholders Estén Presentes

```bash theme={null}
cd your-env-name
 
# Listar todos los placeholders que necesitan ser completados
grep -r "<YOUR_" . --include="*.hcl" --include="*.yaml" | sort
```

Todos los placeholders siguen la convención `<YOUR_*>`. Los pasos a continuación describen cómo completarlos archivo por archivo.

### Paso 3: Provisione Certificados SSL (AWS ACM)

Antes de establecer las variables de entorno necesita los ARNs de los certificados. Use la Consola de AWS para solicitar certificados SSL en AWS Certificate Manager (ACM) para todos los dominios que servirá su entorno.

**Opción A: Un solo certificado wildcard (recomendado)**

Un solo certificado wildcard cubre todos los subdominios con un solo ARN. Por ejemplo, si su dominio base es `app.example.com`, un solo certificado `*.app.example.com` cubre:

| Servicio  | Dominio                       |
| --------- | ----------------------------- |
| Web       | `app.example.com`             |
| FastAPI   | `api-app.example.com`         |
| Automator | `automations-app.example.com` |
| Supabase  | `supabase-app.example.com`    |
| SigNoz    | `signoz-app.example.com`      |

**Opción B: Certificados por servicio**

Solicite un certificado por dominio si no puede usar un wildcard. Repita los pasos a continuación para cada dominio: `<YOUR_WEB_DOMAIN>`, `<YOUR_API_DOMAIN>`, `<YOUR_AUTOMATOR_DOMAIN>`, `<YOUR_SUPABASE_DOMAIN>` (solo si `ENABLE_SUPABASE=true`), `<YOUR_SIGNOZ_DOMAIN>` (solo si `ENABLE_SIGNOZ=true`).

**Solicitando un certificado en la Consola de AWS**

1. Abra la consola de AWS Certificate Manager
2. Cambie a la región correcta (arriba a la derecha) — debe coincidir con `<YOUR_AWS_REGION>`
3. Haga clic en **Solicitar un certificado** → **Solicitar un certificado público** → **Siguiente**
4. Debajo de **Nombre de dominio completo**, ingrese el wildcard (ej., `*.app.example.com`) o un dominio específico
5. Establezca el **Método de validación** en **Validación por DNS**
6. Haga clic en **Solicitar** — el certificado se crea en estado `Pending validation`

**Agregando el registro CNAME de validación DNS**

ACM genera un registro CNAME que debe agregar a su proveedor de DNS para demostrar la propiedad del dominio. Obtenga los valores de la Consola de ACM abriendo el certificado y expandiendo el dominio bajo **Dominios**.

| Campo DNS        | Valor                                                                    |
| ---------------- | ------------------------------------------------------------------------ |
| Tipo de registro | `CNAME`                                                                  |
| Nombre / Host    | ej., `_fa187f22ac17bce6f508bf3c56439c61.signoz-app.example.com.`         |
| Valor / Apunta a | ej., `_c7c97325fe38061e168e232d122c7ff3.jkddzztszm.acm-validations.aws.` |

<Info>
  Incluya el punto final (`.`) al final de los valores CNAME si su proveedor de DNS lo requiere.
</Info>

**Cloudflare**

1. Inicie sesión en Cloudflare → seleccione su dominio → vaya a **DNS** → **Records** → **Add record**
2. Establezca el **Tipo** en `CNAME`
3. Pegue el nombre CNAME de ACM en **Name** y el valor CNAME de ACM en **Target**
4. Establezca el **Estado del proxy** en **DNS only** (icono de nube gris) — el certificado no se validará a través del proxy de Cloudflare
5. Haga clic en **Save**

**Route 53**

1. Abra la consola de Route 53 → **Hosted zones** → seleccione su zona → **Create record**
2. Establezca el **Tipo de registro** en `CNAME`
3. Pegue el nombre CNAME de ACM en **Record name** (solo la parte del subdominio) y el valor en **Value**
4. Establezca TTL en `300` y haga clic en **Create records**

<Tip>
  En ACM también puede hacer clic en **Create records in Route 53** para que ACM agregue el registro automáticamente si la hosted zone está en la misma cuenta.
</Tip>

Una vez que el DNS se propague (típicamente 1–5 minutos), el estado del certificado cambia a **Issued**. Copie el ARN desde la parte superior del certificado: se ve como `arn:aws:acm:<region>:<account-id>:certificate/<uuid>`.

### Paso 4: Establezca las Variables de Entorno

Establezca estas variables de entorno del shell antes de ejecutar cualquier comando de Terragrunt. Son leídas directamente por `terragrunt.hcl` a través de `get_env()`.

```bash theme={null}
export AWS_REGION="<YOUR_AWS_REGION>"          # ej., "eu-west-2", "us-east-2"
export CLUSTER_NAME="<env_folder_name>"          # ej., "env-template-folder"
 
# Configuración de dominios
export WEB_DOMAIN="<YOUR_WEB_DOMAIN>"                    # ej., "app.example.com"
export FASTAPI_DOMAIN="<YOUR_API_DOMAIN>"                # ej., "api-app.example.com"
export AUTOMATOR_DOMAIN="<YOUR_AUTOMATOR_DOMAIN>"        # ej., "automations-app.example.com"
export SUPABASE_DOMAIN="<YOUR_SUPABASE_DOMAIN>"          # ej., "supabase-app.example.com"
export SIGNOZ_DOMAIN="<YOUR_SIGNOZ_DOMAIN>"              # ej., "signoz-app.example.com"
 
# ARNs de certificados SSL — Opción A: Un solo certificado wildcard (recomendado)
export WILDCARD_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_WILDCARD_CERT_ID>"
 
# ARNs de certificados SSL — Opción B: Certificados por servicio
export WEB_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_WEB_CERT_ID>"
export FASTAPI_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_API_CERT_ID>"
export AUTOMATOR_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_AUTOMATOR_CERT_ID>"
export SUPABASE_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_SUPABASE_CERT_ID>"
export SIGNOZ_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_SIGNOZ_CERT_ID>"
 
# Banderas de habilitación de servicios
export ENABLE_ALB_CONTROLLER="true"
export ENABLE_AWS_SERVICES="true"      # Establecer en "true" para habilitar ElastiCache y AmazonMQ
 
# Stack de Supabase (autoalojado) — habilitar los tres juntos si se autoaloja Supabase
export ENABLE_CNPG="true"             # Operador CloudNativePG  (namespace: cnpg-system)
export ENABLE_HA_SUPABASE_DB="true"   # DB HA de Supabase      (namespace: ha-supabase-db)
export ENABLE_SUPABASE="true"         # Aplicación Supabase     (namespace: supabase)
 
export ENABLE_SIGNOZ="true"           # Establecer en "true" para habilitar observabilidad SigNoz
export SSL_TERMINATION="alb"
```

#### Instancias Spot y Cargas de Trabajo con Estado

Las instancias Spot se configuran por NodePool en `values/karpenter.yaml`, no a través de variables de entorno. Cada NodePool declara su propia estrategia de capacidad:

| NodePool            | Etiqueta `workload-type`                     | Tipo de Capacidad            | Justificación                                                                    |
| ------------------- | -------------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------- |
| `general`           | `general`                                    | Spot → respaldo bajo demanda | Optimización de costos para cargas de trabajo por lotes/segundo plano sin estado |
| `compute-intensive` | `compute-intensive`                          | Spot → respaldo bajo demanda | Optimización de costos para cargas de trabajo limitadas por CPU                  |
| `memory-intensive`  | `memory-intensive`                           | Bajo demanda → respaldo Spot | Estabilidad priorizada para pods de alta memoria                                 |
| `gpu`               | `gpu`                                        | Spot → respaldo bajo demanda | Optimización de costos para cargas de trabajo de IA/ML por lotes                 |
| `application`       | `application`                                | Solo bajo demanda            | Servicios de usuario estables (Supabase, Kong, etc.) — sin interrupciones Spot   |
| `database`          | `database` / `node-type: database-dedicated` | Solo bajo demanda            | Con estado — la interrupción Spot es insegura para bases de datos                |

El NodePool `application` usa familias de instancias m/c (generación 5+) con solo bajo demanda. Los pods del servicio Supabase se fijan aquí mediante `nodeSelector: workload-type: "application"` para garantizar que nunca sean interrumpidos por un evento de recuperación Spot.

El NodePool `database-dedicated` nunca usa Spot. Usa `consolidationPolicy: WhenEmpty` para que Karpenter no evict un nodo que aún tiene un pod ejecutándose, lo que lo hace seguro para cargas de trabajo con estado como PostgreSQL y réplicas de CloudNativePG.

**Pautas para aplicaciones con estado en Spot:**

* No programe bases de datos, colas persistentes o cualquier pod con un `PersistentVolumeClaim` en NodePools Spot.
* Use un `nodeSelector` dirigido a `node-type: database-dedicated` con la tolerancia `database-workload: "true"` correspondiente para pods de bases de datos.
* Use `nodeSelector: workload-type: "application"` para servicios de usuario sin estado que deben permanecer disponibles sin interrupciones.
* Para cargas de trabajo de segundo plano (Web, API, Celery, Automator), el NodePool Spot `general` es apropiado — el manejador de interrupciones SQS de Karpenter drena los nodos Spot graciosamente antes de que AWS los recupere, y el conteo mínimo de réplicas de KEDA (≥ 2) garantiza disponibilidad durante la sustitución de nodos.
* Para deshabilitar Spot globalmente, elimine `"spot"` de la lista de valores en cada NodePool dentro de `values/karpenter.yaml`.

**Cómo Karpenter maneja las alertas de interrupción Spot:**

AWS proporciona un aviso de interrupción de 2 minutos antes de terminar una instancia Spot. Karpenter usa EventBridge y SQS para actuar automáticamente:

```
Evento de Interrupción Spot de AWS
        │
        ▼
Amazon EventBridge (CloudWatch Events)
  Regla: EC2 Spot Instance Interruption Warning
        │
        ▼
   Cola SQS (cola de interrupción de Karpenter)
        │
        ▼
  Controlador Karpenter (encuesta SQS continuamente)
        │
        ├── Cordon el nodo (no se programan nuevos pods)
        ├── Drena pods existentes (respeta PodDisruptionBudgets)
        ├── Provisiona un nodo de reemplazo en paralelo
        └── Los pods se reprograman en el nuevo nodo antes de que se cierre la ventana de 2 min
```

Esto se configura en el bloque `karpenter` en `terragrunt.hcl`:

```hcl theme={null}
karpenter = {
  spot_interruption_handling = true   # crea la cola SQS y la regla de EventBridge
  enable_spot_instances       = true   # permite Spot en los requisitos de capacidad de NodePool
}
```

### Paso 5: Actualice los Valores Específicos del Entorno

Realice una búsqueda y reemplazo en todos los archivos de su nueva carpeta de entorno para los siguientes placeholders:

| Placeholder             | Descripción                       | Ejemplo                  |
| ----------------------- | --------------------------------- | ------------------------ |
| `<YOUR_ENV_NAME>`       | Identificador único del entorno   | `app-eks-prod`           |
| `<YOUR_AWS_REGION>`     | Región de AWS del cluster         | `eu-west-2`, `us-east-2` |
| `<YOUR_AWS_ACCOUNT_ID>` | ID de cuenta de AWS de 12 dígitos | `123456789012`           |
| `<YOUR_ENVIRONMENT>`    | Valor de etiqueta de entorno      | `prod`, `staging`, `dev` |
| `<YOUR_PROJECT>`        | Valor de etiqueta de proyecto     | `odin`, `ekb`            |

```bash theme={null}
# Ejecutar desde su nueva carpeta de entorno para encontrar todos los placeholders restantes
grep -r "<YOUR_" .
```

#### 5.1 `terragrunt.hcl` — Configuración central del cluster

```bash theme={null}
nano terragrunt.hcl
```

| Campo                                      | Placeholder                | Notas                                                                                      |
| ------------------------------------------ | -------------------------- | ------------------------------------------------------------------------------------------ |
| `cluster_name`                             | `<YOUR_ENV_NAME>`          | Debe coincidir con el nombre del cluster EKS                                               |
| `cluster_region`                           | `<YOUR_AWS_REGION>`        | Región de AWS                                                                              |
| `aws_account_id`                           | `<YOUR_AWS_ACCOUNT_ID>`    | ID de cuenta de 12 dígitos                                                                 |
| `vpc_cidr`                                 | `<YOUR_VPC_CIDR>`          | ej., `192.168.0.0/16`                                                                      |
| `availability_zones`                       | `<YOUR_REGION>a/b/c`       | 3 AZs en su región                                                                         |
| `tags.Environment`                         | `<YOUR_ENVIRONMENT>`       | ej., `prod`                                                                                |
| `tags.Project`                             | `<YOUR_PROJECT>`           | ej., `odin`                                                                                |
| `aws_services.amazon_mq.rabbitmq.username` | `<YOUR_RABBITMQ_USERNAME>` | Nombre de usuario admin de RabbitMQ (solo cuando `ENABLE_AWS_SERVICES=true`)               |
| `aws_services.amazon_mq.rabbitmq.password` | `<YOUR_RABBITMQ_PASSWORD>` | Mínimo 12 caracteres; debe incluir mayúsculas, minúsculas, dígitos y caracteres especiales |

#### 5.2 `state/terragrunt.hcl` — Bucket de estado S3

```bash theme={null}
nano state/terragrunt.hcl
```

| Campo         | Placeholder                            | Notas                       |
| ------------- | -------------------------------------- | --------------------------- |
| `bucket_name` | `odin-terraform-state-<YOUR_ENV_NAME>` | Debe ser único globalmente  |
| `region`      | `<YOUR_AWS_REGION>`                    | Misma región que el cluster |

#### 5.3 `values/infrastructure.yaml` — AWS Load Balancer Controller

<Warning>
  Obtenga el ID de la VPC **después** de que se cree el cluster EKS antes de desplegar el AWS Load Balancer Controller.
</Warning>

```bash theme={null}
# Obtener ID de VPC después de crear el cluster EKS
aws eks describe-cluster --name <YOUR_ENV_NAME> \
  --query "cluster.resourcesVpcConfig.vpcId" --output text
```

```bash theme={null}
nano values/infrastructure.yaml
```

| Campo                                                   | Placeholder                                | Notas                              |
| ------------------------------------------------------- | ------------------------------------------ | ---------------------------------- |
| `clusterName`                                           | `<YOUR_ENV_NAME>`                          | Nombre del cluster EKS             |
| `region`                                                | `<YOUR_AWS_REGION>`                        | Región de AWS                      |
| `vpcId`                                                 | `<YOUR_VPC_ID>`                            | Requerido antes del despliegue ALB |
| `serviceAccount.annotations.eks.amazonaws.com/role-arn` | `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENV_NAME>` | Rol IAM para el controlador ALB    |

#### 5.4 `values/karpenter-values.yaml` — Controlador Karpenter

<Warning>
  Obtenga el endpoint del cluster EKS **después** de que se cree el cluster EKS y antes de desplegar Karpenter.
</Warning>

```bash theme={null}
# Obtener endpoint del cluster después de crear el cluster EKS
aws eks describe-cluster --name <YOUR_ENV_NAME> \
  --query "cluster.endpoint" --output text
```

```bash theme={null}
nano values/karpenter-values.yaml
```

| Campo                                                   | Placeholder                                | Notas                                    |
| ------------------------------------------------------- | ------------------------------------------ | ---------------------------------------- |
| `serviceAccount.annotations.eks.amazonaws.com/role-arn` | `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENV_NAME>` | Rol IAM para Karpenter                   |
| `env.CLUSTER_NAME`                                      | `<YOUR_ENV_NAME>`                          | Nombre del cluster EKS                   |
| `env.CLUSTER_ENDPOINT`                                  | `<YOUR_EKS_CLUSTER_ENDPOINT>`              | Requerido antes del despliegue Karpenter |
| `settings.aws.defaultInstanceProfile`                   | `<YOUR_ENV_NAME>`                          | Perfil de instancia de nodos Karpenter   |

#### 5.5 `values/karpenter-nodeclasses.yaml` — Clases de nodos Karpenter

```bash theme={null}
nano values/karpenter-nodeclasses.yaml
```

| Campo                                                       | Placeholder          | Notas                                            |
| ----------------------------------------------------------- | -------------------- | ------------------------------------------------ |
| Todas las etiquetas `kubernetes.io/cluster/<YOUR_ENV_NAME>` | `<YOUR_ENV_NAME>`    | Etiqueta de cluster para selectores de subred/SG |
| Nombre de cluster de bootstrap en `user_data`               | `<YOUR_ENV_NAME>`    | Script de bootstrap del nodo                     |
| `tags.Environment`                                          | `<YOUR_ENVIRONMENT>` | ej., `prod`                                      |
| `tags.Project`                                              | `<YOUR_PROJECT>`     | ej., `odin`                                      |

#### 5.6 `values/aws-ebs-csi-driver.yaml` — Controlador EBS CSI

```bash theme={null}
nano values/aws-ebs-csi-driver.yaml
```

| Campo                                                              | Placeholder                                | Notas                               |
| ------------------------------------------------------------------ | ------------------------------------------ | ----------------------------------- |
| `controller.serviceAccount.annotations.eks.amazonaws.com/role-arn` | `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENV_NAME>` | Rol IAM para el controlador EBS CSI |
| `node.serviceAccount.annotations.eks.amazonaws.com/role-arn`       | `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENV_NAME>` | Rol IAM para el nodo EBS CSI        |
| `controller.env.AWS_DEFAULT_REGION`                                | `<YOUR_AWS_REGION>`                        | Región de AWS                       |
| `controller.env.AWS_REGION`                                        | `<YOUR_AWS_REGION>`                        | Región de AWS                       |
| `node.env.AWS_DEFAULT_REGION`                                      | `<YOUR_AWS_REGION>`                        | Región de AWS                       |
| `node.env.AWS_REGION`                                              | `<YOUR_AWS_REGION>`                        | Región de AWS                       |

#### 5.7 `values/karpenter.yaml` — Karpenter NodePools

```bash theme={null}
nano values/karpenter.yaml
```

| Campo                                        | Placeholder                                              | Notas                                      |
| -------------------------------------------- | -------------------------------------------------------- | ------------------------------------------ |
| `*.labels.Environment`                       | `<YOUR_ENVIRONMENT>`                                     | Aplicado a todas las etiquetas de NodePool |
| `*.requirements topology.kubernetes.io/zone` | `["<YOUR_REGION>a", "<YOUR_REGION>b", "<YOUR_REGION>c"]` | AZs para todos los NodePools               |

Los nombres de clases de nodo (`general`, `compute-intensive`, `memory-intensive`, `gpu`, `database`) deben coincidir con las entradas en `karpenter-nodeclasses.yaml`.

#### 5.8 `values/keda.yaml` — KEDA Autoscaler

No se requieren placeholders específicos del entorno. Los límites de recursos y los conteos de réplicas están preconfigurados con valores predeterminados razonables. Revise y ajuste si es necesario.

#### 5.9 `values/supabase.yaml` — Aplicación Supabase (solo si `ENABLE_SUPABASE=true`)

<Warning>
  Todas las claves a continuación deben generarse de forma consistente y compartirse con `ha-supabase-db.yaml`. Generarlas una vez y usar los mismos valores en ambos archivos.
</Warning>

```bash theme={null}
# Generar secreto JWT
openssl rand -hex 32
 
# Generar JWTs de anon/service role (requiere Supabase CLI)
brew install supabase/tap/supabase
supabase gen-keys
 
# Generar contraseñas y tokens
openssl rand -hex 24       # para contraseñas
openssl rand -base64 64    # para secretKeyBase
```

```bash theme={null}
nano values/supabase.yaml
```

| Campo                                 | Placeholder                                | Notas                                                               |
| ------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------- |
| `secret.jwt.anonKey`                  | `<YOUR_SUPABASE_ANON_KEY>`                 | Debe coincidir con `anonKey` en `ha-supabase-db.yaml`               |
| `secret.jwt.serviceKey`               | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>`         | Debe coincidir con `serviceRoleKey` en `ha-supabase-db.yaml`        |
| `secret.jwt.secret`                   | `<YOUR_SUPABASE_JWT_SECRET>`               | Debe coincidir con `jwtSecret` en `ha-supabase-db.yaml`             |
| `secret.db.password`                  | `<YOUR_SUPABASE_DB_PASSWORD>`              | Debe coincidir con `postgresPassword` en `ha-supabase-db.yaml`      |
| `secret.analytics.publicAccessToken`  | `<YOUR_SUPABASE_ANALYTICS_PUBLIC_TOKEN>`   | Token interno de Logflare                                           |
| `secret.analytics.privateAccessToken` | `<YOUR_SUPABASE_ANALYTICS_PRIVATE_TOKEN>`  | Token interno de Logflare                                           |
| `secret.dashboard.username`           | `<YOUR_SUPABASE_DASHBOARD_USERNAME>`       | Acceso al UI de Studio                                              |
| `secret.dashboard.password`           | `<YOUR_SUPABASE_DASHBOARD_PASSWORD>`       | Acceso al UI de Studio                                              |
| `secret.realtime.secretKeyBase`       | `<YOUR_SUPABASE_REALTIME_SECRET_KEY_BASE>` | Clave secreta Phoenix                                               |
| `secret.meta.cryptoKey`               | `<YOUR_SUPABASE_META_CRYPTO_KEY>`          | `openssl rand -hex 32`                                              |
| `secret.s3.keyId`                     | `<YOUR_MINIO_KEY_ID>`                      | Debe coincidir con `secret.minio.user` (`openssl rand -hex 16`)     |
| `secret.s3.accessKey`                 | `<YOUR_MINIO_ACCESS_KEY>`                  | Debe coincidir con `secret.minio.password` (`openssl rand -hex 32`) |
| `secret.minio.user`                   | `<YOUR_MINIO_KEY_ID>`                      | Mismo valor que `secret.s3.keyId`                                   |
| `secret.minio.password`               | `<YOUR_MINIO_ACCESS_KEY>`                  | Mismo valor que `secret.s3.accessKey`                               |

#### 5.10 `values/ha-supabase-db.yaml` — DB HA de Supabase (solo si `ENABLE_HA_SUPABASE_DB=true`)

<Warning>
  Los secretos aquí deben coincidir con `supabase.yaml`. Use los mismos valores generados para `postgresPassword`, `jwtSecret`, `anonKey` y `serviceRoleKey`.
</Warning>

```bash theme={null}
nano values/ha-supabase-db.yaml
```

| Campo                                  | Placeholder                        | Notas                                                         |
| -------------------------------------- | ---------------------------------- | ------------------------------------------------------------- |
| `secrets.inline.postgresPassword`      | `<YOUR_SUPABASE_DB_PASSWORD>`      | Debe coincidir con `secret.db.password` en `supabase.yaml`    |
| `secrets.inline.authenticatorPassword` | `<YOUR_SUPABASE_DB_PASSWORD>`      | Debe ser idéntico a `postgresPassword`                        |
| `secrets.inline.pgbouncerPassword`     | `<YOUR_SUPABASE_DB_PASSWORD>`      | Debe ser idéntico a `postgresPassword`                        |
| `secrets.inline.jwtSecret`             | `<YOUR_SUPABASE_JWT_SECRET>`       | Debe coincidir con `secret.jwt.secret` en `supabase.yaml`     |
| `secrets.inline.anonKey`               | `<YOUR_SUPABASE_ANON_KEY>`         | Debe coincidir con `secret.jwt.anonKey` en `supabase.yaml`    |
| `secrets.inline.serviceRoleKey`        | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Debe coincidir con `secret.jwt.serviceKey` en `supabase.yaml` |

La clase de almacenamiento (`ebs-csi-gp2`), el conteo de instancias y los límites de recursos están preconfigurados. Ajuste `postgres.storage.size` y `postgres.walStorage.size` para su volumen de datos esperado.

#### 5.11 `values/cloudnative-pg.yaml` — Operador CloudNativePG (solo si `ENABLE_CNPG=true`)

No se requieren placeholders específicos del entorno. Esto despliega solo el controlador del operador CNPG. Los valores predeterminados (3 réplicas, límites de recursos) son adecuados para la mayoría de los entornos.

#### 5.12 `values/odin-services.yaml` — Servicios de aplicación Odin

<Warning>
  Los endpoints de Redis y RabbitMQ solo están disponibles después de que Terraform cree esos recursos de AWS. Los ARNs de certificados deben provisionarse en ACM antes del despliegue.
</Warning>

```bash theme={null}
nano values/odin-services.yaml
```

**Configuraciones generales:**

| Campo                  | Placeholder                     | Notas                                                                                                |
| ---------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `server`               | `<YOUR_WEB_DOMAIN>`             | Dominio web principal                                                                                |
| `toolkitEncryptionKey` | `<YOUR_TOOLKIT_ENCRYPTION_KEY>` | Generar: `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` |

**Supabase (`dataServiceConfig`) — autoalojado (`ENABLE_SUPABASE=true`):**

| Campo                        | Placeholder                                                          | Fuente                                               |
| ---------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------- |
| `supabase.projectUrl`        | `http://supabase-kong:8000`                                          | Fijo — Supabase Kong interno                         |
| `supabase.key`               | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>`                                   | Igual que `secret.jwt.serviceKey` en `supabase.yaml` |
| `supabase.postgres.user`     | `postgres`                                                           | Fijo para autoalojado                                |
| `supabase.postgres.host`     | `ha-supabase-db-postgres-pooler-rw.ha-supabase-db.svc.cluster.local` | Fijo — Servicio de Pool de DB dentro del clúster     |
| `supabase.postgres.password` | `<YOUR_SUPABASE_DB_PASSWORD>`                                        | Igual que `secret.db.password` en `supabase.yaml`    |
| `supabase.projectId`         | *(dejar vacío)*                                                      | No se usa en modo autoalojado                        |

**Supabase (`dataServiceConfig`) — Supabase Cloud (`ENABLE_SUPABASE=false`):**

| Campo                        | Placeholder                        | Fuente                                                                         |
| ---------------------------- | ---------------------------------- | ------------------------------------------------------------------------------ |
| `supabase.projectUrl`        | `<YOUR_SUPABASE_PROJECT_URL>`      | Panel de Supabase → Configuración del Proyecto → API                           |
| `supabase.key`               | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Panel de Supabase → API → clave `service_role`                                 |
| `supabase.postgres.user`     | `<YOUR_SUPABASE_DB_USER>`          | Panel de Supabase → Configuración del Proyecto → Base de datos                 |
| `supabase.postgres.host`     | `<YOUR_SUPABASE_DB_HOST>`          | Panel de Supabase → Base de datos (ej., `aws-0-eu-west-2.pooler.supabase.com`) |
| `supabase.postgres.password` | `<YOUR_SUPABASE_DB_PASSWORD>`      | Panel de Supabase → Configuración del Proyecto → Base de datos                 |
| `supabase.projectId`         | `<YOUR_SUPABASE_PROJECT_ID>`       | De la URL de su proyecto Supabase                                              |

**Redis:**

| Campo        | Placeholder                                          | Notas                                     |
| ------------ | ---------------------------------------------------- | ----------------------------------------- |
| `redis.url`  | `rediss://<YOUR_REDIS_HOST>:6379?ssl_cert_reqs=none` | Después de que Terraform cree ElastiCache |
| `redis.host` | `<YOUR_REDIS_HOST>`                                  | Endpoint principal de ElastiCache         |

```bash theme={null}
# Obtener endpoint de Redis después de terraform apply
aws elasticache describe-cache-clusters \
  --show-cache-node-info \
  --query "CacheClusters[?starts_with(CacheClusterId,'<YOUR_ENV_NAME>')].CacheNodes[0].Endpoint.Address" \
  --output text
```

**RabbitMQ:**

| Campo               | Placeholder                                                                           | Notas                                  |
| ------------------- | ------------------------------------------------------------------------------------- | -------------------------------------- |
| `rabbitmq.url`      | `amqps://<YOUR_RABBITMQ_USERNAME>:<YOUR_RABBITMQ_PASSWORD>@<YOUR_RABBITMQ_HOST>:5671` | Después de que Terraform cree AmazonMQ |
| `rabbitmq.host`     | `<YOUR_RABBITMQ_HOST>`                                                                | Endpoint del broker AmazonMQ           |
| `rabbitmq.username` | `<YOUR_RABBITMQ_USERNAME>`                                                            | Establecido en `terragrunt.hcl`        |
| `rabbitmq.password` | `<YOUR_RABBITMQ_PASSWORD>`                                                            | Establecido en `terragrunt.hcl`        |

```bash theme={null}
# Obtener endpoint de RabbitMQ después de terraform apply
aws mq list-brokers \
  --query "BrokerSummaries[?BrokerName=='odin-rabbitmq'].BrokerId" --output text | \
  xargs -I{} aws mq describe-broker --broker-id {} \
  --query "BrokerInstances[0].Endpoints[0]" --output text
```

**SSL / ARNs de Certificados:**

| Campo                                        | Placeholder                        | Notas                              |
| -------------------------------------------- | ---------------------------------- | ---------------------------------- |
| `ssl.services.web.domain`                    | `<YOUR_WEB_DOMAIN>`                | ej., `app.example.com`             |
| `ssl.services.web.certificateArn`            | `<YOUR_WEB_CERTIFICATE_ARN>`       | ARN del certificado ACM            |
| `ssl.services.fastapiBackend.domain`         | `<YOUR_API_DOMAIN>`                | ej., `api-app.example.com`         |
| `ssl.services.fastapiBackend.certificateArn` | `<YOUR_API_CERTIFICATE_ARN>`       | ARN del certificado ACM            |
| `ssl.services.automator.domain`              | `<YOUR_AUTOMATOR_DOMAIN>`          | ej., `automations-app.example.com` |
| `ssl.services.automator.certificateArn`      | `<YOUR_AUTOMATOR_CERTIFICATE_ARN>` | ARN del certificado ACM            |
| `ssl.services.supabase.domain`               | `<YOUR_SUPABASE_DOMAIN>`           | ej., `supabase-app.example.com`    |
| `ssl.services.supabase.certificateArn`       | `<YOUR_SUPABASE_CERTIFICATE_ARN>`  | ARN del certificado ACM            |

```bash theme={null}
# Listar certificados ACM en su región
aws acm list-certificates --region <YOUR_AWS_REGION> \
  --query "CertificateSummaryList[*].[DomainName,CertificateArn]" --output table
```

**Claves Supabase del frontend web — autoalojado (`ENABLE_SUPABASE=true`):**

| Campo                         | Placeholder                        | Fuente                                               |
| ----------------------------- | ---------------------------------- | ---------------------------------------------------- |
| `web.supabase.url`            | `https://<YOUR_SUPABASE_DOMAIN>`   | URL externa enrutada vía ingreso ALB                 |
| `web.supabase.anonKey`        | `<YOUR_SUPABASE_ANON_KEY>`         | Igual que `secret.jwt.anonKey` en `supabase.yaml`    |
| `web.supabase.serviceRoleKey` | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Igual que `secret.jwt.serviceKey` en `supabase.yaml` |
| `web.supabase.clientanonKey`  | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Igual que `secret.jwt.serviceKey` en `supabase.yaml` |

**Claves Supabase del frontend web — Supabase Cloud (`ENABLE_SUPABASE=false`):**

| Campo                         | Placeholder                        | Fuente                                               |
| ----------------------------- | ---------------------------------- | ---------------------------------------------------- |
| `web.supabase.url`            | `<YOUR_SUPABASE_PROJECT_URL>`      | Panel de Supabase → Configuración del Proyecto → API |
| `web.supabase.anonKey`        | `<YOUR_SUPABASE_ANON_KEY>`         | Panel de Supabase → API → clave `anon`               |
| `web.supabase.serviceRoleKey` | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Panel de Supabase → API → clave `service_role`       |
| `web.supabase.clientanonKey`  | `<YOUR_SUPABASE_CLIENT_ANON_KEY>`  | Igual que la clave `service_role`                    |

#### 5.13 `values/signoz.yaml` — Observabilidad SigNoz (solo si `ENABLE_SIGNOZ=true`)

```bash theme={null}
nano values/signoz.yaml
```

| Campo                                                                  | Placeholder                                                                  | Notas                         |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ----------------------------- |
| `global.clusterName`                                                   | `<YOUR_ENV_NAME>`                                                            | Nombre del cluster EKS        |
| `signoz.ingress.annotations.alb.ingress.kubernetes.io/certificate-arn` | `<YOUR_AWS_REGION>`, `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_SIGNOZ_CERTIFICATE_ID>` | Certificado ACM para SigNoz   |
| `signoz.ingress.hosts[0].host`                                         | `<YOUR_SIGNOZ_DOMAIN>`                                                       | ej., `signoz-app.example.com` |

#### 5.14 `values/signoz-k8s-infra.yaml` — Métricas K8s de SigNoz (solo si `ENABLE_SIGNOZ=true`)

```bash theme={null}
nano values/signoz-k8s-infra.yaml
```

| Campo                | Placeholder       | Notas                                              |
| -------------------- | ----------------- | -------------------------------------------------- |
| `global.clusterName` | `<YOUR_ENV_NAME>` | Nombre del cluster EKS para etiquetado de métricas |

El endpoint del colector OTel (`signoz-otel-collector.monitoring.svc.cluster.local:4317`) está preconfigurado asumiendo que tanto SigNoz como k8s-infra se despliegan en el namespace `monitoring`. No se necesita cambio a menos que use un nombre de release personalizado.

#### Recordatorio del Orden de Despliegue

Algunos valores solo están disponibles después de que cierta infraestructura se haya desplegado. Siga este orden:

1. **Antes de cualquier despliegue** — Establecer: `<YOUR_ENV_NAME>`, `<YOUR_AWS_REGION>`, `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENVIRONMENT>`, `<YOUR_PROJECT>`, `<YOUR_VPC_CIDR>`, todos los nombres de dominio, todos los ARNs de certificados, todos los valores de Supabase, `<YOUR_TOOLKIT_ENCRYPTION_KEY>`, usuario/contraseña de RabbitMQ
2. **Después de crear el cluster EKS** — Establecer: `<YOUR_VPC_ID>` (`infrastructure.yaml`), `<YOUR_EKS_CLUSTER_ENDPOINT>` (`karpenter-values.yaml`)
3. **Después de `terraform apply` para servicios AWS** — Establecer: `<YOUR_REDIS_HOST>`, `<YOUR_RABBITMQ_HOST>` (`odin-services.yaml`)

### Paso 6: Verifique que No Queden Placeholders

```bash theme={null}
grep -r "<YOUR_" . --include="*.hcl" --include="*.yaml"
```

La salida esperada debería estar vacía, o contener solo referencias a recursos a punto de ser creados (VPC, Redis, MQ, EKS). Si quedan placeholders, consulte las subsecciones del Paso 5 anteriormente.

**Lista de verificación de archivos:**

| Archivo                             | Paso | Requerido                            |
| ----------------------------------- | ---- | ------------------------------------ |
| `terragrunt.hcl`                    | 5.1  | Siempre                              |
| `state/terragrunt.hcl`              | 5.2  | Siempre                              |
| `values/infrastructure.yaml`        | 5.3  | Siempre                              |
| `values/karpenter-values.yaml`      | 5.4  | Siempre                              |
| `values/karpenter-nodeclasses.yaml` | 5.5  | Siempre                              |
| `values/karpenter.yaml`             | 5.7  | Siempre                              |
| `values/keda.yaml`                  | 5.8  | Siempre                              |
| `values/aws-ebs-csi-driver.yaml`    | 5.6  | Siempre                              |
| `values/odin-services.yaml`         | 5.12 | Siempre                              |
| `values/cloudnative-pg.yaml`        | 5.11 | Solo si `ENABLE_CNPG=true`           |
| `values/ha-supabase-db.yaml`        | 5.10 | Solo si `ENABLE_HA_SUPABASE_DB=true` |
| `values/supabase.yaml`              | 5.9  | Solo si `ENABLE_SUPABASE=true`       |
| `values/signoz.yaml`                | 5.13 | Solo si `ENABLE_SIGNOZ=true`         |
| `values/signoz-k8s-infra.yaml`      | 5.14 | Solo si `ENABLE_SIGNOZ=true`         |

***

## Fase 1: Configuración de Gestión de Estado

**Propósito:** Creación del bucket S3 para el estado de Terraform.

Cada módulo de gestión de estado del entorno crea un bucket S3 con el patrón `odin-terraform-state-{environment-name}`, configura cifrado, versionado y bloqueo de acceso público, y usa estado local para el módulo de estado en sí (patrón de bootstrap).

```bash theme={null}
cd terragrunt/environments/{your-env-name}/state
terragrunt init
terragrunt plan
terragrunt apply
```

***

## Fase 2: Despliegue de Infraestructura EKS

**Propósito:** Red central (VPC, subredes, NAT gateway), roles y políticas IAM, cluster EKS y grupos de nodos administrados.

### 2.1 Ejecución de Prueba — Infraestructura EKS

**Infraestructura Central**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="aws_vpc.main" \
  -target="aws_internet_gateway.main" \
  -target="aws_subnet.public" \
  -target="aws_subnet.private" \
  -target="aws_eip.nat" \
  -target="aws_nat_gateway.main" \
  -target="aws_route_table.public" \
  -target="aws_route_table.private" \
  -target="aws_route_table_association.public" \
  -target="aws_route_table_association.private"
```

**Roles y Políticas IAM**

```bash theme={null}
terragrunt plan -target="aws_iam_role.cluster" \
  -target="aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy" \
  -target="aws_iam_openid_connect_provider.eks" \
  -target="aws_iam_role.node" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly"
```

**Cluster EKS y Grupos de Nodos**

```bash theme={null}
terragrunt plan -target="aws_eks_cluster.main" \
  -target="aws_eks_node_group.main" \
  -target="kubernetes_secret.regcred"
```

#### Usando un Registro Docker Personalizado / Privado

Por defecto, las imágenes de EKB se descargan de Docker Hub usando un secreto llamado `regcred`. Si el cliente aloja imágenes en un registro diferente, siga estos pasos antes de desplegar `odin-services`.

**Paso 1 — Crear el `imagePullSecret` en el namespace destino**

```bash theme={null}
# Registro privado genérico (Docker Hub, Quay, autoalojado, etc.)
kubectl create secret docker-registry regcred \
  --namespace default \
  --docker-server=<YOUR_REGISTRY_HOST> \
  --docker-username=<YOUR_REGISTRY_USERNAME> \
  --docker-password=<YOUR_REGISTRY_PASSWORD> \
  --docker-email=<YOUR_EMAIL>
 
# AWS ECR — el token expira cada 12h; actualizar mediante CronJob o usar la caché de paso de ECR
aws ecr get-login-password --region <YOUR_AWS_REGION> | \
  kubectl create secret docker-registry regcred \
    --namespace default \
    --docker-server=<YOUR_AWS_ACCOUNT_ID>.dkr.ecr.<YOUR_AWS_REGION>.amazonaws.com \
    --docker-username=AWS \
    --docker-password-stdin
```

**Paso 2 — Establecer el nombre del secreto en `values/odin-services.yaml`**

```yaml theme={null}
# values/odin-services.yaml
imagePullSecrets:
  - name: regcred          # debe coincidir con el nombre del secreto creado anteriormente
  # - name: customer-registry-secret  # agregar registros adicionales si es necesario
```

**Paso 3 — Actualizar las referencias de imagen**

```yaml theme={null}
web:
  image: <YOUR_REGISTRY_HOST>/<YOUR_ORG>/web:<TAG>
 
fastapiBackend:
  image: <YOUR_REGISTRY_HOST>/<YOUR_ORG>/server:<TAG>
```

**Paso 4 — Verificar el acceso de descarga antes del despliegue completo**

```bash theme={null}
kubectl run registry-test \
  --image=<YOUR_REGISTRY_HOST>/<YOUR_ORG>/web:<TAG> \
  --overrides='{"spec":{"imagePullSecrets":[{"name":"regcred"}]}}' \
  --restart=Never --rm -it -- echo "Pull successful"
```

### 2.2 Desplegar Infraestructura EKS

**Paso 1: Infraestructura Central**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="aws_vpc.main" \
  -target="aws_internet_gateway.main" \
  -target="aws_subnet.public" \
  -target="aws_subnet.private" \
  -target="aws_eip.nat" \
  -target="aws_nat_gateway.main" \
  -target="aws_route_table.public" \
  -target="aws_route_table.private" \
  -target="aws_route_table_association.public" \
  -target="aws_route_table_association.private"
```

<Warning>
  Después de este paso, actualice `vpcId` en `values/infrastructure.yaml` antes de desplegar el AWS Load Balancer Controller.
</Warning>

**Paso 2: Cluster EKS, Roles y Políticas IAM**

```bash theme={null}
terragrunt apply -target="aws_iam_role.cluster" \
  -target="aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy" \
  -target="aws_iam_openid_connect_provider.eks" \
  -target="aws_iam_role.node" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly"
```

**Paso 3: Grupos de Nodos y Add-ons**

```bash theme={null}
terragrunt apply -target="aws_eks_cluster.main" \
  -target="aws_eks_node_group.main" \
  -target="kubernetes_secret.regcred"
```

<Warning>
  Después de este paso, actualice `CLUSTER_ENDPOINT` en `values/karpenter-values.yaml` antes de desplegar Karpenter.
</Warning>

**Verificar Conectividad del Cluster EKS**

```bash theme={null}
aws eks update-kubeconfig --region $AWS_REGION --name $CLUSTER_NAME
 
kubectl cluster-info
kubectl get nodes
kubectl get secret regcred -n default
```

***

## Fase 3: Almacenamiento y Balanceo de Carga

**Propósito:** Controlador EBS CSI para volúmenes persistentes, AWS Load Balancer Controller ejecutándose en el grupo de nodos administrado.

### 3.1 Ejecución de Prueba — Almacenamiento y Balanceo de Carga

**Controlador EBS CSI**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="aws_iam_role.ebs_csi_driver" \
  -target="aws_iam_role_policy_attachment.ebs_csi_driver" \
  -target="helm_release.ebs_csi_driver"
```

**AWS Load Balancer Controller**

```bash theme={null}
terragrunt plan -target="aws_iam_role.aws_load_balancer_controller" \
  -target="aws_iam_role_policy_attachment.aws_load_balancer_controller" \
  -target="aws_iam_policy.aws_load_balancer_controller" \
  -target="helm_release.infrastructure"
```

### 3.2 Desplegar Almacenamiento y Balanceo de Carga

**Paso 1: Controlador EBS CSI**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="aws_iam_role.ebs_csi_driver" \
  -target="aws_iam_role_policy_attachment.ebs_csi_driver" \
  -target="helm_release.ebs_csi_driver"
```

**Verificación**

```bash theme={null}
helm list -n kube-system | grep ebs
kubectl get pods -n kube-system | grep ebs-csi
kubectl get storageclass
aws iam get-role --role-name $CLUSTER_NAME-ebs-csi-driver-role --region $AWS_REGION
kubectl get sa -n kube-system | grep ebs-csi
kubectl describe sa ebs-csi-controller-sa -n kube-system
```

**Paso 2: AWS Load Balancer Controller**

```bash theme={null}
terragrunt apply -target="aws_iam_role.aws_load_balancer_controller" \
  -target="aws_iam_role_policy_attachment.aws_load_balancer_controller" \
  -target="aws_iam_policy.aws_load_balancer_controller" \
  -target="helm_release.infrastructure"
```

**Verificación**

```bash theme={null}
helm list -n infrastructure
kubectl get pods -n infrastructure | grep aws-load-balancer-controller
kubectl get sa -n infrastructure
kubectl describe sa aws-load-balancer-controller -n infrastructure
aws iam get-role --role-name $CLUSTER_NAME-aws-load-balancer-controller --region $AWS_REGION
kubectl logs -n infrastructure -l app.kubernetes.io/name=aws-load-balancer-controller
kubectl get ingressclass
```

***

## Fase 4: Autoescalado con Karpenter

**Propósito:** Roles IAM para Karpenter, manejo de interrupciones Spot, controlador Karpenter y pools de nodos.

### 4.1 Ejecución de Prueba — Karpenter

**Recursos IAM de Karpenter**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="aws_iam_role.karpenter_controller" \
  -target="aws_iam_policy.karpenter_controller" \
  -target="aws_iam_role_policy_attachment.karpenter_controller" \
  -target="aws_iam_role.karpenter_node" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEC2ContainerRegistryReadOnly" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEBSCSIDriverPolicy" \
  -target="aws_iam_instance_profile.karpenter_node"
```

**Rol de Servicio Vinculado de EC2 Spot (si las instancias Spot están habilitadas)**

```bash theme={null}
terragrunt plan -target="aws_iam_service_linked_role.ec2_spot[0]"
```

**Interrupción Spot de Karpenter (si está habilitada en `terragrunt.hcl`)**

```bash theme={null}
terragrunt plan -target="aws_sqs_queue.karpenter_interruption_queue" \
  -target="aws_sqs_queue_policy.karpenter_interruption_queue" \
  -target="aws_cloudwatch_event_rule.karpenter_interruption" \
  -target="aws_cloudwatch_event_target.karpenter_interruption"
```

**Charts Helm de Karpenter**

```bash theme={null}
terragrunt plan -target="helm_release.karpenter"
```

**Karpenter NodePools y EC2NodeClasses**

```bash theme={null}
terragrunt plan -target="kubernetes_manifest.karpenter_nodepool" \
  -target="kubernetes_manifest.karpenter_nodeclass" \
  -target="kubernetes_config_map.aws_auth"
```

<Info>
  Puede aparecer un error esperado durante el plan: `API did not recognize GroupVersionKind from manifest (CRD may not be installed)`. Esto es seguro de ignorar — Kubernetes valida los recursos contra la API en vivo durante el plan, antes de que se instalen los CRDs.
</Info>

### 4.2 Desplegar Karpenter

**Paso 1: Recursos IAM de Karpenter**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="aws_iam_role.karpenter_controller" \
  -target="aws_iam_policy.karpenter_controller" \
  -target="aws_iam_role_policy_attachment.karpenter_controller" \
  -target="aws_iam_role.karpenter_node" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEC2ContainerRegistryReadOnly" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEBSCSIDriverPolicy" \
  -target="aws_iam_instance_profile.karpenter_node"
```

**Verificación**

```bash theme={null}
aws iam get-role --role-name $CLUSTER_NAME-karpenter-controller --region $AWS_REGION
aws iam get-role --role-name $CLUSTER_NAME-karpenter-node --region $AWS_REGION
aws iam get-instance-profile --instance-profile-name $CLUSTER_NAME-karpenter-node --region $AWS_REGION
aws iam list-attached-role-policies --role-name $CLUSTER_NAME-karpenter-node --region $AWS_REGION
```

**Paso 2: Rol de Servicio Vinculado de EC2 Spot (si las instancias Spot están habilitadas)**

<Warning>
  El rol de servicio vinculado de EC2 Spot es a nivel de cuenta (solo uno por cuenta de AWS) y debe existir antes de que Karpenter pueda lanzar instancias Spot.
</Warning>

**Opción A: Dejar que Terraform lo cree (recomendado para nuevos despliegues)**

```bash theme={null}
terragrunt apply -target="aws_iam_service_linked_role.ec2_spot[0]"
```

**Opción B: Importar si el rol ya existe**

```bash theme={null}
# Verificar si el rol existe
aws iam get-role --role-name AWSServiceRoleForEC2Spot --region $AWS_REGION
 
# Si no existe, crearlo manualmente
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com --region $AWS_REGION
 
# Importar en Terraform (reemplazar ACCOUNT_ID con su ID de cuenta de AWS de 12 dígitos)
terragrunt import 'aws_iam_service_linked_role.ec2_spot[0]' \
  arn:aws:iam::ACCOUNT_ID:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot
```

<details>
  <summary>Solución de Problemas de Creación de Instancias Spot</summary>

  **Verificar logs de Karpenter para errores relacionados con Spot:**

  ```bash theme={null}
  kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter -f | grep -i spot
  ```

  Errores comunes: `AuthFailure.ServiceLinkedRoleCreationNotPermitted`, `UnfulfillableCapacity`, `InsufficientInstanceCapacity`.

  **Verificar que el rol de servicio vinculado existe:**

  ```bash theme={null}
  aws iam get-role --role-name AWSServiceRoleForEC2Spot --region $AWS_REGION
  ```

  **Verificar que la política IAM incluye el permiso Spot:**

  ```bash theme={null}
  aws iam get-policy-version \
    --policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`YOUR_CLUSTER_NAME-karpenter-controller`].Arn' --output text) \
    --version-id $(aws iam get-policy --policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`YOUR_CLUSTER_NAME-karpenter-controller`].Arn' --output text) --query 'Policy.DefaultVersionId' --output text) \
    --region $AWS_REGION | grep -i "CreateServiceLinkedRole"
  ```

  **Verificar disponibilidad de instancias Spot:**

  ```bash theme={null}
  aws ec2 describe-spot-price-history \
    --instance-types r6a.4xlarge r6a.large \
    --product-descriptions "Linux/UNIX" \
    --region $AWS_REGION \
    --max-items 10
  ```

  **Verificar tipos de capacidad de NodePool:**

  ```bash theme={null}
  kubectl get nodepool -o yaml | grep -A 5 "capacity-type"
  ```

  **Verificar nodos Spot vs bajo demanda:**

  ```bash theme={null}
  kubectl get nodes -o json | jq -r '.items[] | "\(.metadata.name)\t\(.metadata.labels."karpenter.sh/capacity-type")\t\(.metadata.labels."node.kubernetes.io/instance-type")"'
  ```
</details>

**Paso 3: Interrupción Spot de Karpenter (si está habilitada en `terragrunt.hcl`)**

```bash theme={null}
terragrunt apply -target="aws_sqs_queue.karpenter_interruption_queue" \
  -target="aws_sqs_queue_policy.karpenter_interruption_queue" \
  -target="aws_cloudwatch_event_rule.karpenter_interruption" \
  -target="aws_cloudwatch_event_target.karpenter_interruption"
```

**Verificación**

```bash theme={null}
aws events describe-rule --name $CLUSTER_NAME-karpenter-interruption --region $AWS_REGION
aws events list-targets-by-rule --rule $CLUSTER_NAME-karpenter-interruption --region $AWS_REGION
aws sqs get-queue-url --queue-name $CLUSTER_NAME-karpenter-interruption-queue --region $AWS_REGION
```

**Paso 4: Chart Helm de Karpenter**

```bash theme={null}
terragrunt apply -target="helm_release.karpenter"
```

**Verificación**

```bash theme={null}
helm list -n kube-system | grep karpenter
kubectl get pods -n kube-system | grep karpenter
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter
kubectl describe sa karpenter -n kube-system
```

**Paso 5: Manifestos de Kubernetes de Karpenter**

```bash theme={null}
terragrunt apply -target="kubernetes_manifest.karpenter_nodepool" \
  -target="kubernetes_manifest.karpenter_nodeclass"
 
# Importar el ConfigMap aws-auth existente
# Nota: Use comillas para evitar que zsh interprete los corchetes como patrones glob
terragrunt import 'kubernetes_config_map.aws_auth[0]' kube-system/aws-auth
 
# Luego aplicar
terragrunt apply -target='kubernetes_config_map.aws_auth[0]'
```

**Verificación**

```bash theme={null}
kubectl get nodepools -o wide
kubectl describe nodepool general
kubectl describe nodepool application
kubectl describe nodepool database
kubectl get ec2nodeclasses -o wide
kubectl get configmap aws-auth -n kube-system -o jsonpath='{.data.mapRoles}' | grep karpenter-node
kubectl get nodepools -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}'
kubectl get nodes -l karpenter.sh/nodepool --show-labels
kubectl get events -n kube-system --field-selector involvedObject.name=karpenter --sort-by='.lastTimestamp'
```

***

## Fase 5: Autoescalado con KEDA

**Propósito:** KEDA para autoescalado a nivel de aplicación.

### 5.1 Ejecución de Prueba — KEDA

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="helm_release.keda"
```

### 5.2 Desplegar KEDA

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="helm_release.keda"
```

**Verificación**

```bash theme={null}
helm list -n keda
kubectl get pods -n keda
kubectl get deployment -n keda
kubectl get crd | grep keda
kubectl get validatingwebhookconfigurations | grep keda
kubectl get svc -n keda
```

***

## Fase 6: Servicios de Datos

**Propósito:** Supabase (base de datos), ElastiCache (Redis), RabbitMQ (cola de mensajes).

<Info>
  Despliegue primero el operador CloudNativePG, luego el clúster HA de Supabase DB, y luego la aplicación Supabase. El clúster de DB debe estar listo antes de que Supabase inicie.
</Info>

### 6.1 Ejecución de Prueba — Servicios de Datos

**Paso 1: Operador CloudNativePG (si está habilitado)**

```bash theme={null}
cd terragrunt/environments/your-env-name
ENABLE_CNPG=true terragrunt plan \
  --target='helm_release.additional_charts["cloudnative-pg"]'
```

**Paso 2: DB HA de Supabase (si está habilitado)**

```bash theme={null}
ENABLE_HA_SUPABASE_DB=true terragrunt plan \
  --target='helm_release.additional_charts["ha-supabase-db"]'
```

**Paso 3: Aplicación Supabase (si está habilitado)**

```bash theme={null}
if [ "${ENABLE_SUPABASE:-false}" = "true" ]; then
  ENABLE_SUPABASE=true terragrunt plan \
    --target='helm_release.supabase[0]'
fi
```

**Paso 4: Servicios AWS — ElastiCache y RabbitMQ (si está habilitado)**

```bash theme={null}
if [ "${ENABLE_AWS_SERVICES:-false}" = "true" ]; then
  terragrunt plan -target="aws_elasticache_subnet_group.redis" \
    -target="aws_security_group.redis" \
    -target="aws_elasticache_replication_group.redis" \
    -target="aws_security_group.rabbitmq" \
    -target="aws_mq_broker.rabbitmq"
fi
```

### 6.2 Desplegar Servicios de Datos

**Paso 1: Operador CloudNativePG (si está habilitado)**

```bash theme={null}
cd terragrunt/environments/your-env-name
ENABLE_CNPG=true terragrunt apply --auto-approve \
  --target='helm_release.additional_charts["cloudnative-pg"]'
```

**Paso 2: DB HA de Supabase (si está habilitado)**

```bash theme={null}
ENABLE_HA_SUPABASE_DB=true terragrunt apply --auto-approve \
  --target='helm_release.additional_charts["ha-supabase-db"]'
```

**Verificar el pooler PgBouncer y credenciales después del despliegue:**

```bash theme={null}
kubectl get svc -n ha-supabase-db | grep pooler
kubectl get secrets -n ha-supabase-db
kubectl get secret ha-supabase-db-authenticator-credentials -n ha-supabase-db \
  -o jsonpath='{.data.username}' | base64 -d && echo ""
```

Use el ClusterIP del pooler (o `EXTERNAL-IP` si es LoadBalancer) como el valor de `SUPABASE_POSTGRES_HOST` en `values/odin-services.yaml` y como `secret.db.postgresHost` en `values/supabase.yaml`.

**Paso 3: Aplicación Supabase (si está habilitado)**

Todos los pods del servicio Supabase se ejecutan exclusivamente en el NodePool `application` de Karpenter (solo bajo demanda) para prevenir interrupciones Spot.

```bash theme={null}
if [ "${ENABLE_SUPABASE:-false}" = "true" ]; then
  ENABLE_SUPABASE=true terragrunt apply --auto-approve \
    --target='helm_release.supabase[0]'
fi
```

**Paso 4: Servicios AWS — ElastiCache y RabbitMQ (si está habilitado)**

```bash theme={null}
if [ "${ENABLE_AWS_SERVICES:-false}" = "true" ]; then
  terragrunt apply \
    -target="aws_elasticache_subnet_group.redis" \
    -target="aws_security_group.redis" \
    -target="aws_elasticache_replication_group.redis" \
    -target="aws_security_group.rabbitmq" \
    -target="aws_mq_broker.rabbitmq"
fi
```

**Verificación**

```bash theme={null}
# Obtener detalles de conexión de las salidas de Terraform
terragrunt output elasticache_endpoint
terragrunt output elasticache_port
terragrunt output rabbitmq_endpoint
terragrunt output rabbitmq_port
 
# Probar conectividad Redis desde el clúster EKS
kubectl run redis-test --image=redis:7-alpine --restart=Never -- \
  sh -c "redis-cli -h <redis-endpoint> -p 6379 --tls --insecure ping && echo 'Redis connection successful'"
kubectl logs redis-test
kubectl delete pod redis-test
 
# Verificar estado de cifrado de Redis
aws elasticache describe-replication-groups \
  --replication-group-id $CLUSTER_NAME-redis \
  --region $AWS_REGION \
  --query 'ReplicationGroups[0].{AtRestEncryption:AtRestEncryptionEnabled,TransitEncryption:TransitEncryptionEnabled}'
```

<Warning>
  Antes de desplegar los Servicios Odin, actualice `values/odin-services.yaml` con el endpoint de Redis, el endpoint de RabbitMQ y todos los ARNs de certificados obtenidos en esta fase.
</Warning>

***

## Fase 7: Servicios Odin

**Propósito:** Despliegue de la aplicación vía Helm.

<Info>
  Antes de desplegar, reduzca temporalmente las réplicas de `fastapiBackend` a una sola para la ejecución inicial de migración de base de datos — establezca `replicaCount: 1`, `workers: 1` y `keda.minReplicas: 1`. Una vez que la migración se complete exitosamente, revierta estos valores a sus valores de producción predeterminados antes de volver a desplegar.
</Info>

### 7.1 Ejecución de Prueba — Servicios Odin

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="helm_release.odin_services"
```

### 7.2 Desplegar Servicios Odin

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="helm_release.odin_services"
```

**Verificación**

```bash theme={null}
kubectl get pods
kubectl get ingress  # Agregue los endpoints ALB a su proveedor de DNS
```

***

## Fase 8: Observabilidad con SigNoz

**Propósito:** Monitoreo de logs y métricas.

### 8.1 Ejecución de Prueba — Charts de SigNoz

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target='helm_release.additional_charts["signoz"]'
terragrunt plan -target='helm_release.additional_charts["k8s-infra"]'
```

### 8.2 Desplegar Charts de SigNoz

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target='helm_release.additional_charts["signoz"]'
terragrunt apply -target='helm_release.additional_charts["k8s-infra"]'
```

**Verificación**

```bash theme={null}
kubectl get pods -n monitoring
kubectl get ingress -n monitoring  # Agregue los endpoints ALB a su proveedor de DNS
```

***

## Fase 9: Despliegue Final

### 9.1 Despliegue Completo

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply
```

Este apply final maneja cualquier recurso restante no explícitamente dirigido en fases anteriores.

### 9.2 Verificar Despliegue

```bash theme={null}
# Actualizar kubeconfig
aws eks update-kubeconfig --region us-east-2 --name your-env-name
 
# Verificar estado del cluster
kubectl get nodes
kubectl get pods --all-namespaces
 
# Verificar Karpenter
kubectl get pods -n kube-system -l app.kubernetes.io/name=karpenter
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter
 
# Verificar AWS Load Balancer Controller
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller
 
# Verificar KEDA
kubectl get pods -n keda
 
# Verificar Servicios Odin
kubectl get pods -n default
kubectl get services -n default
kubectl get ingress -n default
 
# Verificar todos los releases Helm
helm list --all-namespaces
```

***

## Solución de Problemas

**Problemas de bloqueo de estado**

```bash theme={null}
terragrunt force-unlock <lock-id>
```

**Karpenter no funciona**

```bash theme={null}
kubectl describe nodes
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter
```

**Problemas con el Balanceador de Carga**

```bash theme={null}
kubectl describe ingress -n default
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller
```

**Problemas con charts Helm**

```bash theme={null}
helm status <release-name> -n <namespace>
helm rollback <release-name> <revision> -n <namespace>
```

***

## Limpieza

```bash theme={null}
# Destruir infraestructura
cd terragrunt/environments/your-env-name
terragrunt destroy -auto-approve
 
# Destruir bucket de estado (usar con precaución)
cd terragrunt/environments/your-env-name/state
terragrunt destroy -auto-approve
```

***

## Monitoreo y Registro

```bash theme={null}
# Recursos de AWS
aws eks describe-cluster --name your-env-name --region us-east-2
aws ec2 describe-instances --filters "Name=tag:kubernetes.io/cluster/your-env-name,Values=owned"
 
# Recursos de Kubernetes
kubectl top nodes
kubectl top pods --all-namespaces
kubectl get events --sort-by=.metadata.creationTimestamp
```

***

## Referencia Rápida — Todos los Comandos de Despliegue

```bash theme={null}
# Fase 1: Gestión de Estado
cd terragrunt/environments/your-env-name/state
terragrunt apply
 
# Fase 2: Infraestructura EKS
cd terragrunt/environments/your-env-name
 
terragrunt apply -target="aws_vpc.main" -target="aws_internet_gateway.main" \
  -target="aws_subnet.public" -target="aws_subnet.private" -target="aws_eip.nat" \
  -target="aws_nat_gateway.main" -target="aws_route_table.public" \
  -target="aws_route_table.private" -target="aws_route_table_association.public" \
  -target="aws_route_table_association.private" -auto-approve
 
terragrunt apply -target="aws_iam_role.cluster" \
  -target="aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy" \
  -target="aws_iam_openid_connect_provider.eks" -target="aws_iam_role.node" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly" \
  -auto-approve
 
terragrunt apply -target="aws_eks_cluster.main" \
  -target="aws_eks_node_group.main" -target="kubernetes_secret.regcred" -auto-approve
 
# Fase 3: Almacenamiento y Balanceo de Carga
terragrunt apply -target="aws_iam_role.ebs_csi_driver" \
  -target="aws_iam_role_policy_attachment.ebs_csi_driver" \
  -target="helm_release.ebs_csi_driver" -auto-approve
 
terragrunt apply -target="aws_iam_role.aws_load_balancer_controller" \
  -target="aws_iam_role_policy_attachment.aws_load_balancer_controller" \
  -target="aws_iam_policy.aws_load_balancer_controller" \
  -target="helm_release.infrastructure" -auto-approve
 
# Fase 4: Autoescalado con Karpenter
terragrunt apply -target="aws_iam_role.karpenter_controller" \
  -target="aws_iam_policy.karpenter_controller" \
  -target="aws_iam_role_policy_attachment.karpenter_controller" \
  -target="aws_iam_role.karpenter_node" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEC2ContainerRegistryReadOnly" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEBSCSIDriverPolicy" \
  -target="aws_iam_instance_profile.karpenter_node" -auto-approve
 
# Manejo de interrupciones Spot (si spot_interruption_handling = true)
terragrunt apply -target="aws_sqs_queue.karpenter_interruption_queue" \
  -target="aws_sqs_queue_policy.karpenter_interruption_queue" \
  -target="aws_cloudwatch_event_rule.karpenter_interruption" \
  -target="aws_cloudwatch_event_target.karpenter_interruption" -auto-approve
 
terragrunt apply -target="helm_release.karpenter" -auto-approve
 
terragrunt apply -target="kubernetes_manifest.karpenter_nodepool" \
  -target="kubernetes_manifest.karpenter_nodeclass" \
  -target='kubernetes_config_map.aws_auth[0]' -auto-approve
 
# Fase 5: Autoescalado con KEDA
terragrunt apply -target="helm_release.keda" -auto-approve
 
# Fase 6: Servicios de Datos
ENABLE_CNPG=true terragrunt apply --target='helm_release.additional_charts["cloudnative-pg"]' -auto-approve
ENABLE_HA_SUPABASE_DB=true terragrunt apply --target='helm_release.additional_charts["ha-supabase-db"]' -auto-approve
 
if [ "${ENABLE_SUPABASE:-false}" = "true" ]; then
  ENABLE_SUPABASE=true terragrunt apply --target='helm_release.supabase[0]' -auto-approve
fi
 
if [ "${ENABLE_AWS_SERVICES:-false}" = "true" ]; then
  terragrunt apply -target="aws_elasticache_subnet_group.redis" \
    -target="aws_security_group.redis" \
    -target="aws_elasticache_replication_group.redis" \
    -target="aws_security_group.rabbitmq" \
    -target="aws_mq_broker.rabbitmq" -auto-approve
fi
 
# Fase 7: Servicios Odin
terragrunt apply -target="helm_release.odin_services" -auto-approve
 
# Fase 8: SigNoz (si está habilitado)
terragrunt apply -target='helm_release.additional_charts["signoz"]' -auto-approve
terragrunt apply -target='helm_release.additional_charts["k8s-infra"]' -auto-approve
 
# Fase 9: Despliegue Final
terragrunt apply -auto-approve
```

<Info>
  Reemplace `your-env-name` con el nombre real de su entorno en todo momento. Siempre ejecute ejecuciones de prueba (`terragrunt plan`) primero para validar su configuración antes de aplicar cambios.
</Info>
