Problema
Los sistemas de orquestación de flujos (Airflow, Prefect, etc.) suelen requerir que cada worker tenga acceso directo a la base de datos central para leer el estado de los jobs y actualizar sus resultados. En entornos con nodos en la periferia –por ejemplo, un Raspberry Pi en el garaje, una VM detrás de NAT en la casa de los padres o un cluster de GPU aislado en una red de alta seguridad– esa arquitectura obliga a abrir puertos, crear VPNs o exponer credenciales de base de datos. Cada vez que el acceso a la DB falla, los workers se quedan colgados, pierden tareas o, en el peor de los casos, se pierde la trazabilidad completa del flujo. El síntoma típico es “el worker no puede conectarse a Postgres” o “el scheduler se queda sin heartbeat”. El reto es mantener la flexibilidad de ejecutar tareas en contenedores, procesos o pods sin comprometer la seguridad ni la disponibilidad del backend.
Causa
-
Conexión directa a la base de datos
Los workers tradicionales usan JDBC/ODBC para consultar el estado. Cuando el nodo está detrás de NAT o sin ruta directa, la única solución es exponer la DB mediante VPN o reglas de firewall, lo que aumenta la superficie de ataque. -
Acoplamiento de cola y repositorio
En versiones anteriores de algunos motores, la cola de ejecución y el repositorio de metadatos estaban ligados a la misma tecnología (por ejemplo, Kafka + Elasticsearch). Cambiar solo la cola implicaba reescribir gran parte del código, generando bugs duplicados. -
Estado persistente en el worker
Al guardar credenciales y estado localmente, cualquier robo de hardware expone información sensible y dificulta la rotación de claves. -
Escalado rígido
Añadir un nuevo nodo implica replicar la configuración de la base de datos, lo que complica el despliegue en clusters heterogéneos (ARM vs x86) o en entornos de bajo consumo.
Solución
Separar el plano de control del plano de ejecución. Un controller (o “control plane”) centraliza la base de datos y la cola, mientras que los workers son completamente stateless y se comunican con el controller mediante una única conexión saliente gRPC protegida con mTLS. Este patrón elimina la necesidad de abrir puertos inbound, permite que los workers vivan detrás de NAT y reduce la carga de gestión de credenciales.
Pasos generales:
-
Desplegar el controller
- Usa Docker Compose o Helm para lanzar una instancia con Postgres (o MySQL) y la cola elegida (por ejemplo, RabbitMQ o una tabla de tareas en la BD).
- Habilita la generación automática de certificados mTLS (Kestra incluye scripts para crear una CA interna).
-
Configurar los workers
- En cada nodo, instala únicamente el runtime de Kestra (no la base de datos).
- Apunta la variable
KESTRA_CONTROLLER_URLal endpoint del controller y monta los certificados generados. - Selecciona el runner que necesites: Docker socket, proceso local o pod de Kubernetes, mediante la clave
runner.typeen el YAML del flujo.
-
Definir flujos en YAML
- Mantén los archivos de flujo bajo control de versiones (Git).
- Usa
triggerpara cron, webhook, MQTT o Kafka según convenga; los triggers se evalúan en el controller, por lo que la latencia es mínima.
-
Aislar plugins
- Configura
KESTRA_PLUGINS_AUTO_INSTALL_ENABLED=truepara que el controller descargue plugins bajo demanda. - En entornos con ancho de banda limitado, pre‑descarga los plugins críticos y desactiva la auto‑instalación.
- Configura
-
Implementar cuotas y políticas de seguridad
- Define
quota.maxExecutionspor namespace para evitar que un flujo mal configurado consuma todos los recursos. - Usa
credential.storepara que las credenciales se almacenen en el controller y nunca viajen al worker.
- Define
Este enfoque funciona tanto en despliegues monolíticos (Docker run simple) como en clusters Kubernetes, y permite mezclar arquitecturas ARM y x86 sin modificar los flujos.
Cuándo aplicar esta solución
- Entornos con nodos aislados: homelabs, sucursales, dispositivos IoT, o cualquier worker detrás de NAT.
- Requerimientos de seguridad estricta: cuando no se pueden exponer puertos de base de datos ni mantener credenciales en los workers.
- Escalado horizontal frecuente: cuando se añaden o retiran workers dinámicamente y se necesita que el proceso sea “plug‑and‑play”.
- Arquitecturas heterogéneas: mezclas de ARM y x86, o despliegues mixtos Docker/Kubernetes.
No es necesario si todos los workers están en la misma red privada y la exposición de la base de datos no representa riesgo; en ese caso la arquitectura monolítica puede ser más simple.
Código
# 1️⃣ Controller con Docker Compose (Postgres + Kestra)
cat > docker-compose.yml <<'EOF'
version: "3.8"
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: kestra
POSTGRES_PASSWORD: kestra_pass
POSTGRES_DB: kestra
volumes:
- pg_data:/var/lib/postgresql/data
kestra-controller:
image: kestra/kestra:2.0-lts
command: server controller
environment:
KESTRA_DATABASE_URL: jdbc:postgresql://postgres:5432/kestra
KESTRA_DATABASE_USERNAME: kestra
KESTRA_DATABASE_PASSWORD: kestra_pass
KESTRA_CONTROLLER_GRPC_TLS_ENABLED: "true"
KESTRA_CONTROLLER_GRPC_TLS_CA_CERT: /certs/ca.crt
KESTRA_CONTROLLER_GRPC_TLS_CERT: /certs/controller.crt
KESTRA_CONTROLLER_GRPC_TLS_KEY: /certs/controller.key
volumes:
- ./certs:/certs:ro
ports:
- "8080:8080"
- "50051:50051" # gRPC
volumes:
pg_data:
EOF
docker compose up -d
# 2️⃣ Worker (Docker socket runner) en cualquier host
docker run -d --name kestra-worker \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ./certs:/certs:ro \
-e KESTRA_CONTROLLER_URL=grpc://<controller_ip>:50051 \
-e KESTRA_CONTROLLER_GRPC_TLS_CA_CERT=/certs/ca.crt \
-e KESTRA_CONTROLLER_GRPC_TLS_CERT=/certs/worker.crt \
-e KESTRA_CONTROLLER_GRPC_TLS_KEY=/certs/worker.key \
kestra/kestra:2.0-lts server worker
Verificación
- Accede a
http://<controller_ip>:8080y verifica que el UI muestra el nodo controller activo. - En la UI, crea un flujo sencillo con un
scriptque imprimadate. - Ejecuta el flujo manualmente; la ejecución debe aparecer en la lista de workers y el contenedor Docker del worker debe iniciar y terminar sin errores.
- Revisa los logs del worker (
docker logs kestra-worker) para confirmar que la conexión gRPC se estableció con éxito y que no aparecen mensajes de “failed to connect to database”. - Simula una caída de red del worker (desconecta la interfaz) y observa que el controller marca la ejecución como “lost” y la re‑intenta cuando el worker vuelve a conectarse.
Notas adicionales
- Rotación de certificados: Kestra permite recargar los certificados sin reiniciar el proceso; basta con reemplazar los archivos bajo
/certsy enviarSIGHUPal proceso. - Persistencia de plugins: si el worker está en un nodo con espacio limitado, monta un volumen compartido (
/app/plugins) para que los plugins descargados se reutilicen entre reinicios. - Escalado en Kubernetes: usa el chart oficial y habilita
controller.grpc.tls.enabled=true. El Deployment del worker puede escalar conreplicasy el ServiceClusterIPmantiene la comunicación outbound. - Monitoreo: expón los endpoints
/metricsde Prometheus tanto en controller como en workers; la métricakestra_worker_connectedayuda a detectar workers desconectados. - Backup de la BD: programa un
pg_dumpperiódico y almacénalo en S3/MinIO; la restauración es tan simple como volver a levantar el contenedor Postgres con el dump.
Con esta arquitectura, los flujos de trabajo quedan aislados del entorno de red, los workers pueden desplegarse en cualquier hardware y la gestión de credenciales se centraliza, reduciendo la superficie de ataque y simplificando el escalado.