← CI/CD: практика
13 мин · средне · Урок 9 из 10

rules: кто запускается, а кто ждёт кнопку

Разводим выкат по веткам, ставим ручной гейт на прод и разбираемся, почему rules вытеснили only/except.

Задача

Пока пайплайн одинаковый везде: любая ветка делает то же, что main. В работе так не бывает. Тесты гоняют всюду, черновой выкат делают для веток, прод трогают осознанно и не автоматом.

Материалы урока:

git pull --no-rebase upstream main
cp lesson-09-rules/pipeline.yml .gitlab-ci.yml

Условие на job

deploy-review:
  stage: deploy
  script:
    - echo "Черновой выкат для ветки $CI_COMMIT_BRANCH"
  rules:
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH

deploy-prod:
  stage: deploy
  script:
    - echo "Боевой выкат из $CI_COMMIT_BRANCH"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual
      allow_failure: false

rules это список условий, который проверяется сверху вниз до первого совпадения. Совпало — job попадает в пайплайн, не совпало ни одно — job просто отсутствует, без всякого «пропущено».

Проверьте на своём проекте. Запушьте в main, потом создайте ветку:

git checkout -b feature-x
git push -u origin feature-x

Результат: у ветки пайплайн зелёный и в нём deploy-review, у main пайплайн в состоянии Blocked и deploy-prod ждёт нажатия.

when: manual и что такое Blocked

when: manual превращает job в кнопку. Пайплайн доходит до него, останавливается и ждёт человека. В списке пайплайнов такой пайплайн помечен как Blocked, а не Failed: ничего не сломалось, просто дальше нужно решение.

Кнопка живёт на странице пайплайна, значок «Run» напротив job. Если в стадии одни только ручные job, над стадией появляется «Run all manual» и запускает их разом.

Со значением allow_failure есть тонкость, на которой путаются даже в статьях. Умолчаний три:

То есть в примере выше строка allow_failure: false ничего не меняет, она стоит как самодокументация. А вот если бы when: manual был вынесен из rules на уровень job, падение деплоя не покрасило бы пайплайн, и вот там эту строку писать обязательно.

Условия, которые пригодятся

Кроме ветки, rules умеют смотреть на источник запуска, изменённые файлы и переменные:

  rules:
    # только в merge request
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    # только если менялся фронтенд
    - changes:
        - src/**/*
    # ручной запуск с переменной
    - if: $DEPLOY_TARGET == "staging"

changes хорошо экономит минуты в монорепозиториях: правка README не должна запускать сборку и тесты всего проекта. Одна оговорка: это правило имеет смысл только для пайплайнов ветки и merge request. Там, где push-события нет (по расписанию, по тегу, вручную), changes всегда истинно, и job запустится, даже если ничего не менялось.

Явный when: never тоже бывает нужен, чтобы вырезать частный случай перед общим правилом:

  rules:
    - if: $CI_COMMIT_BRANCH == "wip"
      when: never
    - if: $CI_COMMIT_BRANCH

Порядок важен: сработает первое подошедшее правило, остальные не смотрятся.

only и except: наследство

В старых конфигах и туториалах встречается другой синтаксис:

deploy:
  script:
    - echo "деплой"
  only:
    - main

only/except официально помечены устаревшими: они работают ради совместимости, но могут быть удалены в одном из следующих мажорных выпусков. Мешать их с rules в одном job нельзя, GitLab отвергнет конфиг.

Условия по переменным и по изменённым файлам они, вопреки расхожему мнению, умеют (only:variables, only:changes). Чего у них нет, так это связки «условие — действие»: нельзя внутри правила сказать when: manual или allow_failure, а именно это чаще всего и нужно.

Новый конфиг пишем на rules. Старый переписываем, когда всё равно трогаем этот job.

Если job не появился

Самая частая причина в том, что не совпало ни одно правило, и это не ошибка, а поведение по умолчанию. Проверьте фактическое имя ветки: CI_DEFAULT_BRANCH у новых проектов main, у старых бывает master.

Второе по частоте: merge_request_event в правиле, а вы пушите в ветку без открытого merge request. Пайплайн ветки и пайплайн MR это разные события.

Посмотреть, как GitLab понял конфиг, можно в Build -> Pipeline editor -> вкладка Full configuration: она раскрывает include, extends и якоря. Учтите, что условия там считаются для гипотетического пуша в основную ветку, а не для той, на которой вы сейчас.

Что важно запомнить

rules проверяются сверху вниз до первого совпадения; не совпало ничего — job не создаётся вовсе.

when: manual делает из job кнопку, пайплайн при этом получает статус Blocked.

У ручных job allow_failure по умолчанию true на уровне job и false внутри rules; для боевого выката нужен именно false.

changes и условия по переменным экономят минуты и не дают гонять лишнее.

only/except устарели и не смешиваются с rules в одном job.

Дальше собираем разбор антипаттернов: что в пайплайнах ломается чаще всего и как это выглядит вживую.

Прошли материал до конца? Отметьте урок пройденным.