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." Это системный процесс:
-
Аудит Jenkins-пайплайна каждого репозитория. Понять, что он реально делает, а не что кто-то думает. Часто это разные вещи.
-
Маппинг Jenkins-стадий на GitLab-стадии. Большинство транслируются напрямую. Некоторые раскрывают, что Jenkins делал то, о чём никто не знал. А некоторые — что ничего полезного не делал вообще.
-
Замена репо-специфичной логики на include шаблона.
.gitlab-ci.ymlрепозитория становится 15 строк: подключи шаблон, задай переменные, готово. -
Валидация — запуск обоих пайплайнов параллельно на спринт. Сравнение результатов. Фикс расхождений.
-
Переключение. Отключить 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% контроль покрытия.
Это не миграция. Это апгрейд.