Problema
Muchas organizaciones usan GitHub Actions como motor de CI/CD, pero los runners gestionados por GitHub tienen tres limitaciones que aparecen con frecuencia:
- Coste por minuto que supera al de alternativas serverless cuando el flujo de trabajo es intensivo.
- Límite de tiempo (6 h) que impide jobs de larga duración, como compilaciones de firmware o pruebas de integración extensas.
- Compromiso de facturación de 60 s, lo que penaliza pipelines con etapas cortas y alta concurrencia.
El patrón típico es: un equipo necesita ejecutar jobs de forma esporádica, con diferentes arquitecturas (ARM64 vs x86) y sin mantener infraestructura permanente. La solución ideal sería un runner que aparezca sólo cuando hay trabajo, se apague inmediatamente después y cueste lo menos posible.
Causa
Los runners gestionados están diseñados para ofrecer una experiencia “plug‑and‑play” en máquinas virtuales tradicionales. Sus limitaciones provienen de:
- Modelo de facturación fijo: AWS Lambda y otras plataformas serverless facturan por milisegundo, mientras que los runners de GitHub usan un modelo de bloque de 60 s.
- Arquitectura predefinida: Los runners oficiales solo están disponibles en x86_64, lo que impide aprovechar hardware ARM64 más barato.
- Escalado estático: Los runners se provisionan en grupos de autoscaling que pueden permanecer inactivos, generando costos de reserva.
- Restricciones de tiempo: La política de 6 h es una medida de seguridad para evitar procesos colgados, pero no se adapta a jobs que necesitan más tiempo.
Solución
Una arquitectura basada en Lambda MicroVMs (AWS Graviton2 ARM64) permite crear runners “just‑in‑time” (JIT) que cumplen con los requisitos anteriores. El flujo general es:
- GitHub App: crea una aplicación con permisos
actions:writeymetadata:read. Genera un private key para firmar JWT. - IAM Role: concede a la función Lambda permiso para lanzar MicroVMs, leer secretos de Parameter Store y escribir logs en CloudWatch.
- Lambda Function (dispatcher): recibe el webhook
workflow_jobde GitHub, valida el JWT, y lanza una MicroVM con una imagen Docker que contiene el agente de runner. - Terraform module: encapsula toda la infraestructura (App, Role, Lambda, VPC, Security Groups, SSM parameters). Un solo
terraform applycrea el entorno listo para usar. - MicroVM lifecycle: la VM arranca en < 5 s, registra el runner mediante la API de GitHub, ejecuta el job y, al terminar, se deregistra y se apaga automáticamente.
Pasos clave
- Crear la App y exportar
APP_ID,CLIENT_ID,CLIENT_SECRETy la clave privada. - Definir variables en SSM (
/github/runner/app_id,/github/runner/private_key). - Configurar Terraform con los módulos
aws_lambda_function,aws_iam_role,aws_ec2_instance(tipot4g.microbajo la capa de MicroVM) yaws_cloudwatch_event_rulepara el webhook. - Construir la imagen Docker basada en
ubuntu:22.04con el binarioactions-runnery los scripts de registro/deregistro. - Desplegar con
terraform init && terraform apply.
Cuándo aplicar esta solución
- Jobs con alta variabilidad de duración (desde segundos hasta > 6 h) donde el límite de los runners gestionados es un cuello de botella.
- Entornos que pueden usar ARM64 (compilaciones cruzadas, pruebas de contenedores multi‑arch) y buscan reducir el coste de cómputo.
- Equipos que requieren aislamiento: cada job se ejecuta en su propia MicroVM, evitando contaminación de estado.
- Escenarios con bajo nivel de concurrencia: la arquitectura paga solo por el tiempo de ejecución real, ideal para pipelines que se disparan esporádicamente.
No aplicar cuando:
- Se necesita soporte nativo de Windows o GPU, ya que las MicroVMs actuales solo ofrecen ARM64 Linux.
- La política de la empresa prohíbe ejecutar código arbitrario dentro de una función Lambda por motivos de compliance.
Código
# variables.tf
variable "github_app_id" {}
variable "github_private_key" {}
variable "region" {
default = "us-east-1"
}
# main.tf (simplificado)
resource "aws_iam_role" "lambda_role" {
name = "github-runner-dispatcher"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}
resource "aws_lambda_function" "dispatcher" {
function_name = "github-runner-dispatcher"
runtime = "python3.11"
handler = "handler.main"
role = aws_iam_role.lambda_role.arn
filename = "dispatcher.zip"
environment {
variables = {
GITHUB_APP_ID = var.github_app_id
GITHUB_PRIVATE_KEY = var.github_private_key
}
}
}
resource "aws_cloudwatch_event_rule" "github_webhook" {
name = "github-webhook"
event_pattern = jsonencode({
"source": ["aws.lambda"],
"detail-type": ["GitHub Workflow Job"]
})
}
resource "aws_cloudwatch_event_target" "lambda_target" {
rule = aws_cloudwatch_event_rule.github_webhook.name
arn = aws_lambda_function.dispatcher.arn
}
Verificación
- Webhook activo: revisa en la configuración de la GitHub App que la URL apunte a la API Gateway que expone la Lambda.
- Runner registrado: en la página de Settings → Actions → Runners debería aparecer un runner “online” justo después de disparar un workflow.
- Job completado: verifica que el job termina sin errores y que en CloudWatch Logs aparecen los mensajes “Runner registered” y “Runner deregistered”.
- Facturación: en la consola de AWS revisa el uso de MicroVMs; debería coincidir con la duración del job (pocos segundos a minutos).
Notas adicionales
- ARM64 compatibility: la mayoría de los pasos de CI funcionan sin cambios, pero algunos binarios precompilados solo existen para x86_64. Usa
docker buildxpara crear imágenes multi‑arch. - Límites de Lambda: la función dispatcher tiene un timeout máximo de 15 min; si el job supera ese tiempo, la Lambda solo lanza la MicroVM y termina, delegando la gestión al propio runner.
- Seguridad: almacena la clave privada en AWS Secrets Manager con rotación automática y limita el acceso del rol Lambda a
ssm:GetParameterysecretsmanager:GetSecretValue. - Escalado concurrente: si varios jobs llegan al mismo tiempo, la Lambda puede lanzar varias MicroVMs en paralelo; ajusta el límite de instancias en la VPC para evitar sobresaturación.
- Limpieza: la imagen Docker incluye un script
cleanup.shque elimina artefactos temporales y revoca el token de runner antes de apagar la VM. Esto evita fugas de credenciales.