Всем привет!
В первой части мы в общих чертах посмотрели на различия GitHub Actions и GitLab, а также начали разбирать структуру файла .gitlab-ci.yml.
В этой части продолжим разбираться с параметрами и особенностями конфигурации: триггеры, job’ы, артефакты и многое другое.
Не понимаете, «что тут происходит»? Рекомендую начать с первой части:: по ссылке.
Если вам интересны подобные материалы, подписывайтесь на Telegram-канал «Код на салфетке». Там я делюсь гайдами для новичков, полезными инструментами и практическими примерами из реальных проектов. А прямо сейчас у нас там ещё и проходит новогодний розыгрыш.
Триггеры и условия запуска Pipeline
Теперь, когда вы понимаете структуру конфигурационного файла, разберёмся с когда и при каких условиях запускается pipeline. Это жизненно важно: от корректных условий зависит, сколько ресурсов тратится и сколько лишних сборок вы видите в логах.
Что такое триггер
Триггер — это событие или условие, из-за которого GitLab начинает выполнение pipeline. Это может быть push, создание/обновление Merge Request, расписание, ручной запуск, внешнее HTTP-событие и т. п.
Раздел rules — условия запуска job’ов (современный подход)
rules — это основной и рекомендуемый способ управлять тем, будет ли job добавлен в pipeline. Именно он пришёл на смену only и except и сегодня используется практически во всех новых конфигурациях.
Ранее для этого использовались
onlyиexcept— простые директивы, которые ограничивали запуск job’а по веткам, тегам или типу pipeline. Они работали, но были негибкими и плохо масштабировались, поэтому в современных конфигурациях считаются устаревшими и замененыrules.
Важно понимать ключевую идею:
rulesрешают появится ли job в pipeline вообще, а не просто когда он запустится.
Если ни одно правило не сработало — job даже не будет добавлен в pipeline.
Базовый синтаксис rules
job_name:
stage: test
script:
- echo "test"
rules:
- if: condition
when: always
- when: neverКаждый элемент в rules — это правило, которое GitLab проверяет сверху вниз.
Основные поля:
if— условие (логическое выражение, результатtrueилиfalse).when— что делать, если условие выполнилось.
Чаще всего используются значения when:
always— job добавляется и запускается автоматически.never— job не добавляется.manual— job добавляется, но ждёт ручного запуска.delayed— отложенный запуск (реже используется, разберём позже).
Первое сработавшее правило останавливает проверку остальных.
Практические сценарии использования rules
Проверка ветки
Самый частый сценарий — запуск job только для определённой ветки:
test_main_only:
stage: test
script:
- npm test
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- when: neverЕсли ветка не main, второе правило гарантирует, что job не появится в pipeline.
Совет: вместо "main" используйте $CI_DEFAULT_BRANCH, чтобы конфиг был переносимым между проектами:
build_main:
stage: build
script:
- echo "Сборка main"
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: always
- when: neverПроверка тега
Теги чаще всего используют для релизов:
release_build:
stage: build
script:
- npm run build
rules:
- if: '$CI_COMMIT_TAG'
when: always$CI_COMMIT_TAG содержит имя тега или пустую строку. Если pipeline запущен не из тега, условие просто не выполнится.
Проверка источника pipeline
Иногда важно понимать как именно был запущен pipeline:
scheduled_job:
stage: test
script:
- echo "Running scheduled job"
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: always
- when: neverВозможные значения $CI_PIPELINE_SOURCE:
push— обычныйgit push.merge_request_event— событие Merge Request.schedule— запуск по расписанию.web— ручной запуск из интерфейса.api— запуск через API.trigger— запуск из другого pipeline.
Срабатывание на Merge Request
Чтобы запускать job при создании или обновлении Merge Request:
test_on_mr:
stage: test
script:
- npm test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- when: neverПропуск job по сообщению коммита
skip_on_draft:
stage: test
script:
- npm test
rules:
- if: '$CI_COMMIT_MESSAGE =~ /\[skip ci\]/'
when: never
- when: alwaysЕсли в сообщении коммита есть [skip ci], job не добавится. (GitLab умеет полностью пропускать pipeline по этому флагу, но здесь показан пример именно в rules.)
Проверка наличия файлов (exists)
build_only_on_src_change:
stage: build
script:
- npm run build
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
exists:
- src/**/*
when: always
- when: neverexists проверяет, есть ли файлы в репозитории. Это полезно, если структура проекта может отличаться, например в монорепозитории.
Фильтрация по изменённым файлам (changes)
Один из самых эффективных способов сократить время pipeline в больших проектах — запуск job только если изменились нужные файлы:
test_backend:
stage: test
script:
- cd backend && pytest
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
changes:
- backend/**/*
- shared/**/*
when: always
- when: neverchanges проверяет дифф коммита, а не наличие файлов.
Логика AND / OR в rules
В if можно использовать логические операторы AND (&&) и OR (||):
deploy_to_prod:
stage: deploy
script:
- ./deploy.sh
rules:
# Тег ИЛИ ветка main (автодеплой)
- if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH == "main"'
when: always
# Merge Request в main — но вручную (AND)
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
when: manual
# Всё остальное — запрещено
- when: neverЗдесь хорошо видно силу rules:
- Автоматический деплой при пуше тега или при пуше в
main. - Ручной деплой из Merge Request, который нацелен в
main. - Всё остальное — запрещено.
Основные триггеры pipeline
Push — самый частый триггер
Pipeline запускается автоматически при git push в репозиторий (по умолчанию — для веток и тегов). Простой job, который выполняется на любой push:
build_on_push:
stage: build
script:
- echo "Запуск на любом push"Если вам нужно ограничить запускаемые ветки — используйте rules (это современный и гибкий способ; only/except сейчас считаются устаревшими и рекомендуется переходить на rules).
Современный пример фильтра по ветке (через rules):
build_main:
stage: build
script:
- echo "Сборка main"
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: on_success
- when: neverЭтот rules-блок гарантирует, что job добавится в pipeline только если текущая ветка совпадает с дефолтной ($CI_DEFAULT_BRANCH), а во всех остальных случаях job не будет добавлен.
Merge Request (MR)
Чтобы запускать job при создании или обновлении Merge Request, чаще всего используют rules с переменной источника пайплайна. Пример:
test_on_mr:
stage: test
script:
- npm test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: on_success
- when: neverТакой job будет добавлен в pipeline при событии Merge Request. Раньше для этого использовали only: - merge_requests, но rules даёт больше контроля и гибкости.
Schedule — запуск по расписанию (cron)
Когда задача должна запускаться регулярно (ночная сборка, регулярная проверка зависимостей, бэкап и т. п.), используйте Pipeline schedules. Сchedules настраиваются в интерфейсе GitLab (CI/CD → Schedules) и принимают cron-выражения. В расписании вы указываете описание, cron, ветку и опциональные переменные. GitLab Документация
Примеры cron-выражений (основы):
0 2 * * *— ежедневно в 02:00.0 * * * *— каждый час.0 0 * * 0— каждое воскресенье в 00:00.0 0 1 * *— первого числа каждого месяца.
Если требуется программный доступ к расписаниям, есть REST API для управления pipeline schedules (создание, редактирование, запуск).
Manual trigger — ручной запуск
Если хотите, чтобы job не запускался автоматически, а появлялся как кнопка «Run» в интерфейсе, используйте when: manual:
deploy_to_prod:
stage: deploy
when: manual
script:
- ./scripts/deploy-to-production.shЭто удобно для развертываний в production: вы контролируете момент запуска. Job с when: manual не стартует автоматически — его нужно запустить вручную из UI либо через API.
Webhook — запуск со стороны внешних событий
В GitLab можно настроить Webhooks (в проекте: Settings → Integrations → Webhooks), чтобы отправлять уведомления о событиях внешним системам. Сам GitLab также может выполнять вызовы по URL при изменениях; в обратную сторону (чтобы внешний сервис запускал пайплайн) обычно используют API или pipeline triggers (токены), описанные ниже.
API / Trigger tokens — запуск программно
Пайплайн можно запустить программно через API — либо стандартным endpoint-ом pipeline, либо через pipeline trigger (токен) для более безопасной интеграции:
curl --request POST \
--header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/projects/<project_id>/pipeline" \
--form "ref=main"Или через pipeline trigger token: POST /projects/:id/trigger/pipeline. Это удобно для интеграций: CI запускается из внешних систем, job'ы могут получать переменные через параметры запроса.
Короткие практические советы
- Рекомендуется использовать
rulesвместоonly/except.rulesдаёт больше контроля (условия, проверки переменных,changesи т. п.).only/exceptподдерживаются, но к ним уже не добавляют новые возможности — лучше перейти наrules. - Не захардкодьте
main— используйте$CI_DEFAULT_BRANCH. Это делает конфиг переносимым между проектами. - Для расписаний используйте UI или API, и не забывайте про временную зону (cron-timezone в API).
- Если внешняя система должна запускать пайплайн — используйте trigger token или API, а не «подделывайте» webhooks вручную. Это безопаснее и проще в управлении прав.
Раздел workflow — условия для всего pipeline
Помните, во первой части мы вскользь упоминали workflow? Самое важное, что нужно про него запомнить: workflow управляет запуском pipeline целиком, а не отдельных job’ов.
Если условия workflow не выполняются — pipeline вообще не создаётся. Ни один job даже не появится в графе.
Пример:
workflow:
rules:
# Не запускаем pipeline для draft Merge Request
- if: '$CI_MERGE_REQUEST_DRAFT == "true"'
when: never
# Запускаем pipeline для Merge Request
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
# Запускаем pipeline для main-ветки
- if: '$CI_COMMIT_BRANCH == "main"'
# Запускаем pipeline для тегов
- if: '$CI_COMMIT_TAG'
# Всё остальное пропускаем (например, feature-ветки)
- when: neverЧто здесь происходит:
- Draft Merge Request — pipeline не создаётся вообще.
- Обычный Merge Request — pipeline создаётся.
- Push в
main— pipeline создаётся. - Push тега — pipeline создаётся.
- Любые другие сценарии (например, push в
feature/*) — pipeline пропускается.
Обратите внимание: правила проверяются сверху вниз, и первое сработавшее правило останавливает дальнейшую проверку — ровно так же, как и в rules у job’ов.
Когда нужен workflow, а когда — rules у job’ов
Это частый вопрос у новичков, поэтому зафиксируем разницу явно.
Разница между workflow: rules и rules у job’ов:
workflow rules— если условие не выполнено, pipeline не создаётся целиком.job rules— pipeline создаётся, но конкретный job может быть пропущен.
Проще говоря:
workflow— отвечает за вопрос: «Нужен ли нам pipeline вообще?».rulesу job’ов — «Нужен ли этот job внутри уже существующего pipeline?».
На практике это часто комбинируют: workflow отсеивает лишние пайплайны (экономия времени и ресурсов), а rules тонко управляют тем, какие job’ы выполняются внутри.
Переменные для использования в условиях
В условиях if вы почти всегда будете опираться на встроенные переменные GitLab CI. Ниже — самые часто используемые, которых обычно хватает для 90% сценариев:
| Переменная | Описание | Пример значения |
|---|---|---|
$CI_COMMIT_BRANCH | Имя текущей ветки | main, develop, feature/new-ui |
$CI_COMMIT_TAG | Имя тега (если pipeline из тега) | v1.0.0, release-2025-12-01 |
$CI_COMMIT_SHA | Хеш текущего коммита | abc123def456... |
$CI_COMMIT_MESSAGE | Сообщение коммита | Fix: update dependencies |
$CI_PIPELINE_SOURCE | Источник запуска pipeline | push, merge_request_event, schedule |
$CI_MERGE_REQUEST_IID | ID Merge Request (в рамках проекта) | 42 |
$CI_MERGE_REQUEST_TARGET_BRANCH_NAME | Целевая ветка Merge Request | main |
$CI_MERGE_REQUEST_DRAFT | Является ли MR черновиком (draft) | true, false |
$CI_PROJECT_NAME | Имя проекта | my-awesome-app |
$CI_PROJECT_PATH | Полный путь проекта | group/subgroup/my-project |
Небольшие, но важные нюансы:
$CI_COMMIT_BRANCHможет быть пустым в некоторых типах pipeline (например, в pipeline, созданном только для Merge Request). В таких случаях стоит использовать$CI_COMMIT_REF_NAME, если вам нужно универсальное имя ref.$CI_COMMIT_TAG— это строка. Если pipeline не теговый, переменная либо пустая, либо не определена — этого достаточно для проверки вif.- Переменные MR (
$CI_MERGE_REQUEST_*) доступны только в pipeline, связанных с Merge Request.
Регулярные выражения в условиях (regex)
В условиях if можно использовать регулярные выражения. Это позволяет описывать не одно конкретное значение, а целый класс значений: шаблоны имён веток, тегов, сообщений коммита и т. д.
В GitLab CI для этого используется оператор =~:
if: '$VARIABLE =~ /pattern/'Важно:
- Справа всегда используется regex в слэшах
/.../. - Слева — строковая переменная GitLab CI.
- Выражение возвращает
trueилиfalse.
Пример: релизные теги
Частый кейс — запуск job только для релизных тегов вида v1.2.3:
job_for_release:
stage: deploy
script:
- echo "Deploying release version"
rules:
# Теги вида v1.2.3
- if: '$CI_COMMIT_TAG =~ /\d+\.\d+\.\d+$/'
when: always
- when: neverРазберём регулярное выражение:
` — строка начинается сv`.\d+— одна или несколько цифр.\.— точка (экранируется).$— конец строки.
Таким образом:
v1.2.3→ подходит.v2.0→ не подходит.release-1.2.3→ не подходит.
Пример: hotfix-ветки
Если в команде принято заводить hotfix-ветки с префиксом hotfix/, это легко описывается через regex:
job_for_hotfix:
stage: deploy
script:
- echo "Deploying hotfix"
rules:
- if: '$CI_COMMIT_BRANCH =~ /\/.+/'
when: always
- when: neverЗдесь:
/— ветка начинается сhotfix/..+— дальше идёт любое имя (не пустое).
Подойдут, например:
hotfix/login-bug.hotfix/urgent-fix-123.
Пример: фильтрация по сообщению коммита
Регулярные выражения особенно полезны для анализа сообщений коммитов:
job_skip_wip:
stage: test
script:
- npm test
rules:
# Пропускаем job, если коммит помечен как WIP / Draft / skip ci
- if: '$CI_COMMIT_MESSAGE =~ /(WIP|Draft|\[skip ci\])/'
when: never
- when: alwaysЗдесь:
(WIP|Draft|\[skip ci\])— логическое «или».- Квадратные скобки экранированы, потому что в regex они имеют специальное значение.
Такой подход помогает:
- Не запускать тесты для черновых коммитов.
- Экономить время CI.
- Делать поведение pipeline более предсказуемым.
Когда и зачем использовать when: never
Очень распространённый паттерн:
job:
rules:
- if: some_condition
when: always
- when: neverНа первый взгляд when: never кажется избыточным. Но на практике это важная и полезная привычка.
Что тут описано:
- Если
some_condition—true→ job добавляется в pipeline. - Во всех остальных случаях → job не добавляется вообще.
Без when: never:
job:
rules:
- if: some_condition
when: always
# Для остальных случаев: что делать? Неясно!Поведение становится менее очевидным, особенно для тех, кто будет читать конфиг позже. Формально GitLab просто не добавит job, но это не всегда сразу понятно из кода.
Поэтому when: never в конце:
- Делает логику явной.
- Упрощает чтение конфигурации.
- Снижает риск ошибок при доработках.
Хорошее правило:
если вы используете rules, почти всегда заканчивайте их - when: never, если нет причины для другого поведения.
$CI_DEBUG_TRACE — отладка условий и выполнения job’ов
Иногда job «не появляется» в pipeline, и непонятно почему: условие вроде бы верное, переменная вроде бы есть, а результат не тот. В таких случаях помогает встроенный режим отладки.
Для включения детального лога используйте переменную $CI_DEBUG_TRACE:
job:
script:
- echo "This is my job"
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
variables:
CI_DEBUG_TRACE: "true"Что происходит при включённом CI_DEBUG_TRACE:
- GitLab выводит подробный shell-лог выполнения job.
- Показываются все команды, их аргументы и переменные окружения.
- Становится проще понять, какие условия и значения реально использовались.
Это особенно полезно, когда:
- Условие
ifведёт себя «странно». - Переменная оказывается пустой.
- job запускается (или не запускается) не так, как ожидалось.
Важно: CI_DEBUG_TRACE может вывести чувствительные данные (токены, пароли, ключи) в логах. Используйте его только для временной отладки и обязательно отключайте после.
Структура Job'а и его параметры
Job — это отдельная задача внутри pipeline. Именно job’ы выполняют реальную работу: запускают тесты, собирают проект, деплоят код и т. д.
В этом разделе разберём базовую структуру job’а и ключевые параметры, без которых невозможно написать осмысленный .gitlab-ci.yml.
Обязательные параметры
Строго говоря, единственный действительно обязательный параметр job’а — это script. Всё остальное либо имеет значение по умолчанию, либо наследуется.
script — команды для выполнения
script — это список shell-команд, которые GitLab Runner выполнит внутри job’а:
job_name:
script:
- echo "Hello"
- npm install
- npm testВажные моменты:
- Команды выполняются строго по порядку.
- Каждая команда запускается в одной и той же среде (контейнере).
- Если любая команда возвращает ненулевой код выхода, job считается failed, и выполнение останавливается.
Пример с ошибкой:
failing_job:
script:
- echo "This will print"
- false # Эта команда вернёт код ошибки
- echo "This won't print" # До сюда не дойдётПоследняя строка не выполнится — job завершится на команде false.
Игнорирование ошибок в командах
Иногда ошибка допустима (например, необязательные тесты). Тогда можно явно сказать shell’у «игнорируй ошибку»:
tolerant_job:
script:
- npm test || true # Даже если тесты упадут, job продолжит работуНо здесь есть важный нюанс:
job всё равно будет считаться успешным, даже если тесты упали. Это подходит только для второстепенных проверок.
Альтернатива — allow_failure: true (разберём ниже): в этом случае job будет помечен как failed, но pipeline продолжит работу. Это обычно более наглядный и безопасный вариант.
stage — к какому этапу относится job
stage определяет, на каком этапе pipeline будет выполняться job:
build_job:
stage: build
script:
- npm run build
test_job:
stage: test
script:
- npm test
deploy_job:
stage: deploy
script:
- ./deploy.shЧто важно помнить:
- pipeline выполняется по стадиям, а не по job’ам.
- Все job’ы одной стадии могут выполняться параллельно.
- Следующая стадия начнётся только после успешного завершения предыдущей (если не указано иное).
Стадия должна быть объявлена заранее в верхнеуровневой секции stages:
stages:
- build
- test
- deployЕсли job ссылается на несуществующую стадию — pipeline не запустится.
image — Docker образ для выполнения job
GitLab CI чаще всего работает в Docker-контейнерах. Параметр image определяет, в каком окружении будут выполняться команды из script.
Пример с общим образом по умолчанию и переопределением в конкретных job’ах:
default:
image: ubuntu:22.04
python_job:
stage: build
image: python:3.11
script:
- python --version
- pip install -r requirements.txt
node_job:
stage: build
image: node:18
script:
- node --version
- npm installКак это работает:
- Если
imageуказан в job’е — используется он. - Если нет — берётся
imageиз секцииdefault. - Если
imageне указан нигде — используется образ по умолчанию GitLab Runner (что часто приводит к неожиданностям).
Поэтому хорошая практика — всегда явно указывать образ, хотя бы в default.
Как выбрать правильный образ
Чаще всего используют официальные образы с Docker Hub:
- Python:
python:3.11,python:3.12,python:3.13. - Node.js:
node:16,node:18,node:20. - Java:
maven:3.8,gradle:7.5. - Базовые системы:
ubuntu:22.04,alpine:3.19.
Официальный каталог образов: https://hub.docker.com
Вы также можете:
- Использовать приватные образы из собственного Docker Registry.
- Подтягивать образы с авторизацией.
- Собирать кастомные образы под свой проект.
Но это уже отдельная, более продвинутая тема — к ней лучше переходить после понимания базовой структуры job’ов.
Основные дополнительные параметры
Помимо script и stage, у job’ов есть множество параметров, которые делают pipeline управляемым, читаемым и удобным для команды. Ниже — самые часто используемые из них.
name — отображаемое имя job’а
По умолчанию GitLab показывает job под тем именем, которое вы указали в конфиге (ключ в YAML). Иногда это имя техническое и не очень читаемое.
С помощью name можно задать более понятное отображаемое название:
build_frontend:
name: "Build Frontend Application"
stage: build
script:
- npm run buildВ результате:
- В
.gitlab-ci.ymlостаётся короткое техническое имя (build_frontend). - В UI GitLab вы видите понятное описание задачи.
Это особенно полезно в больших pipeline’ах с десятками job’ов.
timeout — максимальное время выполнения job’а
Иногда job может зависнуть: бесконечные тесты, ожидание сети, зацикленный скрипт. Параметр timeout защищает от таких ситуаций.
long_running_tests:
stage: test
timeout: 2h
script:
- pytest --slow-testsЕсли job не завершится за указанное время, GitLab принудительно остановит его и пометит как failed.
Поддерживаемые форматы:
30s.15m.1h.2h 30m.
Также помните: у проекта и у GitLab Runner может быть глобальный таймаут, который ограничивает максимальное значение timeout.
retry — автоматические повторы при ошибке
CI не всегда падает из-за кода. Бывают:
- Временные сетевые сбои.
- Нестабильные внешние сервисы.
- «Флейковые» тесты.
Для таких случаев есть retry:
flaky_tests:
stage: test
retry: 2 # Повторяет до 2 раз, если упадёт
script:
- npm testGitLab автоматически перезапустит job до 2 раз, если он завершится с ошибкой.
Более точная настройка:
job:
retry:
max: 2
when: # Когда повторять
- runner_system_failure
- stuck_or_timeout_failureТакой вариант хорош тем, что вы не повторяете job при логической ошибке в коде, но повторяете его при проблемах инфраструктуры.
allow_failure — разрешить job упасть, но не ломать pipeline
Иногда job важен, но не критичен. В таких случаях используйте allow_failure.
optional_tests:
stage: test
allow_failure: true # Job может упасть, но pipeline продолжит работу
script:
- npm run test:experimentalЧто это даёт:
- job будет помечен как failed.
- pipeline продолжит выполнение и может завершиться успешно.
Типичные кейсы:
- Экспериментальные или нестабильные тесты.
- Дополнительные проверки качества кода.
- Новые инструменты, которые вы только внедряете.
В UI такие job’ы обычно подсвечиваются отдельно, чтобы было видно, что ошибка была, но она не блокирующая.
variables — переменные окружения для job'а
Параметр variables позволяет задать переменные окружения только для конкретного job’а:
build_job:
stage: build
variables:
NODE_ENV: "production"
LOG_LEVEL: "debug"
CUSTOM_VAR: "some value"
script:
- echo $NODE_ENV # Выведет: production
- echo $LOG_LEVEL # Выведет: debugОсобенности:
- Переменные доступны как обычные переменные окружения.
- Используются через
$VARили${VAR}. - Приоритет выше, чем у переменных, заданных на уровне проекта или группы.
Это удобно для:
- Конфигурации окружения сборки.
- Переключения режимов (dev / prod).
- Передачи флагов в скрипты.
environment — деплой в окружение
Параметр environment связывает job с логическим окружением (staging, production и т. п.):
deploy_to_staging:
stage: deploy
environment:
name: staging
url: https://staging.example.com
script:
- ./deploy-to-staging.shИ аналогично для production:
deploy_to_production:
stage: deploy
environment:
name: production
url: https://example.com
script:
- ./deploy-to-production.shЧто это даёт:
- GitLab начинает отслеживать деплой.
- Появляется история развёртываний.
- Становится доступен раздел Deployments → Environments.
- Можно использовать environment-specific переменные и политики доступа.
Это особенно важно для production-деплоев и работы в команде.
coverage — отображение покрытия кода
Если ваши тесты выводят процент покрытия в консоль, GitLab может автоматически его извлечь и показать в интерфейсе.
test_with_coverage:
stage: test
script:
- pytest --cov=src --cov-report=term --cov-report=xml
coverage: '/TOTAL.*\s+(\d+%)$/' # Regex для извлечения % покрытияКак это работает:
- GitLab анализирует stdout job’а.
- Ищет совпадение по regex.
- Берёт значение из первой группы
(...).
Результат:
- Процент покрытия виден в UI.
- Удобно отслеживать динамику покрытия по коммитам и MR.
Артефакты и их сохранение
Во время выполнения job’ов GitLab может сохранять файлы и папки, созданные в процессе работы. Эти сохранённые файлы называются артефактами (artifacts).
Артефакты используются для:
- Передачи результатов между job’ами.
- Скачивания результатов сборки или тестов.
- Анализа отчётов (покрытие, тесты, логи).
- Хранения билдов, которые затем деплоятся.
artifacts — сохранение файлов
Пример job’а, который сохраняет результат сборки:
build_job:
stage: build
script:
- npm install
- npm run build
artifacts:
paths:
- dist/ # Сохраняем всю папку
- build/output.jar # Или конкретный файл
expire_in: 7 days # Удалить через неделю
name: "build-$CI_COMMIT_SHA"Что здесь происходит:
- После выполнения
scriptGitLab упаковывает указанные файлы. - Артефакт сохраняется на стороне GitLab.
- Его можно скачать или использовать в следующих job’ах.
Основные параметры artifacts
paths- список файлов и папок для сохранения. Поддерживаются glob-паттерны (dist/**/*).expire_in- через какое время артефакт будет автоматически удалён. По умолчанию — 30 дней (может быть переопределено настройками проекта).name- имя архива при скачивании. Полезно для удобной навигации и версионирования.when- при каких условиях сохранять артефакты:on_success(по умолчанию).on_failure.always.
Сохранение артефактов даже при ошибках
Частый кейс — сохранить результаты тестов или отчёты даже если job упал:
test_job:
stage: test
script:
- pytest
artifacts:
paths:
- coverage.xml
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xmlЭто особенно полезно, если:
- Тесты упали, но вам нужен отчёт.
- Вы хотите проанализировать логи или покрытие.
- Артефакт используется для отчётов в Merge Request.
Где доступны артефакты
Артефакты можно получить несколькими способами:
- Через UI GitLab —
CI/CD → Pipelines → Job → Download artifacts. - В следующих job’ах pipeline (если они зависят от текущего).
- Через GitLab API.
Важно: артефакты не предназначены для долгосрочного хранения. Для этого лучше использовать Docker Registry, Package Registry или внешние хранилища.
needs — зависимости между job’ами
По умолчанию GitLab выполняет pipeline строго по стадиям:
все job’ы стадии build → потом test → потом deploy.
Параметр needs позволяет задать явные зависимости между job’ами и тем самым ускорить pipeline.
Пример:
stages:
- build
- test
- package
build_app:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
test_app:
stage: test
needs:
- build_app # Ждём артефакты от build_app
script:
- npm test
artifacts:
paths:
- test-results/
package_app:
stage: package
needs:
- build_app # Можем зависеть от нескольких job'ов
- test_app
script:
- ./package.shЧто здесь важно понять:
test_appне ждёт завершения всей стадииbuild, ему нужен толькоbuild_app.package_appстартует, как только готовыbuild_appиtest_app.- Артефакты автоматически передаются между зависимыми job’ами.
DAG вместо линейного pipeline
Использование needs превращает pipeline в DAG (Directed Acyclic Graph) — граф зависимостей.
Преимущества:
- pipeline выполняется быстрее.
- job’ы не ждут лишние стадии.
- Зависимости становятся явными и читаемыми.
Когда needs особенно полезен:
- Большие проекты с множеством job’ов.
- Монорепозитории.
- Сложные сборки, где не все шаги зависят друг от друга.
Практический совет
- Используйте
artifactsдля передачи результатов, а не для кэша. - Используйте
needs, если хотите ускорить pipeline, но не переусердствуйте — слишком сложный DAG сложнее поддерживать. - Если job использует артефакты другого job’а — явно указывайте
needs, даже если они находятся в соседних стадиях.
Кэширование
Если артефакты нужны для передачи результатов работы, то кэш используется исключительно для ускорения pipeline.
Кэш позволяет сохранять файлы между разными запусками pipeline, чтобы не пересобирать одно и то же каждый раз — чаще всего это зависимости.
cache — кэширование файлов между запусками
Простейший пример:
default:
cache:
paths:
- node_modules/ # Кэшируем зависимости
- .cache/
key: "$CI_COMMIT_REF_SLUG" # Разный кэш для разных веток
build_job:
stage: build
script:
- npm install # Будет быстрее благодаря кэшу
- npm run buildЧто здесь происходит:
- GitLab пытается забрать кэш перед выполнением job’а.
- Если кэш найден — файлы уже есть, установка зависимостей проходит быстрее.
- После завершения job’а кэш обновляется.
Основные параметры cache
paths— Какие файлы и директории кэшировать. Обычно это зависимости (node_modules,.venv,.m2,.cache).key— Ключ кэша. Если ключ совпадает — используется существующий кэш. Если ключ изменился — создаётся новый.policy— Управляет тем, как кэш используется:pull— только забирать кэш.push— только сохранять.pull-push(по умолчанию) — забирать и обновлять.
Кэш для разных веток
Частая практика — разделять кэш по веткам, чтобы они не конфликтовали:
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/Это означает:
main,developи feature-ветки будут иметь разные кэши.- Меньше шансов поймать странные баги из-за старых зависимостей.
Кэш с учётом lock-файлов
Более надёжный вариант — привязать кэш к package-lock.json или uv.lock:
cache:
paths:
- .cache/
key:
files:
- package-lock.json
prefix: "$CI_COMMIT_REF_SLUG"Теперь:
- Если lock-файл изменился — ключ кэша тоже изменится.
- Зависимости будут пересобраны корректно.
- Старый кэш не «сломает» сборку.
Разные кэши для разных job’ов
Иногда имеет смысл разделять кэш по типам задач:
build:
cache:
key: "build-$CI_COMMIT_REF_SLUG"
script:
- npm install
test:
cache:
key: "test-$CI_COMMIT_REF_SLUG"
script:
- npm testТакой подход полезен, если:
- job’ы используют разные наборы файлов.
- Тесты портят кэш сборки (или наоборот).
- pipeline стал сложным и тяжёлым.
Важное отличие: cache vs artifacts
Очень частый источник путаницы у новичков:
| Кэш | Артефакты |
|---|---|
| Временные файлы для ускорения | Результаты работы job'а |
| Может использоваться между pipeline | Используется внутри pipeline |
| Хранится у runner’а | Загружается в GitLab |
| Хранится у runner’а | Всегда соответствует job’у |
| Не влияет на результат | Влияет на следующий шаг |
Коротко:
- cache — про скорость.
- artifacts — про результат.
Практические рекомендации
- Не кэшируйте результаты сборки — для этого есть артефакты.
- Не кладите в кэш всё подряд — он должен быть минимальным.
- Если pipeline ведёт себя странно — первым делом попробуйте сбросить кэш.
- Для production-pipeline лучше выбирать детерминированные ключи (через lock-файлы).
Services — вспомогательные контейнеры
Иногда для выполнения job’а одного контейнера недостаточно. Например, для тестов может понадобиться база данных, Redis, RabbitMQ или другой внешний сервис.
В GitLab CI для этого есть services — дополнительные Docker-контейнеры, которые запускаются рядом с основным job’ом.
Как это работает
- Основной job и сервисы запускаются в одной Docker-сети.
- Каждый сервис — это отдельный контейнер.
- Доступ к сервисам осуществляется по имени контейнера.
- Сервисы живут только в рамках текущего job’а.
Простой пример с PostgreSQL
test_with_db:
stage: test
image: python:3.11
services:
- postgres:17
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db"
script:
- pip install -r requirements.txt
- pytestЗдесь важно несколько моментов:
postgres:17— официальный образ PostgreSQL.- hostname
postgresпоявляется автоматически и совпадает с именем сервиса. - Переменные
POSTGRES_*используются самим образом PostgreSQL. DATABASE_URL— это уже ваша переменная, которую читает приложение.
Использование нескольких сервисов
Если тестам нужны сразу несколько зависимостей, их можно указать списком:
integration_tests:
stage: test
image: python:3.11
services:
- postgres:17
- redis:7
variables:
POSTGRES_DB: test_db
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
DATABASE_URL: "postgresql://testuser:testpass@postgres:5432/test_db"
REDIS_URL: "redis://redis:6379"
script:
- pip install -r requirements.txt
- pytestОбращение к сервисам происходит:
- К PostgreSQL — по хосту
postgres. - К Redis — по хосту
redis.
Алиасы сервисов
Иногда удобнее использовать более осмысленные имена. Для этого можно задать alias:
test:
stage: test
image: node:18
services:
- name: postgres:17
alias: db
variables:
POSTGRES_DB: testdb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
variables:
DATABASE_URL: "postgresql://user:pass@db:5432/testdb"
script:
- npm testТеперь база доступна по хосту db, а не postgres.
Готовность сервиса — важный нюанс
Сервисы не гарантированно готовы к моменту старта script.
Частая ошибка новичков — сразу подключаться к БД и ловить connection refused.
GitLab не ждёт, пока сервис полностью поднимется.
Обычно используют один из подходов:
- retry-логику в коде.
sleep(не лучший вариант).- Утилиты ожидания (
wait-for-it,pg_isready,ncи т.п.).
Пример с PostgreSQL:
before_script:
- until pg_isready -h postgres -p 5432; do sleep 1; doneКогда стоит использовать services
services подходят для:
- unit и integration-тестов.
- Локальных БД для CI.
- Проверки миграций.
- Временных очередей и кэшей.
Не стоит использовать их для:
- production-нагрузок.
- Долгоживущих окружений.
- Сложных multi-container сценариев (для этого лучше Docker Compose или Kubernetes).
Порядок выполнения команд в job’е
Каждый job в GitLab CI проходит фиксированный набор шагов. Понимание этого порядка сильно упрощает отладку pipeline’ов.
Общий порядок выглядит так:
- Pulling Docker image — скачивание Docker-образа.
- Starting services — запуск сервисов (PostgreSQL, Redis и т.д.).
- Cloning repository — клонирование репозитория.
- Restoring cache — восстановление кэша.
- before_script — подготовительный скрипт (опционально).
- script — основной скрипт job’а.
- after_script — завершающий скрипт (опционально).
- Uploading artifacts — загрузка артефактов.
- Uploading cache — сохранение кэша.
Важно понимать:
- Если job упал на этапе
script, шаги after_script всё равно выполняются. - Если job упал раньше (например, при pull образа),
scriptдаже не начнётся. after_scriptне может изменить статус job’а — он всегда informational.
before_script и after_script
build_job:
stage: build
before_script:
- echo "Preparing environment..."
- npm install
script:
- npm run build
after_script:
- echo "Cleaning up..."
- rm -rf node_modulesТипичные сценарии использования:
before_script:- Установка зависимостей.
- Подготовка окружения.
- Логин в registry.
- Проверка доступности сервисов.
after_script:- Очистка временных файлов.
- Логирование.
- Отладочная информация.
- Уведомления.
Параллельное выполнение и матрицы
Параллелизм на одном stage
Job’ы, находящиеся в одном stage и не зависящие друг от друга, выполняются параллельно — насколько это позволяет количество runner’ов.
stages:
- test
test_unit:
stage: test
script:
- pytest tests/unit/
test_integration:
stage: test
script:
- pytest tests/integration/
test_e2e:
stage: test
script:
- npm run test:e2eВсе три job’а запустятся одновременно, если:
- Есть свободные runner’ы.
- Нет ограничений по concurrency у runner’ов.
- Между job’ами нет
needs.
Если runner всего один — параллелизма не будет, даже если stage один и тот же.
Матрица параллельных job’ов (parallel: matrix)
Матрицы позволяют запускать один job с разными комбинациями переменных.
test_matrix:
stage: test
image: python:3.9
parallel:
matrix:
- PYTHON_VERSION: ["3.8", "3.9", "3.10", "3.11"]
DJANGO_VERSION: ["3.2", "4.0"]
script:
- echo "Testing Python $PYTHON_VERSION with Django $DJANGO_VERSION"
- pip install django$DJANGO_VERSION
- pytestЧто произойдёт:
- GitLab создаст 8 отдельных job’ов (4 × 2).
- Каждый job получит свою комбинацию переменных.
- В UI они будут отображаться как отдельные задания.
Это особенно удобно для:
- Тестирования на нескольких версиях языка.
- Проверки совместимости библиотек.
- Кросс-платформенных сборок.
Важные ограничения матриц
- Количество job’ов в матрице ограничено (зависит от версии GitLab).
- Большое количество комбинаций может резко увеличить время pipeline’а.
- Каждый job — это отдельный контейнер со своими ресурсами.
Поэтому матрицы стоит использовать осознанно и не превращать pipeline в «взрыв job’ов».
Заключение
В этой части мы закрыли (почти) все основные пробелы, связанные с написанием pipeline в GitLab CI.
При этом всё описанное не обязательно применять «один в один» — к построению CI/CD лучше подходить прагматично: лишняя сложность может сыграть плохую шутку при поддержке, не дав заметных плюсов.
В следующей (надеюсь, завершающей) части подробнее разберём runner’ы и базовые методы отладки пайплайнов.
Если вам интересны подобные материалы, подписывайтесь на Telegram-канал «Код на салфетке». Там я делюсь гайдами для новичков, полезными инструментами и практическими примерами из реальных проектов. А прямо сейчас у нас там ещё и проходит новогодний розыгрыш.
Комментарии
Оставить комментарийВойдите, чтобы оставить комментарий.
Комментариев пока нет.