CI/CD на GitLab: от коммита до продакшена
Как выглядит путь Java/Spring-сервиса от git push до прода — и почему ручные релизы мы вынесли из процесса полностью.
Хороший пайплайн незаметен: разработчик делает git push, а через несколько минут изменение
уже в проде — проверенное, протестированное и откатываемое одной командой. Расскажу, как мы выстроили
этот путь на GitLab CI/CD для Java/Spring-сервисов в банковской среде, где цена ошибки высока.
Этапы пайплайна
Мы держим единый шаблон .gitlab-ci.yml с четырьмя смысловыми стадиями:
stages:
- build # компиляция и unit-тесты
- verify # статанализ, проверки безопасности
- package # сборка образа и push в Harbor
- deploy # helm upgrade в нужное окружение
Build: быстро и с кэшем
Java-сборка медленная, если не кэшировать зависимости. Локальный репозиторий Maven кэшируется между
запусками, а артефакты подтягиваются из внутреннего Nexus — это и быстрее, и не зависит
от внешних реестров.
build:
stage: build
image: maven:3.9-eclipse-temurin-21
cache:
key: "$CI_PROJECT_NAME"
paths: [ .m2/repository ]
script:
- mvn -s ci/settings.xml -B clean test package
artifacts:
paths: [ target/*.jar ]
expire_in: 1 day
Verify: качество и безопасность как gate
На этой стадии — статический анализ, проверка зависимостей на известные уязвимости и сканирование кода. Принцип простой: если gate красный, дальше пайплайн не идёт. Безопасность не должна быть отдельным ручным этапом «когда-нибудь потом» — она встроена в DevSecOps-конвейер.
Package: образ в Harbor
Образ собираем многослойно (multi-stage Dockerfile), тегируем по SHA коммита и пушим в приватный
реестр Harbor, где включено сканирование образов на уязвимости. Тег по SHA даёт
иммутабельность: один коммит — один образ, никаких «latest, который вчера был другим».
package:
stage: package
script:
- docker build -t $HARBOR/$APP:$CI_COMMIT_SHORT_SHA .
- docker push $HARBOR/$APP:$CI_COMMIT_SHORT_SHA
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
Деплой через Helm — и почему без ручных рук
Деплой — это helm upgrade с values для конкретного окружения. В стейдж выкатываемся
автоматически на каждый коммит в main; в прод — той же командой, но с ручным
подтверждением (when: manual) и обязательным ревью.
deploy:prod:
stage: deploy
script:
- helm upgrade --install $APP ./charts/$APP
--namespace prod
--set image.tag=$CI_COMMIT_SHORT_SHA
--atomic --timeout 5m
environment: { name: production }
when: manual
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
Флаг --atomic здесь ключевой: если выкатка не прошла health-check за таймаут, Helm
автоматически откатывается на предыдущий релиз. Откат — это не аварийная процедура, а штатная часть
процесса.
Раньше релизы делались руками по чек-листу в полночь. Каждый ручной шаг — это шанс на ошибку под усталость. Мы заменили чек-лист на код: теперь пайплайн делает то же самое, но одинаково каждый раз.
Git Flow и управление релизами
Ветвление — это тоже часть CI/CD. Мы используем понятную модель: feature-ветки → MR с обязательным
ревью и зелёным пайплайном → main. Релизы тегируются семантически, а changelog генерируется
из истории коммитов. Это даёт прозрачность: по любому тегу видно, что именно поехало в прод.
Что это дало на практике
- Скорость: от коммита до стейджа — минуты, а не часы согласований.
- Предсказуемость: деплой одинаковый в любое время суток и любым инженером.
- Безопасность: уязвимости ловятся в пайплайне, а не на пентесте.
- Спокойствие: любой релиз откатывается одной командой.
Итог
Хороший CI/CD — это не про «модно автоматизировать». Это про то, чтобы убрать человека из рутинных и опасных шагов, оставив ему ревью и принятие решений. Чем скучнее проходит релиз — тем лучше работает пайплайн.