CI/CD, критерии прохода и гейты перед релизом
Где запускать и сколько
- 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 с бейзлайном
Пайплайн в продакшен:
- Run: запускаем тест и сохраняем
summary.json+ «сырые» метрики (--out jsonили Prom/Influx). - Fetch baseline: скачиваем предыдущий эталон из Object Storage (S3/GCS/minio).
- Compare: скрипт считает дельту p95/p99/error rate/бизнес-метрик. Пример псевдо:
node scripts/compare.js --current summary.json --baseline baseline.json --budget 0.1- Gate: if delta > 10% или thresholds нарушены — фейлим пайплайн, прикладываем ссылку на дашборд.
- 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):
-
Разработчик делает "небольшое улучшение":
- Рефакторинг кода авторизации для "лучшей читаемости"
- PR проходит code review ✅
- Unit/E2E тесты зеленые ✅
-
Deploy в production:
- Пайплайн: lint → test → build → deploy → ✅ success
- Monitoring: CPU, memory, error rate — в норме ✅
-
Через 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):
-
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 }, }; -
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"); -
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 месяцев:
-
Caught 23 regressions в CI (до production):
- Smoke-тест заблокировал 18 PR с performance issues
- Nightly load-тест обнаружил 5 медленных деградаций
- Zero performance incidents в production за 6 месяцев
-
Примеры 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 предотвращена
- Smoke-тест провалился: p95
-
ROI:
- Стоимость внедрения: 80 часов (CI setup + тесты + baseline scripts) = $32K
- Избежали: 23 потенциальных инцидента × $20K средний ущерб = $460K
- CI overhead: +4 минуты на PR (smoke), +15 минут на nightly
- ROI: 14.4x
-
Культурный сдвиг:
- Разработчики начали запускать 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 на каждом этапе разработки:
- PR stage: Smoke-тест (3-5 минут) блокирует очевидные регрессии
- Nightly: Load-тест на staging с baseline comparison
- Pre-release: Stress-тест перед major версиями
- Обязательно:
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.