Gitlab CI «Smart» Pipeline: родители и дети

от автора

Раньше даже не мог себе представить, что между Русской классикой и современными CICD инструментами есть связь)
Раньше даже не мог себе представить, что между Русской классикой и современными CICD инструментами есть связь)

С чего все началось

За более 3х летний срок существования продукта у нас собралось более чем 20 репозиториев со spark проектами. Процесс CICD был реализован на Jenkins. С определенного момента у GitLab CI появилась возможность создавать собственные CICD. Но долгое время я совершенно не воспринимал всерьез этот инструмент. Так как мне нравилось, что в Jenkins можно взять и дописать то чего тебе не хватает на Groovy. Настройка WebUI предоставляет широкие возможности для организации параметризованных сборок. Поначалу функционал GitlabCI я воспринимал это как жалкое подобие Jenkins: чтобы реализовать ну что-то очень очевидное и простое, я уже молчу про параметризованную сборку.

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

Для примера у вас где-то в отдельном репозитории лежат yml, которые выполняют что-то вполне определенное, которое у вас может повторяться не только в одном проекте.

    include:     - project: 'gitlabci/cicd'     ref: v1     file:       - 'pipelines/product1/.base_pipelines_spark_project.yml'

Выполнять include какого-то джоба у себя в конвейере и прям стало одной из киллер фич. И в какой-то момент перевод пайплайн на GitLabCI уже не выглядело как необходимость, а собственным желанием реализовать интересную задачу.

Что было

— более 20 репозиториев в gitlab spark проектов;

— часть из них работают со spark часть из них spark + kafka;

— CICD реализован на Jenkins

Что хотелось сделать

Выполнить трансформацию CICD с Jenkins на Gitlab CI c наименьшим количеством шагов: чтобы команда разработки, если хотела бы вникнуть то могла это сделать, а если нет то было бы что-нибудь типа создать такой-то файл и скопировать туда такой-то yaml код и чтобы гарантированно заработало причем без активной помощи со стороны devops разработчика.

Начало

В каждом из Spark проектов реализовано было тестирование по одному из 2-х сценариев: с кафка или без. Описать сценарий в одном job было не возможно и поэтому были созданы 2 yaml, которые подключались следующим образом

    include:       # PRODUCT1       - project: 'gitlabci/integration-test'       ref: v2       file:         - 'product1/etl/.base_integration_test.yml'         - 'product1/etl/.base_integration_test_with_kafka.yml'

Для того, чтобы .gitlab-ci.yml выглядел для каждого проекта одинаковым необходимо было придумать логику таким образом, чтобы пайплайн на основании семантического анализа кода в test/fixtures.py мог определить какой сценарий необходим. Решить эту задачу оказалось достаточно тривиальной задачей, первая проблема была дальше. Предполагалось создать job, который в процессе анализа определял переменную CICD_KAFKA_HOST либо в true либо в false

    prepare_test:       script:         - export CICD_KAFKA_HOST=$(cat test/fixtures.py | grep KAFKA_HOST)         - >           if [ "$CICD_KAFKA_HOST" != "" ]; then             export CICD_KAFKA_HOST="true"           else             export CICD_KAFKA_HOST="false"           fi         - echo "CICD_KAFKA_HOST=$CICD_KAFKA_HOST" >> dotenv.env       artifacts:         reports:           dotenv:             - dotenv.env

и в последующих job нужно запускать тесты либо c кафка либо без. Но по ходу реализации выяснилось, что использовать rules нельзя, потому variables для rules определяются при старте пайплайна и не могут быть переопределены/изменены в процессе работы конвейера и расширения extends должны быть определены в пайплайне однозначным образом.

    integration_test:       extends: .base_integration_test_with_kafka       rules:         - if: '$CICD_KAFKA_HOST == "true"'

Реализация идеи «smart» пайплайна первый раз подверглась сомнению. НО по на помощь должны были прийти триггеры.

Триггер

недавно вышел новый сезон телесериала "Триггер" от продюсерской компании Среда, решение в такой реализации как выход из зоны комфорта, именно этот метод практикует герой Максима Матвеева
недавно вышел новый сезон телесериала «Триггер» от продюсерской компании Среда, решение в такой реализации как выход из зоны комфорта, именно этот метод практикует герой Максима Матвеева

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

Реализация получилась такой

    # --------------- Prepare Test ---------------      prepare_test:       image: platform/docker-images/vault:1.8       variables:         CONTEXT_TEST: |           include:           # PRODUCT           - project: 'gitlabci/integration-test'             ref: v2             file:               - 'product1/etl/.base_integration_test.yml'               - 'product1/etl/.base_integration_test_with_kafka.yml'           integration_test:             variables:               COVERAGE_SOURCE: "./src"         INTEGRATION_TEST: |           $CONTEXT_TEST             extends: .base_integration_test         INTEGRATION_TEST_WITH_KAFKA: |           $CONTEXT_TEST             extends: .base_integration_test_with_kafka      stage: prepare_test       script:         - export CICD_KAFKA_HOST=$(cat test/fixtures.py | grep KAFKA_HOST)         - >           if [ "$CICD_KAFKA_HOST" != "" ]; then             export CICD_KAFKA_HOST="true"             echo "$INTEGRATION_TEST_WITH_KAFKA" >> test.yml           else             export CICD_KAFKA_HOST="false"             echo "$INTEGRATION_TEST" >> test.yml           fi         - env | sort -f       artifacts:         paths:           - test.yml         expire_in: 7200 seconds      # --------------- Integration test ---------------      integration_test:       stage: test       trigger:         include:           - artifact: test.yml             job: prepare_test         strategy: depend

В такой реализации обычный пайплайн трансформировался в мультипайплайн: родительский пайплайн инициировал запуск пайплайна-ребенка

Gitlab CI: Pipeline родитель, у которого есть pipeline ребенок
Gitlab CI: Pipeline родитель, у которого есть pipeline ребенок

Такие образом появилось smart начало: он умеет определять какой сценарий выбрать и в job с интеграционным тестированием переиспользует именно тот сценарий который необходим: либо с кафка либо без. Начало положено, НО возникла проблема №2: результатом выполнения pipeline ребенка — формирование coverage отчета, который не мультипайплайнах мы далее передаем в job c SonarQube. Решить задачу по передаче между job artifact в виде файлов как раньше было нельзя, вернуть artifact из child в parent оказалось невозможно.

Очевидное решение — добавить upload artifact в наш aftifactory и в job c SonarQube просто его скачать. Но хотелось найти более изящный способ, чтобы исключить дополнительные обращения к Artifactory. И способ был найден: Gitlab CI API

Gitlab CI API: download child artifacts

Чтобы иметь возможность подключаться к Gitlab CI API необходимо для пользователя, который имеет права на репозиторий сгенерировать token. Для того чтобы воспользоваться API скачать artifact из pipeline ребенка необходимо выяснить его CI_JOB_ID.

GET /projects/:id/jobs/:job_id/artifacts

Как это сделать из pipeline родителя?

— определяем ID pipeline ребенка

GET /projects/:id/pipelines/:pipeline_id/bridges

— по id pipeline ребенка определяем id job

GET /projects/:id/pipelines/:pipeline_id/jobs

— после этого уже выполняем скачивание методом /projects/:id/jobs/:job_id/artifacts

Итоговая реализация job по скачиванию artifacts будет выглядеть так: в список переменных группы проектов куда входит и наш репозиторий положили значение token — GITLAB_USER_TOKEN и для разбора json ответа от Gitlab API использовали jq

    get_cicd_artifact:       image: platform/docker-images/ansible:2.9.24-9       stage: get_cicd_artifact       script:         - >           export CI_CHILD_PIPELINE_ID=$(curl --header "PRIVATE-TOKEN: $GITLAB_USER_TOKEN" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/pipelines/$CI_PIPELINE_ID/bridges" | jq ".[].downstream_pipeline.id")         - echo $CI_CHILD_PIPELINE_ID         - >           export CI_CHILD_JOB_ID=$(curl --header "PRIVATE-TOKEN: $GITLAB_USER_TOKEN" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/pipelines/$CI_CHILD_PIPELINE_ID/jobs" | jq '.[].id')         - echo $CI_CHILD_JOB_ID         - 'curl --output artifacts.zip --header "PRIVATE-TOKEN: $GITLAB_USER_TOKEN" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/$CI_CHILD_JOB_ID/artifacts"'         - unzip artifacts.zip         - ls -las coverage-reports         - rm -rf artifacts.tar       dependencies:         - integration_test       artifacts:         paths:           - coverage-reports/

Таким образом удалось реализовать Multi-project пайплайн имхо со «smart» фичой

Gitlab CI Multi-project pipelines
Gitlab CI Multi-project pipelines

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какие CI/CD инструменты Вы используете?
27.27% Jenkins 6
86.36% Gitlab CI 19
22.73% TeamCity 5
0% Bamboo CI 0
Проголосовали 22 пользователя. Воздержались 2 пользователя.

ссылка на оригинал статьи https://habr.com/ru/post/646587/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *