Непрерывное нагрузочное тестирование в 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 в ваш пайплайн.







