Как мы вшили нагрузочное тестирование в CI/CD, чтобы не хоронить фичи в проде глубокой ночью
- суббота, 13 сентября 2025 г. в 00:00:08
Привет Хабр! Я Дима, DevOps-инженер в IT-компании.
Эпик-фейлы бывают разные. Можно забыть закоммитить config.json. А можно так упаковать новый эндпоинт, что всё апи ляжет костьми в час-пик и будет тихо плакать под лавиной реквестов. Ручные нагрузочные тесты — это как проверять тормоза на уже летящем с горы автомобиле. Сегодня говорим о том, как автоматизировать эту магию — вшивать проверку производительности прямо в CI/CD, чтобы не краснеть перед продом и спокойно спать по ночам. Запускайте свои пайплайны, щупальцы в руки — погнали.
Смотрите. Раньше было так: написали код → отправили в прод → получили алерт в 3 ночи → вся команда в панике → кофе льётся рекой, кулера пустеют. Знакомо? Это порочная практика. Мы называем её «тушение пожаров вслепую».
А можно иначе. Левостороннее смещение — звучит как термин из анатомии, а на деле это принцип «лови баги раньше, чем они станут багами». Запускать нагрузку не перед релизом, а на каждом пулл-реквесте. Чтобы ещё до того, как код коснётся продовой ветки, мы понимали: этот мерж рейт-лимит не сломает? А этот новый эндпоинт не отправит базу данных в глубокий и болезненный кирпич?
Перестаём тыкать палкой в монитор и начинаем жить как взрослые.
Чтобы встроить нагрузку в CI/CD, нам нужен инструмент, который:
Беспощаден к ошибкам — упал сам, уронил пайплайн.
Быстр как молния — тесты на 10 минут никто ждать не будет.
Умеет в JSON — потому что нас ждут пороги, графики и автоматические алерты.
Наш фаворит — k6. Он быстрый, написан на Go, не требует танцев с JVM, как JMeter, и не заставляет вас вспоминать Scala, как Gatling. Просто берёшь и пишешь скрипт на JavaScript. Элегантно, как удар топором.
Допустим, вы храните код на GitHub. Тогда вам сюда — в /.github/workflows/load-test.yml.
name: Load Test - Don't Merge If Slow
on:
pull_request:
branches: [ main ]
jobs:
load-test-k6:
runs-on: ubuntu-latest
steps:
name: Забираем код
uses: actions/checkout@v4
name: Ставим k6
uses: grafana/setup-k6@v1
name: Гоняем нагрузку как надо
run: |
k6 run --threshold "http_req_duration{p(95)<800}" src/loadtest.js
env:
BASE_URL: ${{ secrets.TEST_ENV }}
name: Если упало — светимся красным
if: failure()
run: |
echo "ВСЁ ПРОПАЛО. ПАЙПЛАЙН УПАЛ. МЕРЖИТЬ НЕЛЬЗЯ."
Что здесь происходит?
Всё просто:
Как только кто-то пытается замержить PR в main — триггерится этот воркфлоу.
Ставим k6.
Запускаем тест. Если 95-й перцентиль задержки превысит 800ms — k6 вернёт ненулевой exit code.
Пайплайн зафейлится. Мерж заблокирован. Коммитер плачет, но продакшен — цел.
Если вы из лагеря GitLab — вот вам настройка в .gitlab-ci.yml:
load_test:
image: grafana/k6:latest
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- k6 run --out json=result.json loadtest.js
artifacts:
reports:
junit: result.xml
allow_failure: false
Красиво, ёмко, и главное — если что-то пойдёт не так, мерж-реквест не пройдёт. Артефакты с результатами прикрепятся к пайплайну — можно будет тыкнуть пальцем, где именно стало плохо.
Не ломайте билды сразу. Первые недели просто собирайте метрики. Потом ставьте реалистичные пороги.
Тестируйте по-разному: для PR — короткие смоук-тесты, по ночам — полные сценарии.
Используйте threshold в скриптах k6:
export const options = {
thresholds: {
'http_req_duration{status:200}': ['p(95) < 500'],
'http_req_failed': ['rate < 0.01'],
},
};
Перевожу: если больше 1% запросов упадут или 95% ответов будут медленнее 500ms — это фейл. Всё.
Если до сих пор вы запускали нагрузку вручную раз в квартал — вы живете в каменном веке. Современный пайплайн должен быть жёстким, автоматическим и безжалостным к регрессиям.
Вшивайте нагрузку в CI/CD. Ловите падение перформанса до того, как его увидят пользователи. И спите спокойно.
(звук уходящего вдаль падающего сервера, затихающий пинг)