
Runners serverless con AWS Fargate: efímeros, "baratos" y sin servidores que mantener
Si alguna vez trabajaste con runners autoalojados para GitHub Actions, sabes lo que implican: al principio basta con una EC2 y el agente instalado, pero después llegan el mantenimiento, los parches, el monitoreo y el costo constante, incluso sin CI corriendo. Por eso decidí hacerlo diferente: runners efímeros que nacen para ejecutar un solo job y desaparecen al terminar. Aquí te cuento cómo funciona.
Runners de GitHub Actions serverless con AWS Fargate: efímeros, baratos y sin servidores que mantener
Si alguna vez has trabajado con runners autoalojados para GitHub Actions probablemente ya conoces el problema. Al inicio parece bastante sencillo: levantar una EC2, instalar el agente y dejarla corriendo. El problema empieza después. Esa máquina necesita actualizaciones, parches, monitoreo y mantenimiento, además de que sigue generando costo aunque nadie esté corriendo CI. En nuestro equipo llegamos a ese punto y decidimos resolverlo de otra forma. En lugar de mantener runners viviendo permanentemente, construimos runners que nacen para ejecutar un solo job y después desaparecen. De ahí nace runnerless, una arquitectura donde AWS Fargate, Lambda, SQS y API Gateway reemplazan las máquinas de larga duración. El resultado es un costo idle cercano a cero y sin credenciales estáticas de AWS circulando entre los runners.
Este artículo explica cómo funciona la arquitectura, las decisiones que se fueron tomando y algunos de los problemas que aparecieron ya con tráfico real. Con tráfico real y varios cientos de ejecuciones en periodos de alta actividad, la carga terminó dando forma a buena parte del diseño actual.
El problema: runners autoalojados que no envejecen tan bien
El modelo clásico del runner autoalojado es tener una o varias máquinas EC2 corriendo los agentes de GitHub Actions de forma permanente. Es una solución que funciona, pero arrastra varios costos que no siempre se ven desde el inicio. Hay un costo fijo de cómputo las 24 horas del día mientras la máquina siga encendida, esté corriendo CI o no. También necesita mantenimiento cada cierto tiempo: parches del sistema operativo, actualizaciones del runner, rotación de credenciales, revisión del disco y del propio servicio. Otro punto es la contaminación entre jobs. Al ser una máquina de larga vida puede arrastrar archivos, procesos o cambios de entorno de una ejecución a otra.
Y además existe una superficie de ataque persistente. Una máquina que vive días, semanas o meses con acceso a secretos y a una red interna termina siendo un objetivo bastante más atractivo que un contenedor que existe unos cuantos minutos. Antes de construir algo propio se evaluaron varias alternativas. Primero, los runners hospedados directamente por GitHub. Cumplen su función y son probablemente la opción más sencilla, pero no tienen acceso directo a nuestra VPC privada, existen límites de concurrencia y minutos en los planes pagos, y tampoco tenemos el mismo control sobre el entorno o la identidad IAM. Otra opción fue actions-runner-controller (ARC) sobre Kubernetes. Es una solución bastante potente y ampliamente utilizada, pero requiere operar un clúster EKS completo: control plane, nodos, upgrades y todo lo que implica Kubernetes.
Es una gran solución, simplemente para nuestro caso era más infraestructura de la que necesitábamos mantener. También están las soluciones tipo warm pool sobre EC2 + Lambda, siguiendo patrones como el popularizado por philips-labs. Tienen más funcionalidades, pero también agregan más piezas y una curva de mantenimiento mayor. La solución a la que llegamos fue construir la infraestructura mínima event-driven que pudiera darnos runners aislados, efímeros y baratos, utilizando Fargate como la capa de cómputo sin tener servidores que administrar.
La idea: efímero + event-driven + serverless
La idea es bastante simple de explicar: reemplazar las máquinas siempre encendidas por tareas de Fargate que nacen cuando llega un webhook, ejecutan exactamente un job y después se destruyen. Ninguna pieza del camino crítico es una máquina que el equipo tenga que administrar.
Capa | Servicio | Se paga por |
|---|---|---|
Ingreso del webhook | API Gateway HTTP API | request, con free tier para volúmenes bajos |
Lógica de despacho | AWS Lambda, Python 3.12, 256 MB, timeout 30s | tiempo de ejecución |
Buffer de jobs | SQS + DLQ | request |
Drenado de respaldo | EventBridge rate(1 minute) → Lambda | invocación |
Cómputo | ECS Fargate, 4 vCPU / 8 GB en producción | segundos que dura el job |
Secretos | AWS Secrets Manager | aproximadamente $0.40 por secreto al mes, actualmente 2 |
Imagen | Amazon ECR | almacenamiento |
Logs | CloudWatch Logs | almacenamiento |
Red | VPC + subnets públicas + Internet Gateway | $0 sin NAT Gateway |
Cuando no hay jobs corriendo prácticamente no existe cómputo facturándose. El costo idle queda principalmente en los dos secretos, almacenamiento de la imagen y logs. SQS, EventBridge y las alarmas de CloudWatch se mantienen dentro del free tier con el volumen actual. Hay un punto importante en esta parte: la configuración de infraestructura ya contempla en la configuración de infraestructura el uso de endpoints Interface para
ecr.api , ecr.dkr y logs . La idea es que el pull de las imágenes y los logs viajen por la propia red de AWS en lugar de salir por el Internet Gateway. Esta configuración todavía no se encuentra aplicada en el entorno actual. Si se aplica, los tres endpoints empiezan a cobrar por hora y por AZ estén corriendo jobs o no. Con varias subnets, estos endpoints pueden convertirse en un costo fijo permanente.Eso representaría el primer costo idle considerable del diseño y es justamente el tipo de costo fijo que se quería evitar al no utilizar NAT Gateway.
Recorrido por la arquitectura
El flujo general es este:
text
GitHub││ workflow_job = queued│ webhook HTTPS firmado HMAC-SHA256▼API Gateway HTTP API│▼Lambda de despacho│├─ valida X-Hub-Signature-256├─ filtra workflow_job + label "fargate"├─ envía SIEMPRE el job a SQS└─ intenta drenar la cola según la capacidad disponible│▼ECS Fargate Task│├─ pull lazy usando SOCI v2├─ obtiene GITHUB_TOKEN desde Secrets Manager├─ solicita registration-token a GitHub├─ config.sh --ephemeral --disableupdate --replace│▼GitHub asigna el job│▼runner ejecuta│▼run.sh termina│▼ECS Task termina + runner desaparece
El drenado de la cola también se intenta cuando llega un webhook
workflow_job.completed , ya que se acaba de liberar capacidad. Adicionalmente tenemos una regla de EventBridge que corre cada minuto como respaldo por si alguno de esos eventos se pierde.Recepción y validación
GitHub emite un evento
workflow_job con action=queued . El webhook llega firmado y la Lambda valida su autenticidad antes de procesarlo. La Lambda recupera el secreto necesario desde Secrets Manager y valida la firma utilizando una comparación segura. Después se filtra por el tipo de evento y por el label objetivo fargate . Esto permite que los runners de Fargate convivan con runners hospedados directamente por GitHub dentro del mismo repositorio.Cola y control de capacidad
Una diferencia importante contra la primera versión es que actualmente no existe una ruta directa de lanzamiento. Todo job que llega como
queued primero se manda a la cola SQS. Se guarda owner, repo, job_id , run_id y labels. Después la misma invocación intenta drenar la cola. El cupo se calcula de esta manera:text
cupo disponible = tope de concurrencia - (RUNNING + PENDING)
Actualmente existe un límite de concurrencia definido para evitar saturación y controlar costos. El número de tasks se obtiene utilizando
ecs:ListTasks sobre la family del runner. También existe ecs:DescribeClusters como respaldo, ya que los contadores del clúster son eventualmente consistentes y anteriormente llegaron a provocar que algunos jobs no fueran lanzados. Cuando llega un workflow_job.completed se vuelve a drenar la cola. EventBridge también ejecuta el drenado cada minuto como respaldo. Si un lanzamiento falla, el mensaje se mantiene en la cola para ser reintentado. Los mensajes tienen una ventana de retención definida y, después de varios fallos, terminan en una dead-letter queue para su revisión.Este buffer no estaba en la primera versión. Se agregó después de encontrar uno de los problemas más importantes del diseño original, del que hablamos más adelante.
Ejecución efímera
Cuando existe capacidad disponible la Lambda llama
ecs:RunTask con count=1 . La task se despliega en una subnet pública con IP pública y se inyectan RUNNER_NAME y RUNNER_LABELS utilizando containerOverrides . ECS obtiene la imagen desde ECR. El pull se hace de manera lazy utilizando el índice SOCI v2 ligado a la imagen. Fargate puede empezar a ejecutar el contenedor sin descargar primero todas las capas y posteriormente trae los bloques conforme los necesita. El token de GitHub se obtiene desde Secrets Manager utilizando el Task Execution Role y se inyecta como variable de entorno. La credencial no está dentro de la imagen, tampoco dentro de la Lambda y no queda escrita como texto plano en el estado de Terraform. Después el entrypoint solicita a GitHub un registration token de corta duración y configura el runner:Bash
config.sh --ephemeral --disableupdate --replace
GitHub manda el job al runner recién conectado. El job utiliza el Task Role para acceder a AWS, por lo que tampoco necesita claves estáticas. Cuando termina
run.sh , el contenedor se detiene, la task termina y el runner efímero desaparece de GitHub. En todo el camino, tanto Lambda como runner utilizan roles IAM en lugar de credenciales estáticas.Las piezas
Imagen del runner
La imagen está construida sobre Ubuntu. Tiene el agente de GitHub Actions fijado. También incluye herramientas base y algunas herramientas SAST listas desde el arranque:
-
bandit
-
semgrep
-
checkov
Tenemos Node.js 24 con pnpm instalado mediante
npm install -g . También openjdk-17-jre-headless , principalmente para poder ejecutar herramientas como DynamoDB Local como un proceso nativo. Incluye además las dependencias de Chromium/Playwright. Actualmente se utiliza una sola imagen para toda la organización. Desde principios de septiembre la imagen también se publica con un índice SOCI v2, Seekable OCI. El Dockerfile no cambia. El índice se genera desde el pipeline de build. La razón para meter SOCI fue principalmente el tamaño de la imagen. Entre Node, Java, Chromium, SAST y AWS CLI, el pull estaba tomando una parte importante del cold start. Con SOCI la task puede empezar a traer solamente los bloques que necesita.Entrypoint y ciclo de vida
El entrypoint es una de las piezas importantes para realmente hacer que el runner sea efímero. Actualmente existen dos límites de tiempo. El primero es el límite total:
Bash
timeout --signal=TERM --kill-after=30s "${RUNNER_TIMEOUT_SECONDS}" ./run.sh
El runner tiene un límite total de ejecución para evitar tareas colgadas indefinidamente. Este límite contempla tanto el tiempo esperando como la ejecución del job. Sin ese límite un job atorado puede mantener
run.sh corriendo indefinidamente y la task de Fargate seguiría cobrando. El segundo límite es un watchdog de asignación. También existe un watchdog de asignación con un tiempo corto de espera. Un subshell espera un marker creado por un hook nativo del runner cuando realmente comienza un job. Si pasan los 180 segundos y ningún job comenzó, se manda SIGTERM . Esto apareció por un problema bastante específico. Había runners que se registraban correctamente pero nunca recibían un job, ya fuera por una carrera entre eventos o porque el job se canceló mientras estaba en cola.Antes un runner podía quedarse ocupando capacidad durante demasiado tiempo. Con el watchdog, ese slot se libera mucho antes. Cuando se recibe una señal de terminación,
deregister() solicita un remove-token y ejecuta config.sh remove . Con esto evitamos dejar runners offline registrados en GitHub.Lambda de despacho
La Lambda utiliza una configuración ligera y un timeout corto, suficiente para validar, encolar y despachar trabajos. No tiene dependencias adicionales fuera de
boto3 . Una decisión importante es que no sobrescribe la configuración de propietario ni la configuración de repositorio dentro de containerOverrides . Esos valores se definen desde la task definition durante el despliegue. Esto es importante porque son los valores que determinan si el runner se registra a nivel de organización o de repositorio. La Lambda también genera algunas métricas mediante EMF, Embedded Metric Format. Se escriben como una línea JSON en logs y CloudWatch las extrae sin necesitar permisos adicionales de PutMetricData . Las métricas actuales son:-
JobsBuffered: cada job que entra a la cola.
-
RunnerLaunched: cada task lanzada.
-
CapReached: cuando no hay cupo disponible.
-
DrainDropped: mensajes ilegibles descartados.
Ahora que todos los jobs pasan por el buffer,
JobsBuffered y RunnerLaunched normalmente se mantienen bastante cerca. Por eso CapReached terminó siendo una métrica mucho más útil para saber cuándo realmente estábamos pegándole al límite de capacidad.Terraform e infraestructura
La infraestructura está separada en diferentes piezas con permisos limitados. Tenemos la VPC sin NAT Gateway y un security group para los runners sin reglas de entrada. También están:
- dos secretos en Secrets Manager;
- el cluster de ECS con Container Insights;
- la cola una cola SQS;
- la DLQ;
- la regla de EventBridge;
- tres roles IAM;
- tres alarmas de CloudWatch;
- un tópico SNS;
- un rol adicional para publicar imágenes en ECR desde GitHub Actions utilizando OIDC.
Las alarmas actuales revisan mensajes en la DLQ, edad del mensaje más viejo y errores en Lambda. El costo de las alarmas es marginal frente al costo de cómputo. Terraform actualmente utiliza un backend en S3 con versionado, cifrado y locking nativo mediante
use_lockfile , sin una tabla DynamoDB. Esto último no estuvo así desde el inicio y también terminó siendo uno de los problemas del proyecto.Los gotchas del arranque
Los siguientes problemas aparecieron durante las primeras semanas, todavía cuando la arquitectura utilizaba una ruta directa hacia
RunTask . No son necesariamente bugs raros, pero varios solamente aparecieron cuando empezamos a correr tráfico real.Síntoma | Causa | Solución |
|---|---|---|
ecs.run_task() regresaba AccessDeniedException aunque existía ecs:RunTask | Utilizar tags= dentro de run_task() genera internamente un ecs:TagResource sobre la nueva task | Agregar ecs:TagResource limitado a task/<cluster>/* al rol de Lambda |
El runner se registraba a nivel de repo aunque el stack estaba configurado a nivel de organización | La Lambda sobrescribía la configuración de repositorio utilizando el repo recibido en el webhook | Dejar la configuración de propietario y la configuración de repositorio definidos por la task definition |
Un job podía quedarse queued cerca de 9 minutos y nunca ejecutarse | El runner estaba desactualizado, se actualizaba al conectar y perdía la asignación del job efímero | Fijar una versión reciente y usar --disableupdate |
docker push fallaba con tag is immutable | ECR se había creado como IMMUTABLE aunque se utilizaba :latest | Utilizar MUTABLE o migrar a tags por git SHA |
Tasks de Fargate llegaron a ejecutarse más de 15 horas | Un runner se registraba pero nunca recibía un job y run.sh se quedaba esperando | Timeout total, watchdog de asignación de 180s y timeout-minutes en los workflows |
La condición IAM del sub de GitHub OIDC no hacía match | La organización utiliza stable/immutable OIDC sub claims y el subject contiene IDs numéricos | Utilizar el formato con IDs en StringLike , validado con CloudTrail |
De 70 alertas de saturación a cero
Después de las primeras semanas los problemas cambiaron. Ya no eran tanto errores básicos del despliegue sino situaciones que solamente aparecen cuando empiezas a meter más volumen.
La ruta directa perdía jobs
Este fue probablemente uno de los problemas más importantes. En el diseño original, si
ecs:RunTask fallaba por throttling, falta de ENIs o una cuota de Fargate, la Lambda respondía 500. Pero el job no se guardaba en ningún lugar. GitHub tampoco vuelve a enviar el webhook queued , así que ese job simplemente podía quedarse perdido. Además, como los jobs nuevos podían intentar lanzarse directamente, se terminaban adelantando a otros que ya llevaban tiempo esperando. En ese momento algunos mensajes incluso podían caducar después de 30 minutos. Por eso actualmente todos los jobs entran primero a SQS. El drenado de esa cola es ahora el único camino para lanzar un runner. Si RunTask falla, el mensaje sigue ahí y se vuelve a intentar. La retención también se aumentó de 30 minutos a 2 horas.Lambda empezó a costar más sin aumentar CI
En algún punto se agregó tolerancia a empty polls. La implementación hacía 2 segundos por 2 intentos. Eso provocó que prácticamente cada invocación, incluyendo el tick de EventBridge de cada minuto, se quedara esperando. La duración facturada pasó de aproximadamente 170 ms a unos 4,900 ms por invocación. El problema real podía solucionarse de una forma mucho más sencilla. Un solo long poll de 1 segundo era suficiente para evitar los falsos vacíos provocados por short-poll. Se quitó entonces el bucle de reintentos. Después apareció otra mejora. Aunque la cola estuviera vacía, el tick de respaldo seguía consumiendo alrededor de 1.1 segundos. Actualmente primero consultamos
ApproximateNumberOfMessages . Si está en cero, la Lambda termina sin hacer long poll ni revisar capacidad. Las invocaciones ociosas bajaron a aproximadamente 50 ms.La validación SOCI estaba revisando el lugar equivocado
El pipeline convierte la imagen en un OCI Image Index utilizando
soci convert y después la publica con skopeo copy . Al final existe una validación que falla el build si el índice no terminó correctamente dentro de ECR. La idea es evitar que una regresión publique silenciosamente una imagen normal y de repente vuelvan a aumentar los cold starts. El problema fue que inicialmente la validación buscaba artifactType en el descriptor del Image Index. Ese valor siempre regresaba null . El marcador que realmente necesitábamos revisar estaba dentro de config.mediaType del manifest hijo. Después de corregir la validación se hizo un canary temporal. El pull de imagen pasó de aproximadamente 38 segundos a 4 segundos.El límite de concurrencia se quedó corto
En una jornada de alta carga se registraron decenas de eventos
CapReached . El límite en ese momento era de 6 tasks. Ese límite se había definido antes de incorporar repositorios con mayor volumen y terminamos llegando a picos de entre 250 y 380 jobs diarios. Se aumentó el límite a 20. Eso ayudó, pero todavía aparecieron eventos de saturación al día siguiente. Los logs confirmaron que el límite seguía alcanzándose. La causa no era solamente la cantidad de slots. Los jobs más pesados, entre ellos cargo test , un build de release en musl y next build , eran CPU-bound ejecutándose sobre 2 vCPU. Como tardaban más, también mantenían ocupados los slots durante más tiempo. La task definition se cambió entonces a 4 vCPU / 8 GB. Fargate exige memoria proporcional, por lo que al aumentar CPU también se aumentó la RAM.Después del ajuste, los eventos de saturación desaparecieron en las observaciones posteriores. Hay otro detalle que todavía está pendiente. Producción actualmente va por delante de
main en dos puntos. Algunos cambios de capacidad se aplicaron antes de quedar completamente reconciliados con la rama principal. El backend remoto soluciona dónde vive el state, pero no evita que exista diferencia entre lo que está desplegado y lo que realmente está dentro del repositorio. Eso sigue dentro del backlog.Seguridad
Uno de los principios del diseño es mantener cero credenciales estáticas. Ni Lambda ni los runners utilizan
AWS_ACCESS_KEY_ID . Todo trabaja mediante roles IAM. Actualmente existen tres roles principales. El rol de Lambda tiene permisos acotados únicamente a las operaciones necesarias para lanzar tareas, consultar capacidad, leer la información requerida y trabajar con la cola de mensajes. El Task Execution Role es utilizado por el agente de ECS. Cuenta con los permisos necesarios para iniciar la tarea y recuperar las credenciales de ejecución requeridas. El Task Role es el rol que realmente ve el job durante la ejecución. Está vacío por defecto y solamente se amplía cuando algún pipeline necesita permisos específicos. También tenemos el security group del runner sin reglas de entrada.Los runners únicamente hacen long polling saliente hacia GitHub por el puerto 443. Cada job corre además en un contenedor nuevo, sin compartir estado con el job anterior. El webhook utiliza HMAC-SHA256 y la comparación de la firma se hace en tiempo constante.
Costos
La diferencia principal contra una EC2 tradicional está en cuándo se paga el cómputo. Una
t3.medium encendida 24/7 cuesta del orden de 30 USD al mes aunque solamente se utilice CI unas dos horas al día. Con Fargate pagamos el tiempo que realmente duran esas ejecuciones. Los costos fijos restantes son principalmente:- dos secretos;
- almacenamiento de la imagen en ECR;
- almacenamiento de logs;
- SQS;
- EventBridge;
- tres alarmas de CloudWatch.
Las tres alarmas están en el orden de $0.30 USD al mes. También hay que considerar que aumentar capacidad aumenta la exposición de costo. El límite de concurrencia también funciona como un techo de exposición de costo: aumentar el número de tasks y los recursos por runner incrementa el consumo máximo posible. El diseño original utilizaba menos concurrencia y menos CPU por runner. Por eso el límite de concurrencia no solamente controla throughput. También sirve como límite de cuánto puede llegar a costar el sistema si algo sale mal. Otro problema de costo eran los runners huérfanos. Actualmente se cubre con tres niveles:
- watchdog de asignación de 180 segundos;
- timeout total de 20 minutos;
-
timeout-minutesdentro de los propios workflows.
Las limitaciones, al punto
El diseño también tiene algunas limitaciones claras.
Docker-in-Docker
Fargate no permite contenedores
privileged ni expone el socket del daemon. Por eso Docker-in-Docker no está soportado. Para builds de imágenes AWS recomienda CodeBuild. Según el caso existen alternativas serverless como BuildKit rootless o Kaniko dentro de la imagen, autenticándose contra ECR mediante Task Role. También se puede mandar el contexto a S3 y lanzar AWS CodeBuild desde el job cuando se necesita docker-compose o testcontainers. Otra alternativa para algunos casos es ejecutar el servicio de pruebas directamente como proceso nativo dentro del contenedor. Actualmente ninguna de estas soluciones está incluida porque ningún pipeline onboardeado lo necesita todavía. La opción de tener un host Docker remoto en EC2 se descartó porque volvería a introducir una máquina encendida 24/7. services: de GitHub Actions
Los
services: de GitHub Actions tampoco funcionan dentro de este modelo. Para una base de datos de pruebas, por ejemplo, la alternativa es correr el servicio como proceso nativo. Un caso sería:Bash
java -jar DynamoDBLocal.jar -inMemory
en lugar de declararlo mediante
services: .Latencia bajo carga
Cuando se alcanza el límite de tasks los jobs esperan dentro de SQS. Normalmente el drenado se activa cuando termina otro job. Si ese evento
completed se pierde, EventBridge vuelve a intentar el drenado en el siguiente tick, por lo que la espera adicional puede ser de hasta un minuto. Si existe saturación durante demasiado tiempo, un mensaje puede llegar a las 2 horas de retención y quedarse sin runner. Para detectar esta situación existe una alarma dedicada.Cold start
SOCI redujo bastante el tiempo de descarga de la imagen. El uso de SOCI redujo de forma considerable el tiempo de descarga de la imagen. Eso no elimina completamente el cold start. Todavía existe el tiempo necesario para provisionar la task y registrar el runner. No tenemos actualmente un warm pool. Existe una propuesta escrita con los cálculos de costo, pero todavía no existe implementación.
Backlog
Algunas cosas ya están terminadas y otras siguen pendientes.
- ~~Backend remoto para Terraform~~ — hecho. S3 con versionado, cifrado, bloqueo de acceso público, política que rechaza conexiones sin TLS y locking nativo.
- ~~SOCI~~ — hecho. SOCI v2 está en producción y redujo de forma considerable el tiempo de pull de la imagen.
- VPC Endpoints — el código ya existe, falta decidir si el costo fijo de los tres endpoints Interface vale la pena contra seguir utilizando Internet Gateway.
- Runner groups — separar runners por equipo o label y limitar qué repositorios pueden utilizar cada pool.
- Tags versionados — utilizar git SHA en lugar de
:latesty volver ECRIMMUTABLE.
- Warm pool / pre-provisioning — existe la propuesta pero todavía no hay código.
- ARM64 / Graviton — podría reducir el costo por segundo de Fargate. Actualmente es mutuamente excluyente con SOCI, que ya está en producción. La decisión se tomaría haciendo un canary.
- Reconciliar
maincon producción — mergear el cambio de 4 vCPU y commitear el límite de 20.
- Imagen más ligera — recuperar la limpieza de
apt, docs y man que quedó dentro de otra rama.
- Suscripción al SNS — actualmente el tópico existe pero todavía no tiene suscripción real por email o Slack.
Para finalizar
API Gateway, Lambda, SQS, Fargate, Secrets Manager y OIDC no son tecnologías nuevas. Lo diferente para este caso fue combinarlas para resolver un problema bastante específico: runners de CI que normalmente siguen generando costo y mantenimiento aunque nadie los esté utilizando. Con este diseño evitamos tener que mantener un clúster Kubernetes completo y al mismo tiempo tenemos mayor control sobre el entorno que utilizando solamente runners hospedados. No es una arquitectura perfecta. Existe cold start, todos los repos comparten una imagen y como pasó con el límite de concurrencia, varias cosas solamente se entendieron cuando empezamos a ver métricas con carga real. También hubo errores que obligaron a cambiar partes importantes del diseño, como pasar todo el lanzamiento por SQS o agregar el watchdog de asignación.
Pero actualmente es una infraestructura que, después de desplegarse y ajustarse, requiere muy poca atención y prácticamente no factura cómputo cuando nadie está ejecutando CI. Para equipos que necesitan runners propios, acceso a recursos de AWS o que están limitados por la cantidad de minutos disponibles dentro de GitHub Actions, es una alternativa que vale la pena evaluar.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article