rules, only/except и условный запуск

Осваиваем rules — современный способ решать, запускать джобу или нет.

rules — список условий, которые GitLab проверяет сверху вниз, чтобы решить, включить джобу в пайплайн и как именно её запустить.

Зачем условный запуск

Не каждая джоба нужна всегда. Деплой на production — только из main. Линтер — на любой push. Тяжёлые e2e-тесты — только в MR. Без условий пришлось бы держать разные файлы для разных случаев. rules позволяет описать всю логику в одном месте.

Подумайте о rules как о маршрутизаторе входящего события. На каждый коммит, открытие merge request, тег или запуск по расписанию GitLab формирует пайплайн заново — и для каждой джобы спрашивает: «при этих обстоятельствах ты вообще нужна?». Список правил — это и есть ответ на вопрос, выраженный декларативно. Вместо императивного «если-иначе» в скриптах вы описываете желаемое состояние, а движок сам решает, какие джобы материализовать. Это принципиально: правила вычисляются до запуска чего-либо, на этапе планирования пайплайна, поэтому ошибка в условии не «упадёт в логах», а просто исключит или включит джобу не там, где вы ждали.

Практическая ценность видна на типичном проекте. Один и тот же .gitlab-ci.yml обслуживает десятки сценариев: разработчик пушит в feature-ветку, ревьюер открывает MR, релиз-инженер ставит тег, ночью срабатывает расписание для регрессии. Без условного запуска каждый из этих контекстов тянул бы за собой все джобы подряд — и деплой на прод запускался бы с любой ветки, что недопустимо. rules превращает один файл в гибкую программу, которая ведёт себя по-разному в зависимости от того, кто и как её вызвал, оставаясь при этом единственным источником истины.

Базовый rules

deploy-prod:
  stage: deploy
  script:
    - echo "Деплой на прод"
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: on_success
    - when: never

GitLab идёт по правилам сверху вниз и применяет первое совпавшее. Если ветка main — джоба включается и запускается при успехе предыдущих стадий. Иначе срабатывает when: never — джоба не добавляется в пайплайн. Финальный when: never — частый идиоматичный «иначе не запускать».

Важно различать два эффекта правила. Первый — включить или исключить джобу из пайплайна: если ни одно правило не совпало или совпавшее имеет when: never, джобы в пайплайне просто не будет, она не появится даже серой. Второй — задать режим запуска включённой джобы через when: запустится ли она автоматически (on_success), будет ждать клика (manual), пойдёт всегда даже после падений (always) или с задержкой (delayed). Одно правило решает обе задачи сразу, и в этом сила механизма: фильтрация события и стратегия запуска описаны в одной строке.

Внутри одного if можно строить сложные логические выражения: && и ||, сравнения == и !=, проверку на пустоту переменной и даже соответствие регулярному выражению через =~. Например, правило if: '$CI_COMMIT_BRANCH =~ /^release/' поймает все ветки, начинающиеся с release. Это даёт выразительность, которой раньше остро не хватало: одно правило заменяет ветвистую логику, которую прежде приходилось писать в bash внутри script.

changes: реагируем на файлы

Условие changes запускает джобу только если изменились определённые файлы. Например, пересобирать фронтенд лишь когда тронули frontend/:

build-frontend:
  script: ["npm run build"]
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      changes:
        - frontend/**/*

Условие changes особенно полезно в монорепозиториях, где в одном репозитории живут фронтенд, бэкенд и инфраструктура. Запускать полную сборку всего на каждый коммит — расточительство; changes позволяет тронуть лишь то, что реально изменилось. Учтите тонкость: changes сравнивает изменённые файлы с заданными glob-шаблонами, и в MR-пайплайне сравнение идёт относительно целевой ветки, а в push-пайплайне — относительно предыдущего коммита. Поэтому одно и то же правило может вести себя по-разному в зависимости от источника, и это нужно держать в голове при отладке «почему джоба не запустилась».

Ещё одна частая комбинация — exists, проверяющая наличие файла в репозитории (например, запускать docker-сборку, только если есть Dockerfile), и подстановка переменных в variables: прямо внутри правила: совпавшее правило может донастроить переменные для джобы. Так из набора простых кирпичей — if, changes, exists, when, variables — собирается практически любая стратегия запуска без единой строчки императивного кода.

only/except — устаревший подход

Раньше условия задавали ключами only и except (only: [main], except: [tags]). Они проще, но менее гибкие: нельзя комбинировать условия и менять when в зависимости от ситуации. GitLab рекомендует rules; only/except считается устаревшим и не должен смешиваться с rules в одной джобе.

Сравнение с GitHub Actions

В GitHub Actions условия выражаются через on: (триггеры workflow) и if: на уровне джоб/шагов. GitLab объединяет это в rules на уровне джобы: и фильтр события, и условие, и режим запуска. Один механизм вместо двух.

Эта разница не косметическая, а архитектурная. В Actions фильтр события (on:) живёт на уровне всего workflow, а условие if: — отдельно у джобы или шага, и контекст у них разный. В GitLab всё стянуто в один список правил у джобы, оперирующий предопределёнными переменными CI_*. Ниже — карта соответствий, которая поможет переносить конфиги между системами:

ЗадачаGitLab CI (rules)GitHub Actions
Фильтр по веткеif: $CI_COMMIT_BRANCH == "main"on: push: branches: [main]
Только в MR/PRif: $CI_PIPELINE_SOURCE == "merge_request_event"on: pull_request
По изменённым файламchanges: [frontend]on: push: paths: [frontend]
Условие на джобеrules: у джобыjobs.x.if:
Ручной запускwhen: manualworkflow_dispatch

Принципиальное преимущество подхода GitLab — единый словарь и единое место. Разработчику не нужно помнить, какой контекст доступен на каком уровне: правила джобы всегда видят весь набор переменных пайплайна. Минус — список правил у каждой джобы может разрастаться и дублироваться, и тогда на помощь приходят extends и якоря YAML, выносящие общий набор правил в переиспользуемый шаблон.

Как работает под капотом

На этапе создания пайплайна GitLab для каждой джобы вычисляет её rules в контексте текущих переменных (ветка, источник, изменённые файлы). Первое совпавшее правило определяет: включать ли джобу и с каким when (on_success, manual, always, never, delayed). Если совпало never или ни одно правило не совпало — джоба не попадает в пайплайн.

Частые ошибки

  • Смешивать rules и only/except в одной джобе — это ошибка конфигурации.
  • Забыть, что применяется первое совпавшее правило, и ставить общее условие выше частного.
  • Использовать changes в schedule-пайплайнах, где «изменённых файлов» по сути нет — поведение будет неожиданным.

Итоги

  • rules — современный механизм условного запуска; применяется первое совпавшее правило.
  • Условия: if (по переменным), changes (по файлам), а when задаёт режим запуска.
  • only/except устарели; не смешивать с rules.
Проверьте себя
1. Какое правило из списка rules применяется к джобе?
AПоследнее в списке
BВсе одновременно
CПервое совпавшее сверху вниз
DСлучайное
2. Почему предпочитают rules вместо only/except?
Aonly/except быстрее
Brules гибче: комбинируют условия и управляют режимом when, only/except устарели
Crules работают только с тегами
Donly/except нельзя использовать с Docker