GitLab CI/CD components: повторно используемый CI как путь к чистому и здоровому GitLab

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

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

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

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

CI/CD components — переиспользуемые блоки конфигурации пайплайна, оформленные как версионируемые модули. Появились с 16-й версии GitLab. По сути это просто шаблоны, которые с помощью директивы include подключаются в 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.