Лиса снова копирует код
Лиса снова копирует код

Ходит легенда, что однажды разработчики GitLab закупились шапками, сделанными из переработанных крышечек от бутылок. И их настолько вдохновила идея повторного использования, что они решили добавить такую возможность и в свой продукт. Подкрепив это всё стандартизацией CI, они представили комьюнити новый механизм — GitLab CI/CD components.

В этой статье я хочу рассказать Хабру, для чего вообще нужны компоненты, как ими пользоваться и где использовать.

Советую перед прочтением ознакомиться со статьёй по важным механизмам GitLab.

Что же это за механизм?

CI/CD components — переиспользуемые блоки конфигурации пайплайна, оформленные как версионируемые модули. Появились с 16-й версии GitLab. По сути это просто шаблоны, которые с помощью директивы inlude подключаются в CI. Однако, в отличие от обычных шаблонов, они публикуются в отдельный каталог, благодаря чему доступны за пределами проекта, в котором реализованы, и мы можем использовать общий шаблон для нескольких проектов.

При использовании self-managed версии мы сразу забираем общие шаблоны, а также имеем возможность добавлять свои, что особенно актуально для решения проблемы дублирования кода.

Каждый шаблон — yaml-файл, содержащий определённую конфигурацию. Существует несколько паттернов их использования:

  • Просто добавляем директиву include и наслаждаемся результатом работы готового пайплайна.

  • После включения через include используем extends для использования каких-то шаблонных задач (обычно это скрытые задачи, начинающиеся с точки. Пример: .deploy-ansible). Подробнее об этом далее.

Как этим пользоваться?

Теперь давайте подробнее рассмотрим, как можно использовать компоненты.

Подключение компонента

Основной и самый популярный способ — это директива include.

Шаблон использования:

---
include:
  - component: <FQDN>/<namespace>/<project>/<component-name>@<version>
    inputs:
      <param1>: <value1>
      <param2>: <value2>

Пример использования:

# Пример использования встроенного компонента GiLab - Autodevops
---
include:
  - component: gitlab.com/components/autodevops/build@2.11.0
  inputs:
    stage: build

# Пример использования своего компонента
---
include:
  - component: $CI_SERVER_FQDN/my-org/deploy-components/java@1.0.0
    inputs:
      stage: build
      version: 21

Разберём на примере нашего компонента: здесь переменная $CI_SERVER_FQDN — домен, используемый для GitLab. Далее путь до самого проекта my-org/deploy-components/ , потом идёт имя шаблона и версия компонента javа@1.0.0.

.gitlab-ci.yml         # CI, например, для тестирования компонента
README.md              # документация по эксплуатации
templates/             # основная директория с шаблонами
├── template1.yml
└── template2.yml

Гибкая настройка при использовании компонента

Важное преимущество шаблонов — возможность переопределять отдельные части при помощи директивы extends. Давайте быстро вспомним, в чём суть обратного глубокого слияния, используемого в реализации extends: задача дополняет базовую, при конфликте новых ключей с ключами шаблона берутся новые значения. Рассмотрим пример компонента, содержащего скрытую задачу .test, которая требует определённых переменных, тогда для её использования мы напишем свою задачу, унаследованную от .test:

test-job:
  extends: .tests
  variables:
    SOME_FLAG: "true"

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

Структура компонента

Структура CI/CD компонента в GitLab основана на yaml-файлах — шаблонах. Чтобы мы могли версионировать и делиться компонентом между проектами, мы используем отдельный проект, который также можно опубликовать в CI/CD Catalog для общего использования.

Структура проекта

Основная структура проекта выглядит примерно так:

.gitlab-ci.yml         # CI например для тестирования компонента
README.md              # документация по эксплуатации
templates/             # основная директория с шаблонами
├── template1.yml
└── template2.yml
  • templates/: Основная директория для компонента. Она содержит все необходимые шаблоны.

  • README.MD: Обязательный файл с описанием компонента, примерами использования, доступными inputs (входными параметрами) и инструкциями.

  • .gitlab-ci.yml: Рекомендую добавить CI для самого компонента. Полезно для автоматизации тестирования и версионирования.

Структура шаблонов компонента

Структура yaml-конфига для компонента делится на две части, разделённые ---:

  • spec:: Метаданные компонента, включая входные параметры (inputs). Здесь мы определяем, что именно должен на вход получать наш компонент для корректной работы.

  • Основная конфигурация: Стандартный CI-конфиг, который использует inputs для гибкой настройки.

Давайте поговорим немного о самих inputs — параметрах, которые мы задаём, когда подключаем компонент. Именно они обеспечивают гибкость. Параметры определяются в spec.input и содержат следующие поля:

  • type:

    • string (по умолчанию);

    • boolean;

    • number;

    • array.

  • default: Значение по умолчанию.

  • description: Описание (отображается в CI/CD Catalog).

  • options: Опционально, список допустимых значений. Полезно, когда мы хотим ограничить возможные варианты значений.

Чтобы подставить inputs в конфиг, используется следующий синтаксис: $[[ inputs.name ]] .

Пример inputs:

spec:
  inputs:
    stage:
      type: string
      default: test
      description: "Этап пайплайна, на котором запускаем job"
    enable_cache:
      type: boolean
      default: false
      description: "Включить кэширование зависимостей"
    versions:
      type: array
      default: ["1.0", "2.0"]
      description: "Массив версий для тестирования"

Пример компонента:

spec:
  inputs:
    message:
      type: string
      default: "Hello, World!"
      description: "Сообщение для вывода"
    stage:
      type: string
      default: test
      description: "Этап пайплайна"
---
echo-job:
  stage: $[[ inputs.stage ]]  # Подставляем inputs.stage
  script: 
    - echo "$[[ inputs.message ]]"  # Подставляем inputs.message

Однако есть ещё одна практика для передачи значений в компонент — variables внутри самих задач. Давайте посмотрим на пример компонента:

spec:
  inputs:
    message:
      type: string
      default: "Hello, World!"
      description: "Сообщение для вывода"
    stage:
      type: string
      default: test
      description: "Этап пайплайна"
---
.echo-job:  # Скрытая job 
  stage: $[[ inputs.stage ]]  # Подставляем inputs.stage
  variables:  # Определение variables
    GREETING_PREFIX: "Output:"  # Определяем variable
    MESSAGE_TEXT: "$[[ ` ]]"  # Variable зависит от input
  script: 
    - echo "$GREETING_PREFIX $MESSAGE_TEXT"  # Использование variables в скрипте

И его использование:

include:
  - component: gitlab.example.com/my-org/echo-component@1.0.0
    inputs:
      message: "I'm absolute!"
      stage: build

my-echo:
  extends: .echo-job  # Наследует скрытую job из компонента, включая variables и script
  variables:  # Переопределение variables
    GREETING_PREFIX: "New Prefix:"
  rules:  # Добавляем rules
    - if: '$CI_COMMIT_BRANCH == "main"'

Здесь мы использовали переменную, определённую в variables. Также хочется обратить внимание на то, что inputs.message мы вынесли в переменные job. Это хорошая практика, которая делает визуально код более читаемым.

У такого варианта определения переменных есть свои плюсы и минусы:

  • Гибкость: таким образом мы получаем более гибкую настройку конкретных job, так как задаём переменные более локально относительно inputs.

  • Валидация: inputs позволяет задавать варианты значений и типы переменных, что повышает надёжность. variables такой функциональностью не обладают.

  • Сложность и читаемость: inputs более понятны для конечного пользователя компонента, чем variables и скрытые job. Подобные решения нужно подробно пояснять в документации.

Рекомендации по использованию компонентов

  • Не бойтесь использовать компоненты, ограничения на include достаточно большие, чтобы вы могли включить столько компонентов, сколько потребуется.
    Пример: проект на Java будет включать компоненты по сборке, тестированию, деплою и анализаторы (например, SAST).

  • extends лучше, чем копирование кода. Вынесите общую логику в компонент и дополняйте уже с помощью директивы.

  • Используйте rules для директивы include, когда есть шаблоны, которые не должны выполняться всегда.

  • Перед использованием обращайтесь к документации компонента. Там вы найдёте доступные переменные, область их определения (inputs или через variables и extends), а также возможные способы применения и примеры.

Рекомендации по созданию компонентов:

  • Используйте уникальные имена для задач, потому что при включении шаблона в конфигурации он мерджится с ней. Одинаковые имена приведут к конфликту и некорректному поведению. Также при необходимости можно преднамеренно переопределять задачи из шаблона, используя одинаковые названия.

  • Не злоупотребляйте с вложенностью зависимостей. Компоненты могут сами включать другие компоненты, но это может усложнить поведение. Советую использовать их в меру и указывать статические версии (не @main).

  • Документируйте в README.MD. Опишите логику, переменные и примеры использования. Это поможет вашим коллегам и вам самим в будущем.

  • Версионирование и релизы обеспечат более стабильную работу компонента. Статические версии ведут себя более предсказуемо, чем latest.

  • Тестирование самого компонента в его CI — отличная практика, особенно если вы используете автоматизацию для релизов.

Чуть-чуть выводов

GitLab CI/CD components — мощный инструмент для быстрого создания пайплайнов из готовых шаблонов. Придерживайтесь минимально необходимого переопределения, гибкой настройки через переменные и контроля версий, и вы получите воспроизводимый и легко поддерживаемый CI.

Также советую ознакомиться с основными общедоступными компонентами GitLab. Они доступны по ссылке.

© 2025 ООО «МТ ФИНАНС»

Комментарии (4)


  1. kenomimi
    21.07.2025 15:24

    В общем случае не надо это делать, просто не надо. Экономия на сотне строчек выливается потом в полную нечитаемость конфигов ci/cd, невозможность их оперативной правки, и разрастанию костыльной базы "ну тут не можем опцию сборки добавить, пайп стандартный править нельзя, пропиши в Makefile ручками и потом не забудь при мерже". Я молчу о том, что чтобы понять что тут происходит, надо открыть десяток файлов с инклудами, страничку с переменными, и мержить это поделие сумрачного гения в уме... Не нужна дедупликация на таких крохотных обьемах кода. А если у вас ci\cd в одной репе в пару десятков тысяч строк - вы что-то делаете не так.

    Да, есть кейсы, в которых инклуды полезны, например типовой пуш артефакта, но это именно что отдельные случаи, каждый из которых надо смотреть под лупой, прежде чем выносить в компонент.


    1. m1skam
      21.07.2025 15:24

      Решение о применении GitLab Components зависит от контекста. В небольших командах с парой проектов и простыми пайплайнами избыточная абстракция может создать больше проблем, чем решить, это правда. Но мой опыт показывает, что типовые задачи встречаются в большинстве проектов, и их стандартизация приносит пользу. Что касается читаемости - имхо ключевую роль тут играет качество документации и именования. Хорошо спроектированные компоненты с понятными интерфейсами могут наоборот упростить понимание пайплайна, скрыв сложную логику за простым API.

      Компоненты - это инструмент, и как и любой инструмент, его нужно применять осознанно, а не ради самого инструмента.


    1. Andrey_Solomatin
      21.07.2025 15:24

      Переиспользование через библиотеку это палка о двух концах. И те кто топит за и те кто против неправы в абстрактном контексте.


  1. Andrey_Solomatin
    21.07.2025 15:24

    Было бы неплохо сравнить с другими сервисами. Пользовался таким на GitHub несколько лет назад.