Jenkins хорошо послужил. Но 22 репозитория с 22 уникальными пайплайнами? Это не CI/CD. Это хаос с зелёной иконкой.

Нарисую картину. Двадцать два репозитория. У каждого свой Jenkins-пайплайн. Каждый написан другим разработчиком в другом году с другим пониманием слова "деплоймент." У одних были тесты. У других были... намерения тестов. В одном был комментарий "TODO: add tests" от 2021 года.

Каждый пайплайн — уникальная снежинка. Красив в изоляции. Кошмар в масштабе.

Когда паттерн деплоя нужно поменять — скажем, добавить health check после деплоя — кто-то должен был тронуть 22 файла. В 22 репозиториях. С 22 чуть разными синтаксисами. И надеяться, что разработчик, написавший пайплайн #7 три года назад, оставил достаточно комментариев.

Не оставил.

Почему Jenkins должен был уйти

Я не хейтер Jenkins. Jenkins — рабочая лошадка. Он запускал билды, когда половина индустрии не знала, что такое CI. Но Jenkins в 2025 году для команды, мигрирующей на GitLab — это как ехать на лошади по автобану. Технически возможно. Практически больно.

Реальные проблемы:

  • Нет нативной Git-интеграции. Jenkins считает код входными данными. GitLab считает код контекстом. Пайплайны на merge request, деплоймент по веткам, трекинг environments — всё нативно в GitLab, всё плагины в Jenkins.
  • Plugin hell. Каждая возможность Jenkins требует плагин. У каждого плагина свой цикл обновлений, security advisory и матрица совместимости. Я видел инстансы Jenkins с 80+ плагинами, где никто не знал, какие реально нужны.
  • Пайплайны-снежинки. Без системы общих шаблонов каждая команда строит пайплайн с нуля. Те же паттерны, разные реализации. Умножить на 22 репозитория.

Архитектура шаблона

Я не писал 22 GitLab CI пайплайна. Я написал один.

Один enterprise-grade шаблон, который подключает каждый репозиторий. Один источник правды о том, как работают билды, тесты и деплои. Одно место для фикса бага — и все 22 репозитория получают исправление мгновенно.

Шаблон обрабатывает:

Build stage: .NET, Node, Java — автоопределение по структуре проекта. Один билд, артефакт везде. Тот же бинарник, что прошёл тесты в DEV — идёт в продакшн. Никаких "у меня работает."

Test stage: Юнит-тесты с обязательным контролем покрытия. Не "у нас есть тесты." А "если покрытие упало ниже порога, пайплайн умирает." Потому что опциональные тесты — это никакие тесты.

Security gates: Сканирование зависимостей, обнаружение секретов, SAST. Зашито в шаблон, не opt-in. Ты не выбираешь, сканируется ли твой код. Он сканируется.

Deployment stage: Мультисредовая прогрессия — DEV, UAT, PROD — с обязательной валидацией на каждом гейте. Пропустить в продакшн нельзя. Физически нельзя. Пайплайн принуждает DEV → UAT → PROD. Всегда.

Zero-downtime slots: Продакшн-деплои используют deployment slots Azure App Service. Деплой в staging slot, валидация, свап. Если что-то ломается — свап обратно. Без даунтайма. Без молитв.

Паттерн миграции

Мигрировать 22 репозитория — это не "скопировать Jenkinsfile в .gitlab-ci.yml." Это системный процесс:

  1. Аудит Jenkins-пайплайна каждого репозитория. Понять, что он реально делает, а не что кто-то думает. Часто это разные вещи.

  2. Маппинг Jenkins-стадий на GitLab-стадии. Большинство транслируются напрямую. Некоторые раскрывают, что Jenkins делал то, о чём никто не знал. А некоторые — что ничего полезного не делал вообще.

  3. Замена репо-специфичной логики на include шаблона. .gitlab-ci.yml репозитория становится 15 строк: подключи шаблон, задай переменные, готово.

  4. Валидация — запуск обоих пайплайнов параллельно на спринт. Сравнение результатов. Фикс расхождений.

  5. Переключение. Отключить Jenkins. Без постепенного заката. Чистый разрыв.

Результат: 22 репозитория, каждый с .gitlab-ci.yml короче этого абзаца. Все указывают на один общий шаблон. Все деплоятся одинаково. Все тестируются одинаково. Все защищаются одинаково.

Аргумент про покрытие

"Мы добавим тесты потом."

Нет. Не добавите. "Потом" в разработке означает "никогда." Я видел это достаточно раз, чтобы знать.

Шаблон принуждает покрытие тестами с первого дня. Не 100% — это vanity metric. Но осмысленный порог, который ловит регрессии. Если merge request роняет покрытие — он не мёрджится. Конец дискуссии.

Вот тут разработчики сопротивляются. "Но мой сервис другой." "Но мы спешим." "Но тесты flaky."

Твой сервис следует тому же паттерну деплоя, что и 21 другой. Ты всегда спешишь. А flaky тесты — это баг, а не отмазка.

Shared Scripts через Package Registry

Вот трюк, который сэкономил недели: PowerShell-скрипты деплоя, хранящиеся в GitLab Package Registry.

Вместо копирования скриптов в каждый репозиторий, шаблон забирает последнюю версию из реджистри в рантайме. Обновил скрипт один раз, опубликовал — и каждый пайплайн подхватит при следующем запуске.

Фиксация версий доступна для команд, которым нужна стабильность. Но дефолт — всегда "latest." Потому что общие скрипты должны развиваться с платформой, а не стагнировать в чьём-то форке.

Что изменилось

До: 22 снежинки. Без стандарта. Без принуждения. "Деплоймент" значил разное в каждом репозитории.

После: Один шаблон. Обязательное тестирование. Обязательное сканирование безопасности. Обязательная прогрессия сред. Zero-downtime продакшн-деплои.

Команды сопротивлялись сначала. Изменения — это некомфортно. Но после первого продакшн-деплоя без единого ручного шага — билд, тест, скан, деплой DEV, валидация, деплой UAT, валидация, ручной апрув, деплой PROD, валидация — всё в одном запуске пайплайна... сопротивление испарилось.

Потому что никто не скучает по Jenkins, когда новая система просто работает.

22 репозитория. Один пайплайн. Ноль снежинок. 100% контроль покрытия.

Это не миграция. Это апгрейд.