Guía práctica: cómo autoescalar runners self‑hosted en GitHub Actions para pipelines CI de proyectos PHP
Por Kevin Marcondes · Publicado el · Actualizado el · 9 min de lectura
Guía práctica: cómo autoescalar runners self‑hosted en GitHub Actions para pipelines CI de proyectos PHP. Aquí aprenderás cuándo tiene sentido adoptar esta estrategia, cómo diseñar labels, usar runners efímeros, elegir patrones de autoscaling, configurar workflows y evitar trampas comunes. El objetivo es reducir costos y latencia sin comprometer seguridad ni fiabilidad.
Cuándo tiene sentido autoescalar runners self‑hosted
No todo pipeline necesita runners self‑hosted. Son especialmente útiles cuando buscas menor latencia, control de costes y entornos a medida. Considéralos cuando la cola en los runners hospedados crezca y los builds tarden más en arrancar.
Casos típicos en PHP: Composer con extensiones nativas, builds Docker de la aplicación, pruebas que acceden a recursos internos y pipelines que requieren red privada.
Si necesitas una visión general de CI/CD en PHP antes de avanzar, consulta nuestra guía de CI/CD para PHP/Laravel y luego vuelve al tema de autoscaling.
- Control de costes por minuto y tamaño de máquina.
- Menor latencia cerca del registry, base de datos y cache.
- Entorno a medida: versiones de PHP, extensiones y caches precalentados.
- Acceso seguro a redes privadas y artefactos internos.
- Cumplimiento: aislamiento, auditoría y retención de logs.
Arquitectura: labels, runners efímeros y aislamiento
La base es simple: etiquetas tus runners y tus jobs solicitan esas labels. Usa runners efímeros para que cada job obtenga una VM/contenedor limpio y, al terminar, el runner se auto‑desregistre.
Define una taxonomía de labels que represente capacidades. El scheduler de GitHub empareja jobs y runners por labels, mejorando la predictibilidad y el paralelismo.
Para PHP, combina labels de versión, SO, Docker y tamaño. Esto facilita rutas específicas por stack y optimiza caches. Cuando integres con deploy, alinea las labels con tu pipeline de despliegue automático.
- Ejemplos: self‑hosted, linux, php‑8‑2, php‑8‑3, docker, large, ssd.
- Mantén labels estables y versionadas (php‑8‑3 en vez de latest).
- Prefiere runners efímeros a persistentes para reducir deriva.
- Separa entornos por sensibilidad: prod‑ci y sandbox‑ci.
Patrones de autoscaling: cuál usar en cada escenario
Existen tres patrones recurrentes. Elige según el nivel de control, la complejidad y el coste. Compara a continuación y selecciona el que se ajuste a tu operación y al nivel de habilidades del equipo.
| Patrón | Cómo escala | Pros | Contras | Cuándo usar |
|---|---|---|---|---|
| Kubernetes + controlador de runners | Pods bajo demanda; cada pod ejecuta un runner efímero | Aislamiento fuerte; integración nativa; alta densidad | Requiere k8s; curva de aprendizaje mayor | Equipos con k8s y alta variación de carga |
| VMs con cloud-init/terraform + systemd | Crea/termina VMs según la cola; cada VM corre 1 runner | Máximo aislamiento; control de tamaño y región | Provisionamiento más lento que pods | Workloads pesados, builds Docker y seguridad estricta |
| VM única con múltiples contenedores runner | Escala contenedores en el host según métricas | Operación simple; costes menores | Menos aislamiento; competencia por I/O y red | Equipos pequeños y pipelines moderados |
Setup mínimo de runner efímero en VM (script base)
Este ejemplo registra un runner efímero en un repositorio y ejecuta un único job. Requiere un token con permisos adecuados y acceso root para instalar dependencias. Adáptalo a tu nube y a la política de imágenes doradas.
El runner efímero elimina el registro al concluir. En casos concretos puedes invocar la eliminación explícitamente después del fin.
#!/usr/bin/env bash
set -euo pipefail
# Variáveis de ambiente esperadas:
# GH_REPO="owner/repo" # alvo do runner
# GH_PAT # token com escopo para criar registration tokens
# RUNNER_LABELS="self-hosted,linux,php-8-3,docker"
WORKDIR=/opt/actions-runner
mkdir -p "$WORKDIR" && cd "$WORKDIR"
# 1) Obter registration token de curta duração
REG_TOKEN=$(curl -s -X POST \
-H "Authorization: Bearer ${GH_PAT}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/${GH_REPO}/actions/runners/registration-token \
| jq -r .token)
# 2) Baixar e preparar o runner (ajuste a versão conforme necessidade)
RUNNER_VERSION="2.317.0"
curl -L -o actions-runner.tar.gz \
https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz
tar xzf actions-runner.tar.gz
# 3) Configurar como efêmero e etiquetar
./config.sh \
--unattended \
--url https://github.com/${GH_REPO} \
--token "${REG_TOKEN}" \
--name "runner-${HOSTNAME}-$(date +%s)" \
--ephemeral \
--labels "${RUNNER_LABELS}"
# 4) Executar um único job; o runner encerra após concluir
./run.sh
# Opcional: remoção explícita (caso tenha política de limpeza extra)
# REMOVE_TOKEN=$(curl -s -X POST \
# -H "Authorization: Bearer ${GH_PAT}" \
# -H "Accept: application/vnd.github+json" \
# https://api.github.com/repos/${GH_REPO}/actions/runners/remove-token \
# | jq -r .token)
# ./config.sh remove --unattended --token "${REMOVE_TOKEN}"
Workflow YAML: labels, cache de Composer y concurrencia
Apunta tus jobs a labels específicas. Usa cache de Composer y controla la concurrencia para evitar colas innecesarias. El ejemplo ejecuta tests en distintas versiones de PHP y limita ejecuciones simultáneas por rama.
Ajusta las labels a tu convención. Si usas Docker en el runner, asegúrate de que el usuario tenga permisos sin exponer el socket a jobs no confiables.
name: ci-php
on:
push:
branches: [ main ]
pull_request:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: PHPUnit (PHP ${{ matrix.php }})
runs-on: [self-hosted, linux, docker, php-${{ matrix.php }}]
strategy:
fail-fast: false
matrix:
php: [ '8-1', '8-2', '8-3' ]
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, intl, pdo_mysql, redis
coverage: none
- name: Cache Composer
uses: actions/cache@v4
with:
path: |
~/.composer/cache
key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
composer-${{ runner.os }}-${{ matrix.php }}-
- name: Install deps
run: composer install --no-interaction --prefer-dist --no-progress
- name: Run tests
run: vendor/bin/phpunit --colors=always
timeout-minutes: 30
Orquestando el autoscaling: señales, límites y disparadores
Escalar bien es reaccionar a la cola y evitar el sobre‑aprovisionamiento. Mide el tiempo en cola de los jobs y el tiempo de bootstrap del runner, y ajusta el objetivo de instancias.
Crea disparadores de scale‑out por colas por encima de un umbral y scale‑in cuando la cola esté en cero durante un periodo. Respeta límites de cuota y aplica backoff exponencial al llamar APIs de registro para no superar rate limits.
Los tokens de registro expiran rápido. Généralos bajo demanda en el bootstrap y evita almacenarlos en disco. Precalienta imágenes para reducir el cold start.
- Prioriza PRs recientes para reducir el ciclo de feedback.
- Usa ventanas de enfriamiento entre eventos de escala.
- Precalienta imágenes con PHP, Composer y extensiones.
- Define límites máximos para impedir explosión de costes.
- Cuotas por repositorio y organización, con auditoría.
Observabilidad, coste y rendimiento en pipelines PHP
Recolecta métricas de cola, tiempo de provisionamiento y duración del job. Relaciónalas con el tamaño de máquina y tipo de almacenamiento. Ajusta el autoscaling con datos, no solo con intuición.
Reduce costes acelerando los builds. Imágenes más pequeñas y cache eficiente impactan directamente. Si construyes imágenes Docker en el CI, el cache remoto de BuildKit ayuda mucho. Combínalo con buenas prácticas de multistage y, cuando proceda, con imágenes multiarquitectura.
- Métricas clave: queue_time, runner_boot, job_duration, success_rate.
- Monitorea consumo de CPU, memoria, disco y egress.
- Bake de imagen: PHP, Composer, extensiones y herramientas de test.
- Reutiliza capas Docker entre ramas con cache remoto.
- Evita instalar toolchains en tiempo de job siempre que sea posible.
Seguridad de los runners self‑hosted: prácticas esenciales
Los runners ejecutan código no confiable en PRs. Aislar es obligatorio. Usa runners efímeros, usuarios sin privilegios y segmentación de red. Evita montar secretos innecesarios.
Restringe cuándo PRs desde forks pueden usar tus runners. Prefiere ejecutar solo validaciones seguras o usa entornos dedicados con privilegios mínimos y políticas de secretos estrictas.
Mantén las imágenes actualizadas. Rota tokens y limpia caches entre ejecuciones. Registra eventos de escala y accesos a runners para auditoría.
- Principio de menor privilegio: no root en el job y sin docker.sock en PRs.
- Secretos por entorno y repositorio, con alcance mínimo.
- Bloquea puertos innecesarios y limita egress a destinos requeridos.
- Actualiza el binario del runner y el SO periódicamente.
- Limpieza agresiva de caches entre ejecuciones sensibles.
Preguntas frecuentes
¿Cuándo vale la pena migrar a runners self‑hosted?
Cuando la cola de jobs crece, el boot de los hosted añade latencia, necesitas extensiones nativas específicas, acceso a red privada o quieres controlar costes y tamaños de máquina. Empieza con un piloto en los pipelines más pesados.
¿Los runners efímeros eliminan el registro automáticamente?
Sí, el modo efímero detiene el runner tras un job y elimina el registro. Aun así, implementa rutinas defensivas de limpieza para fallos en el bootstrap o timeouts que puedan dejar registros huérfanos temporalmente.
¿Cómo evitar que PRs desde forks usen mis runners?
Separa workflows por evento. Evita disparar self‑hosted en pull_request desde forks. Reserva self‑hosted para ramas internas o usa entornos aislados con mínimos privilegios y políticas de secretos restringidas.
¿Cómo acelerar Composer en CI sin comprometer la seguridad?
Usa cache por versión de PHP y sistema, invalidado por composer.lock. Almacena vendor solo cuando sea necesario. Ejecuta install con flags de producción cuando aplique y limpia credenciales tras el job. Evita reusar caches entre proyectos distintos.
¿Qué métricas debo vigilar para el autoscaling?
Monitorea queue_time, tiempo de bootstrap del runner, duración del job, tasa de éxito y costes por job. También supervisa CPU, memoria, disco y red para detectar cuellos de botella. Ajusta límites de escala según series históricas.
¿Quieres mejorar el autoscaling de tus runners en GitHub Actions?
Si necesitas revisar labels, aislar la seguridad y reducir latencia en pipelines PHP, describe tu contexto y objetivos para trazar los próximos pasos.
Hablar sobre mi pipeline CIVer también
- Guía: checklist de pre‑deploy y plan de rollback rápido para aplicaciones PHP en producción
- Checklist de seguridad para pipelines CI/CD con runners self‑hosted y gestión de secretos
- Guía: centralización de logs con Fluentd y Elasticsearch para aplicaciones PHP en VPS
- Guía: organizar migraciones y rollbacks en PostgreSQL para equipos pequeños