k6 и Locust: современные инструменты нагрузочного тестирования
Updated Jul 2026
k6 (Grafana k6)
k6 — это ориентированный на разработчиков инструмент нагрузочного тестирования, написанный на Go с возможностью скриптинга на JavaScript. Он стал отраслевым стандартом для команд, практикующих тестирование производительности по принципу shift-left. Его ключевые преимущества — низкое потребление ресурсов, нативная интеграция с CI и модель скриптинга, с которой разработчикам действительно приятно работать.
Почему k6 выигрывает для CI/CD
- Единый бинарный файл. Без JVM, без зависимостей. Скачайте и запустите.
- Скриптинг на JavaScript. Знакомый синтаксис для большинства команд разработки.
- Пороговые значения для прохождения/провала. Определяйте SLO прямо в тесте — при их нарушении код возврата ненулевой, и CI-пайплайн завершается с ошибкой.
- Встроенный экспорт метрик. Стриминг в Grafana, Datadog, Prometheus или JSON.
- Движок сценариев. Моделирование нескольких паттернов трафика в одном файле теста.
Полный пример k6: нагрузочный тест интернет-магазина
// k6-load-test.js -- Realistic e-commerce load test with multiple scenarios
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend } from 'k6/metrics';
// Custom metrics for business-specific SLOs
const errorRate = new Rate('errors');
const checkoutDuration = new Trend('checkout_duration');
// Traffic shaping: ramp up, sustain, spike, cool down
export const options = {
scenarios: {
// Steady-state browsing traffic (70% of users)
browse: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 100 }, // ramp up
{ duration: '5m', target: 100 }, // steady state
{ duration: '1m', target: 300 }, // spike
{ duration: '5m', target: 100 }, // back to steady
{ duration: '2m', target: 0 }, // ramp down
],
exec: 'browseProducts',
},
// API consumers hitting the search endpoint (30% of traffic)
api_search: {
executor: 'constant-arrival-rate',
rate: 50, // 50 requests per second
timeUnit: '1s',
duration: '15m',
preAllocatedVUs: 20,
maxVUs: 100,
exec: 'searchAPI',
},
},
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1500'], // SLO enforcement
errors: ['rate<0.01'], // <1% error rate
checkout_duration: ['p(95)<3000'], // checkout under 3s
},
};
export function browseProducts() {
group('Homepage', () => {
const res = http.get('https://store.example.com/');
check(res, {
'homepage status 200': (r) => r.status === 200,
'homepage loads under 2s': (r) => r.timings.duration < 2000,
});
errorRate.add(res.status >= 400);
});
sleep(Math.random() * 3 + 1); // realistic think time: 1-4 seconds
group('Product Page', () => {
const productId = Math.floor(Math.random() * 1000) + 1;
const res = http.get(`https://store.example.com/products/${productId}`);
check(res, {
'product page status 200': (r) => r.status === 200,
'has product title': (r) => r.body.includes('<h1'),
});
errorRate.add(res.status >= 400);
});
sleep(Math.random() * 5 + 2); // browsing think time: 2-7 seconds
}
export function searchAPI() {
const queries = ['laptop', 'phone', 'headphones', 'keyboard', 'monitor'];
const query = queries[Math.floor(Math.random() * queries.length)];
const res = http.get(`https://api.store.example.com/search?q=${query}`, {
headers: { 'Authorization': `Bearer ${__ENV.API_TOKEN}` },
});
check(res, {
'search returns 200': (r) => r.status === 200,
'search returns results': (r) => JSON.parse(r.body).results.length > 0,
'search under 300ms': (r) => r.timings.duration < 300,
});
errorRate.add(res.status >= 400);
}
Запуск k6
# Local execution with default output
k6 run k6-load-test.js
# With environment variables for secrets
k6 run -e API_TOKEN=secret123 k6-load-test.js
# Output results to Grafana Cloud k6
k6 cloud k6-load-test.js
# Output to JSON for CI analysis
k6 run --out json=results.json k6-load-test.js
# Run with a specific scenario only
k6 run --scenario browse k6-load-test.js
Типы исполнителей k6
Понимание исполнителей необходимо для моделирования реалистичного трафика:
| Исполнитель | Управляет | Лучше всего подходит для |
|---|---|---|
shared-iterations |
Общее число итераций для всех VU | Быстрые smoke-тесты |
per-vu-iterations |
Итерации на каждый VU | Гарантия выполнения каждым VU N раз |
constant-vus |
Фиксированное число VU | Тестирование в устойчивом состоянии |
ramping-vus |
Число VU во времени | Паттерны нарастания/спада нагрузки |
constant-arrival-rate |
Фиксированная частота запросов | Валидация SLO (на основе RPS) |
ramping-arrival-rate |
Частота запросов во времени | Поиск точки отказа |
externally-controlled |
Через REST API | Динамическая регулировка нагрузки |
Locust (на основе Python)
Locust использует классы Python для определения поведения пользователей. Это предпочтительный выбор для команд с сильными навыками Python, сложной логикой сценариев или при необходимости совместного использования кода с существующей тестовой инфраструктурой на Python.
Почему стоит выбрать Locust
- Чистый Python. Используйте существующие библиотеки (requests, клиенты баз данных, собственные SDK).
- Распределённая работа по умолчанию. Встроенная архитектура master/worker для горизонтального масштабирования.
- Веб-интерфейс в реальном времени. Мониторинг хода тестирования и настройка параметров на лету.
- Обработчики событий. Глубокая кастомизация жизненного цикла запросов, обработки ошибок и отчётности.
Полный пример Locust
# locustfile.py -- Multi-persona e-commerce load test
from locust import HttpUser, task, between, tag, events
import random
import logging
logger = logging.getLogger(__name__)
class BrowsingUser(HttpUser):
"""Simulates casual browsing behavior -- 70% of traffic."""
wait_time = between(1, 5)
weight = 7 # 70% of simulated users
@task(5)
def view_homepage(self):
with self.client.get("/", name="Homepage", catch_response=True) as response:
if response.status_code != 200:
response.failure(f"Homepage returned {response.status_code}")
elif "Welcome" not in response.text:
response.failure("Homepage missing welcome text")
@task(3)
def view_product(self):
product_id = random.randint(1, 1000)
self.client.get(f"/products/{product_id}", name="/products/[id]")
@task(1)
@tag("checkout")
def add_to_cart(self):
self.client.post("/cart", json={"product_id": 42, "quantity": 1})
def on_start(self):
"""Runs once per simulated user at start."""
logger.info("BrowsingUser session started")
class APIUser(HttpUser):
"""Simulates API consumer traffic -- 30% of traffic."""
wait_time = between(0.1, 0.5)
weight = 3 # 30% of simulated users
@task
def search(self):
query = random.choice(["laptop", "phone", "headphones", "keyboard"])
self.client.get(f"/api/search?q={query}", name="API Search")
@task
def get_inventory(self):
product_id = random.randint(1, 100)
self.client.get(f"/api/inventory/{product_id}", name="API Inventory")
# Custom event listener for reporting
@events.request.add_listener
def on_request(request_type, name, response_time, response_length, exception, **kwargs):
if response_time > 5000:
logger.warning(f"Slow request: {name} took {response_time}ms")
Запуск Locust
# Local single-process run
locust -f locustfile.py --host=https://staging.example.com
# Headless mode for CI (no web UI)
locust -f locustfile.py --host=https://staging.example.com \
--headless -u 100 -r 10 --run-time 5m
# Distributed mode: start master
locust -f locustfile.py --master --host=https://staging.example.com
# Distributed mode: start workers (run on multiple machines)
locust -f locustfile.py --worker --master-host=192.168.1.100
# Filter by tag
locust -f locustfile.py --tags checkout --host=https://staging.example.com
k6 vs Locust: фреймворк принятия решений
При выборе между k6 и Locust учитывайте контекст вашей команды:
| Фактор | Выбирайте k6 | Выбирайте Locust |
|---|---|---|
| Язык команды | JavaScript/TypeScript | Python |
| Основное применение | Автоматические шлюзы CI/CD | Исследовательское тестирование + CI |
| Потребности в протоколах | HTTP, gRPC, WebSocket | HTTP + кастомные протоколы через Python |
| Бюджет ресурсов | Ограниченный (k6 чрезвычайно эффективен) | Умеренный |
| Мониторинг в реальном времени | Интеграция с Grafana | Встроенный веб-интерфейс |
| Распределённое выполнение | k6-operator на K8s | Нативный master/worker |
| Сложность скриптинга | Простой — средний | Средний — сложный |
| Существующая тестовая инфраструктура | Экосистема Node.js | Экосистема Python |
Гибридный подход
Многие зрелые команды используют оба инструмента для разных целей:
- k6 в CI для автоматических шлюзов производительности (прохождение/провал на основе пороговых значений)
- Locust для исследовательского тестирования, где блистает интерактивный веб-интерфейс и гибкость Python
- k6 Cloud для масштабных тестов, когда вам нужны тысячи VU в разных регионах
Главное — не какой инструмент вы выберете, а что вы выберете один из них и интегрируете в CI, чтобы регрессии производительности обнаруживались до попадания в продакшен.