AWS Builder Center
Runners serverless con AWS Fargate: efímeros, "baratos" y sin servidores que mantener

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.

DevOps & Cloud Enthusiast

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-minutes  dentro 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  :latest  y volver ECR  IMMUTABLE .
  • 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  main  con 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.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article