Перейти к содержимому
К программе курса
k6: нагрузочное тестирование как система
12 / 1771%

CI/CD, критерии прохода и гейты перед релизом

20 минут

Где запускать и сколько

  • PR: 2–3 минуты smoke (3 VU) по ключевым флоу.
  • Nightly: load (реалистичный RPS) 20–30 минут на stage.
  • Релиз: stress/spike на pre-prod, артефакты и дашборды в отчет Go/No-Go.

Базовый тест для пайплайнов

// tests/load.js
import http from "k6/http";
import { check, sleep } from "k6";
 
export const options = {
  scenarios: {
    checkout: {
      executor: "ramping-vus",
      startVUs: 5,
      stages: [
        { duration: "2m", target: 50 },
        { duration: "10m", target: 50 },
        { duration: "2m", target: 0 },
      ],
      exec: "checkoutFlow",
      tags: { flow: "checkout" },
    },
  },
  thresholds: {
    http_req_duration: ["p(95)<700", "p(99)<1100"],
    http_req_failed: ["rate<0.01"],
    "checks{flow:checkout}": [{ threshold: "rate>0.97", abortOnFail: true }],
  },
};
 
export function checkoutFlow() {
  const res = http.post(`${__ENV.BASE_URL}/api/checkout`, { payment: "test" });
  check(res, { 200: (r) => r.status === 200 });
  sleep(1);
}

GitLab CI

# .gitlab-ci.yml
stages: [test, load]
 
smoke:
  stage: test
  image: grafana/k6:latest
  script:
    - k6 run tests/load.js --vus 3 --duration 2m --summary-export summary.json
  artifacts:
    when: always
    paths: [summary.json]
 
load:
  stage: load
  image: grafana/k6:latest
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  script:
    - k6 run tests/load.js --summary-export summary.json --out json=raw.json
  artifacts:
    when: always
    paths: [summary.json, raw.json]

GitHub Actions

# .github/workflows/load-test.yml
name: k6 load test
 
on:
  workflow_dispatch:
  schedule:
    - cron: "0 3 * * 1-5"
 
jobs:
  k6:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run k6
        uses: grafana/k6-action@v0.2.0
        with:
          filename: tests/load.js
          flags: --summary-export summary.json
      - uses: actions/upload-artifact@v4
        with:
          name: k6-summary
          path: summary.json

Отвал по thresholds

  • k6 сам вернет код выхода 1, если threshold не выполнен.
  • В Jenkins/GitLab/GitHub этого достаточно, чтобы фаза упала.
  • Добавьте Slack/Telegram hook, чтобы команда знала, где искать summary/дашборд.

Quality gate с бейзлайном

Пайплайн в продакшен:

  1. Run: запускаем тест и сохраняем summary.json + «сырые» метрики (--out json или Prom/Influx).
  2. Fetch baseline: скачиваем предыдущий эталон из Object Storage (S3/GCS/minio).
  3. Compare: скрипт считает дельту p95/p99/error rate/бизнес-метрик. Пример псевдо:
node scripts/compare.js --current summary.json --baseline baseline.json --budget 0.1
  1. Gate: if delta > 10% или thresholds нарушены — фейлим пайплайн, прикладываем ссылку на дашборд.
  2. Publish: грузим новый baseline назад в стор (после одобрения) + отправляем отчет в канал #perf.

Пример compare.js (фрагмент)

import fs from "fs";
 
const current = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
const baseline = JSON.parse(fs.readFileSync(process.argv[3], "utf8"));
const budget = Number(process.argv[4] || 0.1);
 
function delta(metric) {
  const cur = current.metrics[metric].thresholds ?? {};
  const base = baseline.metrics[metric].thresholds ?? {};
  const curP95 = current.metrics[metric].values["p(95)"];
  const baseP95 = baseline.metrics[metric].values["p(95)"];
  return (curP95 - baseP95) / baseP95;
}
 
const d = delta("http_req_duration");
if (d > budget) {
  console.error(`p95 regressed by ${(d * 100).toFixed(1)}%`);
  process.exit(1);
}
console.log("Performance OK");

Секреты (BASE_URL, токены) держите в секрет-хранилище CI, артефакты summary/raw — в S3/GCS с тегами release/env для истории.

📊 Реальный кейс: Fintech-платформа и "автоматический quality gate"

Контекст: Платформа для онлайн-банкинга (2.5M активных клиентов), deploy 15-20 раз в день через GitLab CI/CD.

Проблема: В течение 3 месяцев произошло 8 инцидентов с performance degradation после релиза. Каждый инцидент обнаруживался пользователями, не мониторингом.

Типичный сценарий (до внедрения k6 в CI):

  1. Разработчик делает "небольшое улучшение":

    • Рефакторинг кода авторизации для "лучшей читаемости"
    • PR проходит code review ✅
    • Unit/E2E тесты зеленые ✅
  2. Deploy в production:

    • Пайплайн: lint → test → build → deploy → ✅ success
    • Monitoring: CPU, memory, error rate — в норме ✅
  3. Через 30 минут:

    • Support tickets: "Приложение тормозит"
    • Grafana: p95 /api/auth/login вырос с 180ms до 850ms
    • Rollback через 15 минут, но пользователи уже недовольны

Потери за 3 месяца (8 инцидентов):

  • Downtime/degradation: ~4 часа total
  • Support tickets: 1200+ жалоб
  • SLA breach: $45K компенсаций по контрактам
  • Churn: 340 клиентов (~$170K годового дохода)

Root cause: CI/CD не проверяет производительность. Регрессии попадают в production незамеченными.

Что сделали (внедрение k6 в CI):

  1. Smoke-тест на каждом PR (merge request pipeline):

    # .gitlab-ci.yml
    smoke-test:
      stage: test
      image: grafana/k6:latest
      script:
        - k6 run --quiet --no-color
          --env BASE_URL=$STAGING_URL
          --summary-export=smoke-summary.json
          tests/smoke.js
      artifacts:
        paths:
          - smoke-summary.json
        expire_in: 30 days
      rules:
        - if: $CI_PIPELINE_SOURCE == 'merge_request_event'

    Thresholds:

    // tests/smoke.js
    export const options = {
      vus: 10,
      duration: "3m",
      thresholds: {
        checks: [{ threshold: "rate>0.99", abortOnFail: true }],
        http_req_failed: ["rate<0.01"],
        http_req_duration: ["p(95)<600"], // критичные endpoints
        "http_req_duration{endpoint:login}": ["p(95)<300"], // SLO для login
      },
    };
  2. Load-тест nightly (каждую ночь на staging):

    load-test:
      stage: performance
      image: grafana/k6:latest
      script:
        - k6 run --out prometheus-remote-write=$PROMETHEUS_URL
          --env BASE_URL=$STAGING_URL
          tests/load.js
        - node scripts/compare-baseline.js
      artifacts:
        reports:
          performance: load-summary.json
      only:
        - schedules # cron: 0 2 * * *

    Baseline comparison:

    // scripts/compare-baseline.js
    const current = require("./load-summary.json");
    const baseline = fetchFromS3("baselines/main-branch-latest.json");
     
    const degradation = {
      p95: delta(current, baseline, "http_req_duration", "p(95)"),
      p99: delta(current, baseline, "http_req_duration", "p(99)"),
      errorRate: delta(current, baseline, "http_req_failed", "rate"),
    };
     
    if (degradation.p95 > 0.1) {
      // > 10% regression
      console.error(
        `🔴 p95 regressed by ${(degradation.p95 * 100).toFixed(1)}%`
      );
      sendSlackAlert(degradation);
      process.exit(1);
    }
     
    console.log("✅ Performance OK");
  3. Stress-тест перед major release:

    stress-test:
      stage: pre-release
      image: grafana/k6:latest
      script:
        - k6 run --env BASE_URL=$PREPROD_URL tests/stress.js
      rules:
        - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.0$/ # major releases only
      allow_failure: false # блокирует release

Результаты через 6 месяцев:

  1. Caught 23 regressions в CI (до production):

    • Smoke-тест заблокировал 18 PR с performance issues
    • Nightly load-тест обнаружил 5 медленных деградаций
    • Zero performance incidents в production за 6 месяцев
  2. Примеры caught regressions:

    Case 1: N+1 query в API auth

    • Smoke-тест провалился: p95 /login = 920ms (SLO: <300ms)
    • CI заблокировал merge
    • Разработчик нашел N+1 query за 20 минут
    • Исправил, smoke прошел: p95 = 210ms ✅

    Case 2: Memory leak в background job

    • Nightly load-тест показал: memory usage растет с 2GB до 8GB за 6 часов
    • Alert в Slack: "Performance degradation detected"
    • Команда расследовала: background job не очищал старые sessions
    • Фикс за 1 день, до production не дошло

    Case 3: Regex catastrophic backtracking

    • Smoke провалился: timeout на /api/validate-iban
    • Новый regex для валидации IBAN: O(2^n) на некоторых входах
    • CI заблокировал → разработчик переписал на simple parser
    • Regression предотвращена
  3. ROI:

    • Стоимость внедрения: 80 часов (CI setup + тесты + baseline scripts) = $32K
    • Избежали: 23 потенциальных инцидента × $20K средний ущерб = $460K
    • CI overhead: +4 минуты на PR (smoke), +15 минут на nightly
    • ROI: 14.4x
  4. Культурный сдвиг:

    • Разработчики начали запускать k6 локально до создания PR
    • PR description теперь включает: "Performance impact: tested with k6"
    • Performance regression → приравнивается к bug, требует hotfix

War story:

Senior developer: "Раньше я боялся деплоить по пятницам. Теперь я боюсь мерджить PR, который не прошел k6. Один раз smoke-тест поймал мой N+1 query — я был благодарен, что CI остановил меня, а не пользователи".

CTO на quarterly review: "k6 в CI — лучшая инвестиция года. Мы не просто предотвратили инциденты, мы изменили культуру: performance теперь в CI/CD, а не постфактум".

Урок: k6 в CI/CD — это не про "иногда запустить тест", а про автоматический quality gate на каждом этапе разработки:

  1. PR stage: Smoke-тест (3-5 минут) блокирует очевидные регрессии
  2. Nightly: Load-тест на staging с baseline comparison
  3. Pre-release: Stress-тест перед major версиями
  4. Обязательно: abortOnFail: true для критичных thresholds + Slack alerts

Performance degradation — это bug, и он должен ломать CI, как сломанный unit-тест.

✅ Чек-лист завершения урока

После этого урока вы должны уметь:

Интеграция в CI/CD:

  • Настроить k6 в GitLab CI или GitHub Actions
  • Определить, когда запускать какой тест: PR (smoke), nightly (load), release (stress)
  • Использовать Docker image grafana/k6:latest в пайплайнах

Thresholds и exit codes:

  • Понимать, что k6 возвращает exit code 1 при нарушении thresholds
  • Использовать abortOnFail: true для критичных метрик
  • Настроить падение пайплайна при регрессии производительности

Quality gates:

  • Создать скрипт сравнения с baseline (compare.js)
  • Сохранять baseline в Object Storage (S3/GCS/minio)
  • Автоматически блокировать релиз при деградации > 10%

Артефакты и отчеты:

  • Сохранять summary.json как артефакт
  • Экспортировать raw метрики (--out json) для анализа
  • Отправлять уведомления в Slack/Telegram при фейле

Практическое задание:

  • Добавьте smoke-тест в ваш CI/CD (запуск на каждом PR)
  • Создайте nightly job для load-теста
  • Настройте сохранение и сравнение с baseline
  • Проверьте, что пайплайн падает при нарушении thresholds

Если чек-лист пройден — переходите к уроку 13: изучим Chaos Engineering под и gRPC.