Первый smoke-тест и минимальный набор опций
Установка в двух вариантах
- Brew (macOS):
brew install k6 - Docker (универсально):
docker run --rm -i grafana/k6 run - <test.js
Проверка: k6 version (ожидаем v0.4x.x).
Первый smoke: жив ли ShopStack?
// smoke.js
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
vus: 3, // shortcut для простых тестов
duration: "2m",
thresholds: {
http_req_failed: ["rate<0.02"],
http_req_duration: ["p(95)<800"],
},
};
export default function () {
const res = http.get(`${__ENV.BASE_URL}/api/catalog/popular`);
check(res, {
"status is 200": (r) => r.status === 200,
"has products": (r) => (r.json("items") || []).length > 0,
});
sleep(1); // think time
}Запуск:
BASE_URL=https://stage.shopstack.io k6 run smoke.jsРазбор вывода k6: что означают метрики
После запуска k6 выводит два типа данных: прогресс в реальном времени и финальный summary.
Метрики в реальном времени (progress bar)
running (0m15.0s), 3/3 VUs, 45 complete and 0 interrupted iterations
default ✓ [======================================] 3 VUs 2m0s3/3 VUs— активных виртуальных пользователей (все запущены)45 complete— завершенные итерации (вызовыexport default function)0 interrupted— прерванных итераций (должно быть 0)
Финальный summary: ключевые метрики
checks.........................: 98.50% ✓ 197 ✗ 3
data_received..................: 2.4 MB 20 kB/s
data_sent......................: 28 kB 230 B/s
http_req_blocked...............: avg=1.2ms min=0s med=0s max=234ms p(90)=0s p(95)=1ms
http_req_connecting............: avg=580µs min=0s med=0s max=115ms p(90)=0s p(95)=0s
http_req_duration..............: avg=142ms min=98ms med=135ms max=456ms p(90)=189ms p(95)=234ms
{ expected_response:true }...: avg=142ms min=98ms med=135ms max=456ms p(90)=189ms p(95)=234ms
http_req_failed................: 0.50% ✓ 1 ✗ 199
http_req_receiving.............: avg=1.2ms min=200µs med=800µs max=12ms p(90)=2.4ms p(95)=3.1ms
http_req_sending...............: avg=89µs min=45µs med=78µs max=456µs p(90)=145µs p(95)=189µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=140ms min=97ms med=134ms max=454ms p(90)=187ms p(95)=232ms
http_reqs......................: 200 1.666667/s
iteration_duration.............: avg=1.14s min=1.09s med=1.13s max=1.46s p(90)=1.19s p(95)=1.23s
iterations.....................: 200 1.666667/s
vus............................: 3 min=3 max=3
vus_max........................: 3 min=3 max=3Что означает каждая метрика
Checks (проверки корректности):
checks: 98.50% ✓ 197 ✗ 3— 98.5% проверок прошли успешно (197 из 200)- ❌ Если checks < 97% — скорее всего API возвращает некорректные данные
- ✅ Цель: checks > 97% для smoke-тестов
HTTP Request Timing (breakdown времени запроса):
http_req_duration— ГЛАВНАЯ МЕТРИКА: полное время запроса (от отправки до получения)p(95)=234ms— 95% запросов быстрее 234ms (сравниваем с SLO!)p(99)— 99-й перцентиль (для критичных endpoint'ов)
http_req_waiting— время ожидания ответа сервера (без сети)- Рост
waitingпри стабильномconnecting→ проблема на сервере/БД
- Рост
http_req_blocked— время ожидания свободного сокета из пула- Большое значение → увеличьте
batchили connection pool
- Большое значение → увеличьте
http_req_connecting— время установки TCP-соединения- Рост → проблемы с сетью или DNS
http_req_tls_handshaking— время TLS handshake (для HTTPS)http_req_sending— время отправки данных (обычно микросекунды)http_req_receiving— время получения ответа (зависит от размера payload)
Ошибки и throughput:
http_req_failed: 0.50% ✓ 1 ✗ 199— 0.5% запросов завершились ошибкой- ✅ Норма: < 0.5% для smoke, < 0.1% для production load
- ❌ > 1% → расследуем (5xx? timeout? сеть?)
http_reqs: 200 (1.67/s)— всего запросов и RPS (requests per second)data_received/sent— объем переданных данных
VU и итерации:
iterations: 200 (1.67/s)— сколько раз выполниласьexport default functioniteration_duration— время полной итерации (включаяsleep)vus: 3— количество активных виртуальных пользователей
Как читать результат smoke-теста:
- Проверьте
checks≥ 97% (API возвращает корректные данные) - Проверьте
http_req_failed< 1% (нет массовых ошибок) - Сравните
http_req_duration p(95)с вашим SLO (например, < 800ms) - Если все зеленое — API жив, можно двигаться к load-тестам
Как интерпретировать проблемы
| Симптом | Возможная причина | Что проверить |
|---|---|---|
checks < 95% | API возвращает некорректные данные | Проверьте response body, статус коды |
http_req_failed > 1% | Сервис отдает 5xx или timeout | Логи сервиса, мониторинг CPU/RAM |
http_req_waiting растет | Медленная обработка на сервере | БД queries, внешние API, CPU сервиса |
http_req_connecting растет | Проблемы с сетью/DNS | Проверьте DNS, сетевую латентность |
http_req_blocked > 10ms | Мало сокетов в пуле | Увеличьте connection pool или используйте http.batch() |
Пример интерпретации
✓ checks: 100% → Все проверки прошли
✓ http_req_failed: 0% → Нет ошибок
✓ p(95): 234ms < 800ms → Укладываемся в SLO
✅ Smoke test PASSED: API здоров, готов к нагрузке✗ checks: 87% → 13% некорректных ответов
✗ http_req_failed: 3% → 3% запросов с ошибками
✗ p(95): 1.2s > 800ms → Превышаем SLO
❌ Smoke test FAILED: расследуем проблемы перед load-тестомПереопределение опций из CLI
# Больше VU и короче тест
k6 run smoke.js --vus 5 --duration 1m
# Тише вывод
k6 run smoke.js --quiet
# Экспорт summary в JSON для CI
k6 run smoke.js --summary-export summary.jsonВнутри теста одиночные опции (vus, duration, stages) — shortcuts. Для кастомных executors и миксов переходим на options.scenarios (следующий урок), но для smoke они удобны и валидны.
Практика: что сделать до следующего урока
1.Установите k6 и напишите свой первый smoke-тест
- —Установите k6 (brew install k6 или через Docker)
- —Создайте файл smoke.js с простым GET-запросом к вашему API (или к https://httpbin.org/get)
- —Добавьте check для проверки status === 200
- —Запустите тест: k6 run smoke.js
- —Найдите в выводе метрики: checks, http_req_duration (p95), http_req_failed
2.Проанализируйте результаты
- —Проверьте checks >= 97% (если меньше — разберитесь, почему API возвращает ошибки)
- —Сравните p95 http_req_duration с вашим SLO (если нет SLO — запишите текущее значение как baseline)
- —Проверьте http_req_failed < 1%
- —Если все зелёное — API здоров, переходите к следующему уроку
3.Экспериментируйте с опциями
- —Запустите с --vus 10 --duration 30s и сравните результаты
- —Добавьте threshold в options: http_req_duration: ['p(95)<1000']
- —Запустите тест — k6 должен вернуть exit code 0 если threshold прошел
- —Экспортируйте summary: k6 run smoke.js --summary-export summary.json
📊 Реальный кейс: SaaS-платформа и "быстрый smoke перед деплоем"
Контекст: SaaS для управления проектами (15K активных компаний), деплоят 3-5 раз в день через CI/CD.
Проблема: В пятницу вечером деплой "небольшого рефакторинга" сломал авторизацию для 40% пользователей. Инцидент длился 23 минуты, пока не откатили.
Что пошло не так:
-
Рефакторинг JWT-валидации:
- Разработчик переписал функцию
validateToken()для "упрощения кода" - Unit-тесты прошли ✅ (тестировали только happy path)
- E2E тесты прошли ✅ (использовали свежие токены, TTL > 24h)
- Разработчик переписал функцию
-
Production incident:
- Пользователи с токенами старше 12 часов получали 401 Unauthorized
- Причина: новый код не поддерживал старый формат
issclaim (issuer) - 40% активных сессий использовали токены со старым форматом
-
Потери:
- 23 минуты downtime для 6K пользователей
- 847 support tickets за 2 часа
- Репутационный ущерб: trending в Twitter #ProjectManagerDown
- SLA breach: monthly uptime 99.95% → 99.89% (потеряли error budget на 3 недели)
Root cause: Smoke-тест в CI проверял только /health, не проверял реальные user journeys.
Что сделали после инцидента:
-
Добавили comprehensive smoke-тест:
// smoke-auth-flows.js export const options = { vus: 5, duration: "3m", thresholds: { checks: [{ threshold: "rate>0.99", abortOnFail: true }], http_req_failed: ["rate<0.01"], http_req_duration: ["p(95)<500"], }, }; export default function () { // Scenario 1: Fresh token (created < 1h ago) const freshToken = getTokenFromPool("fresh"); // TTL 30min checkAuth(freshToken, "Fresh token auth"); // Scenario 2: Old token (created 12h ago, refreshed) const oldToken = getTokenFromPool("legacy"); // Old format, still valid checkAuth(oldToken, "Legacy token auth"); // Scenario 3: Expired token (should fail gracefully) const expiredToken = getTokenFromPool("expired"); checkAuthExpected401(expiredToken, "Expired token rejection"); sleep(randomIntBetween(1, 3)); } function checkAuth(token, label) { const res = http.get(`${BASE_URL}/api/projects`, { headers: { Authorization: `Bearer ${token}` }, }); check(res, { [`${label}: status 200`]: (r) => r.status === 200, [`${label}: has projects`]: (r) => r.json("projects").length > 0, }); } -
Интегрировали в CI/CD pipeline:
- Smoke запускается после деплоя на staging, перед production
- Использует realistic test data: токены из pool (fresh + legacy + expired)
- Exit code != 0 → автоматический rollback
-
Добавили проверку на breaking changes:
- Static analysis: CodeQL проверяет изменения в auth-модулях
- Mandatory review: любой PR, касающийся JWT/auth → 2 approvals от senior
Результат через 6 месяцев:
- 42 деплоя заблокированы smoke-тестом до production (regression caught early)
- Zero auth-related инцидентов с момента внедрения
- Время от коммита до production: 12 минут (было 8 минут, +4 минуты на smoke — acceptable)
- Monthly uptime: стабильно 99.98%
ROI:
- Стоимость внедрения: 16 часов (smoke-тест + CI интеграция + test data pool) = $4K
- Избежали: 42 потенциальных инцидента × $15K средний ущерб = $630K
- ROI: 157x
War story: После этого инцидента команда ввела термин "Friday Night Deploy Syndrome". Теперь все критичные изменения (auth, payments, billing) проходят extended smoke с legacy compatibility checks. Один разработчик сказал: "Раньше я боялся деплоить в пятницу. Теперь я боюсь деплоить без smoke-теста".
Урок: Smoke-тест — это не про "проверить, что API жив", а про проверить, что критичные user journeys работают для всех типов пользователей/токенов/данных. Включайте в smoke: - Legacy compatibility (старые форматы данных) - Edge cases (expired tokens, empty responses) - Realistic data (не только свежесозданные объекты)
✅ Чек-лист завершения урока
После этого урока вы должны уметь:
Установка и базовые команды:
- k6 установлен и работает (
k6 versionвыдает версию) - Умеете запускать тест:
k6 run smoke.js - Умеете передавать параметры через env:
BASE_URL=... k6 run smoke.js - Умеете переопределять опции из CLI:
--vus,--duration,--quiet
Написание smoke-теста:
- Создали файл smoke.js с базовым HTTP GET запросом
- Добавили
check()для проверки status code и response body - Настроили
optionsс vus, duration, thresholds - Добавили
sleep()для think time
Чтение результатов:
- Понимаете, что такое
checksи почему должно быть ≥ 97% - Знаете, где найти p95/p99 latency в выводе k6
- Умеете интерпретировать
http_req_failed(должно быть < 1%) - Понимаете breakdown времени запроса:
blocked/connecting/sending/waiting/receiving - Умеете отличить проблему на сервере от проблемы в сети
Troubleshooting:
- Знаете, когда смотреть на
http_req_waiting(проблемы сервера) - Знаете, когда смотреть на
http_req_connecting(проблемы сети/DNS) - Умеете использовать
--summary-exportдля сохранения результатов
Практическое задание:
- Запустили smoke-тест на вашем API (или httpbin.org)
- Проанализировали результаты по чек-листу выше
- Зафиксировали baseline: записали p95, error rate, checks
- Тест прошел thresholds (exit code 0)
Если чек-лист пройден — переходите к уроку 06: научимся управлять нагрузкой через scenarios и executors.