Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи?

Написать
Войти
Дайджесты
Иллюстрация к материалу: Централизация пайплайнов GitLab CI и автоматизация версионирования Helm-чартов

Централизация пайплайнов GitLab CI и автоматизация версионирования Helm-чартов

Практическое руководство по выносу повторяющейся сборочной логики GitLab CI во внешние шаблоны с помощью директивы include. В карточке описано автоматическое вычисление версий Helm-чартов через утилиту yq и git describe, а также безопасный кросс-репозиторный пуш с помощью OAuth2 токенов.

Централизация пайплайнов GitLab CI и автоматизация версионирования Helm-чартов

В процессе масштабирования микросервисной архитектуры команды разработки неизбежно сталкиваются с проблемой поддержки CI/CD пайплайнов (конвейеров непрерывной интеграции и доставки). Когда количество микросервисов превышает десяток, ручное копирование конфигурационных файлов .gitlab-ci.yml превращается в административный кошмар. В каждом репозитории накапливаются сотни строк дублирующегося кода, а внесение изменений в процесс сборки или деплоя (например, обновление версии Docker или Helm) требует ручного редактирования десятков репозиториев.

Аналогичные проблемы возникают и с управлением Helm-чартами (пакетами для развертывания приложений в Kubernetes). Обновление параметров values или версий чартов вручную отнимает много времени и чревато человеческими ошибками.

Для решения этих проблем применяется методика централизации пайплайнов через директиву include в GitLab CI и автоматизация версионирования чартов с помощью утилиты yq и защищенных токенов авторизации.


Шаг 1. Создание центрального репозитория шаблонов

Первым шагом является вынос всей общей логики сборки, тестирования и развертывания в отдельный выделенный репозиторий. Назовем его infrastructure/ci-templates. Разработчики микросервисов больше не будут писать логику выполнения задач — они будут лишь подключать готовые шаблоны.

Рекомендуемая структура файлов в центральном репозитории infrastructure/ci-templates:

.gitlab/
├── templates/
│   ├── build.yml       # Сборка Docker-образов
│   ├── test.yml        # Тестирование кода
│   ├── deploy.yml      # Развертывание в Kubernetes
│   └── helm.yml        # Работа с Helm-чартами
└── jobs/
    ├── build-job.yml   # Конкретные реализации джоб
    └── deploy-job.yml

Шаг 2. Описание универсального шаблона сборки Docker-образов

В файле templates/build.yml центрального репозитория описывается скрытый джоб-шаблон (его имя начинается с точки, чтобы GitLab не запускал его самостоятельно):

.build_template:
  stage: build
  image: docker:24.0.7
  services:
    - docker:24.0.7-dind
  variables:
    DOCKER_HOST: tcp://docker:2376
    DOCKER_TLS_CERTDIR: "/certs"
    DOCKER_TLS_VERIFY: "1"
    DOCKER_CERT_PATH: "/certs/client"
  script:
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
    - docker build --tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
    - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: manual
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: always

!IMPORTANT Если вы используете собственный сервер GitLab Runner (self-managed), для работы сборщика Docker-in-Docker (dind) в конфигурационном файле раннера config.toml обязательно должен быть настроен параметр монтирования томов: volumes = ["/certs/client", "/cache"]. Без этого клиент Docker внутри контейнера сборки не сможет получить доступ к TLS-сертификатам демона, и задача завершится с ошибкой соединения. Для общих раннеров на GitLab.com эта настройка уже сделана по умолчанию.


Шаг 3. Создание шаблона деплоя с помощью Helm

В файле templates/deploy.yml описывается логика развертывания приложения в кластер Kubernetes. В качестве базового образа используется официальный образ Helm:

.deploy_template:
  stage: deploy
  image: alpine/helm:3.12.0
  script:
    - helm upgrade --install "$CI_PROJECT_NAME" ./chart/ \
        --set image.repository=$CI_REGISTRY_IMAGE \
        --set image.tag=$CI_COMMIT_SHORT_SHA \
        --set environment=$DEPLOY_ENV \
        --namespace $DEPLOY_ENV \
        --create-namespace \
        --wait \
        --timeout 5m
  environment:
    name: $DEPLOY_ENV
    url: https://$CI_PROJECT_NAME.$DEPLOY_ENV.example.com
  rules:
    - if: '$DEPLOY_ENV == "prod" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
    - if: '$DEPLOY_ENV == "staging" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: on_success

Флаг --wait указывает Helm ожидать, пока все ресурсы Kubernetes (поды, сервисы) не перейдут в состояние готовности. Тайм-аут в 5 минут предотвращает вечное зависание пайплайна в случае сбоя при деплое. Окружение prod деплоится только вручную, а staging — автоматически при успешном завершении предыдущих этапов.


Шаг 4. Подключение шаблонов в микросервисах

Теперь в репозитории конкретного микросервиса файл .gitlab-ci.yml становится лаконичным. Разработчику достаточно импортировать внешние шаблоны с помощью include и расширить их (extends):

include:
  - project: 'infrastructure/ci-templates'
    ref: main
    file: '/templates/build.yml'
  - project: 'infrastructure/ci-templates'
    ref: main
    file: '/templates/deploy.yml'

variables:
  SERVICE_NAME: $CI_PROJECT_NAME

stages:
  - prepare
  - build
  - test
  - deploy

build:
  extends: .build_template

deploy_staging:
  extends: .deploy_template
  variables:
    DEPLOY_ENV: staging

deploy_prod:
  extends: .deploy_template
  variables:
    DEPLOY_ENV: prod

Шаг 5. Автоматическое вычисление версий Helm-чартов

Ручное обновление поля version в файле Chart.yaml часто приводит к конфликтам версий. Есть два сценария автоматизации этого процесса в GitLab CI.

Сценарий А. Если Helm-чарт лежит внутри репозитория самого сервиса

В этом случае перед этапом сборки добавляется задача предварительной подготовки, которая вычисляет версию чарта на основе тегов Git и коммита, а затем обновляет YAML-файл с помощью современной утилиты yq (версии 4+):

update_chart_version:
  stage: prepare
  image: alpine:3.18
  variables:
    GIT_DEPTH: 0 # Отключаем shallow clone, чтобы получить все теги Git
  script:
    - apk add --no-cache yq git
    - |
      RAW=$(git describe --tags --always --dirty 2>/dev/null || true)
      VERSION=${RAW#v} # Убираем префикс "v", если он есть (v1.2.3 -> 1.2.3)
      if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+'; then
        VERSION="0.0.0-${CI_COMMIT_SHORT_SHA}"
      fi
      echo "Chart version: $VERSION"
      yq -i ".version = \"${VERSION}\"" chart/Chart.yaml
    - cat chart/Chart.yaml
  artifacts:
    paths:
      - chart/Chart.yaml

Обновленный файл Chart.yaml передается на следующие стадии как артефакт сборки.

Сценарий Б. Если Helm-чарты хранятся в общем центральном репозитории

Если все чарты лежат в отдельном проекте (например, infrastructure/helm-charts), пайплайн микросервиса должен скопировать свои файлы конфигурации чарта туда и сделать коммит.

Для безопасной авторизации в стороннем репозитории стандартный токен CI_JOB_TOKEN не подходит, так как он не дает прав на выполнение команды git push. Решением является использование Project Access Token (или Group Access Token) с правами Developer и областью видимости write_repository. Этот токен сохраняется в переменных CI/CD проекта под именем HELM_CHARTS_TOKEN (с флагами Masked и Protected).

Пример задачи для пуша изменений в репозиторий чартов:

update_helm_repo:
  stage: deploy
  image: alpine/git:2.40.1
  script:
    # Клонируем репозиторий чартов, используя access token. Имя пользователя — oauth2.
    - git clone https://oauth2:${HELM_CHARTS_TOKEN}@gitlab.com/infrastructure/helm-charts.git
    - cd helm-charts
    - mkdir -p $SERVICE_NAME
    - cp -r ../chart/* ./$SERVICE_NAME/
    - git config user.email "[email protected]"
    - git config user.name "GitLab CI"
    - git add .
    # Коммитим только при наличии изменений, чтобы не ломать пайплайн пустым коммитом
    - git diff-index --quiet HEAD || git commit -m "Update $SERVICE_NAME to $CI_COMMIT_SHORT_SHA"
    # Опция ci.skip предотвращает запуск бесконечного цикла пайплайнов в репозитории чартов
    - git push -o ci.skip origin HEAD:main
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Использование опции -o ci.skip при пуше — важная мера предосторожности, которая указывает GitLab не запускать сборочные конвейеры для этого коммита в целевом репозитории, исключая бесконечные циклы запуска задач. Пайплайн запускается только при коммитах в ветку по умолчанию (main).