Непрерывное нагрузочное тестирование в CI/CD
При каждом деплое в продакшен риск внести регрессию производительности — высок. Один неоптимальный SQL-запрос или забытая N+1 проблема могут увеличить время ответа на 50%, и пользователь уйдёт к конкурентам. Мы интегрируем автоматические нагрузочные тесты прямо в ваш CI/CD пайплайн, чтобы ловить такие регрессии до попадания в master. Результат — вы уверены, что каждый коммит не ломает SLA по latency и throughput.
Настроим smoke-тесты с пороговыми значениями, которые превращают пайплайн в gatekeeper: если p95 latency превысил 500ms или процент ошибок выше 1% — пайплайн падает, и разработчик получает уведомление. Это не замена полноценному нагрузочному тестированию, а быстрый предохранитель.
Проблемы, которые решаем
- Регрессия производительности после деплоя — даже микроизменение в коде может замедлить критический endpoint. Без автоматических тестов вы узнаете о проблеме только после жалоб пользователей или алертов мониторинга. Мы настраиваем baseline-сравнение: каждый тест запускается против стабильной ветки, и при отклонении >20% пайплайн блокируется.
- Неэффективные запросы и узкие места — наши скрипты эмулируют типичные пользовательские сценарии (просмотр списка, создание поста, поиск). Если запрос начал выполняться дольше — мы видим это на графиках метрик.
- Отсутствие культуры performance-first — разработчики часто не думают о производительности на этапе code review. Continuous Load Testing делает performance видимым: каждый PR сопровождается комментарием с результатами тестов (p95, error rate).
Инструменты и их место в CI
k6 — лучший выбор для CI: JS-скрипты, встроенная статистика, threshold-based pass/fail, нативная интеграция с GitHub Actions и GitLab CI. Подробнее в k6 documentation.
Artillery — YAML-конфигурация, удобен для описания сценариев без кода. Gatling — Scala/Java, детальные HTML-отчёты, удобен для Java-команд.
Базовый k6 скрипт
// tests/performance/api-smoke.js import http from 'k6/http' import { check, sleep } from 'k6' import { Rate, Trend } from 'k6/metrics' // Кастомные метрики const errorRate = new Rate('errors') const postCreateDuration = new Trend('post_create_duration') export const options = { // Профиль нагрузки для CI: быстро, не разрушительно stages: [ { duration: '30s', target: 10 }, // разогрев { duration: '1m', target: 10 }, // устойчивая нагрузка { duration: '10s', target: 0 }, // остывание ], // Пайплайн сломается если порог не достигнут thresholds: { http_req_duration: [ 'p(95)<500', // p95 < 500мс 'p(99)<1000', // p99 < 1000мс ], errors: ['rate<0.01'], // ошибок < 1% http_req_failed: ['rate<0.01'], // HTTP ошибок < 1% post_create_duration: ['p(95)<800'], } } const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000' const AUTH_TOKEN = __ENV.AUTH_TOKEN export function setup() { // Один раз: получить токен или подготовить данные const res = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({ email: '[email protected]', password: 'testpassword' }), { headers: { 'Content-Type': 'application/json' } }) return { token: res.json('token') } } export default function(data) { const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${data.token || AUTH_TOKEN}` } // Сценарий 1: список постов (70% трафика) const postsList = http.get(`${BASE_URL}/api/posts?limit=20`, { headers }) check(postsList, { 'posts list: status 200': (r) => r.status === 200, 'posts list: has items': (r) => r.json('data').length > 0 }) errorRate.add(postsList.status !== 200) sleep(Math.random() * 0.5) // случайная пауза 0-500мс // Сценарий 2: создание поста (20% трафика) if (Math.random() < 0.2) { const start = Date.now() const createPost = http.post(`${BASE_URL}/api/posts`, JSON.stringify({ title: `Test post ${Date.now()}`, content: 'Load test content' }), { headers }) postCreateDuration.add(Date.now() - start) check(createPost, { 'create post: status 201': (r) => r.status === 201, }) errorRate.add(createPost.status !== 201) } sleep(0.3) } Как настроить пороги производительности в k6?
Пороги (thresholds) — ключевой механизм для автоматического fail пайплайна. В опциях скрипта мы задаём допустимые границы: например, http_req_duration: ['p(95)<500', 'p(99)<1000']. Это означает, что 95% запросов должны выполняться быстрее 500 мс, а 99% — быстрее 1 секунды. Если порог нарушен — тест завершается с ошибкой, и CI останавливает деплой.
Мы также используем кастомные метрики для конкретных бизнес-операций (например, время создания поста) и выставляем отдельные пороги на них. Так вы точно знаете, что критическая функциональность не деградирует.
GitHub Actions интеграция
# .github/workflows/performance.yml name: Performance Tests on: push: branches: [main, staging] pull_request: branches: [main] jobs: performance: runs-on: ubuntu-latest services: postgres: image: postgres:15 env: POSTGRES_DB: testdb POSTGRES_PASSWORD: testpass ports: ['5432:5432'] options: >- --health-cmd pg_isready --health-interval 10s steps: - uses: actions/checkout@v4 - name: Start application run: | docker compose -f docker-compose.test.yml up -d api npx wait-on http://localhost:3000/health --timeout 60000 - name: Run k6 smoke test uses: grafana/[email protected] with: filename: tests/performance/api-smoke.js flags: --out json=results.json env: BASE_URL: http://localhost:3000 K6_PROMETHEUS_RW_SERVER_URL: ${{ secrets.PROMETHEUS_URL }} - name: Parse results if: always() run: | # Показать summary в PR комментарии jq -r '.metrics | { p95: .http_req_duration["p(95)"], p99: .http_req_duration["p(99)"], errors: .http_req_failed.rate }' results.json - name: Comment PR with results if: github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | const fs = require('fs') const results = JSON.parse(fs.readFileSync('results.json')) const p95 = results.metrics.http_req_duration['p(95)'].toFixed(0) const errorRate = (results.metrics.http_req_failed.rate * 100).toFixed(2) github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `## Performance Test Results\n\n| Metric | Value | Threshold |\n|--------|-------|-----------|\n| p95 latency | ${p95}ms | <500ms |\n| Error rate | ${errorRate}% | <1% |` }) Baseline сравнение между деплоями
#!/bin/bash # scripts/compare-performance.sh CURRENT_BRANCH=$(git branch --show-current) BASELINE_BRANCH="main" # Тест текущего кода k6 run --out json=current.json tests/performance/api-smoke.js # Переключиться на baseline git stash git checkout $BASELINE_BRANCH docker compose up -d --build api sleep 10 k6 run --out json=baseline.json tests/performance/api-smoke.js # Сравнение node - <<'EOF' const current = require('./current.json') const baseline = require('./baseline.json') const metrics = ['http_req_duration'] for (const m of metrics) { const cp95 = current.metrics[m]['p(95)'] const bp95 = baseline.metrics[m]['p(95)'] const delta = ((cp95 - bp95) / bp95 * 100).toFixed(1) if (cp95 > bp95 * 1.2) { // регрессия > 20% console.error(`REGRESSION: ${m} p95 degraded by ${delta}%`) process.exit(1) } console.log(`${m} p95: ${cp95}ms vs ${bp95}ms baseline (${delta}%)`) } EOF # Вернуться на текущую ветку git checkout $CURRENT_BRANCH git stash pop Artillery для описания сценариев
# tests/performance/user-journey.yml config: target: "{{ $processEnvironment.BASE_URL }}" phases: - duration: 60 arrivalRate: 5 rampTo: 20 name: "Ramp up" - duration: 120 arrivalRate: 20 name: "Sustained load" ensure: thresholds: - http.response_time.p95: 500 - http.request_rate: 15 scenarios: - name: "Browse and purchase" weight: 70 flow: - get: url: "/api/products" expect: - statusCode: 200 - post: url: "/api/cart" json: productId: "{{ $randomInt(1, 100) }}" quantity: 1 - name: "Search only" weight: 30 flow: - get: url: "/api/search?q={{ $randomString(5) }}" Сравнение инструментов нагрузочного тестирования
| Характеристика | k6 | Artillery | Gatling |
|---|---|---|---|
| Язык скриптов | JavaScript | YAML | Scala/Java |
| Нативная CI-интеграция | Да (GitHub Actions, GitLab CI) | Да (через NPM) | Да (Maven/Gradle) |
| Встроенные threshold | Да | Да (через ensure) | Да |
| Генерация отчётов | JSON, Prometheus, HTML | JSON, HTML | HTML (детальные) |
| Производительность | Высокая (Go) | Средняя (Node.js) | Высокая (JVM) |
| Лицензия | Open Source (AGPL) | Open Source (MPL) | Open Source (ALv2) |
Почему стоит использовать k6 для CI?
k6 имеет встроенную поддержку CI/CD: он работает как CLI-утилита, не требует графического интерфейса, а результаты можно выводить в JSON для дальнейшей обработки. В отличие от Gatling (требует Scala/Java и генерации HTML-отчётов) или Artillery (YAML-конфиги, но меньше метрик), k6 предоставляет гибкие метрики и простую интеграцию с GitHub Actions через готовые action. Для команд, уже использующих JavaScript, порог входа минимален.
Процесс работы
- Аналитика — определяем критические эндпоинты и пользовательские сценарии.
- Проектирование — разрабатываем нагрузочные сценарии, настраиваем профили нагрузки (ramp-up, sustained).
- Реализация — пишем скрипты (k6, Artillery или Gatling), интегрируем их в CI.
- Тестирование — запускаем smoke-тесты на staging, корректируем thresholds.
- Деплой — включаем тесты в пайплайн, настраиваем автоматические комментарии в PR.
Сроки и что входит
Настройка базового набора k6 smoke-тестов с порогами и интеграцией в CI занимает от 1 до 2 рабочих дней. Если требуется сравнение с baseline и кастомные метрики — расширяем до 3-5 дней. В состав работ входит:
- Скрипты нагрузочных тестов (2-3 сценария)
- Конфигурация thresholds
- Интеграция с GitHub Actions или GitLab CI (YAML pipeline)
- PR-комментарий с результатами тестов
- Документация по запуску и поддержке
- Консультация команды по интерпретации результатов
Мы гарантируем, что тесты не будут мешать основному процессу разработки: они запускаются параллельно и занимают не более 5 минут.
Почему выбирают нас
- Более 5 лет опыта в нагрузочном тестировании и CI/CD
- 50+ успешных проектов — от стартапов до enterprise
- Используем только проверенные инструменты (k6, Grafana, Prometheus)
- Предоставляем гарантию на корректную работу тестов в течение месяца после внедрения
Свяжитесь с нами, чтобы обсудить ваш проект: получите консультацию по интеграции Continuous Load Testing в ваш пайплайн.







