- PVSM.RU - https://www.pvsm.ru -
Эта статья продолжает первую часть [1], содержащую подробное описание нашего пайплайна:
… и рассказывает о проблемах, с которыми мы столкнулись для его реализации, и их решении.
Итак, я остановился на том, что созданный .gitlab-ci.yml [2] не позволяет реализовать пайплайн в полной мере, поскольку GitLab CI не предоставляет директив для разделения задач по пользователям и для описания зависимостей выполнения задач от статуса выполнения других задач, а также не позволяет разрешить модификацию .gitlab-ci.yml
только для отдельных пользователей.
Любой пользователь, который имеет разрешение на git push
, может изменить .gitlab-ci.yml
и сломать production. Эту проблему обсуждают в тикетах GitLab: как минимум — в #24794 [3] и #20826 [4] с подачи нашего коллеги.
Пока сложно сказать, будет ли когда-либо реализована защита из коробки, но на данный момент мы реализовали её упрощённый вариант с помощью небольшого патча [5]: push’ить коммиты с изменениями в .gitlab-ci.yml
могут только некоторые пользователи — обычно это команда DevOps, т.к. сборка и развёртывание в их зоне ответственности.
Помимо применения патча потребуется добавить boolean-столбец ci_admin
в таблицу с пользователями. Кому в столбце установлено true
, тот может делать git push
с изменениями в .gitlab-ci.yml
.
Вторая проблема, которую получилось решить довольно легко, — переменные среды GITLAB_USER_ID
и GITLAB_USER_EMAIL
для скрипта задачи с идентификатором пользователя и его почтой. Эти переменные можно использовать, чтобы определить, может ли пользователь запустить задачу. Реализовано как решение тикета #21825 [6], принято в основную ветку (upstream) и доступно в GitLab CI начиная с версии 8.12:
Ещё одной проблемой на пути к реализации можно считать некоторую путаницу в автоматических и ручных задачах, в зависимостях между стадиями. Автоматические задачи запускаются всегда при старте пайплайна, их запуск зависит только от результата выполнения автоматических задач на предыдущей стадии. При этом ручные задачи и статус их выполнения полностью игнорируются.
То есть, во-первых, автоматические задачи запускаются только в момент создания пайплайна, а во-вторых, нельзя сделать такой процесс, когда успешное выполнение ручной задачи запустило бы автоматические задачи, расположенные дальше в пайплайне. Документация [7] по сути описывает поведение автоматических задач. Ручные же задачи живут «сами по себе» и могут быть запущены в любой момент, независимо от статуса выполнения задач на предыдущих стадиях.
На этот счёт есть несколько тикетов, где предлагается изменить поведение ручных и автоматических задач:
Но похоже, что эти предложения противоречат друг другу. Даже в нашем случае нужны ручные задачи, которые можно запустить независимо, и ручные задачи, которые должны реагировать на успешное выполнение одной или нескольких задач. После некоторых раздумий возникла мысль использовать артефакты задач и в скриптах проверять наличие файлов от предыдущих стадий.
Артефакт задачи [11] — это файлы, указанные в директиве artifact
, которые будут доступны (после успешного завершения задачи) всем остальным задачам на последующих стадиях. Тут, правда, есть свои подводные камни: файлы со всех задач стадии будут доступны на дальнейших стадиях и удалить что-то из этого набора нельзя. В то же время файлы артефакта задачи недоступны в других задачах той же стадии.
Рассмотрим подробнее на двух примерах. Сначала на примере стадий testing и staging:
По описанию пайплайна, задачи развёртывания на окружения тестировщиков (deploy to qa-*) можно запускать только после выполнения всех тестов, а остальные задачи такой зависимости не имеют. Для реализации этой логики в конце успешного выполнения тестов делается touch
файла с именем задачи, а в начале выполнения задач deploy to qa-*, на стадии staging, проверяется наличие этих файлов.
Вот для примера листинги задач test integration и deploy to qa-1:
test integration:
stage: testing
tags: [deploy]
script:
- mkdir -p .ci_status
- echo "test integration"
- touch .ci_status/test_integration
artifacts:
paths:
- .ci_status/
deploy to qa-1:
tags: [deploy]
stage: staging
when: manual
script:
- if [ ! -e .ci_status/test_unit -o ! -e .ci_status/test_integration -o ! -e .ci_status/test_selenium ]; then echo "Нужно успешное выполнение всех тестов"; exit 1; fi
- echo "execute job ${CI_BUILD_NAME}"
- touch .ci_status/deploy_to_qa_1
artifacts:
paths:
- .ci_status/
Добавилась директива artifact
, которая определяет пути в репозитории, сохраняемые GitLab CI в архив после выполнения задачи и разархивируемые перед выполнением следующей задачи. Чтобы не перечислять все файлы, указывается директория .ci_status
, которую не помешает создать во время выполнения задачи (mkdir -p
).
Исходник: файл .gitlab-ci.yml с зависимостью стадии staging от testing доступен здесь [12].
Второй пример немного сложнее — это зависимость стадии production от стадии approve:
Задачи approve и not approve создают файлы, которые проверяет задача production. Это можно сделать так же, как и в предыдущем примере, но хочется, чтобы задачи NOT approve и approve работали как переключатель. Такой работе мешает факт, что нельзя удалить файлы артефактов от другой задачи. Поэтому задачи не просто создают файл, а пишут в него timestamp. В начале выполнения задачи deploy to production выполняется проверка: если в файле от задачи approve timestamp больше, то можно продолжать, если нет — задача завершается с ошибкой.
approve:
script:
- mkdir -p .ci_status
- echo $(date +%s) > .ci_status/approved
artifacts:
paths:
- .ci_status/
NOT approve:
script:
- mkdir -p .ci_status
- echo $(date +%s) > .ci_status/not_approved
artifacts:
paths:
- .ci_status/
deploy to production:
script:
- if [[ $(cat .ci_status/not_approved) > $(cat .ci_status/approved) ]]; then echo "Нужно разрешение от релиз-инженера"; exit 1; fi
- echo "deploy to production!"
После выполнения задачи appove успешно выполняется deploy to production:
После выполнения задачи NOT approve следующая за ней задача deploy to production завершается с ошибкой:
Исходники:
Осталось не озвученным требование разрешать отдельные задачи только некоторым пользователям. На данном этапе стало понятно, как это можно реализовать: нужен REST API, который можно будет запросить через curl с передачей переменных GITLAB_USER_ID
и GITLAB_USER_EMAIL
. Создание такого REST API выходит за рамки данной статьи.
В приведённых примерах скрипт, проверяющий зависимости, хранится в .gitlab-ci.yml
. Это очень неудобно, если проектов много и нужно что-то поправить, например, если появится новое окружение для qa или окружений для pre-production станет больше. Мы это решили с помощью вынесения скриптов в один внешний скрипт, который не хранится в каждом репозитории, а устанавливается на машины с раннерами.
Такому скрипту доступно несколько переменных среды [15]. На основе этих переменных скрипт принимает решение, какой вид задачи запущен, по файлам от предыдущих стадий проверяет, можно ли запускать эту задачу. Если нужно, проверяет доступ для пользователя через внешний REST-сервис. Скрипт содержит в себе инструкции, которые нужно выполнить для задачи и после их успешного выполнения создаёт файлы, на которые будет реагировать следующая задача.
Обычно в пайплайне не так много вариаций задач, наш скрипт знает о трёх:
Инструкции тоже получают переменные среды и могут подстраиваться под конкретную задачу. Так как вариантов сборки и развёртывания в окружениях великое множество, да и количество проектов в GitLab тоже у всех отличается, то приводить реализацию такого скрипта считаю излишним. Впрочем, если есть вопросы — давайте обсудим в комментариях.
Надеюсь, что данная статья раскроет для вас новые интересные возможности GitLab CI и даст отправную точку для реализации собственных крутых пайплайнов.
P.S. Не забывайте про проверку .gitlab-ci.yml
по адресу https://мой-гитлаб/ci/lint
. Поможет сэкономить время!
Автор: diafour
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/open-source/260188
Ссылки в тексте:
[1] первую часть: https://habrahabr.ru/company/flant/blog/332712/
[2] .gitlab-ci.yml: https://github.com/flant/gitlab-ci-examples/blob/001_use_only/.gitlab-ci.yml
[3] #24794: https://gitlab.com/gitlab-org/gitlab-ce/issues/24794
[4] #20826: https://gitlab.com/gitlab-org/gitlab-ce/issues/20826
[5] небольшого патча: https://gitlab.com/gitlab-org/gitlab-ce/issues/20826#note_19343531
[6] тикета #21825: https://gitlab.com/gitlab-org/gitlab-ce/issues/21825
[7] Документация: https://docs.gitlab.com/ee/ci/yaml/#stages
[8] #25892: [CI] Stages after a manual stage should not be started automatically: https://gitlab.com/gitlab-org/gitlab-ce/issues/25892
[9] #26499: Retry subsequent jobs if upstream job is retried: https://gitlab.com/gitlab-org/gitlab-ce/issues/26499
[10] #20594: Manual job ignore dependencies: https://gitlab.com/gitlab-org/gitlab-ce/issues/20594
[11] Артефакт задачи: https://docs.gitlab.com/ce/ci/yaml/README.html#artifact
[12] здесь: https://github.com/flant/gitlab-ci-examples/blob/002_testing_staging/.gitlab-ci.yml
[13] вариант .gitlab-ci.yml: https://github.com/flant/gitlab-ci-examples/blob/003_approve/.gitlab-ci.yml#L83-L115
[14] полный вариант получившегося .gitlab-ci.yml: https://github.com/flant/gitlab-ci-examples/blob/final_pipeline/.gitlab-ci.yml
[15] переменных среды: https://docs.gitlab.com/ce/ci/variables/
[16] Источник: https://habrahabr.ru/post/332842/
Нажмите здесь для печати.