Материал открыт для чтения

Войдите, чтобы сохранять прогресс и результаты.

Войти
GitLab CI/CD: от первых проверок до деплоя

Pipeline, stage и job: порядок и параллельность

чтение ~8 минУрок 4 из 20

Pipeline, stage и job: порядок и параллельность

module9 gitlab ci Image02

В первой лабораторной ты сделал самый маленький CI/CD-сценарий: один файл .gitlab-ci.yml, один или два jobs и несколько простых команд. Этого достаточно, чтобы увидеть главный цикл:

изменил файл -> commit -> push -> GitLab создал pipeline -> runner выполнил jobs

В этой главе примеры тоже будут простыми: echo, date, pwd, условные ./run-tests.sh и ./build-image.sh. Сейчас нам важно разобраться с самим написанием и выполнением pipeline. В реальном проекте на месте этих команд будут настоящие действия: тесты приложения, сборка, публикация Docker image, миграции, deploy и проверки после выкладки.

Теперь разберём, как из одного job получается нормальный pipeline для проекта. Нам нужно ответить на три вопроса:

  • какие действия запускать;
  • какие из них можно делать одновременно;
  • какие должны ждать результат предыдущих действий.

Job: одно конкретное действие

Job - это отдельная работа внутри pipeline. Например:

  • проверить код;
  • запустить тесты;
  • собрать приложение;
  • собрать Docker image;
  • выложить новую версию на тестовый стенд.

В .gitlab-ci.yml job выглядит как именованный блок:

lint:
  script:
    - echo "check code style"

test:
  script:
    - echo "run tests"

Здесь есть два jobs: lint и test. GitLab покажет их в интерфейсе как отдельные задачи. У каждого job будет свой статус и свой log.

Stage: крупный этап pipeline

Обычно jobs группируют по stages. Stage - это этап pipeline: сначала проверка, потом сборка, потом deploy.

stages:
  - verify
  - package
  - deploy

lint:
  stage: verify
  script:
    - echo "lint"

test:
  stage: verify
  script:
    - echo "test"

build:
  stage: package
  script:
    - echo "build"

GitLab выполняет jobs одного stage параллельно. Поэтому lint и test могут стартовать одновременно: они оба находятся в verify. А build начнётся только после успешного завершения всего stage verify.

Важно: место job внутри файла .gitlab-ci.yml почти не влияет на порядок выполнения. Job можно написать выше или ниже другого job, но GitLab всё равно будет смотреть на stage, needs, rules и другие настройки.

Например, этот вариант тоже корректный:

build:
  stage: package
  script:
    - echo "build"

lint:
  stage: verify
  script:
    - echo "lint"

test:
  stage: verify
  script:
    - echo "test"

Хотя build написан первым, он не запустится раньше lint и test, потому что stage package идёт после stage verify. Поэтому при добавлении нового job думай не о том, куда именно вставить блок в файле, а о том, к какому stage он относится и должен ли он явно ждать другие jobs через needs.

Это первая идея параллельности: независимые проверки лучше класть в один stage. Если тесты и проверка кода не зависят друг от друга, им не нужно ждать друг друга.

Почему jobs не должны полагаться на общее состояние

Pipeline часто выглядит как один общий процесс, но на самом деле каждый job выполняется отдельно. GitLab организует pipeline, а команды из script выполняет runner.

В нашем курсе runner использует Docker executor. Это значит: для каждого job создаётся отдельный временный контейнер из image, указанного в .gitlab-ci.yml.

job lint  -> контейнер из alpine:3.20 -> команды lint -> контейнер удалён
job test  -> контейнер из alpine:3.20 -> команды test -> контейнер удалён
job build -> контейнер из docker:27   -> сборка image -> контейнер удалён

Контейнер job эфемерный: он живёт только во время выполнения job. Поэтому нельзя думать так: “в job build я создал файл, значит job test увидит его сам”. Не увидит. У соседнего job будет своё рабочее окружение.

Это касается не только Docker executor. У разных runner-ов есть свои особенности, но хороший pipeline не должен зависеть от случайно оставшихся файлов после предыдущего job.

Если результат одного job нужен следующему job, его нужно передать явно.

Как передавать результат дальше

Допустим, job build собрал папку dist, а job test-package должен проверить файлы из этой папки. Просто создать dist недостаточно: после завершения job контейнер исчезнет. Значит, результат нужно передать явно.

Один из основных способов такой передачи - artifacts: job сохраняет нужные файлы после выполнения, а следующие jobs могут их получить. Подробный синтаксис, срок хранения и cache разберём в следующей отдельной статье, а reports посмотрим позже как специальный вид результата, который GitLab умеет показывать в интерфейсе. Здесь важно запомнить только причину: jobs изолированы, поэтому важные результаты не должны жить “просто где-то в контейнере”.

В реальных pipeline результат можно передавать разными способами:

  • artifacts - файлы между jobs и stages;
  • cache - ускорение повторных запусков, например зависимости;
  • registry - Docker images, которые собрал pipeline;
  • variables - параметры запуска;
  • внешний сервис - например тестовый стенд, куда pipeline выложил приложение.

Когда stage недостаточно точный

Stages дают простой порядок: весь verify, потом весь package, потом весь deploy. Для маленького проекта этого хватает.

Но в большом pipeline не всегда нужно ждать весь предыдущий stage. Представь такую ситуацию:

  • build-image собрал Docker image;
  • unit-tests долго гоняют тесты;
  • deploy-review должен выложить приложение на тестовый стенд сразу после build-image;
  • ждать unit-tests для review-стенда команда не хочет, потому что стенд нужен быстро.

Тогда используется needs: job явно говорит, какие jobs ему нужны.

stages:
  - package
  - test
  - deploy

build-image:
  stage: package
  script:
    - ./build-image.sh

unit-tests:
  stage: test
  script:
    - ./run-tests.sh

deploy-review:
  stage: deploy
  needs:
    - job: build-image
  script:
    - ./deploy-review.sh

Что мы здесь изменили:

  • build-image собирает image;
  • unit-tests находится в следующем stage и может выполняться долго;
  • deploy-review находится ещё позже, в stage deploy;
  • но у deploy-review есть needs, где указан только build-image.

Без needs GitLab выполнял бы pipeline в обычном порядке: сначала весь stage package, потом весь stage test, потом stage deploy. Значит deploy-review ждал бы, пока завершится unit-tests.

С needs GitLab получает более точную зависимость: deploy-review нужен результат именно от build-image. Поэтому после успешного build-image job deploy-review может стартовать, даже если unit-tests ещё выполняется.

Короткая запись делает то же самое:

deploy-review:
  stage: deploy
  needs: ["build-image"]
  script:
    - ./deploy-review.sh

Такую форму удобно использовать, когда нужно просто сказать: “этот job зависит от вот этих jobs”. Если дальше понадобятся дополнительные настройки, например как передавать artifacts через needs, запись раскрывают в длинный вариант:

deploy-review:
  stage: deploy
  needs:
    - job: build-image
      artifacts: true
  script:
    - ./deploy-review.sh

Есть и обратный случай: needs: []. Он означает, что job не ждёт предыдущие stages и может стартовать сразу после создания pipeline:

fast-info:
  stage: deploy
  needs: []
  script:
    - echo "this job can start immediately"

Так делают редко и осознанно. Например, если job просто печатает информацию, проверяет конфигурацию или запускает независимую быструю задачу. Для обычного deploy чаще всё-таки нужно явно указать, после чего он имеет право стартовать.

В этой статье главное запомнить не синтаксис каждой настройки, а логику:

  • pipeline состоит из jobs;
  • stages задают грубый порядок;
  • jobs одного stage могут выполняться параллельно;
  • jobs обычно изолированы и не должны полагаться на файлы друг друга;
  • результат передают явно;
  • needs помогает описать точные зависимости и ускорить большой pipeline.

Материал изучен?

Войдите, чтобы сохранить прогресс и перейти к практической лаборатории.

Войти и продолжить