Terraform¶
Los equipos que gestionan su nube con Terraform pueden desplegar Turbo EA de la misma forma. El repositorio incluye cuatro módulos raíz en deploy/terraform/: uno por cada servicio de contenedores gestionado que cubre la página Servicios de contenedores gestionados, más uno que instala el chart de Helm de la página Kubernetes y nube en un clúster que ya opera. Esta página trata de los módulos; las dos páginas enlazadas siguen siendo la referencia sobre lo que cada plataforma puede y no puede hacer.
Qué construyen los módulos¶
| Módulo | Plataforma | Crea | Usted aporta |
|---|---|---|---|
ecs-fargate |
AWS ECS Fargate | Clúster, tarea y servicio, Application Load Balancer con HTTPS, EFS, secretos de Secrets Manager, RDS for PostgreSQL (opcional) | Una VPC con subredes públicas y privadas (NAT), un certificado ACM |
azure-container-apps |
Azure Container Apps | Entorno, container app, Log Analytics, cuenta de almacenamiento y recurso compartido de archivos, Flexible Server (opcional) | Un grupo de recursos; opcionalmente una subred delegada y una zona DNS privada |
cloud-run |
Google Cloud Run | Servicio, Filestore, secretos de Secret Manager, repositorio remoto de Artifact Registry, balanceador de carga HTTPS global con certificado gestionado, Cloud SQL (opcional) | Un proyecto, una VPC con una subred, acceso a servicios privados en esa VPC |
kubernetes |
Cualquier clúster de Kubernetes | Namespace, Secret de credenciales, la release de Helm | Un clúster y un kubeconfig, un servidor PostgreSQL |
Los tres módulos de nube construyen el mismo grupo de contenedores que las plantillas de la página Servicios de contenedores gestionados: el nginx de borde en el puerto 8920 delante del frontend, el backend y el servidor MCP opcional, todos compartiendo localhost; exactamente un backend, nunca escalado y nunca reducido a cero; /app/data en un recurso compartido persistente propiedad del usuario 1000; TLS terminado en el borde de la plataforma. Misma forma, distinta herramienta: todo lo que la página de la plataforma dice sobre sondas, solapamiento en el despliegue y el bloqueo de arranque se aplica sin cambios.
Tres convenciones rigen para los cuatro:
- La versión es una entrada explícita.
image_tag(ochart_versionen el módulo de Kubernetes) no tiene un valor por defecto que pueda quedar obsoleto; elterraform.tfvars.examplejunto a cada módulo lleva la versión actual. - La base de datos se crea por defecto, con un conmutador para traer la suya.
create_database = falsejunto condb_hostydb_passwordapunta el backend a un servidor que ya opera. Sea cual sea el origen de la base, el módulo es dueño de los objetos del almacén de secretos (SECRET_KEYy la contraseña de la base), de modo que la definición del contenedor tiene una sola forma. - La red nunca se crea. Los identificadores de VPC, VNet y subredes son entradas; el README de cada módulo enumera lo que ya deben proporcionar.
Inicio rápido¶
cd deploy/terraform/<módulo>
cp terraform.tfvars.example terraform.tfvars
# edite terraform.tfvars y mantenga el secreto fuera de cualquier archivo:
export TF_VAR_secret_key="$(openssl rand -base64 48)"
terraform init
terraform plan
terraform apply
Apunte el DNS a la salida que nombra el módulo — alb_dns_name en AWS, fqdn en Azure, load_balancer_ip en Google Cloud —, abra public_url y regístrese: el primer usuario se convierte en administrador.
El estado contiene secretos
secret_key, las contraseñas de base generadas y los valores de los secretos de Container Apps acaban todos en el estado de Terraform. Use un backend remoto cifrado con control de acceso (S3 con SSE y bloqueo, un contenedor de Azure Storage, un bucket de GCS, Terraform Cloud); nunca un terraform.tfstate en un portátil para una instancia real. Guarde SECRET_KEY junto con las copias de seguridad de la base: perderlo invalida todas las sesiones y todos los ajustes cifrados, y la base por sí sola no puede recuperarlos.
AWS ECS Fargate¶
Antes de empezar: una VPC con al menos dos subredes públicas (balanceador) y dos subredes privadas en zonas de disponibilidad distintas (tarea, destinos de montaje de EFS, base de datos); una puerta de enlace NAT para que las subredes privadas puedan descargar imágenes de ghcr.io; un certificado ACM para el nombre de host en la misma región.
Entradas que fijar: region, vpc_id, public_subnet_ids, private_subnet_ids, certificate_arn, public_url, image_tag. Opcionalmente route53_zone_id; el módulo crea entonces el registro alias por sí mismo. La instancia RDS creada es privada, cifrada, con protección contra eliminación activada y conserva siete días de copias; traiga la suya con create_database = false, db_host y db_security_group_id (el módulo abre el puerto desde la tarea).
Los despliegues son parar-y-arrancar (deployment_minimum_healthy_percent = 0), así que una actualización de versión cuesta uno o dos minutos de inactividad y nunca ejecuta dos backends. Para abrir una shell: aws ecs execute-command … --container backend --interactive --command sh.
Azure Container Apps¶
Antes de empezar: un grupo de recursos existente. Para la integración con VNet, una subred /27 delegada a Microsoft.App/environments. Para una base no accesible desde Internet, una segunda subred delegada a Microsoft.DBforPostgreSQL/flexibleServers y una zona DNS privada terminada en .postgres.database.azure.com vinculada a la VNet — fije postgresql_delegated_subnet_id y postgresql_private_dns_zone_id juntos. Sin ellos, el servidor creado conserva un punto de conexión público restringido a los servicios de Azure.
Entradas que fijar: subscription_id, resource_group_name, location, public_url, image_tag, y unos storage_account_name y postgresql_server_name únicos a nivel global. El primer despliegue responde en la salida fqdn; vincule un dominio personalizado con az containerapp hostname add / bind como describe la página Servicios de contenedores gestionados, fije después public_url y vuelva a aplicar. Azure no tiene un indicador de protección contra eliminación en un Flexible Server, así que el módulo coloca bloqueos CanNotDelete en el servidor y en la cuenta de almacenamiento (db_deletion_protection, storage_deletion_protection).
Google Cloud Run¶
Antes de empezar: un proyecto, una red VPC con una subred en la región y acceso a servicios privados en esa VPC; la IP privada de Cloud SQL lo necesita. Si la VPC aún no lo tiene, fije una vez create_private_service_connection = true; un segundo emparejamiento en una VPC que ya lo tiene falla.
Entradas que fijar: project_id, region, network, subnetwork, public_url, image_tag. El módulo habilita las API, crea un repositorio remoto de Artifact Registry que hace de proxy de ghcr.io (Cloud Run no puede descargar directamente de ghcr.io), una instancia de Filestore para /app/data (el nivel por defecto BASIC_HDD empieza en 1 TiB y es el coste dominante), ejecuta un trabajo puntual que asigna el recurso compartido al usuario 1000 y coloca delante del servicio un balanceador HTTPS global con un certificado gestionado por Google. Cree el registro DNS A del host de public_url apuntando a load_balancer_ip; el certificado permanece en PROVISIONING hasta que ese registro resuelva. Las subidas de más de 32 MiB — una importación grande de espacio de trabajo — fallan en la ruta HTTP/1 de Cloud Run; es un límite de la plataforma, no un ajuste del módulo.
Kubernetes¶
El módulo kubernetes envuelve el chart publicado en un helm_release, para equipos cuyos clústeres también se gestionan con Terraform. Crea el namespace y un Secret con SECRET_KEY y POSTGRES_PASSWORD (o usa mediante existing_secret uno producido por External Secrets o Sealed Secrets), genera las claves de values propias del chart y pasa el Secret por nombre: los secretos nunca viajan por los values. Entradas que fijar: chart_version, public_url, db_host, y o bien secret_key + db_password o bien existing_secret. La clase de Ingress, las anotaciones y TLS van por el objeto ingress; todo lo que el módulo no expone (backend.resources, seed.demo…) va por extra_values, una lista de documentos de values del chart fusionados después del generado.
Los dos bloques provider leen un kubeconfig; sustitúyalos por la autenticación propia de su clúster (token de EKS, credenciales de AKS, plugin de autenticación de GKE) cuando Terraform también cree el clúster.
Actualizaciones y eliminación¶
Una actualización de versión es un cambio de image_tag (o chart_version) seguido de terraform apply; el backend ejecuta las migraciones al arrancar, bajo el bloqueo de arranque en las plataformas que solapan la instancia antigua y la nueva. Lea primero las notas de la versión y haga una copia de la base como en cualquier otra instalación — Operaciones y actualizaciones se aplica.
terraform destroy se rechaza mientras la protección contra eliminación esté activa: desactive db_deletion_protection (AWS, Google Cloud y los bloqueos en Azure), deletion_protection en el servicio de Cloud Run y, en AWS, decida sobre la instantánea final de RDS (db_skip_final_snapshot), aplique y destruya. El nombre de una instancia de Cloud SQL eliminada no puede reutilizarse durante una semana.
Validación sin nube¶
Cada módulo incluye pruebas en tests/ que se ejecutan contra proveedores simulados: esquemas de proveedor reales, valores inventados, sin credenciales, nada creado. Fijan el cableado del que dependen las páginas de plataforma — un backend, el borde en el puerto 8920, secretos por referencia, el volumen de datos, los conmutadores para traer lo suyo — y la CI las ejecuta junto con terraform validate y tflint en cada cambio. Lo que no pueden probar es que una nube acepte el plan; el primer terraform plan contra una cuenta real es esa comprobación. OpenTofu no se ejercita, pero los módulos evitan toda característica exclusiva de Terraform.
Solución de problemas¶
| Síntoma | Causa y solución |
|---|---|
Error creating Service Networking Connection … already exists (Google Cloud) |
La VPC ya tiene acceso a servicios privados. Fije create_private_service_connection = false. |
El certificado gestionado por Google permanece en PROVISIONING |
El registro DNS A del host de public_url aún no resuelve a load_balancer_ip. Corrija el DNS y espere; nada que aplicar. |
Permission denied bajo /app/data en Cloud Run |
El recurso compartido no pertenece al usuario 1000, por ejemplo tras restaurarlo. Cambie chown_job_token y aplique para ejecutar el trabajo de nuevo. |
postgresql_delegated_subnet_id and postgresql_private_dns_zone_id must be set together (Azure) |
El acceso privado necesita ambos; la zona debe estar vinculada a la VNet y la subred debe ser distinta de la del entorno. |
db_host is required when create_database is false |
Traer su base requiere db_host y db_password (TF_VAR_db_password). |
terraform destroy se niega |
La protección contra eliminación o un bloqueo CanNotDelete está activo; véase Actualizaciones y eliminación. |
La aplicación responde en la URL de la plataforma pero no en public_url |
El DNS apunta a otro sitio, o public_url aún nombra el FQDN de la plataforma: fíjelo al origen definitivo y aplique; la cookie de sesión está ligada a él. |