Guías

Pruebas de carga en GitHub Actions: guía práctica

7 min de lectura

Para hacer pruebas de carga en GitHub Actions basta un archivo de workflow: instalar la herramienta, lanzar una prueba corta contra una URL de staging y dejar que el código de salida de la herramienta haga fallar el job cuando no se cumple un umbral. Sube los resultados como artefacto para que cada ejecución deje constancia. Mantén la prueba pequeña, porque un runner estándar alojado por GitHub es una sola máquina virtual con pocos núcleos de CPU.

Los datos sobre las herramientas y sobre GitHub salen de la documentación de cada proveedor, comprobados el 9 de octubre de 2026, y los workflows siguen ese uso documentado.

HerramientaSe instala conQué hace fallar el jobResultados que conservar
k6setup-k6-action de GrafanaUn umbral incumplidoSalida JSON
Artilleryartilleryio/action-cliUna comprobación ensure fallidareport.json
Locustpip install locustCualquier petición fallida o una regla propiaArchivos CSV y HTML
JMeterEl archivo de la versiónNada integrado: añade un paso de controlArchivo .jtl e informe HTML

¿Qué necesita un workflow de pruebas de carga?

Un disparador, un runner, un paso que instale la herramienta, un paso que ejecute la prueba con umbrales y un paso que suba los resultados. Todo lo demás es opcional.

Guarda el script de prueba en el repositorio, por ejemplo en tests/load/. Lee la URL de staging de una variable del repositorio mediante el contexto vars y fija timeout-minutes en el job: el valor por defecto es de 360 minutos (comprobado el 9 de octubre de 2026).

¿Qué eventos deberían lanzar una prueba de carga?

Ejecuta una prueba corta en los pull requests y otra más larga de forma programada o a mano.

Usa pull_request para detectar una regresión antes del merge, workflow_dispatch para ejecuciones manuales y schedule para ejecuciones cron (en UTC, salvo que indiques una zona horaria). La referencia de eventos de GitHub añade algunos límites (comprobado el 9 de octubre de 2026): las programaciones solo se ejecutan en la rama por defecto, como mucho cada cinco minutos, y pueden retrasarse al comienzo de cada hora. Los pull requests desde forks no reciben secretos, salvo GITHUB_TOKEN.

¿Cómo se ejecuta k6 en GitHub Actions?

Usa las dos acciones oficiales de Grafana, una para instalar k6 y otra para ejecutar el script. El paso falla en cuanto k6 termina con un código de salida distinto de cero, que es lo que provoca un umbral incumplido.

Tanto setup-k6-action como run-k6-action están en v1 (comprobado el 9 de octubre de 2026):

name: load-test
on:
  pull_request:
  workflow_dispatch:

jobs:
  k6:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    env:
      BASE_URL: ${{ vars.STAGING_URL }}
    steps:
      - uses: actions/checkout@v7
      - uses: grafana/setup-k6-action@v1
      - uses: grafana/run-k6-action@v1
        with:
          path: tests/load/smoke.js
          flags: --out json=results.json
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: k6-results
          path: results.json
          retention-days: 14

Los umbrales viven en el script, que lee BASE_URL del entorno:

import http from "k6/http";

export const options = {
  vus: 10,
  duration: "1m",
  thresholds: {
    http_req_failed: ["rate<0.01"],
    http_req_duration: ["p(95)<500"],
  },
};

export default function () {
  http.get(`${__ENV.BASE_URL}/health`);
}

La documentación de k6 aclara que los checks no afectan al código de salida, así que la decisión de aprobar o suspender tiene que venir de los umbrales.

¿Cómo se ejecutan Artillery, Locust o JMeter en su lugar?

Cambia los pasos de instalación y ejecución, conserva el resto del workflow y usa el mecanismo propio de cada herramienta para fallar cuando no se alcanza un objetivo.

Artillery tiene una GitHub Action oficial cuyo README indica que @v2 sigue la última versión 2.x (comprobado el 9 de octubre de 2026). El paso es uses: artilleryio/action-cli@v2 con command: run --target ${{ vars.STAGING_URL }} tests/load/api.yml --output report.json, y el plugin ensure hace que Artillery termine con un código distinto de cero cuando no se alcanza un objetivo. La sección config del script:

config:
  target: "https://staging.example.com"
  phases:
    - duration: 60
      arrivalRate: 5
  plugins:
    ensure:
      thresholds:
        - "http.response_time.p95": 500

Locust termina con código 1 si alguna petición falló, y para reglas de latencia fijas environment.process_exit_code en un listener del evento quitting, como muestra esa misma página (comprobado el 9 de octubre de 2026). Después de actions/setup-python@v7 con python-version: "3.13":

      - run: pip install locust
      - run: >
          locust -f tests/load/locustfile.py --headless
          -u 20 -r 5 -t 2m -H "$BASE_URL"
          --csv results --html report.html

JMeter se ejecuta sin interfaz gráfica con -n, y las opciones de línea de comandos de su manual no incluyen ningún umbral de aprobado o suspenso (comprobado el 9 de octubre de 2026). Un paso corto puede leer el archivo de resultados, en CSV con una columna success por defecto, y hacer fallar el job por su cuenta:

      - uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: "21"
      - name: Install JMeter 5.6.3
        env:
          JM_BASE: https://archive.apache.org/dist/jmeter/binaries
        run: curl -sSL "$JM_BASE/apache-jmeter-5.6.3.tgz" | tar -xzf -
      - name: Run the test plan
        run: >
          apache-jmeter-5.6.3/bin/jmeter -n -t tests/load/plan.jmx
          -l results.jtl -e -o report
      - name: Fail above 1% errors
        run: |
          python3 - <<'EOF'
          import csv, sys
          rows = list(csv.DictReader(open("results.jtl")))
          bad = sum(r["success"] != "true" for r in rows)
          print(f"{bad} of {len(rows)} samples failed")
          sys.exit(1 if not rows or bad > 0.01 * len(rows) else 0)
          EOF

¿Cómo se hace fallar el job cuando la prueba no alcanza un objetivo?

Escribe los objetivos en la prueba como umbrales y deja que el código de salida haga el resto. GitHub marca un paso como fallido cuando su comando termina con un código distinto de cero, y el job falla con él.

Empieza con una tasa de errores estricta y un percentil de latencia generoso, y ajusta el límite de latencia cuando sepas cuánto varían tus cifras entre ejecuciones. La guía paso a paso de pruebas de carga explica cómo elegir esos objetivos.

¿Dónde deben quedar los resultados de cada ejecución?

Súbelos como artefacto del workflow, con if: always() para que una prueba fallida conserve igualmente su informe. Los artefactos se guardan 90 días por defecto, y retention-days acorta ese plazo.

El README de upload-artifact documenta los 90 días por defecto (comprobado el 9 de octubre de 2026). Fija if-no-files-found: error para que un informe ausente haga fallar el paso de forma visible.

¿Cuánta carga puede generar un runner alojado por GitHub?

Suficiente para una prueba de humo o un pequeño control de regresiones. Un runner Linux estándar tiene 4 CPU y 16 GB de RAM en repositorios públicos, 2 CPU y 8 GB en privados, y un job se detiene a las 6 horas.

Esas cifras salen de la referencia de runners y de los límites de Actions de GitHub (comprobado el 9 de octubre de 2026), que no dan ninguna cifra de ancho de banda de red.

Un job corre en una sola máquina, así que es un único generador de carga: si su CPU se satura, los tiempos de respuesta incluyen el tiempo de espera en el propio runner. Además, todas las peticiones salen de esa máquina, de modo que los limitadores de tasa que cuentan por dirección de cliente ven un único cliente muy activo. GitHub desaconseja usar en listas de permitidos los rangos de IP de Azure de sus runners Ubuntu; los runners más grandes (larger runners) de los planes Team y Enterprise Cloud añaden CPU, memoria y direcciones IP estáticas.

¿Cuándo debe venir la carga de otro sitio?

Cuando necesitas más carga de la que produce una máquina, tráfico desde varias regiones o ejecuciones más largas que el límite de un job.

Con k6, run-k6-action ejecuta la prueba en Grafana Cloud k6 si fijas cloud-run-locally: false y pasas K6_CLOUD_TOKEN, K6_CLOUD_PROJECT_ID y, con k6 v2, K6_CLOUD_STACK_ID. Una ejecución en la nube ignora por defecto el entorno del runner, así que añade -e BASE_URL=${{ vars.STAGING_URL }} a flags (comprobado el 10 de octubre de 2026). La acción de Artillery puede llamar a run-fargate o run-lambda para generar la carga desde tu propia cuenta de AWS (ambos comprobados el 9 de octubre de 2026). La comparativa k6 vs Artillery cubre ambas.

Para JMeter, LoadFocus documenta una integración con GitHub Actions basada en su cliente npm. Guardas la prueba en tu cuenta de LoadFocus, añades la clave de API y el ID de equipo como secretos y llamas a run-test. Las opciones de abajo siguen la ayuda del cliente:

      - run: npm install -g @loadfocus/loadfocus-api-client
      - env:
          LF_KEY: ${{ secrets.LOADFOCUS_API_KEY }}
          LF_TEAM: ${{ secrets.LOADFOCUS_TEAM_ID }}
        run: |
          test -n "$LF_KEY"
          test -n "$LF_TEAM"
          loadfocus-api config set \
            --apikey "$LF_KEY" --teamid "$LF_TEAM"
          loadfocus-api jmeter run-test --name checkout-api \
            --thresholds "avgresponse<=200,errors==0,p95<=250"

El cliente espera hasta 1.800 segundos por defecto (--wait-timeout lo cambia), así que da al job un timeout-minutes de más de 30, por ejemplo 40. Termina con código 1 cuando no se cumple un umbral y con 0 cuando falta la clave de API o el ID de equipo, por eso las líneas test -n hacen fallar el paso antes si un secreto está vacío. La carga sale de LoadFocus, que ejecuta scripts de JMeter desde 25 regiones en la nube.

FAQ

¿Cuánto debe durar una prueba de carga en un pull request? Unos pocos minutos: lo bastante para superar la rampa de subida y obtener un percentil estable, y lo bastante poco para que nadie tenga que esperarla.

¿Está permitido hacer pruebas de carga desde runners alojados por GitHub? Las condiciones de GitHub para Actions prohíben perturbar cualquier servicio y limitan los runners alojados al trabajo sobre el propio proyecto del repositorio (comprobado el 9 de octubre de 2026). Probar el entorno de staging de ese mismo proyecto, sin sobrecargar los servidores de GitHub, es el tipo de prueba que describen esas condiciones.

¿Puedo usar un runner autoalojado en su lugar? Sí. Funciona en tu propio hardware y en tu red, y la página de límites de GitHub permite que un job dure ahí hasta 5 días. Sigue siendo un único generador de carga.

Lecturas relacionadas


Las afirmaciones técnicas de este artículo no se han revisado de forma independiente.

LoadFocus team
Escrito por el equipo de LoadFocus, las personas detrás de un servicio de pruebas de carga en la nube.