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:
- Gestión de Estado — Inicializa el bucket S3 usado para almacenar el estado de Terraform del entorno.
- Infraestructura EKS — Provisiona la VPC, subredes, NAT gateways, roles IAM y el cluster EKS con grupos de nodos administrados.
- Almacenamiento y Balanceo de Carga — Despliega el controlador EBS CSI para volúmenes persistentes y el AWS Load Balancer Controller para el ingreso ALB.
- Autoescalado con Karpenter — Configura el provisionamiento dinámico de nodos con soporte para instancias Spot y manejo de interrupciones via SQS y EventBridge.
- Autoescalado con KEDA — Despliega KEDA para el autoescalado a nivel de pod basado en umbrales de CPU y memoria.
- Servicios de Datos — Provisiona Supabase (autoalojado o Cloud), ElastiCache Redis y Amazon MQ RabbitMQ.
- Servicios Odin — Despliega el stack de aplicación EKB (Web, FastAPI, Celery, Automator) vía Helm.
- Observabilidad con SigNoz — Despliega la traza distribuida, métricas y agregación de logs a través de SigNoz y el agente k8s-infra.
- 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)
Linux (apt)
# 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)
Instalando kubectl
macOS (Homebrew)
Linux
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)
choco install kubernetes-cli
Instalando Helm
macOS (Homebrew)
Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
Windows (Chocolatey)
choco install kubernetes-helm
Verificando la Instalación
terragrunt --version
terraform --version
kubectl version --client
helm version
Configuración de AWS CLI
# 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.
# 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
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
- Abra la consola de AWS Certificate Manager
- Cambie a la región correcta (arriba a la derecha) — debe coincidir con
<YOUR_AWS_REGION>
- Haga clic en Solicitar un certificado → Solicitar un certificado público → Siguiente
- Debajo de Nombre de dominio completo, ingrese el wildcard (ej.,
*.app.example.com) o un dominio específico
- Establezca el Método de validación en Validación por DNS
- 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. |
Incluya el punto final (.) al final de los valores CNAME si su proveedor de DNS lo requiere.
Cloudflare
- Inicie sesión en Cloudflare → seleccione su dominio → vaya a DNS → Records → Add record
- Establezca el Tipo en
CNAME
- Pegue el nombre CNAME de ACM en Name y el valor CNAME de ACM en Target
- Establezca el Estado del proxy en DNS only (icono de nube gris) — el certificado no se validará a través del proxy de Cloudflare
- Haga clic en Save
Route 53
- Abra la consola de Route 53 → Hosted zones → seleccione su zona → Create record
- Establezca el Tipo de registro en
CNAME
- Pegue el nombre CNAME de ACM en Record name (solo la parte del subdominio) y el valor en Value
- Establezca TTL en
300 y haga clic en Create records
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.
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().
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:
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 |
# 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
| 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
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
Obtenga el ID de la VPC después de que se cree el cluster EKS antes de desplegar el AWS Load Balancer Controller.
# 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
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
Obtenga el endpoint del cluster EKS después de que se cree el cluster EKS y antes de desplegar Karpenter.
# Obtener endpoint del cluster después de crear el cluster EKS
aws eks describe-cluster --name <YOUR_ENV_NAME> \
--query "cluster.endpoint" --output text
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
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
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
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)
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.
# 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
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)
Los secretos aquí deben coincidir con supabase.yaml. Use los mismos valores generados para postgresPassword, jwtSecret, anonKey y serviceRoleKey.
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
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.
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 |
# 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 |
# 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 |
# 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)
| 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)
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:
- 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
- Después de crear el cluster EKS — Establecer:
<YOUR_VPC_ID> (infrastructure.yaml), <YOUR_EKS_CLUSTER_ENDPOINT> (karpenter-values.yaml)
- 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
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).
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
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
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
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
# 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
# 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
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
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
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"
Después de este paso, actualice vpcId en values/infrastructure.yaml antes de desplegar el AWS Load Balancer Controller.
Paso 2: Cluster EKS, Roles y Políticas IAM
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
terragrunt apply -target="aws_eks_cluster.main" \
-target="aws_eks_node_group.main" \
-target="kubernetes_secret.regcred"
Después de este paso, actualice CLUSTER_ENDPOINT en values/karpenter-values.yaml antes de desplegar Karpenter.
Verificar Conectividad del Cluster EKS
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
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
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
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
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
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
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
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)
terragrunt plan -target="aws_iam_service_linked_role.ec2_spot[0]"
Interrupción Spot de Karpenter (si está habilitada en terragrunt.hcl)
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
terragrunt plan -target="helm_release.karpenter"
Karpenter NodePools y EC2NodeClasses
terragrunt plan -target="kubernetes_manifest.karpenter_nodepool" \
-target="kubernetes_manifest.karpenter_nodeclass" \
-target="kubernetes_config_map.aws_auth"
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.
4.2 Desplegar Karpenter
Paso 1: Recursos IAM de Karpenter
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
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)
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.
Opción A: Dejar que Terraform lo cree (recomendado para nuevos despliegues)
terragrunt apply -target="aws_iam_service_linked_role.ec2_spot[0]"
Opción B: Importar si el rol ya existe
# 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
Paso 3: Interrupción Spot de Karpenter (si está habilitada en terragrunt.hcl)
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
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
terragrunt apply -target="helm_release.karpenter"
Verificación
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
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
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
cd terragrunt/environments/your-env-name
terragrunt plan -target="helm_release.keda"
5.2 Desplegar KEDA
cd terragrunt/environments/your-env-name
terragrunt apply -target="helm_release.keda"
Verificación
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).
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.
6.1 Ejecución de Prueba — Servicios de Datos
Paso 1: Operador CloudNativePG (si está habilitado)
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)
ENABLE_HA_SUPABASE_DB=true terragrunt plan \
--target='helm_release.additional_charts["ha-supabase-db"]'
Paso 3: Aplicación Supabase (si está habilitado)
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)
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)
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)
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:
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.
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)
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
# 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}'
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.
Fase 7: Servicios Odin
Propósito: Despliegue de la aplicación vía Helm.
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.
7.1 Ejecución de Prueba — Servicios Odin
cd terragrunt/environments/your-env-name
terragrunt plan -target="helm_release.odin_services"
7.2 Desplegar Servicios Odin
cd terragrunt/environments/your-env-name
terragrunt apply -target="helm_release.odin_services"
Verificación
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
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
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
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
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
# 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
terragrunt force-unlock <lock-id>
Karpenter no funciona
kubectl describe nodes
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter
Problemas con el Balanceador de Carga
kubectl describe ingress -n default
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller
Problemas con charts Helm
helm status <release-name> -n <namespace>
helm rollback <release-name> <revision> -n <namespace>
Limpieza
# 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
# 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
# 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
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.