Environments, staging и production
Учимся объявлять окружения деплоя, получать историю релизов и кнопку отката.
Environment — именованная цель деплоя в GitLab (например,
stagingилиproduction) с историей деплоев и ссылкой на работающее приложение.
Зачем объявлять окружения
Можно просто выполнить ./deploy.sh в джобе — и приложение задеплоится. Но GitLab не будет знать, что произошёл деплой: ни истории, ни кнопки отката, ни ссылки. Ключ environment превращает обычную джобу в осмысленный деплой, который GitLab отслеживает.
Разница тут не косметическая, а операционная. Без окружений у вас есть только лента пайплайнов: «зелёный», «красный», «зелёный». По ней невозможно ответить на простые, но критичные в инциденте вопросы: какая версия сейчас крутится на проде, кто и когда её выкатил, и на какой коммит откатиться, если всё сломалось. Объявив environment, вы получаете отдельную сущность, которая помнит свою историю независимо от того, сколько пайплайнов прогналось мимо. Это переводит деплой из разряда «побочный эффект скрипта» в разряд «учётная запись о состоянии системы».
Окружение — это ещё и язык общения внутри команды. Когда тестировщик пишет «баг воспроизводится на staging, но не на production», он опирается на те же имена, что и CI; когда менеджер спрашивает «что уже на проде», ответ ищется на странице окружения, а не в переписке. Именованные цели деплоя дисциплинируют процесс: появляется чёткая граница между «проверяем» и «отдаём пользователям».
deploy-staging:
stage: deploy
script:
- ./deploy.sh staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: '$CI_COMMIT_BRANCH == "main"'Теперь в разделе Deployments → Environments появится окружение staging с историей: какой коммит, когда, кем задеплоен. Кнопка-ссылка ведёт на живое приложение, а кнопка отката переразвернёт предыдущий успешный деплой.
Обратите внимание, что name и url — это не просто строки для красоты. name служит ключом, по которому GitLab группирует все деплои в одну историю: пока имя совпадает, записи копятся в одно окружение. url хранится как метаданные и показывается кликабельной кнопкой «Open» прямо из карточки окружения и из виджета пайплайна — мелочь, которая экономит команде десятки переходов в день, потому что не нужно помнить адреса стендов наизусть.
Лесенка staging → production
Типичный поток: автоматический деплой в staging из main, затем ручной гейт на production. Так изменения сначала живут на staging, где их проверяют, и лишь по решению человека едут в прод.
Почему именно ручной гейт, а не полная автоматизация до прода? Потому что технически зелёные тесты и бизнес-готовность к релизу — это разные вещи. Тесты подтверждают, что код работает как написан; они не знают, что маркетинг ещё не объявил фичу, что сейчас пиковая нагрузка и катить рискованно, или что параллельно идёт миграция БД. Ручная кнопка на проде — это место, где человек берёт на себя ответственность за момент выката. При этом всё, что можно автоматизировать без риска (сборка, тесты, деплой в staging), остаётся автоматическим, и человеку оставляют ровно одно решение — «жмём?».
deploy-production:
stage: deploy
script:
- ./deploy.sh production
environment:
name: production
url: https://example.com
when: manual
rules:
- if: '$CI_COMMIT_BRANCH == "main"'Связка environment + when: manual + rules на main — каноничный безопасный деплой: автоматическая проверка, ручной релиз, отслеживаемое окружение.
Важная деталь: rules на main закрывают не один сценарий, а целый класс ошибок. Без них прод-джоба попала бы в пайплайн любой ветки и feature-ветки, и любой разработчик, даже не имея намерения, увидел бы у себя кнопку «деплой в production». Ограничив джобу веткой main, вы превращаете «возможность по случайности» в «осознанное действие из защищённой ветки». А поскольку main обычно защищена и доступна для пуша только через MR, выкат на прод автоматически наследует все ваши правила ревью.
Защита окружений и кто имеет право жать кнопку
GitLab умеет помечать окружение как protected: тогда задеплоить в него и нажать ручную кнопку могут только пользователи с нужным уровнем доступа (например, maintainers). Это второй контур поверх rules: даже если джоба попала бы в пайплайн, человек без прав не увидит активную кнопку. Так прод-деплой становится привилегией — особенно ценно в больших командах, где код пишут десятки, а за релизы отвечают единицы.
Остановка окружения
У окружения может быть джоба остановки (on_stop) — она убирает развёрнутое приложение. Это особенно важно для эфемерных окружений (review apps из следующего урока), которые надо гасить, чтобы не плодить мусор.
Для долгоживущих окружений вроде staging и production остановка нужна реже, но сама способность «корректно погасить» полезна: она формализует обратную операцию к деплою. Если деплой что-то создаёт (контейнер, namespace, поддомен), то on_stop описывает, как это аккуратно демонтировать. Без явной джобы остановки уборка превращается в ручную работу, которую кто-то забудет сделать — а забытая инфраструктура стоит денег и иногда становится дырой в безопасности.
Как работает под капотом
Когда джоба с environment успешно завершается, GitLab создаёт запись о деплое: связывает окружение, коммит, пайплайн и время. История деплоев — это последовательность таких записей. Откат запускает деплой-джобу предыдущего успешного деплоя того же окружения. URL просто хранится как метаданные окружения и показывается ссылкой.
Полезно понимать, что GitLab здесь — бухгалтер, а не исполнитель. Он не разворачивает приложение сам и не знает, что делает ваш ./deploy.sh. GitLab лишь фиксирует факт: «джоба с таким-то окружением завершилась успешно на таком-то коммите». Поэтому и «откат» — это не магическое возвращение состояния серверов, а повторный запуск той же деплой-джобы на коммите предыдущего успешного деплоя. Если скрипт детерминированно разворачивает версию по коммиту — откат сработает; если он зависит от изменчивого состояния (например, «последний образ из реестра») — откат выкатит не то, что вы ждёте. Отсюда правило: деплой-скрипт должен быть воспроизводимым по коммиту.
Схематично жизненный цикл записи об окружении выглядит так:
commit -> pipeline -> deploy-job (environment: staging)
|
v
[запись деплоя: коммит + время + автор]
|
v
Environments: staging -> история -> [Open] [Rollback]Чем это отличается от GitHub Actions
В GitHub Actions есть свои Environments, но их роль смещена: они в первую очередь про гейты и секреты — required reviewers, wait timer, защищённые секреты, привязанные к окружению. История деплоев и кнопка отката в нативном виде там беднее: «развёртывания» (deployments) ведутся через Deployments API, а откат обычно реализуют как отдельный workflow или ручной re-run. В GitLab окружение из коробки даёт связку «история + ссылка + откат» как часть UI, без дополнительного кода. Грубо говоря, GitHub-окружение — это прежде всего политика доступа к секретам и гейт, а GitLab-окружение — это журнал деплоев с действиями. Понимая этот акцент, проще переносить ментальную модель между двумя системами и не искать в одной то, чего там нет по дизайну.
Частые ошибки
- Деплоить без
environmentи лишаться истории, отката и ссылки. - Не ограничить прод-деплой
rulesнаmain— и получить возможность деплоя из любой ветки. - Создавать динамические окружения без
on_stop— они накапливаются и не убираются. - Делать деплой-скрипт зависимым от «последнего» образа вместо коммита — тогда откат через GitLab выкатывает не ту версию.
- Оставлять прод-окружение незащищённым, полагаясь только на
rules— кнопку сможет нажать любой, у кого есть доступ к пайплайну.
Итоги
environmentделает деплой отслеживаемым: история, откат, ссылка на приложение.- Каноничный поток: авто-деплой в staging, ручной гейт в production.
on_stopгасит окружение — критично для эфемерных сред.- GitLab — бухгалтер деплоев: откат воспроизводим, только если скрипт детерминирован по коммиту.
- Защищённые окружения добавляют второй контур: кто именно вправе жать кнопку прода.