Первый .gitlab-ci.yml: стадии и джобы

Пишем первый рабочий пайплайн и разбираем базовые сущности: стадии и джобы.

Джоба (job) — минимальная единица работы пайплайна: набор команд, выполняемых раннером в изолированном окружении.

Минимальный пайплайн

Файл .gitlab-ci.yml — это YAML. Каждый ключ верхнего уровня (кроме зарезервированных слов) считается джобой. Самый простой пайплайн — одна джоба с командой:

hello:
  script:
    - echo "Привет, пайплайн!"

Положите этот файл в корень репозитория, сделайте git push — и GitLab запустит джобу hello. Ключ script обязателен: он перечисляет команды оболочки, которые выполнит раннер.

Стоит на секунду остановиться на том, что именно делает раннер с этим коротким файлом. Он берёт чистое окружение, клонирует ваш репозиторий, заходит в его корень и по очереди выполняет строки из script как обычные команды shell. Если команда вернула ненулевой код выхода, джоба считается упавшей и красится в красный; первая же неуспешная команда обрывает script — последующие строки не выполняются. Это поведение «упал на первой ошибке» (аналог set -e) удобно держать в голове: пайплайн честно падает там, где впервые пошло не так, а не маскирует проблему успешным финалом.

Зарезервированные слова и якоря джоб

Раз «любой ключ верхнего уровня — джоба», возникает вопрос: а как тогда GitLab отличает джобу от настройки вроде stages? Ответ — список зарезервированных ключей. Слова stages, variables, default, include, workflow и ещё несколько имеют особое значение и джобами не становятся. Всё остальное на верхнем уровне GitLab пытается прочитать как джобу и обязательно ждёт у неё script (или trigger). Отсюда частая загадка новичка: «я объявил build: с одними настройками, а GitLab ругается на отсутствие script» — потому что build не зарезервировано и трактуется как джоба. Ещё одно соглашение: имя, начинающееся с точки (например, .template), считается скрытой джобой — она не выполняется, а служит заготовкой, которую другие джобы подключают через extends. Это родной для GitLab способ переиспользования, тогда как GitHub Actions решает ту же задачу через готовые actions из маркетплейса.

Стадии задают порядок

Реальный конвейер состоит из нескольких этапов: сначала проверить код, потом собрать, потом задеплоить. Для этого есть стадии (stages). Джобы одной стадии идут параллельно, а стадии — последовательно. Если стадия упала, следующая не запускается.

stages:
  - test
  - build
  - deploy

run-tests:
  stage: test
  script:
    - echo "Запускаю тесты..."

build-app:
  stage: build
  script:
    - echo "Собираю приложение..."

deploy-prod:
  stage: deploy
  script:
    - echo "Деплою на production..."

Здесь GitLab сначала выполнит run-tests, и только если она зелёная — перейдёт к build-app, затем к deploy-prod. Так строится классическая лесенка пайплайна.

stage: test      stage: build     stage: deploy
┌───────────┐    ┌───────────┐    ┌───────────┐
│ run-tests │ -> │ build-app │ -> │deploy-prod│
└───────────┘    └───────────┘    └───────────┘
   (если упало — дальше не идём)

Почему параллельность внутри стадии — это хорошо, стоит объяснить отдельно. Если на стадии test у вас три независимых набора проверок — юнит-тесты, линтер и проверка типов — нет смысла гонять их по очереди: они не зависят друг от друга. Положив их в одну стадию, вы получаете три джобы, которые раннеры разбирают одновременно, и общее время стадии равно времени самой долгой из трёх, а не их сумме. Это первый и самый дешёвый способ ускорить пайплайн: ищите шаги, которые не зависят друг от друга, и сваливайте их в одну стадию.

Стадии по умолчанию

Если не объявить stages, GitLab использует три встроенные: build, test, deploy (именно в этом порядке). Джоба без явного stage попадает в test. Поэтому минимальный пример из начала урока работает: hello молча оказалась в стадии test.

Есть тонкость, которую легко проворонить: порядок стадий определяется порядком строк в списке stages, а не алфавитом и не «здравым смыслом». Если вы перечислите deploy раньше test, GitLab честно попытается деплоить до тестов — синтаксически это валидно, логически это катастрофа. Поэтому список stages читайте как сценарий конвейера сверху вниз и держите его осмысленным.

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

В GitHub Actions единица — workflow с jobs, и порядок задаётся через needs. В GitLab порядок по умолчанию диктуют именно стадии — это нагляднее для линейных конвейеров. Позже мы увидим, что needs в GitLab тоже есть и позволяет строить DAG, ломая жёсткую последовательность стадий.

ВопросGitLabGitHub Actions
Как задаётся порядок по умолчаниюсписком stagesзависимостями needs
Параллельностьджобы одной стадииджобы без needs друг на друга
Файлов конфигурацииодин .gitlab-ci.ymlмного в .github/workflows/
Переиспользование заготовокскрытые джобы + extendsreusable workflows / actions

Грубо говоря, GitLab по умолчанию мыслит «лесенкой стадий» и при желании усложняется до графа через needs; GitHub Actions мыслит «графом зависимостей» сразу. Для типичного линейного процесса «проверил → собрал → выкатил» модель стадий читается легче и не требует явно прописывать, кто за кем следует.

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

Получив push, сервер парсит YAML, проверяет его на валидность (можно проверить заранее в разделе CI/CD → Editor) и строит список стадий. Для каждой стадии он находит входящие в неё джобы и ставит их в очередь одновременно. Раннеры разбирают очередь. Сервер ждёт завершения всех джоб стадии, и лишь потом открывает следующую. Статус пайплайна — агрегат статусов джоб.

Из этой механики вытекает практический совет: проверяйте YAML до пуша. У GitLab есть встроенный редактор пайплайнов с валидацией и визуализацией графа стадий, а также API-эндпоинт CI Lint, который скажет, корректен ли файл, не запуская его. Цикл «поправил отступ → запушил → подождал → увидел ошибку парсинга» расточителен; цикл «поправил → проверил в редакторе → запушил рабочее» экономит минуты на каждой итерации. Привыкайте относиться к .gitlab-ci.yml как к коду, который тоже надо «компилировать» перед запуском.

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

  • Опечатка в отступах YAML. YAML чувствителен к пробелам; табы запрещены. Используйте встроенный редактор с валидацией.
  • Объявить стадию в stage: джобы, но забыть её в общем списке stages — джоба не запустится.
  • Ожидать, что джобы одной стадии выполнятся по порядку. Внутри стадии порядок не гарантирован, они параллельны.
  • Перепутать порядок строк в stages: GitLab идёт сверху вниз, поставите deploy раньше test — так и будет деплоить до тестов.
  • Назвать настройку как джобу: незарезервированный ключ верхнего уровня без script вызовет ошибку «missing script».

Итоги

  • Каждый незарезервированный ключ верхнего уровня — джоба; script обязателен и падает на первой же ошибке.
  • Стадии задают последовательность; джобы внутри стадии идут параллельно — это первый способ ускорить пайплайн.
  • По умолчанию есть стадии build, test, deploy; джоба без stage попадает в test; порядок диктует список stages.
  • Скрытые джобы (имя с точкой) и extends — родной способ переиспользования; валидируйте YAML в редакторе до пуша.
Проверьте себя
1. Как соотносятся джобы внутри одной стадии?
AВыполняются строго по порядку объявления
BВыполняются параллельно
CВыполняются только по очереди вручную
DНе выполняются, пока не закончится следующая стадия
2. В какую стадию попадёт джоба без явного ключа stage и без объявленного списка stages?
Abuild
Btest
Cdeploy
DНе запустится вовсе