ConfiguraciónAprovisionamiento

Aprovisionamiento

Lo que el motor instala en el paso Infraestructura.

El paso Infraestructura es donde se monta todo en el host. El motor instala, en secuencia, con progreso transmitido en vivo:

  • k3s — Kubernetes ligero y production-grade (escala a multi-node más adelante).
  • Ingress (Traefik) + cert-manager — enrutamiento y TLS automático vía Let's Encrypt.
  • PostgreSQL y Redis in-cluster — la base de datos definitiva y la cola/pub-sub de logs.
  • Registry de artefactos y los Runners de CI.
  • Runtime serverless de Deploy.
  • Las imágenes definitivas de api y web (despliegue + migraciones + seed del super admin como Jobs in-cluster).
Los objetos Kubernetes llevan el prefijo equantic-space-* (namespace equantic-space, Deployments equantic-space-api / equantic-space-platform, base de datos equantic-space-db).

Runners de CI

Los jobs de pipeline corren como pods aislados en el clúster — un pod por job, con sus propios límites de CPU/memoria, de modo que un job pesado nunca deja sin recursos a la plataforma ni a sus vecinos. La capacidad se deriva del propio clúster (reservando siempre margen para el control plane): los jobs que exceden el techo esperan en la cola, y añadir un nodo a la fleet aumenta la capacidad de CI automáticamente. Un job para el que el clúster todavía no tiene sitio cuando se crea su pod también espera, y lo dice en su log, en lugar de fallar. Su timeout-minutes solo empieza a contar cuando el pod existe.

Cada contenedor del pod de un job está acotado — el propio job, sus sidecars de services: y el daemon Docker por job llevan techos explícitos, y el disco del job tiene un tamaño propio (abajo), de modo que un job desbocado se desaloja a sí mismo en lugar de presionar al nodo. El timeout-minutes del job también queda estampado en el pod: aunque el proceso de la plataforma muera a mitad del job, el clúster retira el pod por su cuenta.

¿Necesitas docker build (o Testcontainers) dentro de los jobs? Pasa EQS_RUNNER_DIND=true al instalador — EQS_RUNNER_DIND=true bash install.sh, en una primera instalación o en una actualización. Cada job recibe entonces su propio daemon Docker privado, nunca compartido entre jobs ni workspaces. Se aplica a todos los jobs de la instalación, y el daemon es privilegiado, así que actívalo a propósito.

Ajustar la capacidad

El cálculo es un buen valor por defecto, no una sentencia: en Configuración → Plataforma → Capacidad de CI decides cuánto de la máquina puede tomar el runner, y cada cambio muestra lo que rendiría en este clúster antes de guardarlo.

El panel junto a los campos responde la pregunta que los números solos nunca respondieron: ¿por qué solo esta cantidad? Para el nodo que lee, muestra lo que el nodo tiene, lo que ocupa la plataforma (su uso medido más el margen de la reserva, no solo el porcentaje), lo que queda para el CI, la porción de cada job y el límite que decidió la cuenta, ya sea la CPU objetivo por job, el piso de memoria, el tope fijo, un tamaño fijo o el piso de admisión. Con más de un nodo, elige cuál leer, y el total de la flota aparece al lado.

Dos mandos importan más. La reserva de la plataforma es la porción de cada nodo que CI nunca toca: bajarla da más a CI en total, aunque entonces caben más plazas repartiendo la memoria y cada job puede quedar más pequeño. Y el «no iniciar por debajo de» es un suelo de admisión: el techo de un job queda congelado al crearse su pod, así que una bajada pasajera de memoria libre puede darle a un build de dos horas un límite con el que no termina. Por debajo del suelo el nodo no ofrece plazas y el job espera en la cola: tarde es mejor que lisiado.

La reserva tiene además un suelo absoluto: al menos 2 núcleos y 3 GiB se quedan con el control plane, diga lo que diga el porcentaje (en hosts diminutos el suelo se encoge por sí solo). Un nodo dedicado a runners, sin plataforma encima, puede poner ese suelo a cero vía EQS_RUNNER_MIN_PLATFORM_RESERVE_CPU / EQS_RUNNER_MIN_PLATFORM_RESERVE_MEMORY_GI.

Guardar esta pantalla toma el mando definitivamente: desde entonces la política guardada es toda la verdad, incluidos los campos que dejaste vacíos, y una variable EQS_RUNNER_* solo siembra una instalación que nunca ha guardado aquí. El cambio se aplica en un ciclo del presupuesto (30 s), sin reiniciar nada. Vacía un campo para devolver esa decisión a la plataforma, y la línea bajo la etiqueta siempre dice de dónde viene el valor.

Memoria que crece con el job

Un job recibía una porción de memoria decidida al crear su pod y fija durante toda su vida. Ese número es una suposición sobre una curva: una compilación es pequeña mientras instala, grande mientras comprueba tipos de todo a la vez, y pequeña otra vez mientras empaqueta. Supón alto y el nodo se desperdicia; supón bajo y una compilación de dos horas muere al 95%.

Con la memoria elástica activada (viene activada), la plataforma observa cada job en ejecución y le sube la memoria en caliente cuando se acerca al techo y el nodo tiene sitio. Petición y límite suben juntos, así que el pod mantiene su clase de QoS y el planificador sigue sabiendo la verdad... y el presupuesto ve el nuevo tamaño, así que el siguiente job no entra en la memoria que este acaba de tomar.

Solo crece hacia el sitio que ve la misma aritmética de la admisión: nunca en la reserva de la plataforma, nunca en la porción de otro job. El techo de crecimiento limita a un job en un nodo grande. Ambos están en Ajustes → Plataforma → Capacidad de CI.

Esto no elimina el exit 137: una asignación única mayor que el sitio restante sigue matando el contenedor entre dos lecturas, y un nodo sin nada libre no tiene qué dar. Necesita Kubernetes 1.33 o posterior para el redimensionado en caliente — un clúster más antiguo mantiene la porción fija de siempre.

Un disco por job

Un job tiene un solo disco, como en un runner de GitHub: su workspace, las imágenes y la caché de build de Docker y todo lo demás que escribe comparten el mismo espacio. Son al menos 14 GB, lo que GitHub documenta para su runner, y normalmente más: la plataforma mide el disco libre de cada nodo, mantiene el piso de espacio libre que defiende el almacenamiento de CI (Configuración → Plataforma → Almacenamiento de CI, 15% por defecto) y reparte el resto entre los jobs que corren. Cuando el disco es lo primero que se agota, decide cuántos jobs corren, y el panel junto a los campos lo indica.

Un job que escribe más allá de su disco es desalojado por el nodo, y su log dice qué límite pasó y dónde se define. En Configuración → Plataforma → Capacidad de CI defines el piso con el que se cuenta un job, o fijas el disco de todos los jobs.

En un nodo que no tiene el piso libre, corre un job a la vez con el disco que haya, y lo avisa en su propio log. Baja el piso para tener más jobs más pequeños, o libera espacio en el nodo.

Ejecuciones que nunca terminan

El techo también se aplica desde fuera. Cuando el clúster nunca tiene sitio — normalmente un tamaño de job fijado por encima de lo que cualquier nodo puede ofrecer — las ejecuciones que vienen detrás se quedarían días en la cola, y un job cuyo ejecutor ha desaparecido seguiría en curso para siempre.

Así que la plataforma se rinde por su cuenta. Una ejecución esperando runner más allá del techo en cola (24 h por defecto), y una ejecución en curso más allá del techo de ejecución (12 h), terminan por tiempo; lo mismo vale para un job suelto más allá del techo por job (6 h), una vez que quien lo ejecutaba ha dejado de contar. El timeout-minutes del propio job siempre gana a este último. Los tres están en Ajustes → Plataforma → Capacidad de CI, y 0 desactiva cualquiera de ellos.

Y cuando la cola no avanza porque nada puede empezar, la pantalla de ejecuciones lo dice, con el tamaño que necesitaría un job.

Apagar una máquina de runner

Un runner Windows o macOS es una VM que esta instalación ejecuta, y mientras nadie le pide nada son 8 GiB parados. En la instalación para la que se escribió esto, las dos retuvieron 14,2 GiB y unos 2,4 cores durante dieciséis horas sin un solo job.

Ajustes → Plataforma → Máquinas de runner ahora tiene un interruptor para cada máquina que la propia plataforma ejecuta. Apagarla pide primero el apagado al sistema invitado y solo entonces detiene la VM, lo que es más limpio y más seguro que matar el proceso, y la memoria vuelve enseguida al resto de la instalación.

Una máquina que registraste por su dirección no se ve afectada: la plataforma se conecta a ella, no apaga tu hardware.

Una máquina que se apaga sola

Cada máquina que la plataforma ejecuta tiene el ajuste Apagar tras … min inactiva, junto a su interruptor. 0 es el valor por defecto y significa nunca — una VM que tarda minutos en arrancar no es lo que toda instalación quiere apagando tras una comida tranquila.

Con un techo definido, la plataforma apaga la máquina cuando nada corre en ella durante ese tiempo, y la enciende de nuevo por su cuenta cuando un job pide ese sistema operativo. El job espera en su propio log mientras arranca, en vez de fallar en una conexión con algo que no está levantado.

Logs y retención

Un solo step puede almacenar hasta 50.000 líneas de log (EQS_RUNNER_MAX_LOG_LINES_PER_STEP; 0 elimina el tope). Superado el tope, el step sigue ejecutándose hasta su conclusión real: se escribe un marcador de truncado bien visible y el resto de la salida se descarta, lo que protege la base de datos y el stream en vivo de un set -x desbocado.

Los logs de step caducan a los 90 días, configurados en la pantalla de Almacenamiento de CI de abajo (0 los conserva para siempre). Las ejecuciones, los jobs, las conclusiones y las anotaciones se mantienen — el historial sigue siendo navegable, solo expira la transcripción en bruto. El recolector de basura de blobs del registry y el ejecutor de retención también vienen activados por defecto (cada EQS_REGISTRY_*_ENABLED=false los desactiva): los blobs sin referencias se recuperan solos, y una política de retención guardada en la pantalla de Artefactos simplemente se ejecuta.

Almacenamiento de CI

La caché de Actions y los artefactos de las ejecuciones viven en el almacenamiento de la plataforma, y en una instalación de un solo servidor ese almacenamiento es el propio disco del servidor. Configuración → Plataforma → Almacenamiento de CI los mantiene dentro de una política, aplicada cada hora:

  • Caché: las reglas de GitHub. Se elimina la entrada que nadie restauró en más de 7 días, y un repositorio que pasa de 10 GB pierde primero las entradas usadas hace más tiempo.
  • Artefactos: se conservan 90 días como máximo (de 1 a 400). El retention-days de un workflow se respeta cuando pide menos, nunca más. Bajar el valor acorta también los artefactos ya guardados.
  • Límite total: la caché y los artefactos juntos pueden ocupar una quinta parte del disco por defecto (o una cantidad de GB; 0 para ninguno). Por encima, se elimina primero lo que se usó hace más tiempo, caché o artefacto, y los artefactos de una ejecución en curso nunca se eliminan. Las reglas por antigüedad no bastan para contener un disco: un repositorio que sube un paquete grande en cada build mantiene todo con menos de 90 días y lo llena igualmente. Con almacenamiento S3 no hay disco que medir, así que define el límite en GB.
  • Mantener libre: el 15 % del disco por defecto. Cuando el disco baja de ese margen por cualquier motivo, CI devuelve exactamente lo que falta, antes de que Kubernetes empiece a desalojar pods. Se comprueba cada cinco minutos.

La pantalla muestra lo que ocupa CI frente al límite, quién ocupa más y qué eliminaron los últimos barridos, fallos incluidos con su motivo; un número que escribas se previsualiza contra este disco antes de guardarlo. Barrer ahora aplica un límite recién bajado sin esperar a la hora. Las variables EQS_CI_CACHE_IDLE_DAYS, EQS_CI_CACHE_REPO_LIMIT_GB, EQS_CI_ARTIFACT_RETENTION_DAYS, EQS_CI_LOG_RETENTION_DAYS, EQS_CI_STORAGE_LIMIT_GB, EQS_CI_DISK_FLOOR_PERCENT solo sirven de semilla a una instalación que nunca guardó esa pantalla.

La porción de la propia plataforma

La plataforma dimensiona sus propios pods a partir del host: base de datos, api y web declaran resource requests derivadas en la instalación (y rellenadas a posteriori por las actualizaciones), de modo que bajo carga el kernel arbitra a favor de la plataforma en lugar de tratar a un job de CI y a la base de datos como iguales. Se puede sobrescribir por componente con variables del estilo EQS_PG_CPU_REQUEST cuando conoces tu máquina mejor que la fórmula.