Guia prático: como auto‑escalar runners self‑hosted no GitHub Actions para pipelines CI de projetos PHP

Por · Publicado em · Atualizado em · 9 min de leitura

Profissional de TI usando laptop em frente a racks de servidores com reflexo no vidro
Ambiente de servidores e operação técnica, adequado para temas de CI e autoscaling. Foto: Christina Morillo / Pexels

Guia prático: como auto‑escalar runners self‑hosted no GitHub Actions para pipelines CI de projetos PHP. Aqui você aprende quando faz sentido adotar, como desenhar labels, usar runners efêmeros, escolher padrões de autoscaling, configurar workflows e evitar armadilhas. O objetivo é reduzir custo e latência sem comprometer segurança e confiabilidade.

Quando faz sentido auto‑escalar runners self‑hosted

Nem todo pipeline precisa de runners self‑hosted. Eles brilham quando você busca menor latência, controle de custo e ambientes sob medida. Considere‑os quando a fila em hosted crescer e builds demorarem apenas para iniciar.

Casos típicos em PHP: Composer com extensões nativas, builds Docker da aplicação, testes que acessam recursos internos e pipelines que exigem rede privada.

Se precisa de uma visão geral de CI/CD em PHP antes de avançar, veja o nosso guia de CI/CD para PHP/Laravel e depois retorne ao tema de autoscaling.

  • Controle de custo por minuto e tamanho de máquina.
  • Menor latência perto do registry, banco e cache.
  • Ambiente sob medida: versões de PHP, extensões e caches pré‑aquecidos.
  • Acesso seguro a redes privadas e artefatos internos.
  • Conformidade: isolamento, auditoria e retenção de logs.

Arquitetura: labels, runners efêmeros e isolamento

A base é simples: você etiqueta seus runners e seus jobs pedem essas labels. Use runners efêmeros para que cada job ganhe uma VM/contêiner limpo e, ao terminar, o runner se autodesregistre.

Defina uma taxonomia de labels que represente capacidades. O agendador do GitHub casa jobs e runners por labels, melhorando previsibilidade e paralelismo.

Para PHP, combine labels de versão, SO, Docker e tamanho. Isso facilita rotas específicas por stack e otimiza caches. Quando for integrar com deploy, alinhe as labels ao seu pipeline de deploy automático.

  • Exemplos: self‑hosted, linux, php‑8‑2, php‑8‑3, docker, large, ssd.
  • Mantenha labels estáveis e versionadas (php‑8‑3 em vez de latest).
  • Prefira runners efêmeros a persistentes para reduzir deriva.
  • Separe ambientes por sensibilidade: prod‑ci e sandbox‑ci.

Padrões de autoscaling: qual usar em cada cenário

Existem três padrões recorrentes. Escolha pelo nível de controle, complexidade e custo. Compare a seguir e selecione o que combina com sua operação e skills da equipe.

Padrão Como escala Prós Contras Quando usar
Kubernetes + controlador de runners Pods sob demanda; cada pod executa um runner efêmero Isolamento forte; integração nativa; alta densidade Requer k8s; curva de aprendizado maior Times com k8s e alta variação de carga
VMs com cloud-init/terraform + systemd Cria/destrói VMs conforme fila; cada VM roda 1 runner Máximo isolamento; controle de tamanho e região Provisionamento mais lento que pods Workloads pesados, builds Docker e segurança rígida
VM única com múltiplos containers runner Escala contêineres no host conforme métricas Operação simples; custos menores Menos isolamento; disputar I/O e rede Times pequenos e pipelines moderados

Setup mínimo de runner efêmero em VM (script base)

Este exemplo registra um runner efêmero em um repositório e executa um único job. Exige token com permissões adequadas e acesso root para instalar dependências. Adapte para sua nuvem e política de imagens douradas.

O runner efêmero remove o registro ao concluir. Em casos específicos, você pode chamar a remoção explicitamente após o término.

#!/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 do Composer e concorrência

Aponte seus jobs para labels específicas. Use cache do Composer e controle concorrência para evitar filas desnecessárias. O exemplo roda testes em diferentes versões de PHP e limita execuções simultâneas por branch.

Ajuste as labels ao seu padrão. Se usar Docker no runner, garanta que o usuário tenha permissão sem expor o socket a jobs inseguros.

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

Orquestrando o autoscaling: sinais, limites e gatilhos

Escalar bem é reagir à fila e evitar superprovisionamento. Meça o tempo em fila dos jobs e o tempo de bootstrap do runner, e ajuste o alvo de instâncias.

Crie gatilhos de scale‑out por filas acima de um limiar e scale‑in quando a fila zera por um período. Respeite limites de cota e faça backoff exponencial ao chamar APIs de registro para não estourar rate limits.

Tokens de registro expiram rápido. Gere‑os sob demanda no momento do bootstrap e evite armazená‑los em disco. Pré‑aqueça imagens para reduzir cold start.

  • Priorize PRs recentes para reduzir ciclo de feedback.
  • Use janelas de resfriamento entre eventos de escala.
  • Pré‑aqueça imagens com PHP, Composer e extensões.
  • Defina limites máximos para impedir explosão de custos.
  • Quotas por repositório e organização, com auditoria.

Observabilidade, custo e performance em pipelines PHP

Colete métricas de fila, tempo de provisionamento e duração do job. Relacione com tamanho de máquina e tipo de storage. Ajuste o autoscaling com dados, não apenas intuição.

Reduza o custo acelerando os builds. Imagens menores e cache eficiente impactam diretamente. Se você constrói imagens Docker no CI, o cache remoto do BuildKit ajuda muito. Combine com boas práticas de multistage e, quando fizer sentido, com imagens multi‑arquitetura.

  • Métricas chave: queue_time, runner_boot, job_duration, success_rate.
  • Acompanhe consumo de CPU, memória, disco e egress.
  • Bake de imagem: PHP, Composer, extensões e ferramentas de teste.
  • Reaproveite camadas Docker entre branches com cache remoto.
  • Evite instalar toolchains em tempo de job sempre que possível.

Segurança dos runners self‑hosted: práticas essenciais

Runners processam código não confiável em PRs. Isolar é obrigatório. Use runners efêmeros, usuários sem privilégios e segmentação de rede. Evite montar segredos desnecessários.

Restrinja quando PRs de forks podem usar self‑hosted. Prefira executar apenas validações seguras ou use ambientes dedicados com privilégios mínimos.

Mantenha imagens atualizadas. Faça rotação de tokens e limpe caches entre execuções. Registre eventos de escala e acesso aos runners para auditoria.

  • Menor privilégio: sem root no job e sem docker.sock em PRs.
  • Segredos por ambiente e por repositório, com escopo mínimo.
  • Bloqueie portas desnecessárias e limite egress a destinos requeridos.
  • Atualize o runner binário e o SO periodicamente.
  • Limpeza agressiva de caches entre execuções sensíveis.

Perguntas frequentes

Quando vale a pena migrar para self‑hosted runners?

Quando a fila de jobs cresce, o boot dos hosted adiciona latência, você precisa de extensões nativas específicas, acesso a rede privada ou deseja controlar custos e tamanhos de máquina. Comece com um piloto nos pipelines mais pesados.

Runners efêmeros removem o registro automaticamente?

Sim, o modo efêmero encerra o runner após um job e remove o registro. Ainda assim, tenha rotina de limpeza defensiva para falhas no bootstrap ou timeouts que deixem registros órfãos temporariamente.

Como evitar que PRs de forks usem meus runners?

Separe workflows por evento. Evite acionar self‑hosted em pull_request de forks. Reserve self‑hosted para branches internas ou use ambientes isolados com privilégios mínimos e políticas de segredos restritas.

Como acelerar Composer no CI sem comprometer segurança?

Use cache por versão de PHP e sistema, invalidado por composer.lock. Armazene vendor apenas quando necessário. Rode install com flags de produção quando aplicável e limpe credenciais após o job. Evite reusar caches entre projetos.

Quais métricas devo observar no autoscaling?

Monitore queue_time, tempo de bootstrap do runner, duração do job, taxa de sucesso e custos por job. Acompanhe também CPU, memória, disco e rede para detectar gargalos. Ajuste limites de escala com base nessas séries históricas.

Quer evoluir o autoscaling dos seus runners no GitHub Actions?

Se você precisa revisar labels, isolar segurança e reduzir latência em pipelines PHP, descreva seu contexto e objetivos para traçar próximos passos.

Conversar sobre seu pipeline CI

Veja também