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, conimport, 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
npmninode_modules. No instalas dependencias connpm install. Los módulos que usas vienen dentro de k6:k6/httppara hacer peticiones HTTP,checkysleepdel módulok6,k6/metricspara 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, nirequireen general —k6 usa módulos ES (import ... from ...), no elrequirede CommonJS de Node—. Tampoco hayprocess, ni elhttpde 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.js | k6 | |
|---|---|---|
| Qué es | Un entorno de ejecución de JavaScript de propósito general | Un generador de carga (binario de Go) que ejecuta JS |
| Motor JS | V8 (el de Chrome) | Un motor propio incrustado en Go |
| Instalar dependencias | npm install, node_modules | No; los módulos (k6/http) vienen dentro |
| APIs disponibles | fs, http, process, npm entero | Solo módulos de k6 (k6/http, check, sleep...) |
| Para qué sirve | Correr apps, servidores, herramientas JS | Solo: generar carga y medir |
| Cómo se invoca | node app.js | k6 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 installynode_modulesson 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/httpy demás vienen dentro del binario; en Node, el cliente HTTP o se importa del core o se instala de npm. - (e) k6.
k6 runes el comando de k6; Node se invoca connode.
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
- k6 — ¿Qué es k6? — la página oficial de introducción: qué es, para qué sirve, y su filosofía de "testing as code".
- k6 — Cómo funciona k6 (runtime y arquitectura) — el detalle de que k6 ejecuta JS en su propio motor y por qué no es Node.
- k6 — Módulos de JavaScript de k6 (
k6/http, etc.) — el catálogo de módulos que vienen dentro del binario, con los que se escriben las pruebas. - Node.js — sitio oficial — para contrastar: el entorno de propósito general que k6 no es, útil para tener claro el punto de comparación.