Как написать Pipeline в GitLab для схожих репозиториев

Ответ

Для поддержания единообразия и избежания дублирования в нескольких репозиториях я использовал подход с шаблонами (templates), подключаемыми через директиву include. Это позволяет централизованно управлять логикой сборки, тестирования и деплоя.

1. Создаем общий шаблон в отдельном репозитории или проекте (gitlab-ci-templates):

Файл: .gitlab-ci-common.yml

# Общие переменные для всех проектов
variables:
  DOCKER_REGISTRY: "registry.example.com"
  DOCKER_BUILDKIT: 1

# Шаблон для сборки Docker-образа
.docker-build:
  image: docker:20.10
  services:
    - docker:20.10-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $DOCKER_REGISTRY
  script:
    - docker build --pull -t $DOCKER_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHORT_SHA .
    - docker push $DOCKER_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHORT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# Шаблон для запуска unit-тестов (пример для Node.js)
.unit-tests:
  image: node:16-alpine
  script:
    - npm ci
    - npm run test:unit
  artifacts:
    when: always
    reports:
      junit: reports/junit.xml
    paths:
      - coverage/

2. В .gitlab-ci.yml каждого микросервиса подключаем и расширяем шаблоны:

# Подключаем общие шаблоны
include:
  - project: 'devops/gitlab-ci-templates'
    ref: main
    file: '/.gitlab-ci-common.yml'

# Определяем стадии для этого конкретного проекта
stages:
  - test
  - build
  - deploy

# Job для линтера (может быть уникальным для проекта)
eslint:
  stage: test
  image: node:16-alpine
  script:
    - npm ci
    - npm run lint

# Job для сборки — наследуем и расширяем общий шаблон
build-image:
  extends: .docker-build
  stage: build
  # Добавляем специфичные для проекта переменные
  variables:
    DOCKERFILE_PATH: ./Dockerfile.prod

# Job для деплоя в Kubernetes (также может быть шаблонным)
deploy-staging:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context staging
    - kubectl set image deployment/$CI_PROJECT_NAME app=$DOCKER_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHORT_SHA -n staging
  environment:
    name: staging
    url: https://staging.example.com

Преимущества такого подхода:

  • Единый источник истины: Изменения в процессе CI/CD вносятся в одном месте.
  • Гибкость: Каждый проект может переопределять или расширять шаблонные jobs под свои нужды.
  • Снижение ошибок: Минимизируется копипаста конфигураций между репозиториями.

Ответ 18+ 🔞

Слушай, ну вот реально, как же заебало каждый раз в каждом проекте одно и то же писать! Как будто на дворе 2002-й год, и мы в каждом репозитории копируем один и тот же хуй в пальто. Терпения ноль ебать, честно.

Так вот, чтобы не быть распиздяем и не плодить этот пиздец, я сделал всё по уму. Создал отдельную папку с шаблонами — типа такой общей библиотеки для всех наших CI/CD-скриптов. Это, блядь, как общая кухня в коммуналке, только чтобы никто не срал.

1. Создал общий файл с шаблонами (.gitlab-ci-common.yml):

Вот смотри, что там внутри. Там всё, что нужно всем, собрано в одном месте.

# Общие переменные для всех проектов
variables:
  DOCKER_REGISTRY: "registry.example.com"
  DOCKER_BUILDKIT: 1

# Шаблон для сборки Docker-образа
.docker-build:
  image: docker:20.10
  services:
    - docker:20.10-dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $DOCKER_REGISTRY
  script:
    - docker build --pull -t $DOCKER_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHORT_SHA .
    - docker push $DOCKER_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHORT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# Шаблон для запуска unit-тестов (пример для Node.js)
.unit-tests:
  image: node:16-alpine
  script:
    - npm ci
    - npm run test:unit
  artifacts:
    when: always
    reports:
      junit: reports/junit.xml
    paths:
      - coverage/

2. А теперь в каждом микросервисе я просто подключаю эту общую хуйню и дополняю своим.

Вот как выглядит .gitlab-ci.yml в каком-нибудь сервисе:

# Подключаем общие шаблоны. Всё, бля, как по маслу.
include:
  - project: 'devops/gitlab-ci-templates'
    ref: main
    file: '/.gitlab-ci-common.yml'

# А тут уже своя специфика, если нужна
stages:
  - test
  - build
  - deploy

# Например, линтер свой, потому что у всех проекты разные, ёпта
eslint:
  stage: test
  image: node:16-alpine
  script:
    - npm ci
    - npm run lint

# А сборка — это просто расширение общего шаблона. Красота!
build-image:
  extends: .docker-build
  stage: build
  # И если надо, свой Dockerfile подсунуть
  variables:
    DOCKERFILE_PATH: ./Dockerfile.prod

# И деплой, который тоже можно было бы в шаблон вынести, если припрет
deploy-staging:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context staging
    - kubectl set image deployment/$CI_PROJECT_NAME app=$DOCKER_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHORT_SHA -n staging
  environment:
    name: staging
    url: https://staging.example.com

И в чём, собственно, профит, чувак?

  • Один раз написал — везде работает. Изменил что-то в общем шаблоне — и на всех проектах автоматом обновилось. Никакой рутины, ебать мои старые костыли.
  • Гибкость полная. Хочешь — используй шаблон как есть. Хочешь — дополнишь или перекроешь. Никто тебя в сраку не гонит.
  • Меньше ошибок. Раньше можно было в одном месте опечататься, и потом полдня искать, почему сборка не запускается. А теперь доверия ебать ноль к копипасте, зато к шаблонам — полное. Сам от себя охуел, насколько стало проще.