В прошлый раз я писал про то, когда нагрузочное тестирование вообще имеет смысл. Теперь — про самый скучный, но важный этап: как поставить k6 так, чтобы он не врал и не падал в середине прогона.
Я долго тянул с этим шагом. Казалось, что главное — написать скрипт, а установка — дело пяти минут. Оказалось, что пять минут превращаются в два часа, если не понимаешь, почему k6 не видит переменные окружения, или почему на macOS M1 бинарник ведёт себя иначе, или почему CI падает с непонятной ошибкой, хотя локально всё работает.
Здесь я собрал то, что проверил сам: установка на разные системы, минимальная инфраструктура для стенда и несколько ловушек, на которые я наступил.
Установка k6: варианты, которые работают
macOS
Самый простой путь — Homebrew:
brew install k6Проверяю:
k6 versionЕсли видишь версию — всё готово. На Apple Silicon (M1/M2/M3/M4) всё работает нативно, Rosetta не нужна.
Linux (Debian/Ubuntu)
Чуть длиннее, но тоже без сюрпризов:
curl -fsSL https://dl.k6.io/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/k6-archive-keyring.gpg
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 k6Windows
Я на Windows не работаю, но коллеги используют winget:
winget install k6 --source wingetИли скачивают бинарник с сайта. Главное — добавить в PATH, иначе терминал не найдёт команду.
Docker — можно, но не обязательно
Я пробовал запускать k6 в Docker (образ grafana/k6). Работает, но для локальной разработки неудобно: надо монтировать volumes, пробрасывать переменные окружения, объяснять контейнеру, где искать скрипты. Для CI — ок, для ежедневной работы — проще нативный бинарник.
Первая проверка: дымовой прогон
После установки я всегда запускаю что-то совсем простое, чтобы убедиться, что k6 вообще работает:
k6 run --vus 1 --duration 10s - <<EOF
import http from 'k6/http';
export default function () {
http.get('https://httpbin.org/get');
}
EOFЕсли видишь таблицу с метриками — k6 жив. Если ошибка — смотри на версию, на права доступа, на то, не блокирует ли корпоративный прокси исходящие запросы.
Инфраструктура стенда: о чём я не думал, а надо было
Генератор нагрузки и тестовый стенд — в одном регионе
Мой первый прогон я запускал с ноутбука через домашний Wi-Fi. Стенд был в облаке, в другом регионе. Задержки были 200-300 мс, и я полдня искал проблему в приложении, пока не понял, что это просто сеть.
Теперь у меня правило: генератор нагрузки и стенд — в одном дата-центре, желательно в одной availability zone. Если это невозможно — хотя бы в одном регионе.
Отдельная машина для генератора
Запускать k6 с ноутбука можно только для дымовых прогонов. При серьёзной нагрузке ноутбук сам становится bottleneck: CPU уходит на другие процессы, Wi-Fi проседает, батарея может перейти в энергосбережение.
Я использую отдельную VM для прогонов. Минимальные требования зависят от нагрузки, но для начала хватит 2 CPU и 4 GB RAM. Главное — стабильная сеть и отсутствие посторонних процессов.
Мониторинг стенда
k6 показывает метрики со стороны клиента. Но если p95 растёт, ты не знаешь — это приложение тормозит, или БД, или сеть. Минимум, который я настраиваю:
- CPU и память на сервере приложения
- Метрики БД (active connections, slow queries)
- Логи приложения на время прогона
Не нужен сразу полноценный Grafana. Достаточно top, iostat и логов. Главное — иметь данные, чтобы ответить на вопрос «что именно сломалось».
Моки внешних интеграций
Современная система — редко монолит. Обычно это ряд микросервисов, и у каждого своя пропускная способность, своя надёжность и свои лимиты. Если гнать нагрузочный тест через реальные зависимости, вы измеряете не свою систему, а самое слабое звено чужой: упал соседний сервис платежей — и ваш p95 рассказывает уже не про вас.
Поэтому внешние сервисы я мокирую. Мок фиксирует контракт: мы заранее знаем, что и как отдаёт сервис — формат ответа, задержки, коды ошибок. Заодно упрощается подготовка тестовых данных: вместо согласования состояния с чужой командой я наполняю базы моков теми данными, под которые пишу сценарий.
По сложности моки у меня делятся на два лагеря. Простые, декларативные — Bento (бывший Benthos): конфиг на YAML, поднимаешь HTTP-эндпоинты, описываешь правила ответов, готово. Сложные, stateful — когда мок должен помнить состояние: корзину, статус заказа, идемпотентность повторных запросов. Такие пишу сам — Python + FastAPI + Redis или TypeScript + Fastify, в зависимости от того, что ближе команде.
Важно: моки — это часть инфраструктуры тестирования, а не временный костыль. Они версионируются вместе с контрактами, поднимаются в той же сети, что и стенд, и обновляются, когда меняется API реального сервиса.
И честная оговорка: мок идеален — отвечает быстро и предсказуемо. Поэтому хотя бы раз в релиз я прогоняю тест против реальных интеграций (или staging-окружения с ними), чтобы убедиться, что контракты не разошлись. Иначе есть риск оттестировать сказку.
Переменные окружения и секреты
Я долго хардкодил URL и токены прямо в скриптах. Потом случайно закоммитил тестовый API-ключ в репозиторий. Теперь использую переменные окружения — свои называю без префикса K6_, он зарезервирован под опции самого k6:
export BASE_URL=https://test-api.example.com
export API_TOKEN=your_t...
k6 run script.jsВ скрипте:
const baseUrl = __ENV.BASE_URL || 'http://localhost:3000';
const apiToken = __ENV.API_TOKEN;Для CI — через secrets репозитория. Никаких токенов в коде.
Что часто ломается
Rate limiting и WAF
Первый прогон с 50 VU я запустил на продовый API. Через минуту все запросы стали получать 429. Оказалось, что Cloudflare считает это DDoS-атакой.
Теперь я перед прогоном проверяю: есть ли rate limiting, какие лимиты, можно ли получить тестовый токен без ограничений. Или мокаю внешние сервисы.
Неправильные тестовые данные
Скрипт бьёт в API и получает 200. Но ответ пустой, потому что тестовая база пустая. Или получает 404, потому что ID, которые я использую, не существуют.
Я стал готовить данные заранее: скрипт-загрузчик, который создаёт тестовых пользователей, заказы, товары. Или фиксированный тестовый набор, который знает команда.
Игнорирование warm-up
JVM, кэши, пулы соединений — всё это прогревается. Первые 30-60 секунд прогона часто показывают завышенные задержки. Если смотреть на них как на «реальный результат», можно напрасно паниковать.
Я либо добавляю разгонную стадию в скрипт, либо просто знаю, что первую минуту можно не учитывать при анализе.
Минимальный рабочий набор
Если собрать всё вместе, вот что нужно для первого осмысленного прогона:
- k6 установлен и проверен дымовым прогоном.
- Тестовый стенд доступен и изолирован от прода.
- Генератор нагрузки в той же сети, что и стенд.
- Тестовые данные подготовлены и проверены.
- Моки внешних сервисов подняты и отдают ожидаемые контракты.
- Rate limits и WAF либо отключены, либо известны.
- Мониторинг стенда настроен минимально.
- Цель и thresholds прогона записаны в одном предложении.
К следующей части
С инфраструктурой разобрались. Теперь можно писать скрипты. В следующей части я покажу, как собирать сценарии из нескольких шагов, как передавать данные между запросами и как организовать код так, чтобы он не превращался в спагетти после третьего эндпоинта.
Discussion
No comments yet - start the thread.