Módulo 1: Por qué load y performance testing

5. Qué es k6 y su runtime

Descripción

Ya sabes qué preguntas responde una prueba de carga y qué tipos existen; toca conocer la herramienta con la que la industria las hace. Se llama k6: un generador de carga de código abierto, mantenido por Grafana Labs, pensado para desarrolladores. Su idea central —y la que a más gente confunde— es esta: tú escribes tus pruebas en JavaScript, pero k6 no es Node. k6 es un programa escrito en el lenguaje Go que trae dentro su propio motor de JavaScript; cuando corres k6 run script.js, no es Node el que ejecuta tu archivo, sino ese motor incrustado en k6. Suena a detalle técnico, pero tiene consecuencias muy prácticas: no hay npm install, no hay node_modules, no funcionan las librerías de Node ni sus APIs (fs, require, express), y a cambio tienes un solo binario rapidísimo capaz de simular miles de usuarios. En esta lección entendemos qué es k6, por qué corre en su propio runtime, qué puedes y qué no puedes hacer por eso, y por qué en esta guía sus scripts van como contenido rotulado (k6 no está instalado en este entorno).

Conexión con el módulo: esta lección presenta la herramienta; la anatomía de un script k6 —la función default, http.get/post, check(), sleep(), export const options— es el módulo 2 completo. Aquí nos quedamos en el nivel de "qué clase de cosa es k6 y cómo se relaciona con lo que ya conoces (Node, npm, Python)". En la lección 7 verás tu primer script k6 real (como contenido) golpeando Reservo, y su hermano ejecutable en Python. Piensa en esta lección como la presentación del actor antes de verlo actuar.

El electrodoméstico que habla tu idioma pero no es de tu marca

Imagina una batidora industrial que se programa con recetas escritas en español —"bate 2 minutos, reposa 30 segundos, bate 1 minuto"—. Tú ya sabes español, así que escribir la receta te resulta natural. Pero la batidora no es tu cocina de casa: no puedes enchufarle los accesorios de tu licuadora doméstica, no acepta los ingredientes de tu despensa personal, y no tiene los botones de tu microondas. Habla tu idioma (español), pero es una máquina distinta, con su propio motor y sus propias piezas, diseñada para una sola cosa: batir a escala industrial, toneladas de masa, sin recalentarse.

k6 es esa batidora. El "idioma" es JavaScript: escribes tus pruebas en JS porque es un lenguaje que muchos desarrolladores ya conocen, y eso baja la barrera de entrada. Pero la "máquina" no es Node.js, tu entorno JavaScript de siempre. Es un motor distinto, incrustado dentro de un programa escrito en Go, especializado en una sola cosa: generar carga a escala —miles de usuarios virtuales— sin ahogarse. Por eso puedes escribir tus pruebas con la comodidad de un idioma conocido, pero no puedes traer "los accesorios de casa": las librerías de npm, los módulos de Node, las APIs del sistema de archivos. Entender esta doble naturaleza —idioma familiar, máquina especializada— es la clave para no frustrarte cuando require('fs') no funcione.

Qué es k6, en concreto

k6 es una herramienta de código abierto para pruebas de carga y rendimiento. Sus rasgos definitorios:

  • Es un binario único, escrito en Go. Lo instalas como un solo ejecutable (k6), sin un entorno de ejecución alrededor. No arrastra dependencias: descargas el binario y ya corre. Go es un lenguaje pensado para la concurrencia, y por eso k6 puede simular miles de usuarios virtuales con un consumo de recursos modesto —mucho más eficiente que si cada usuario virtual fuera un proceso pesado—.
  • Las pruebas se escriben en JavaScript. Un script de k6 es un archivo .js. Se eligió JavaScript porque es familiar para una enorme cantidad de desarrolladores; escribir una prueba de carga se parece a escribir cualquier script JS, con import, funciones y objetos.
  • Está orientado a desarrolladores y a "testing as code". La prueba es código: se versiona en git, se revisa en un pull request, se corre en CI. No es una herramienta de clics y menús; es un archivo de texto que vive junto a tu código, filosofía que comparte con pytest y Playwright.
  • Se corre desde la terminal. El comando central es k6 run script.js, que ejecuta el script con la carga configurada y, al final, imprime un resumen con las métricas (latencia, throughput, errores) que ya conoces de la lección 3.

En una frase: k6 es un binario de Go que ejecuta scripts de JavaScript para lanzar tráfico contra tu sistema y medir cómo responde bajo carga.

El runtime propio: qué significa "no es Node"

Aquí está el punto que hay que interiorizar, porque explica casi todas las sorpresas de un principiante en k6. Cuando escribes JavaScript, tu instinto (si vienes del mundo web) dice "esto corre en Node". Con k6, no. k6 lleva incrustado su propio motor de JavaScript —un intérprete escrito en Go— que ejecuta tu script. Node nunca interviene. Tu archivo .js es leído y ejecutado por k6, no por node.

Esto tiene consecuencias muy concretas:

  • No hay npm ni node_modules. No instalas dependencias con npm install. Los módulos que usas vienen dentro de k6: k6/http para hacer peticiones HTTP, check y sleep del módulo k6, k6/metrics para métricas propias. Son módulos que el binario ya trae.
  • No funcionan las APIs de Node. Nada de require('fs') para leer archivos como en Node, ni require en general —k6 usa módulos ES (import ... from ...), no el require de CommonJS de Node—. Tampoco hay process, ni el http de Node, ni frameworks como Express. Si copias un snippet de Node que usa esas APIs, no correrá en k6.
  • No corres tu app JavaScript dentro de k6. k6 no es para ejecutar tu servidor Node; es para golpearlo desde fuera. El script k6 es el cliente que genera carga, no la aplicación bajo prueba.
  • A cambio, ganas escala y simplicidad. Un solo binario, sin instalar un ecosistema entero, capaz de generar carga masiva con eficiencia. Ese es el trato: renuncias a las librerías de Node y obtienes un generador de carga especializado y rápido.

Para situarlo frente a lo que ya conoces en el ecosistema Testing:

Node.jsk6
Qué esUn entorno de ejecución de JavaScript de propósito generalUn generador de carga (binario de Go) que ejecuta JS
Motor JSV8 (el de Chrome)Un motor propio incrustado en Go
Instalar dependenciasnpm install, node_modulesNo; los módulos (k6/http) vienen dentro
APIs disponiblesfs, http, process, npm enteroSolo módulos de k6 (k6/http, check, sleep...)
Para qué sirveCorrer apps, servidores, herramientas JSSolo: generar carga y medir
Cómo se invocanode app.jsk6 run script.js

La regla mental: k6 usa JavaScript como idioma, no como plataforma. El idioma es familiar; la plataforma es suya.

Cómo se ve un script de k6 (contenido)

Para que la idea aterrice, aquí tienes el esqueleto de un script de k6 mínimo. Rótulo importante: este bloque es CONTENIDO, no una ejecución de este entorno —k6 no está instalado aquí—. Es correcto y fiel a la documentación oficial de k6; su anatomía la desmenuzamos en el módulo 2. Míralo solo para reconocer la forma:

// CONTENIDO (no ejecutado aquí): un script de k6 minimo. Ver grafana.com/docs/k6
// Los modulos vienen DENTRO de k6 (no de npm): 'k6/http' y 'k6'.
import http from "k6/http";
import { check, sleep } from "k6";

// La funcion 'default' es el codigo que cada usuario virtual (VU) ejecuta en bucle.
export default function () {
  const res = http.get("http://localhost:8000/rooms");

  // check() verifica corrección BAJO carga (no detiene la prueba si falla).
  check(res, {
    "status es 200": (r) => r.status === 200,
  });

  sleep(1); // "think time": pausa como la de un usuario real entre acciones.
}

Fíjate en tres cosas que delatan que no es Node: los import son de "k6/http" y "k6" —módulos que trae el binario, no paquetes de npm—; no hay require por ningún lado; y no hay package.json ni node_modules que acompañen a este archivo. Es un solo .js que k6 sabe correr por sí mismo. Si intentaras ejecutarlo con node script.js, fallaría —Node no conoce "k6/http"—; y al revés, k6 no correría un script que use require('express'). Cada uno habla su dialecto.

Y así se vería el comando y el arranque de su salida —también contenido, no ejecutado aquí—, para que reconozcas la interfaz:

// CONTENIDO (no ejecutado aquí): forma de la invocacion y el arranque de k6 run
$ k6 run script.js

     execution: local
        script: script.js

     scenarios: (100.00%) 1 scenario, 1 max VUs, 10m0s max duration
              * default: 1 iterations shared among 1 VUs

El resumen completo con las métricas (http_req_duration, p95, checks) lo veremos en la lección 7 y, a fondo, en el módulo 3. Por ahora basta con reconocer: k6 run, un script JS con módulos de k6, y un resumen al final.

Por qué en esta guía k6 va como contenido (y por qué está bien)

Como el entorno donde se preparó esta guía no tiene k6 instalado —es un binario de Go aparte, y no lo damos por hecho—, todos los scripts y salidas de k6 los presentamos como contenido rotulado: correctos y fieles a la documentación oficial, pero nunca disfrazados de "lo ejecuté y esto salió". Esta honestidad importa: en performance testing, presentar números inventados como medidos es exactamente el pecado que la disciplina combate. Preferimos decir "así se ve k6" y, para los conceptos (latencia, percentiles, throughput, punto de quiebre), ejecutar de verdad un mini-generador en Python que hace conceptualmente lo mismo a menor escala. Así ves números reales aunque el runner k6 sea contenido. Si tú instalas k6 en tu máquina (con un gestor de paquetes o descargando el binario), estos mismos scripts correrán tal cual; están escritos para ejecutarse, no solo para leerse.

Errores comunes

Intentar npm install o require() en un script de k6. Qué pasa: alguien quiere usar una librería de npm (por ejemplo, una de fechas) dentro de su prueba y hace require('some-npm-lib'); k6 falla. Por qué pasa: se asume que k6 es Node. Cómo detectarlo: si tu script usa require, process, fs o un paquete de npm, no correrá en k6. Cómo corregirlo: usa los módulos que k6 trae (k6/http, check, sleep, k6/metrics) e import de módulos ES. (Sí existen maneras avanzadas de empaquetar dependencias con un bundler, pero eso es terreno especializado; el 95% de las pruebas se escriben solo con los módulos de k6.)

Confundir "la app bajo prueba" con "el script de k6". Qué pasa: alguien cree que tiene que meter su servidor dentro del script de k6, o que k6 "corre su app". Por qué pasa: como ambos son JavaScript, se mezclan los roles. Cómo detectarlo: si tu script k6 intenta ser el servidor en vez de golpearlo, invertiste los papeles. Cómo corregirlo: recuerda que k6 es el cliente que genera carga desde fuera; tu aplicación (Reservo, tu API) corre por separado, y k6 le pega con http.get/http.post. Son dos programas distintos.

Tratar de correr el script de k6 con node. Qué pasa: alguien escribe node script.js esperando que su prueba de k6 corra, y obtiene un error sobre "k6/http" no encontrado. Por qué pasa: el archivo es .js, así que parece que Node debería correrlo. Cómo detectarlo: si invocas node sobre un archivo que importa de "k6/...", estás usando la máquina equivocada. Cómo corregirlo: los scripts de k6 se corren con k6 run script.js, nunca con node. El idioma es JavaScript; el intérprete es k6.

Ejercicios

Ejercicio 1 — ¿Node o k6? Para cada afirmación, di si describe a Node.js, a k6, o a ambos. (a) "Ejecuta código JavaScript." (b) "Se instalan dependencias con npm install y node_modules." (c) "Su propósito es generar carga contra un sistema y medir su rendimiento." (d) "Los módulos como el cliente HTTP vienen dentro del binario, no de un gestor de paquetes." (e) "Se corre con k6 run."

Ver solución
  • (a) Ambos. Los dos ejecutan JavaScript —Node con V8, k6 con su motor propio en Go—.
  • (b) Node. npm install y node_modules son de Node; k6 no los usa.
  • (c) k6. Generar carga y medir rendimiento es el propósito específico de k6; Node es de propósito general.
  • (d) k6. En k6, k6/http y demás vienen dentro del binario; en Node, el cliente HTTP o se importa del core o se instala de npm.
  • (e) k6. k6 run es el comando de k6; Node se invoca con node.

Ejercicio 2 — Diagnostica el error. Un compañero copió este snippet a un script de k6 y no le funciona:

const fs = require("fs");
const axios = require("axios");
const data = fs.readFileSync("rooms.json");

Explica en dos o tres frases por qué ninguna de esas tres líneas corre en k6, y con qué la reemplazarías conceptualmente para hacer una petición HTTP.

Ver solución

Ninguna corre porque las tres asumen que están en Node, no en k6: require no existe en k6 (usa import de módulos ES), fs es una API de Node que k6 no tiene, y axios es un paquete de npm que k6 no puede instalar ni usar. Para hacer una petición HTTP en k6 se usa su módulo propio: import http from "k6/http" y luego http.get(url) o http.post(url, body). (Para leer datos de un archivo, k6 tiene sus propios mecanismos —como open() o SharedArray—, tema avanzado que no toca aquí.)

Ejercicio 3 — Explica el trade-off. En tus palabras (dos o tres frases), ¿qué pierde k6 al no ser Node, y qué gana a cambio? ¿Por qué ese trato tiene sentido para una herramienta cuyo único trabajo es generar carga?

Ver solución

k6 pierde el acceso al ecosistema de Node: no puede usar los cientos de miles de paquetes de npm, ni las APIs del sistema (fs, process), ni frameworks como Express. A cambio gana un solo binario sin dependencias, muy eficiente, capaz de simular miles de usuarios virtuales concurrentes con poco consumo de recursos (gracias a que Go está hecho para la concurrencia). El trato tiene sentido porque el trabajo de k6 es uno solo —generar carga a escala y medir— y para eso no necesita el ecosistema entero de Node; necesita velocidad, concurrencia y simplicidad de despliegue, que es justo lo que su runtime propio le da.

Resumen y siguiente paso

En esta lección conociste k6: un generador de carga de código abierto, un binario único escrito en Go, cuyas pruebas se escriben en JavaScript pero corren en un runtime propio —no en Node—. Entendiste la consecuencia central de eso: no hay npm ni node_modules, no funcionan las APIs de Node (require, fs, process) ni los paquetes externos; en su lugar usas los módulos que k6 trae dentro (k6/http, check, sleep). A cambio ganas escala y simplicidad: un solo binario capaz de simular miles de usuarios. Es la batidora que habla tu idioma pero no es de tu marca.

También quedó claro el porqué de la regla de la guía: como k6 no está instalado en este entorno, sus scripts y su salida van como contenido rotulado —correcto y fiel a la doc oficial, jamás disfrazado de ejecutado—, mientras los conceptos se ejecutan de verdad con el generador en Python. Viste ya la forma de un script (import http from "k6/http", la función default, check, sleep) y del comando (k6 run script.js).

Antes de avanzar deberías poder: explicar por qué un script de k6 no corre con node; nombrar de dónde vienen los módulos de k6 (del binario, no de npm); y decir en una frase qué pierde y qué gana k6 por tener su propio runtime.

Lo que sigue es dejar de hablar de la herramienta y levantar el blanco. En la lección 6 construimos la API de Reservo canónica entera en Python —GET /rooms, POST /quote, POST /book— y la vemos responder los números-ancla (7500, 6000) con salida ejecutada de verdad. Sin blanco no hay carga que medir.

Recursos