open atlas
↑ К треку
CI/CD-пайплайны CICD · 03 · 02

Environments, апрувы и откат

Environment в GitHub — это ворота деплоя. Required reviewers, wait timer и branch-правила ставят прод-джобу на паузу; env-секреты заперты до апрува. А когда деплой ломается, roll-forward часто лучше отката, если уже прошла миграция.

CICD Middle ◷ 17 min
Уровень
ОсновыJuniorMiddleSenior

Пятничный хотфикс прошёл CI зелёным, выкатился на staging, и коллега нажал «Approve» на прод-воротах, не глянув в диф, — одиннадцатый апрув за неделю, все рутинные. Этот выкатил конфиг, который направил прод на staging-базу. Сам деплой «успешен»: джоба зелёная, апрув записан. Пейджер сработал через четыре минуты, когда записи начали уходить не туда. Ворота сделали своё дело — поставили паузу и спросили человека, — но человек был натренирован десятью скучными апрувами кликать не глядя. Контроль был; внимание уже потрачено.

Environment — это именованные ворота, а не сервер

В GitHub Actions environmentstaging, production — это не машина. Это именованный объект на репозитории, несущий две вещи: protection rules (правила защиты), решающие, когда джоба, целящаяся в него, может запуститься, и секреты и переменные уровня environment, которые резолвятся только внутри джобы, назвавшей этот environment. Джоба подключается одной строкой:

jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment: production        # <- привязывает джобу к прод-воротам
    steps:
      - run: ./deploy.sh
        env:
          API_KEY: ${{ secrets.API_KEY }}   # резолвится в *прод*-секрет

В тот миг, когда джоба объявляет environment: production, становятся истинными три вещи. Она не стартует, пока не пройдут protection rules. Её ссылки secrets.* резолвятся в секреты этого environment, перекрывая одноимённые секреты уровня репозитория. И если задан required reviewer, джоба сидит в состоянии Waiting — видимо приостановлена в UI запуска, — пока кто-то с доступом её не апрувнет. Важнейшее: джоба, ждущая апрува, ещё не может прочитать секреты environment: прод-ключ недостижим, пока ворота не открыты. Это и есть свойство, которое ты покупаешь. Утёкший workflow, который никогда не дойдёт до апрувнутой прод-джобы, никогда не увидит прод-учётки.

Protection rules: ревьюеры, таймеры и кто может деплоить

Когда настраиваешь прод-environment, правильный вопрос не «как добавить ворота?», а «что именно эти ворота останавливают?». GitHub даёт environment небольшой, грубоватый набор правил защиты, и сеньор читает их как контроль радиуса поражения.

ПравилоЧто делаетЖёсткий лимит / подвох
Required reviewersДжоба ждёт апрува человека (до 6 ревьюеров/команд), прежде чем запуститьсяАвто-фейл через 30 дней без апрува; кликнуть должен лишь один из списка
Prevent self-reviewТот, кто запустил деплой, не может апрувнуть свой жеВыключено по умолчанию — прямой фикс «штамповки» собственного пуша
Wait timerЗадерживает джобу на N минут после триггера (окно «отлёжки» / отмены)1–43 200 мин (30 дней); не идёт в оплачиваемое время
Deployment branches/tagsОграничивает, какие ветки/теги могут деплоить в этот environmentнапример, в production попадают лишь main или теги v*

На планах Free/Pro/Team эти правила работают только для публичных репозиториев; приватным нужен GitHub Team или Enterprise. И кусается одна особенность дизайна: protection rules цепляются к джобе, а не к workflow. Если три джобы целятся в production, ревьюер апрувит три раза — быстрейшая дорога к усталости от апрувов.

Модель promotion: staging авто, production на паузе

Стандартная форма — единый пайплайн build → staging → production, где staging автоматический, а production — гейтированная promotion того же артефакта. Ты собираешь один раз, выкатываешь ровно эту сборку на staging без ворот, а прод-джоба — зависящая от staging через needs: — встаёт на паузу у своих ворот, пока ревьюер не продвинет её. Добавь concurrency:, чтобы два пуша не гонялись в один environment.

concurrency:
  group: deploy-${{ github.ref }}   # один прод-деплой за раз на ref
  cancel-in-progress: false         # дай текущему деплою доехать, следующий — в очередь

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging            # нет required reviewer -> запускается автоматически
    steps:
      - run: ./deploy.sh staging

  deploy-production:
    needs: deploy-staging           # продвигаем ту же сборку
    runs-on: ubuntu-latest
    environment:
      name: production              # required reviewers заданы в настройках репо
      url: https://app.example.com
    steps:
      - run: ./deploy.sh production

concurrency здесь — тихий герой. Без него два мёржа в main с разницей в минуты запускают два прод-деплоя, которые переплетаются — старый артефакт может приземлиться после нового и тихо победить. Ключ группы — любая строка; cancel-in-progress: false даёт безопасный дефолт «доделай текущий деплой, потом следующий», максимум один в работе и один в ожидании.

Почему это работает

Почему environment запирает секреты, если можно просто хранить прод-ключ как секрет репозитория? Потому что секрет репо резолвится в любой джобе, включая запущенную из fork-PR или менее доверенного workflow. Секрет environment резолвится только в джобе, которая назвала environment и прошла его protection rules — поэтому прод-учётка недостижима, пока человек не апрувнул деплой из разрешённой ветки. Ворота и скоуп секрета — один механизм: апрув — это ещё и момент, когда секрет становится читаемым.

Когда ломается: откат vs roll-forward

Плохой деплой уехал. Что теперь? Два хода. Откат (rollback): переразвернуть предыдущий заведомо рабочий артефакт — быстро и тривиально, когда пайплайн умеет переподнять деплой-джобу прошлой сборки. Roll-forward (он же fix-forward): пишешь фикс, прогоняешь через тот же гейтированный пайплайн, выкатываешь новую версию поверх.

Откат — инстинкт, но у него острый край: как только деплой прогнал миграцию базы, переразвёртывание старого кода не откатывает схему. Старый бинарник теперь встречает схему, под которую его не писали, — удалённую колонку, переименованную таблицу, бэкфилленный enum. Гайд самого GitLab прямолинеен: нет гарантии, что приложение и база откатятся вместе, так что самая безопасная стратегия — обычно roll-forward. Откатывай код, продвигай вперёд данные. Если миграции аддитивны и обратно совместимы (дисциплина expand/contract), откат кода безопасен — старый код по-прежнему работает с новой схемой. Если они деструктивны, откат может испортить или потерять данные, и форвард-фикс — компенсирующая миграция плюс исправленный код — единственный честный ход.

Викторина

Джоба с `environment: production` и required reviewer триггернута. До того как кто-либо апрувнул, что верно?

Выбери лучший вариант

Деплой v2 прогнал миграцию, переименовавшую колонку, затем в новом коде всплыл баг. Как восстанавливаешься?

Вспомните перед уходом
  1. 01
    Что меняется в тот миг, когда джоба объявляет `environment: production`, и почему это делает секреты environment безопаснее секретов репозитория?
  2. 02
    После деплоя, прогнавшего деструктивную миграцию, почему roll-forward обычно безопаснее отката к предыдущему артефакту, и когда откат всё-таки норм?
Итог

Environment в GitHub Actions — это именованные ворота, а не сервер: он несёт protection rules, решающие, когда целящаяся в него джоба может запуститься, и секреты, которые резолвятся только внутри джобы, назвавшей этот environment. Объявление environment: production держит джобу в Waiting, пока её required reviewer не апрувнет, и прод-секреты остаются нечитаемыми до этого апрува — ровно поэтому секреты environment лучше секретов репо, ведь секрет репо резолвился бы в любой джобе, включая недоверенную. Protection rules — грубоватые контролы радиуса поражения: required reviewers (авто-фейл через 30 дней, апрувнуть должен лишь один), Prevent self-review против штамповки собственного пуша, wait timer от 1 до 43 200 минут как окно отлёжки и ограничения по веткам/тегам на то, кто может деплоить. Они цепляются к джобе, а не к workflow, поэтому три прод-джобы означают три апрува — дорога к усталости от апрувов, где ревьюер, натренированный десятью рутинными кликами, машет одиннадцатому, направившему прод не на ту базу. Стандартный пайплайн собирает один раз и продвигает тот же артефакт staging→production, со staging автоматическим и production гейтированным, и concurrency по ref, чтобы два пуша не загнали устаревший артефакт поверх свежего. Когда деплой ломается, откат — переразвёртывание прошлого артефакта — быстр и верен лишь когда миграции аддитивны и обратно совместимы; как только прошла деструктивная миграция, старый код больше не совпадает со схемой, поэтому делаешь roll-forward с компенсирующей миграцией через те же ворота, а не ставишь на откат или живую хирургию БД. Теперь, когда в инциденте кто-то скажет «давай просто откатимся» — первый вопрос, который ты задаёшь: прошла ли миграция? Этот ответ определяет, поможет откат или создаст второй инцидент поверх первого.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.