PostMedium ⏱ ~5 min

Нагрузочное тестирование: установка k6 и инфраструктура

Table of contents

В прошлый раз я писал про то, когда нагрузочное тестирование вообще имеет смысл. Теперь — про самый скучный, но важный этап: как поставить 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 k6

Windows

Я на 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 секунд прогона часто показывают завышенные задержки. Если смотреть на них как на «реальный результат», можно напрасно паниковать.

Я либо добавляю разгонную стадию в скрипт, либо просто знаю, что первую минуту можно не учитывать при анализе.

Минимальный рабочий набор

Если собрать всё вместе, вот что нужно для первого осмысленного прогона:

  1. k6 установлен и проверен дымовым прогоном.
  2. Тестовый стенд доступен и изолирован от прода.
  3. Генератор нагрузки в той же сети, что и стенд.
  4. Тестовые данные подготовлены и проверены.
  5. Моки внешних сервисов подняты и отдают ожидаемые контракты.
  6. Rate limits и WAF либо отключены, либо известны.
  7. Мониторинг стенда настроен минимально.
  8. Цель и thresholds прогона записаны в одном предложении.

К следующей части

С инфраструктурой разобрались. Теперь можно писать скрипты. В следующей части я покажу, как собирать сценарии из нескольких шагов, как передавать данные между запросами и как организовать код так, чтобы он не превращался в спагетти после третьего эндпоинта.

Previous

Discussion

No comments yet - start the thread.

No comments yet - start the thread.

Leave a comment

Comments are published immediately after submission.