Что такое k6?
k6 — это современный open-source инструмент нагрузочного тестирования, созданный Grafana Labs. В отличие от GUI-подхода JMeter, k6 использует скрипты на JavaScript, которые вы пишете в редакторе кода и запускаете из командной строки. Это делает его естественным выбором для разработчиков и инженеров автоматизации, предпочитающих код конфигурации.
k6 написан на Go, что обеспечивает отличные характеристики производительности. Одна машина с k6 может симулировать тысячи виртуальных пользователей с низким потреблением ресурсов по сравнению с JMeter. Инструмент естественно интегрируется в CI/CD пайплайны, что делает его идеальным для shift-left подхода к тестированию производительности.
k6 vs JMeter: Когда использовать какой инструмент
Прежде чем погружаться в k6, полезно понять, где он превосходит JMeter.
| Характеристика | k6 | JMeter |
|---|---|---|
| Скриптинг | JavaScript (код) | GUI + XML |
| Потребление ресурсов | Низкое (Go runtime) | Высокое (Java/JVM) |
| Поддержка протоколов | HTTP, WebSocket, gRPC | HTTP, JDBC, FTP, SMTP, LDAP, JMS |
| Интеграция с CI/CD | Нативная (CLI-first) | Требует плагинов |
| Распределённое тестирование | k6 Cloud или xk6-distributed | Встроенный master/slave |
| Тестирование браузера | Модуль k6 browser | Не поддерживается |
| Кривая обучения | Легко, если знаете JS | Средняя (на основе GUI) |
| Сообщество | Быстро растёт | Очень большое, зрелое |
Выбирайте k6, когда: Ваша команда знает JavaScript, нужна интеграция с CI/CD, тестируете HTTP/gRPC/WebSocket API или хотите лёгкое локальное выполнение.
Выбирайте JMeter, когда: Нужна поддержка протоколов помимо HTTP (JDBC, FTP, JMS), команда предпочитает GUI или нужна зрелая экосистема с обширными плагинами.
Установка k6
# macOS
brew install k6
# Windows
choco install k6
# Linux (Debian/Ubuntu)
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D68
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6
# Docker
docker run --rm -i grafana/k6 run - <script.js
Проверьте установку:
k6 version
Ваш первый скрипт k6
Вот базовый скрипт k6, отправляющий HTTP GET-запросы:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10, // 10 виртуальных пользователей
duration: '30s', // выполнять 30 секунд
};
export default function () {
const res = http.get('https://test-api.k6.io/public/crocodiles/');
check(res, {
'статус 200': (r) => r.status === 200,
'время ответа < 500мс': (r) => r.timings.duration < 500,
'body содержит crocodiles': (r) => r.body.includes('crocodiles'),
});
sleep(1); // ждать 1 секунду между итерациями
}
Запуск:
k6 run script.js
Структура скрипта
Каждый скрипт k6 состоит из трёх ключевых частей:
- Imports: Загрузка модулей k6 (
k6/http,k6,k6/metrics) - Options: Настройка VUs, длительности, stages, thresholds
- Функция default: Код, который каждый VU выполняет в цикле
Функция default выполняется один раз за итерацию для каждого VU. При 10 VUs и sleep в 1 секунду вы получаете примерно 10 запросов в секунду.
Виртуальные пользователи (VUs) и итерации
Virtual User (VU) — это конкурентный поток выполнения. Каждый VU выполняет функцию default в цикле до истечения длительности теста или достижения числа итераций.
- VUs: Количество одновременных пользователей
- Iterations: Общее число выполнений функции default (по всем VUs)
- Duration: Как долго выполняется тест
Можно указать либо iterations, либо duration, но не оба одновременно с фиксированным числом VUs.
// Фиксированные итерации: 100 итераций распределены между 10 VUs
export const options = {
vus: 10,
iterations: 100,
};
// На основе длительности: 10 VUs в течение 1 минуты
export const options = {
vus: 10,
duration: '1m',
};
Stages: Нарастание, Стабильная нагрузка, Снижение
Для реалистичных паттернов нагрузки используйте stages для изменения числа VUs во времени:
export const options = {
stages: [
{ duration: '2m', target: 50 }, // нарастание до 50 VUs за 2 минуты
{ duration: '5m', target: 50 }, // удержание 50 VUs 5 минут
{ duration: '2m', target: 0 }, // снижение до 0 за 2 минуты
],
};
Это создаёт классический профиль нагрузочного теста: постепенное увеличение, устойчивая нагрузка и плавное снижение.
Thresholds: Критерии прохождения/провала
Thresholds определяют критерии успеха теста. При нарушении любого threshold k6 завершается с ненулевым кодом возврата — идеально для CI/CD пайплайнов.
export const options = {
thresholds: {
http_req_duration: ['p(95)<500'], // 95% запросов менее 500мс
http_req_failed: ['rate<0.01'], // менее 1% ошибок
http_reqs: ['rate>100'], // минимум 100 запросов/секунду
checks: ['rate>0.99'], // 99% checks проходят
},
};
Популярные метрики для thresholds:
http_req_duration— Время ответа (p50, p90, p95, p99, avg, max)http_req_failed— Процент неудачных запросовhttp_reqs— Частота запросов (запросов в секунду)checks— Процент пройденных проверок
Checks: Встроенные проверки
Checks проверяют данные ответа без остановки теста (в отличие от assertions в JMeter, которые помечают запросы как неудачные):
check(res, {
'статус 200': (r) => r.status === 200,
'body содержит ожидаемое поле': (r) => JSON.parse(r.body).hasOwnProperty('id'),
'время ответа ОК': (r) => r.timings.duration < 300,
});
Checks записываются как метрики. Можно устанавливать thresholds на процент прохождения checks.
Работа с HTTP-методами
import http from 'k6/http';
// GET
const getRes = http.get('https://api.example.com/users');
// POST с JSON body
const payload = JSON.stringify({ name: 'Test User', email: 'test@example.com' });
const params = { headers: { 'Content-Type': 'application/json' } };
const postRes = http.post('https://api.example.com/users', payload, params);
// PUT
const putRes = http.put('https://api.example.com/users/1', payload, params);
// DELETE
const delRes = http.del('https://api.example.com/users/1');
// Batch-запросы (параллельные)
const responses = http.batch([
['GET', 'https://api.example.com/users'],
['GET', 'https://api.example.com/products'],
['GET', 'https://api.example.com/orders'],
]);
Упражнение: Многоэндпоинтный нагрузочный тест с k6
Напишите скрипт k6, который тестирует API интернет-магазина с аутентификацией, несколькими эндпоинтами, thresholds и checks.
Сценарий
Протестируйте API интернет-магазина со следующим потоком:
- Логин для получения токена аутентификации
- Просмотр каталога товаров
- Просмотр конкретного товара
- Добавление товара в корзину
Требования
- Используйте stages: нарастание до 25 VUs за 1 минуту, удержание 3 минуты, снижение за 1 минуту
- Установите thresholds: p(95) < 800мс, процент ошибок < 1%, прохождение checks > 99%
- Добавьте checks для кодов состояния и содержимого ответа
- Используйте токен аутентификации в последующих запросах
- Добавьте реалистичное время ожидания между запросами
Подсказка: Структура скрипта
import http from 'k6/http';
import { check, sleep, group } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 25 },
{ duration: '3m', target: 25 },
{ duration: '1m', target: 0 },
],
thresholds: {
// определите thresholds здесь
},
};
export default function () {
// Группа 1: Логин
// Группа 2: Просмотр каталога
// Группа 3: Просмотр товара
// Группа 4: Добавление в корзину
}
Используйте group() для организации запросов в логические секции. Это даёт метрики по группам в результатах.
Решение: Полный скрипт k6
import http from 'k6/http';
import { check, sleep, group } from 'k6';
const BASE_URL = 'https://api.ecommerce.example.com';
export const options = {
stages: [
{ duration: '1m', target: 25 },
{ duration: '3m', target: 25 },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<800'],
http_req_failed: ['rate<0.01'],
checks: ['rate>0.99'],
},
};
export default function () {
let token;
// Шаг 1: Логин
group('Логин', function () {
const loginPayload = JSON.stringify({
username: `user${__VU}@example.com`,
password: 'testpass123',
});
const loginRes = http.post(`${BASE_URL}/api/auth/login`, loginPayload, {
headers: { 'Content-Type': 'application/json' },
});
check(loginRes, {
'логин статус 200': (r) => r.status === 200,
'логин возвращает токен': (r) => JSON.parse(r.body).token !== undefined,
});
token = JSON.parse(loginRes.body).token;
});
sleep(1);
const authHeaders = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
};
// Шаг 2: Просмотр каталога
let productId;
group('Просмотр каталога', function () {
const catalogRes = http.get(`${BASE_URL}/api/products`, authHeaders);
check(catalogRes, {
'каталог статус 200': (r) => r.status === 200,
'каталог возвращает товары': (r) => JSON.parse(r.body).products.length > 0,
});
const products = JSON.parse(catalogRes.body).products;
productId = products[Math.floor(Math.random() * products.length)].id;
});
sleep(2);
// Шаг 3: Просмотр деталей товара
group('Просмотр товара', function () {
const productRes = http.get(`${BASE_URL}/api/products/${productId}`, authHeaders);
check(productRes, {
'товар статус 200': (r) => r.status === 200,
'товар имеет название': (r) => JSON.parse(r.body).name !== undefined,
'товар имеет цену': (r) => JSON.parse(r.body).price > 0,
});
});
sleep(1);
// Шаг 4: Добавление в корзину
group('Добавление в корзину', function () {
const cartPayload = JSON.stringify({
productId: productId,
quantity: 1,
});
const cartRes = http.post(`${BASE_URL}/api/cart`, cartPayload, authHeaders);
check(cartRes, {
'корзина статус 200 или 201': (r) => r.status === 200 || r.status === 201,
'корзина подтверждает добавление': (r) => JSON.parse(r.body).success === true,
});
});
sleep(1);
}
Запуск теста:
k6 run ecommerce-load-test.js
На что обращать внимание в результатах:
http_req_duration— p95 должен быть менее 800мсhttp_req_failed— должен быть близок к 0%checks— процент прохождения должен быть выше 99%http_reqs— общая частота запросов- Метрики по группам показывают, какой эндпоинт самый медленный
Экспорт результатов для анализа:
# JSON-вывод
k6 run --out json=results.json ecommerce-load-test.js
# CSV-вывод
k6 run --out csv=results.csv ecommerce-load-test.js
# InfluxDB (для дашбордов Grafana)
k6 run --out influxdb=http://localhost:8086/k6 ecommerce-load-test.js
Профессиональные советы
- Используйте
__VUи__ITER: Встроенные переменные__VU(номер текущего VU) и__ITER(номер текущей итерации) помогают создавать уникальные данные для каждого пользователя без внешних CSV-файлов. - SharedArray для тестовых данных: Загружайте большие наборы данных один раз и используйте совместно между VUs с помощью
SharedArrayизk6/data. Это предотвращает загрузку собственной копии каждым VU. - Кастомные метрики: Создавайте пользовательские метрики с помощью
Counter,Gauge,RateиTrendизk6/metricsдля отслеживания бизнес-специфичных KPI. - Scenarios для сложных паттернов: Используйте
scenariosвместоstages, когда нужны несколько типов исполнителей (constant-vus, ramping-vus, per-vu-iterations, constant-arrival-rate), работающих одновременно. - Модуль k6 browser: Для нагрузочного тестирования на уровне браузера k6 теперь включает модуль браузера на базе Chromium, способный измерять Core Web Vitals под нагрузкой.