Переменные на уровне джобы и предопределённые переменные

Разбираем, как объявлять переменные в пайплайне и какие переменные GitLab даёт бесплатно.

Предопределённые переменные — переменные окружения, которые GitLab автоматически подставляет в каждую джобу: имя ветки, SHA коммита, URL реестра и десятки других.

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

Объявление своих переменных

Переменные задаются ключом variables — глобально или внутри джобы. Они доступны в командах как обычные переменные окружения оболочки.

variables:
  APP_ENV: "staging"

build:
  variables:
    OPTIMIZE: "true"
  script:
    - echo "Окружение $APP_ENV, оптимизация $OPTIMIZE"

Глобальные переменные видны всем джобам; объявленные внутри джобы — только ей. Если имя совпадает, локальное значение перекрывает глобальное.

Это даёт удобный паттерн «разумные умолчания плюс точечное переопределение»: глобально задаёте безопасное значение (например, APP_ENV: "staging"), а в конкретной джобе деплоя на прод переопределяете его на production. Важная тонкость о типах: значения переменных в GitLab — всегда строки. Поэтому OPTIMIZE: "true" — это строка true, а не булево значение, и в условиях rules вы будете сравнивать именно строки: $OPTIMIZE == "true". Кавычки вокруг значений стоит ставить осознанно, особенно если внутри есть пробелы, двоеточия или ведущие нули, которые YAML иначе попытается интерпретировать по-своему.

Предопределённые переменные

GitLab щедро снабжает джобы контекстом. Самые полезные:

ПеременнаяЧто содержит
CI_COMMIT_REF_NAMEимя ветки или тега
CI_COMMIT_SHAполный SHA коммита
CI_COMMIT_SHORT_SHAкороткий SHA (8 символов)
CI_PIPELINE_SOURCEисточник пайплайна (push/schedule/...)
CI_REGISTRY_IMAGEбазовый путь образа в реестре проекта
CI_JOB_TOKENвременный токен прав текущей джобы

Их часто используют для тегирования образов уникальным SHA, чтобы каждый билд был отслеживаемым:

docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .

Стоит сгруппировать предопределённые переменные по смыслу, чтобы держать их в голове. Есть коммитные (CI_COMMIT_SHA, CI_COMMIT_REF_NAME, CI_COMMIT_TAG, CI_COMMIT_MESSAGE) — они отвечают на вопрос «что именно мы собираем». Есть пайплайновые (CI_PIPELINE_SOURCE, CI_PIPELINE_ID) — «откуда и почему запущено»: по CI_PIPELINE_SOURCE вы отличаете обычный push от запуска по расписанию или merge-request-пайплайна. Есть джобные (CI_JOB_TOKEN, CI_JOB_ID) — контекст текущей задачи. И есть реестровые (CI_REGISTRY, CI_REGISTRY_IMAGE, CI_REGISTRY_USER) — для входа и публикации в Container Registry проекта. Отдельно отметим CI_JOB_TOKEN: это короткоживущий токен с правами текущей джобы, которым удобно логиниться в реестр или дёргать API GitLab без хранения отдельных секретов — он создаётся при старте джобы и протухает при её завершении.

Переменные в значениях

Переменные можно подставлять в другие значения YAML, в том числе в одну в другую. Это удобно для сборки путей и URL. Например, тег образа собирают из имени реестра и короткого SHA. Помните: подстановка происходит во время выполнения в оболочке, а не на этапе парсинга YAML.

Эта деталь — источник частой путаницы, поэтому остановимся подробнее. Когда вы пишете TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA", GitLab НЕ вычисляет значение в момент чтения файла. Он сохраняет строку как есть и разворачивает $CI_* уже в оболочке, перед запуском script. Практическое следствие: ссылаться можно только на переменные, которые к этому моменту существуют, и нельзя полагаться на сложную логику внутри YAML — вся вычислительная работа происходит в shell. Ещё одно следствие — знак доллара в литеральных строках надо экранировать (в GitLab это делается удвоением: $$), иначе раннер попытается подставить несуществующую переменную и получит пустую строку.

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

В GitHub Actions контекст доступен через выражения ${{ github.sha }} и контексты env, secrets. В GitLab всё проще и ближе к обычной оболочке: всё — переменные окружения вида $CI_*, без особого синтаксиса выражений. Это снижает порог входа, но и контекстных функций меньше.

Разница глубже, чем кажется. В Actions выражения ${{ ... }} вычисляются движком до запуска шага: там можно вызывать функции (contains, startsWith), обращаться к структурированным объектам (github.event.pull_request.title) — это маленький язык выражений. В GitLab такого слоя почти нет: контекст плоский и строковый, всё разруливается обычными средствами оболочки (if, case, grep). Это сознательный компромисс: GitLab проще объяснить и не нужно держать в голове, что вычисляется движком, а что — шеллом. Зато сложную условную логику в GitLab чаще выносят в отдельный rules-блок или в bash-скрипт, а не в выражения внутри значений.

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

Перед запуском script раннер формирует окружение джобы: складывает вместе предопределённые переменные, переменные из .gitlab-ci.yml, переменные проекта/группы из настроек и экспортирует их в оболочку. Поэтому в командах они доступны как любые $VAR. Приоритет (от низшего к высшему) примерно такой: предопределённые → проектные → объявленные в файле → переданные вручную при запуске.

Этот порядок приоритета лучше всего держать как картинку слоёв: чем «ближе к ручному запуску», тем выше приоритет. Снизу — то, что GitLab знает сам (предопределённые), сверху — то, что человек указал явно прямо сейчас.

выше приоритет
  ^  значение из формы "Run pipeline" (ручной запуск)
  |  trigger / downstream-переменные
  |  variables: в .gitlab-ci.yml (джоба перекрывает глобальное)
  |  переменные группы / проекта (Settings -> CI/CD)
  v  предопределённые CI_* переменные
ниже приоритет

Эта лесенка объясняет два повседневных сюрприза. Первый: значение, заданное в UI при ручном запуске, перекрывает то же имя из файла — и это правильно, ведь человек специально его указал. Второй: переменная проекта (например, секрет API) перекрывается одноимённой переменной из .gitlab-ci.yml — поэтому секреты НЕ дублируют в файле, чтобы случайно не затереть защищённое значение незащищённым.

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

  • Считать, что variables внутри джобы видны другим джобам — нет, только глобальные.
  • Путать порядок приоритета и удивляться, что значение из UI перекрыло файл (это ожидаемо для ручного запуска).
  • Хранить секреты прямо в variables файла — секретам место в защищённых CI/CD-переменных проекта (следующий раздел).
  • Забыть, что все значения — строки, и сравнивать $FLAG == true вместо $FLAG == "true".
  • Не экранировать литеральный $ в значениях и получить пустую строку вместо знака доллара.

Итоги

  • variables объявляют переменные глобально или на уровне джобы; локальные перекрывают глобальные.
  • GitLab даёт десятки предопределённых CI_* переменных с контекстом коммита, пайплайна, джобы и реестра.
  • В командах всё это — обычные строковые переменные окружения оболочки; подстановка происходит в shell, а не в YAML.
  • Приоритет растёт от предопределённых к ручному запуску; секреты держите в защищённых переменных проекта, а не в файле.
Проверьте себя
1. Какая переменная содержит короткий SHA коммита, удобный для тегирования образов?
ACI_PIPELINE_SOURCE
BCI_COMMIT_SHORT_SHA
CCI_JOB_TOKEN
DCI_REGISTRY
2. Где видны переменные, объявленные в variables внутри конкретной джобы?
AВо всех джобах пайплайна
BТолько в этой джобе
CТолько в after_script
DТолько в стадии deploy