Arquitectura
Cómo encajan el gateway, el frontend, el backend y el ejecutor de Ansible, y dónde viven tus secretos.
Playbook Runner es un monorepo con tres servicios detrás de un gateway Caddy, más PostgreSQL. Solo el gateway publica un puerto, así que el navegador ve un único origen y el ejecutor que toca tus hosts nunca es accesible desde fuera.
| Servicio | Stack | Puerto | Función |
|---|---|---|---|
| Gateway | Caddy | 80 (publicado), 50050 (interno) |
Entrada HTTP pública y router gRPC interno. |
| Frontend | Astro SSR, React | 4321 (interno) |
La interfaz web. |
| Backend | Bun, Hono, oRPC, Better Auth, Drizzle | 3000 (interno) |
Autenticación, la API, las reglas de negocio y el scheduler. Es el dueño de la base de datos. |
| Ejecutor | Python, FastAPI, gRPC, ansible-runner | 50051 (interno) |
Ejecuta Ansible. No tiene base de datos. |
| PostgreSQL | PostgreSQL 17 | 5432 (interno) |
Usuarios, inventario, credenciales, playbooks, jobs e historial. |
Recorrido de las peticiones
El gateway envía /rpc, /api, /scalar y /openapi.json al backend y cualquier otra ruta al frontend.
El backend llega al ejecutor por gRPC a través del router interno del gateway en :50050, que enruta por paquete proto: /run.* va al ejecutor en :50051 y cualquier otra cosa responde UNIMPLEMENTED.
Anatomía de una ejecución
- Pulsas Ejecutar en el navegador. El frontend llama al backend por oRPC y mantiene abierta la respuesta como un flujo de eventos.
- El backend comprueba tu sesión y resuelve la ejecución contra su base de datos: el contenido del playbook (o, si viene de Git, el repositorio y el commit en el que se sincronizó), la lista de hosts sin duplicados y, para cada uno, su usuario SSH y su clave privada descifrada.
- Envía ese paquete completo al ejecutor en una sola llamada gRPC con streaming desde el servidor, autenticada con el
SERVICE_TOKENcompartido. - El ejecutor escribe un inventario y unos ficheros de clave temporales (y, para playbooks de Git, exporta el commit fijado desde su mirror), y se los pasa a
ansible-runner. - Cada evento que emite Ansible vuelve por la misma llamada gRPC, y el backend lo reenvía al navegador. El backend registra la ejecución en el historial.
- Cuando la ejecución termina, o cierras la pestaña y la llamada se cancela, el ejecutor detiene Ansible, mata los procesos que queden y borra los ficheros de clave de esa ejecución.
Los jobs programados siguen el mismo camino, lanzados por el bucle cron del backend en lugar de por un navegador.
Modelo de seguridad
- Las claves privadas nunca llegan al navegador. Se cifran en reposo con AES-256-GCM usando
CREDENTIALS_ENCRYPTION_KEY, la API nunca las devuelve y solo se descifran mientras el backend prepara una ejecución. - El ejecutor solo confía en el backend. Cada llamada gRPC debe llevar
SERVICE_TOKEN, y el ejecutor no se publica en ningún puerto del host. - Las claves de host se verifican según
SSH_HOST_KEY_POLICY(confianza en el primer uso, por defecto). - La entrada del usuario está acotada. Las variables extra no pueden empezar por
ansible_(el ejecutor lo vuelve a comprobar), y los nombres de dispositivos y grupos solo admiten letras, dígitos,.,_y-. - Las ejecuciones tienen límite. Como mucho corren
MAX_CONCURRENT_RUNSprocesos de Ansible a la vez, las peticiones de más se rechazan con too many requests, un job nunca se solapa consigo mismo yforkstiene un máximo de 50. - Git está acotado. Los repositorios solo se sincronizan por
httpsyssh, sin configuración global de Git, con un tiempo máximo por comando y un límite de tamaño. Elansible.cfgpropio del repositorio se ignora.
La API
La API está versionada bajo /rpc/v1 (oRPC, la que usa el frontend) y /api/v1 (OpenAPI). La referencia interactiva está en /scalar y la especificación en bruto en /openapi.json.
Desde scripts, autentícate con una API key personal, creada en la página API keys y enviada en la cabecera x-api-key:
curl -H "x-api-key: $PLAYBOOK_RUNNER_KEY" https://ansible.example.com/api/v1/playbooks/list