K6 — это современный, дружественный к разработчикам инструмент нагрузочного тестирования, разработанный для тестирования производительности и надёжности API, микросервисов и веб-сайтов. Построенный на Go и скриптуемый на JavaScript, K6 приносит performance тестирование в DevOps workflow с бесшовной интеграцией CI/CD и cloud-native возможностями.
Почему K6 для Современного Load Testing?
K6 решает ограничения традиционных инструментов performance тестирования, фокусируясь на опыте разработчика и современной инфраструктуре:
- JavaScript API: Знакомый скриптинг для frontend и backend разработчиков
- CLI-first дизайн: Идеально для автоматизации и CI/CD пайплайнов
- Производительность: Эффективное использование ресурсов, может генерировать высокую нагрузку с одной машины
- Cloud & Local: Запускать локально во время разработки, масштабировать в облако для production-level тестирования
- Современные протоколы: Поддержка HTTP/1.1, HTTP/2, WebSockets, gRPC
- Observability: Метрики в реальном времени, интеграция Prometheus, дашборды Grafana
K6 vs Традиционные Инструменты Load Testing
Функция | K6 | JMeter | Gatling | Locust |
---|---|---|---|---|
Язык Скриптинга | JavaScript | GUI/XML | Scala | Python |
Эффективность Ресурсов | Отличная | Средняя | Хорошая | Средняя |
Интеграция CI/CD | Нативная | Плагины | Хорошая | Хорошая |
Кривая Обучения | Низкая | Средняя | Высокая | Низкая |
Поддержка Cloud | Нативная (K6 Cloud) | Плагины | Gatling Enterprise | Нет нативной |
Поддержка Протоколов | HTTP/2, WS, gRPC | Обширная | HTTP/2, WS | HTTP |
Результаты в Реальном Времени | Отличные | Ограниченные | Хорошие | Хорошие |
Начало Работы с K6
Установка
# macOS (Homebrew)
brew install k6
# Windows (Chocolatey)
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 C5AD17C747E3415A3642D57D77C6C491D6AC1D69
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 pull grafana/k6
Ваш Первый K6 Тест
// simple-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10, // Virtual users (виртуальные пользователи)
duration: '30s', // Длительность теста
};
export default function () {
const response = http.get('https://test-api.k6.io');
check(response, {
'статус 200': (r) => r.status === 200,
'время ответа < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
Запустить тест:
k6 run simple-test.js
Конфигурация Тестов и Профили Нагрузки
Виртуальные Пользователи и Длительность
export const options = {
// Простая конфигурация
vus: 50,
duration: '5m',
// Или использовать stages для ramping
stages: [
{ duration: '2m', target: 100 }, // Подъём до 100 пользователей
{ duration: '5m', target: 100 }, // Удержание 100 пользователей
{ duration: '2m', target: 0 }, // Спуск до 0 пользователей
],
};
Сценарии Load Testing
export const options = {
scenarios: {
// Постоянная нагрузка
constant_load: {
executor: 'constant-vus',
vus: 50,
duration: '5m',
},
// Ramping VUs
ramping_load: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '1m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '1m', target: 0 },
],
},
// Постоянная частота запросов
constant_rps: {
executor: 'constant-arrival-rate',
rate: 1000, // 1000 запросов в секунду
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 50,
maxVUs: 200,
},
// Spike тест
spike_test: {
executor: 'ramping-arrival-rate',
startRate: 100,
timeUnit: '1s',
preAllocatedVUs: 50,
maxVUs: 500,
stages: [
{ duration: '30s', target: 100 }, // Нормальная нагрузка
{ duration: '1m', target: 1000 }, // Spike
{ duration: '30s', target: 100 }, // Обратно к нормальной
],
},
},
};
Thresholds для Критериев Pass/Fail
export const options = {
thresholds: {
// HTTP ошибки должны быть менее 1%
http_req_failed: ['rate<0.01'],
// 95% запросов должны быть ниже 500ms
http_req_duration: ['p(95)<500'],
// 99-й перцентиль должен быть ниже 1s
'http_req_duration{type:api}': ['p(99)<1000'],
// Thresholds кастомных метрик
checks: ['rate>0.99'], // 99% проверок должны пройти
},
};
Продвинутое HTTP Тестирование
HTTP Методы и Request Bodies
import http from 'k6/http';
import { check } from 'k6';
export default function () {
// GET запрос
const getResponse = http.get('https://api.example.com/users');
// POST с JSON
const payload = JSON.stringify({
name: 'John Doe',
email: 'john@example.com',
});
const params = {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123',
},
};
const postResponse = http.post(
'https://api.example.com/users',
payload,
params
);
check(postResponse, {
'создан успешно': (r) => r.status === 201,
'есть user id': (r) => r.json('id') !== undefined,
});
// PUT запрос
const updatePayload = JSON.stringify({ name: 'Jane Doe' });
http.put(`https://api.example.com/users/${postResponse.json('id')}`, updatePayload, params);
// DELETE запрос
http.del(`https://api.example.com/users/${postResponse.json('id')}`, null, params);
}
Batch Запросы
import http from 'k6/http';
export default function () {
// Выполнить запросы параллельно
const responses = http.batch([
['GET', 'https://api.example.com/users'],
['GET', 'https://api.example.com/products'],
['GET', 'https://api.example.com/orders'],
]);
// Проверить все ответы
responses.forEach((response, index) => {
check(response, {
[`запрос ${index} статус 200`]: (r) => r.status === 200,
});
});
}
Аутентификация и Управление Сессиями
import http from 'k6/http';
import { check } from 'k6';
export function setup() {
// Логин один раз за прогон теста
const loginResponse = http.post('https://api.example.com/auth/login', {
username: 'testuser',
password: 'testpass',
});
return { token: loginResponse.json('token') };
}
export default function (data) {
// Использовать токен из setup
const params = {
headers: {
'Authorization': `Bearer ${data.token}`,
},
};
const response = http.get('https://api.example.com/protected', params);
check(response, {
'аутентифицирован': (r) => r.status === 200,
});
}
Параметризация Данных
Использование CSV Данных
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
const csvData = new SharedArray('users', function () {
return papaparse.parse(open('./users.csv'), { header: true }).data;
});
export default function () {
// Получить случайного пользователя
const user = csvData[Math.floor(Math.random() * csvData.length)];
const response = http.post('https://api.example.com/login', {
username: user.username,
password: user.password,
});
check(response, {
'логин успешен': (r) => r.status === 200,
});
}
Использование JSON Данных
import { SharedArray } from 'k6/data';
const testData = new SharedArray('products', function () {
return JSON.parse(open('./products.json'));
});
export default function () {
const product = testData[__VU % testData.length]; // Round-robin выбор
http.post('https://api.example.com/cart/add', JSON.stringify({
productId: product.id,
quantity: 1,
}), {
headers: { 'Content-Type': 'application/json' },
});
}
Кастомные Метрики и Tags
Кастомные Метрики
import http from 'k6/http';
import { Trend, Counter, Rate, Gauge } from 'k6/metrics';
const myTrend = new Trend('waiting_time');
const myCounter = new Counter('my_counter');
const myRate = new Rate('my_rate');
const myGauge = new Gauge('my_gauge');
export default function () {
const response = http.get('https://api.example.com');
// Добавить кастомные метрики
myTrend.add(response.timings.waiting);
myCounter.add(1);
myRate.add(response.status === 200);
myGauge.add(response.timings.duration);
}
Tags для Организации Метрик
import http from 'k6/http';
import { check } from 'k6';
export default function () {
// Тегировать отдельные запросы
const response = http.get('https://api.example.com/users', {
tags: {
type: 'api',
endpoint: 'users',
},
});
check(response, {
'статус 200': (r) => r.status === 200,
}, { type: 'api' }); // Тегировать проверки
}
export const options = {
thresholds: {
// Threshold для конкретных тегов
'http_req_duration{type:api}': ['p(95)<500'],
'http_req_duration{endpoint:users}': ['p(99)<1000'],
},
};
Реальные Сценарии Тестирования
Пользовательский Поток E-commerce
import http from 'k6/http';
import { check, group, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 50 },
{ duration: '5m', target: 50 },
{ duration: '2m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
'http_req_duration{page:home}': ['p(95)<300'],
'http_req_duration{page:product}': ['p(95)<400'],
},
};
export default function () {
let response;
group('Домашняя Страница', function () {
response = http.get('https://ecommerce.example.com', {
tags: { page: 'home' },
});
check(response, { 'homepage загружена': (r) => r.status === 200 });
sleep(1);
});
group('Поиск Продуктов', function () {
response = http.get('https://ecommerce.example.com/search?q=laptop', {
tags: { page: 'search' },
});
check(response, { 'результаты поиска': (r) => r.status === 200 });
sleep(2);
});
group('Просмотр Продукта', function () {
response = http.get('https://ecommerce.example.com/products/123', {
tags: { page: 'product' },
});
check(response, { 'детали продукта': (r) => r.status === 200 });
sleep(3);
});
group('Добавить в Корзину', function () {
response = http.post(
'https://ecommerce.example.com/cart',
JSON.stringify({ productId: 123, quantity: 1 }),
{
headers: { 'Content-Type': 'application/json' },
tags: { page: 'cart' },
}
);
check(response, { 'добавлено в корзину': (r) => r.status === 200 });
sleep(1);
});
group('Checkout', function () {
response = http.post(
'https://ecommerce.example.com/checkout',
JSON.stringify({
payment: 'card',
address: '123 Main St',
}),
{
headers: { 'Content-Type': 'application/json' },
tags: { page: 'checkout' },
}
);
check(response, { 'checkout успешен': (r) => r.status === 200 });
});
}
Нагрузочный Тест API с Обработкой Ошибок
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
const errorRate = new Rate('errors');
export const options = {
scenarios: {
stress_test: {
executor: 'ramping-arrival-rate',
startRate: 10,
timeUnit: '1s',
preAllocatedVUs: 50,
maxVUs: 500,
stages: [
{ duration: '1m', target: 10 },
{ duration: '2m', target: 50 },
{ duration: '2m', target: 100 },
{ duration: '1m', target: 10 },
],
},
},
thresholds: {
errors: ['rate<0.1'], // Частота ошибок должна быть менее 10%
http_req_duration: ['p(95)<2000', 'p(99)<5000'],
},
};
export default function () {
const response = http.get('https://api.example.com/data');
const success = check(response, {
'статус 200': (r) => r.status === 200,
'время ответа OK': (r) => r.timings.duration < 1000,
'нет ошибок в body': (r) => !r.body.includes('error'),
});
errorRate.add(!success);
if (response.status !== 200) {
console.error(`Запрос не удался: ${response.status} ${response.body}`);
}
sleep(1);
}
Интеграция CI/CD
GitHub Actions
name: Load Test
on:
push:
branches: [main]
schedule:
- cron: '0 2 * * *' # Ежедневно в 2 AM
jobs:
k6_load_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run K6 Load Test
uses: grafana/k6-action@v0.3.1
with:
filename: tests/load-test.js
cloud: true
token: ${{ secrets.K6_CLOUD_TOKEN }}
- name: Upload Results
uses: actions/upload-artifact@v3
if: always()
with:
name: k6-results
path: results/
Jenkins Pipeline
pipeline {
agent any
stages {
stage('Load Test') {
steps {
sh '''
k6 run --out json=results.json \
--out influxdb=http://influxdb:8086/k6 \
tests/load-test.js
'''
}
}
stage('Check Thresholds') {
steps {
script {
def exitCode = sh(
script: 'k6 run --quiet tests/load-test.js',
returnStatus: true
)
if (exitCode != 0) {
error("Thresholds нагрузочного теста провалились")
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'results.json', fingerprint: true
}
}
}
GitLab CI
k6-test:
stage: test
image: grafana/k6:latest
script:
- k6 run --summary-export=summary.json tests/load-test.js
artifacts:
when: always
paths:
- summary.json
reports:
junit: summary.json
Observability и Анализ Результатов
Опции Output
# JSON вывод
k6 run --out json=results.json script.js
# CSV вывод
k6 run --out csv=results.csv script.js
# InfluxDB + Grafana
k6 run --out influxdb=http://localhost:8086/k6 script.js
# Prometheus Remote Write
k6 run --out experimental-prometheus-rw script.js
# K6 Cloud
k6 run --out cloud script.js
# Множественные выводы
k6 run --out json=results.json --out influxdb=http://localhost:8086/k6 script.js
Чтение Результатов
# Статистика резюме
k6 run script.js
# Детальный вывод
k6 run --verbose script.js
# Тихий режим (только ошибки)
k6 run --quiet script.js
# Сохранить резюме в файл
k6 run --summary-export=summary.json script.js
Кастомное Резюме
import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";
export function handleSummary(data) {
return {
"summary.html": htmlReport(data),
"summary.json": JSON.stringify(data),
stdout: textSummary(data, { indent: " ", enableColors: true }),
};
}
Лучшие Практики
Эффективность Ресурсов
// ❌ Плохо: Создаёт новый объект каждую итерацию
export default function () {
const headers = { 'Content-Type': 'application/json' };
http.get('https://api.example.com', { headers });
}
// ✓ Хорошо: Переиспользовать объекты
const params = {
headers: { 'Content-Type': 'application/json' },
};
export default function () {
http.get('https://api.example.com', params);
}
Think Time и Pacing
import { sleep } from 'k6';
export default function () {
// Симулировать время размышления пользователя
http.get('https://api.example.com/page1');
sleep(Math.random() * 3 + 2); // 2-5 секунд
http.get('https://api.example.com/page2');
sleep(Math.random() * 2 + 1); // 1-3 секунды
}
Модульные Тестовые Скрипты
// modules/auth.js
export function login(username, password) {
const response = http.post('https://api.example.com/login', {
username,
password,
});
return response.json('token');
}
// modules/users.js
export function getUser(token, userId) {
return http.get(`https://api.example.com/users/${userId}`, {
headers: { Authorization: `Bearer ${token}` },
});
}
// main-test.js
import { login } from './modules/auth.js';
import { getUser } from './modules/users.js';
export default function () {
const token = login('user', 'pass');
getUser(token, 123);
}
Заключение
K6 представляет собой смену парадигмы в performance тестировании, принося дружественный разработчикам инструментарий и современные практики в load testing. Его JavaScript API, эффективное использование ресурсов и бесшовная интеграция с CI/CD пайплайнами делают его идеальным для DevOps команд, практикующих непрерывное тестирование.
Ключевые преимущества:
- Дружественный разработчикам JavaScript API
- Эффективное использование ресурсов для генерации высокой нагрузки
- Нативная интеграция CI/CD
- Поддержка современных протоколов (HTTP/2, WebSockets, gRPC)
- Гибкие режимы выполнения (локальный, облачный, гибридный)
- Богатая экосистема и опции observability
Независимо от того, тестируете ли вы микросервисы, API или полные веб-приложения, K6 предоставляет инструменты и гибкость для обеспечения надёжной работы ваших систем под нагрузкой. Интегрируя performance тестирование рано в цикл разработки, команды могут обнаружить проблемы до того, как они достигнут production, и поддерживать высококачественные, производительные приложения.