Problema

En entornos serverless es frecuente necesitar un contenedor que mantenga estado durante horas o días, acepte conexiones WebSocket y ofrezca una URL permanente. Los modelos tradicionales de Cloud Run (Service, Job, Worker Pool) imponen límites de tiempo (máximo 60 min) o escalan a cero sin preservar datos en memoria. Cuando una aplicación necesita estar “always‑on”, evitar reinicios frecuentes y conservar datos temporales sin migrar a una VM completa, el patrón encaja peor con los modelos existentes y se vuelve difícil de mantener.

Causa

  1. Timeout de request/response – Los Services de Cloud Run cierran la petición después de 5 min por defecto (configurable hasta 60 min). Cualquier conexión que supere ese límite, como un WebSocket de larga duración, se corta automáticamente.
  2. Escalado a cero – Cuando no hay tráfico, la instancia se destruye y con ella el contenido de /tmp y cualquier volumen efímero. La pérdida de estado local obliga a volver a cargar datos desde Cloud Storage o bases externas en cada arranque.
  3. Ausencia de autoscaling para singleton – Un “instance” de Cloud Run es una única réplica que no escala. Si la carga supera la capacidad asignada, las peticiones se encolan o se agotan, lo que puede derivar en DoS involuntario.
  4. Almacenamiento no persistente – Aunque se pueden montar buckets vía FUSE, la latencia y la falta de bloqueo POSIX hacen que el acceso a archivos sea poco fiable para operaciones críticas. El disco efímero es volátil y se reinicia cada 1‑2 semanas.

Solución

Utilizar Cloud Run Instances como entorno serverless de un solo nodo, con una URL fija y sin límite de timeout de petición. La arquitectura típica incluye:

  • Contenedor con lógica de sesión que mantiene datos en memoria o en un disco efímero mientras la instancia está viva.
  • Persistencia externa (Firestore, Cloud SQL, o Cloud Storage) para cualquier estado que deba sobrevivir a reinicios programados.
  • Configuración de timeout a 60 min (máximo permitido) para operaciones que necesiten más tiempo, aunque la instancia en sí no está sujeta a ese límite.
  • Health checks ligeros para evitar reinicios inesperados y para que el LPU detecte fallos rápidamente.

Pasos de despliegue

  1. Construir la imagen con el runtime necesario (Python, Node, Go, etc.) y exponer el puerto 8080.
  2. Crear la instancia usando gcloud run instances create. Se especifica la memoria y la CPU que la aplicación necesita; el precio es lineal y suele rondar los $5.70/mes para 1 GiB.
  3. Adjuntar volúmenes opcionales:
    • Un ephemeral disk (--disk-size-gb) para archivos temporales de alta velocidad.
    • Un bucket Cloud Storage montado vía gcsfuse solo para lecturas/escrituras no críticas.
  4. Definir variables de entorno que apunten a la base de datos externa y a la configuración de la aplicación.

Cuándo aplicar esta solución

  • Sesiones interactivas largas: agentes IA, entornos de depuración remota o aplicaciones que usan WebSocket para streaming continuo.
  • Bastiones temporales: acceso seguro a VPC privadas sin provisionar una VM completa.
  • Entornos de pruebas aislados: sandboxes de corta duración donde el costo y la velocidad de arranque son críticos.
  • Workloads con tráfico predecible y bajo: menos de 2‑3 peticiones concurrentes, donde la ausencia de autoscaling no genera cuellos de botella.

No aplicar cuando se espera alta concurrencia, picos de tráfico impredecibles o cuando la aplicación depende de un disco persistente local. En esos casos, un Service de Cloud Run, GKE o Compute Engine son opciones más seguras.

# 1. Compilar y subir la imagen
docker build -t gcr.io/$PROJECT_ID/long-lived-agent:latest .
docker push gcr.io/$PROJECT_ID/long-lived-agent:latest

# 2. Crear la instancia
gcloud run instances create long-lived-agent \
  --image gcr.io/$PROJECT_ID/long-lived-agent:latest \
  --region us-central1 \
  --memory 1Gi \
  --cpu 1 \
  --max-request-timeout 60m \
  --set-env-vars DB_URI=postgresql://user:pass@host/db,BUCKET_NAME=my-bucket \
  --disk-size-gb 10 \
  --no-allow-unauthenticated

# 3. Obtener la URL permanente
INSTANCE_URL=$(gcloud run instances describe long-lived-agent --region us-central1 --format='value(status.url)')
echo "Instancia disponible en: $INSTANCE_URL"

Verificación

  1. Conexión WebSocket: usa wscat o un cliente propio para abrir una sesión contra $INSTANCE_URL. La conexión debe permanecer abierta más de 60 min sin cerrarse.
  2. Persistencia de estado: escribe un registro en /tmp/state.txt, reinicia la instancia con gcloud run instances stop y vuelve a iniciar. Verifica que el archivo desapareció y que la aplicación recuperó su estado desde la base externa.
  3. Escalado: envía simultáneamente 3 peticiones largas (por ejemplo, curl -N $INSTANCE_URL/stream). Observa que la tercera petición se atiende sin timeout, pero una cuarta empezará a fallar, confirmando el límite de singleton.
  4. Costo: revisa la facturación en la consola de Cloud Billing; la instancia debe aparecer bajo “Cloud Run Instances” con el cargo esperado.

Notas adicionales

  • Reinicios programados: Cloud Run Instances se reinician cada 1‑2 semanas. Programa una tarea de “warm‑up” que recargue datos críticos al iniciar.
  • Límites de concurrencia: la configuración por defecto permite una sola petición concurrente. Si tu caso necesita 2‑3 peticiones simultáneas, usa la bandera --concurrency (máximo 10) y ajusta la memoria en consecuencia.
  • Seguridad: habilita IAM para limitar quién puede invocar la URL. Usa --no-allow-unauthenticated y protege el endpoint con Cloud Endpoints o Identity-Aware Proxy.
  • Monitoreo: exporta métricas a Cloud Monitoring; crea alertas para uso de CPU > 80 % o para reinicios inesperados.
  • Alternativas: si el requisito de “always‑on” es crítico y la carga puede crecer, considera una combinación: un Service para el tráfico público y una Instance dedicada para tareas de larga duración que no requieren alta concurrencia.