Зеркалирование репозиториев Git с помощью стадии публикации в GitLab CI

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

Публикация кода в открытые репозитории Git. Допустим, вы разрабатываете продукт и какие-то его ветки должны быть доступны для сообщества или вы, в принципе, разрабатываете продукт с открытым кодом, но ведете разработку в GitLab, выполняете тестирование и доставку с помощью GitLab CI. Обычно вы хотите, чтобы пользователи имели возможность постоянного доступа к исходному коду продукта, поэтому хотите выкладывать его на GitHub или BitBucket.

Публикация кода в репозиторий клиента. Вы разрабатываете программный продукт для одного или более клиентов, а при создании релизов публикуете исходный код на их внутренние GitLab серверы, на которых для релизного кода отрабатывают соответствующие процедуры CI/CD и выполняется развертывание кода в продуктовую или ‘staging’ среду. При этом, вы можете хотеть публиковать разным клиентам разные ветки репозитория, которые содержат специализированные модификации.

Мы рассмотрим как решить эту задачу с помощью GitLab CI. Использовать такой подход особенно удобно, поскольку это позволяет выполнять публикацию только в том случае, когда конвейер (pipeline) CI/CD завершается успешно, то есть вы можете гарантировать доставку рабочего кода в удаленный репозиторий.

Для особо нетерпеливых сразу предоставим ссылку на рабочий пример публикации ветки master в GitHub.

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

Синхронизация с удаленным зеркалом вручную

Первый шаг процесса – первичная синхронизация с удаленным репозиторием. Для этого выполните следующие действия:

Клонируйте репозиторий-донор на локальный компьютер:

git clone ssh://git@git.server.com:/group/project.git

Добавьте удаленный репозиторий в качестве upstream-репозитория:

git remote add mirror git@github.com:group/project.git

Загрузите необходимую ветку в удаленный репозиторий в принудительном порядке (в примере используется master):

git push --force --progress mirror HEAD:master

Задайте переменные в GitLab

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

Переменные задаются для групп проектов. Для того, чтобы определить переменные, перейдем в: Группа проектов (group) > Settings > Pipelines:

Как можно видеть, мы для нашей группы проектов опредили переменные GITHUB_MIRROR_PRIVATE и GITHUB_MIRROR_PUBLIC, а для публикации собранных артефактов в Sonatype Nexus – SONATYPE_NEXUS_LOGIN и SONATYPE_NEXUS_PASSWORD. Переменные SONATYPE_NEXUS_* для публикации в GitHub не используются.

Для ключа GITHUB_MIRROR_PRIVATE мы выполнили дополнительное кодирование в base64: cat id_rsa | base64 -w0 для того, чтобы блок закрытого ключа принял форму одной строки без символов переноса строк. В процессе работы задания мы выполним обратную процедуру base64 -d, чтобы вернуть его в исходный вид. Это решение продиктовано тем, что мы хотим избежать случайное перекодирование переноса строк при загрузке, выгрузке ключа в/из GitLab. Вообще, можно рассмотреть упаковку всех переменных с потенциально небезопасными символами в base64.

Определим стадию и задание в GitLab CI

Предполагаем, что у вас уже имеется файл .gitlab-ci.yml, в котором определены необходимые шаги CI/CD. Добавим в него задание зеркалирования и разберем его подробно:

stages:
  ...
  - mirror

Здесь мы определили новую стадию mirror, в которой определим действия по зеркалированию репозитория. Далее, определим задание зеркалирования:

mirror-master:
  stage: mirror
  variables:
    UPSTREAM_REPOSITORY: "git@github.com:bwsw/cloud-plugin-kv-storage.git"
    UPSTREAM_BRANCH: "master"
    GIT_SUBMODULE_STRATEGY: none
    GIT_STRATEGY: clone
  cache: {}
  only:
    - master
  script:
    - mkdir -p ~/.ssh                                                  # 1
    - echo $GITHUB_MIRROR_PRIVATE | base64 -d > ~/.ssh/id_rsa          # 2
    - echo $GITHUB_MIRROR_PUBLIC > ~/.ssh/id_rsa.pub                   # 3
    - ssh-keyscan -t rsa,dsa,ecdsa github.com >> ~/.ssh/known_hosts    # 4
    - chmod -R go-rwx ~/.ssh                                           # 5
    - git remote add mirror $UPSTREAM_REPOSITORY                       # 6
    - git remote show mirror                                           # 7
    - git fetch mirror                                                 # 8
    - git push --progress mirror HEAD:master                           # 9
  ...

Разбор задания по частям

Задание выполняется на стадии mirror.

stage: mirror

Запрещаем скачивать сабмодули, если они определены для проекта, что ускоряет обработку стадии.

GIT_SUBMODULE_STRATEGY: none

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

GIT_STRATEGY: clone

Говорим GitLab, что на стадии не будет использоваться кэш, это ускоряет обработку стадии, так как кэш не будет скачиваться с сервера GitLab.

cache: {}

Указываем, что задание будет выполняться только для ветки master.

only:
  - master

Команды задания по шагам:

- mkdir -p ~/.ssh                                                  # 1
- echo $GITHUB_MIRROR_PRIVATE | base64 -d > ~/.ssh/id_rsa          # 2
- echo $GITHUB_MIRROR_PUBLIC > ~/.ssh/id_rsa.pub                   # 3
- ssh-keyscan -t rsa,dsa,ecdsa github.com >> ~/.ssh/known_hosts    # 4
- chmod -R go-rwx ~/.ssh                                           # 5
- git remote add mirror $UPSTREAM_REPOSITORY                       # 6
- git remote show mirror                                           # 7
- git fetch mirror                                                 # 8
- git push --progress mirror HEAD:$UPSTREAM_BRANCH                 # 9
  1. Создадим каталог .ssh, куда разместим ключи. Флаг -p позволяет выполнять команду без ошибок, если каталог уже существует.
  2. Сохраним приватный ключ из переменной, выполнив его раскодирование.
  3. Сохраним публичный ключ аналогичным образом.
  4. Выполним сканирование GitHub на предмет активных ключей сервера, необходимо, чтобы SSH не генерировал приглашение подтвердить ключи.
  5. Установим права, которые требует SSH для использования ключей.
  6. Добавим удаленный репозиторий под именем mirror.
  7. Используется для отладки задания.
  8. Скачиваем изменения с удаленного репозитория. Используется для отладки и отслеживания некорректной работы с удаленными репозиториями.
  9. Загружаем изменения в удаленный репозиторий.

Тестирование задания

Для тестирования задания выполните вручную запуск конвейера для ветки master.

Весь конвейер сборки должен отработать успешно, а в удаленном репозитории должно появиться обновленное дерево файлов.

На этапе тестирования и отладки удобно поместить стадию mirror на первое место по порядку следования, что позволит не тратить время на сборку, а выполнить отладку только публикации за минимальное время.

Заключение

В этом примере мы рассмотрели простой подход, который может использоваться для зеркалирования репозитория в публичный Git, на примере GitHub. Этот подход с доработками вполне можно применять и для доставки обновлений кода клиентам, при этом в репозиториях клиентов могут возникать цепочки событий CI/CD, которые будут выполнять работу по развертыванию системы на их стороне в безопасном режиме, без раскрытия информации за пределы контура организации.

Если вам понравился этот пост, поделитесь им с друзьями.