Эта статья продолжает первую часть, содержащую подробное описание нашего пайплайна:
… и рассказывает о проблемах, с которыми мы столкнулись для его реализации, и их решении.
Итак, я остановился на том, что созданный .gitlab-ci.yml не позволяет реализовать пайплайн в полной мере, поскольку GitLab CI не предоставляет директив для разделения задач по пользователям и для описания зависимостей выполнения задач от статуса выполнения других задач, а также не позволяет разрешить модификацию
.gitlab-ci.yml
только для отдельных пользователей.1. Защита .gitlab-ci.yml от изменений
Любой пользователь, который имеет разрешение на
git push
, может изменить .gitlab-ci.yml
и сломать production. Эту проблему обсуждают в тикетах GitLab: как минимум — в #24794 и #20826 с подачи нашего коллеги.Пока сложно сказать, будет ли когда-либо реализована защита из коробки, но на данный момент мы реализовали её упрощённый вариант с помощью небольшого патча: push’ить коммиты с изменениями в
.gitlab-ci.yml
могут только некоторые пользователи — обычно это команда DevOps, т.к. сборка и развёртывание в их зоне ответственности.Помимо применения патча потребуется добавить boolean-столбец
ci_admin
в таблицу с пользователями. Кому в столбце установлено true
, тот может делать git push
с изменениями в .gitlab-ci.yml
.2. Переменные для скрипта задачи
Вторая проблема, которую получилось решить довольно легко, — переменные среды
GITLAB_USER_ID
и GITLAB_USER_EMAIL
для скрипта задачи с идентификатором пользователя и его почтой. Эти переменные можно использовать, чтобы определить, может ли пользователь запустить задачу. Реализовано как решение тикета #21825, принято в основную ветку (upstream) и доступно в GitLab CI начиная с версии 8.12:3. Зависимости между стадиями
Ещё одной проблемой на пути к реализации можно считать некоторую путаницу в автоматических и ручных задачах, в зависимостях между стадиями. Автоматические задачи запускаются всегда при старте пайплайна, их запуск зависит только от результата выполнения автоматических задач на предыдущей стадии. При этом ручные задачи и статус их выполнения полностью игнорируются.
То есть, во-первых, автоматические задачи запускаются только в момент создания пайплайна, а во-вторых, нельзя сделать такой процесс, когда успешное выполнение ручной задачи запустило бы автоматические задачи, расположенные дальше в пайплайне. Документация по сути описывает поведение автоматических задач. Ручные же задачи живут «сами по себе» и могут быть запущены в любой момент, независимо от статуса выполнения задач на предыдущих стадиях.
На этот счёт есть несколько тикетов, где предлагается изменить поведение ручных и автоматических задач:
- #25892: [CI] Stages after a manual stage should not be started automatically
- #26499: Retry subsequent jobs if upstream job is retried
- #20594: Manual job ignore dependencies
Но похоже, что эти предложения противоречат друг другу. Даже в нашем случае нужны ручные задачи, которые можно запустить независимо, и ручные задачи, которые должны реагировать на успешное выполнение одной или нескольких задач. После некоторых раздумий возникла мысль использовать артефакты задач и в скриптах проверять наличие файлов от предыдущих стадий.
Артефакт задачи — это файлы, указанные в директиве
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 доступен здесь.
Второй пример немного сложнее — это зависимость стадии 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 завершается с ошибкой:
Исходники:
- вариант .gitlab-ci.yml с зависимостью стадии production от стадии approve;
- полный вариант получившегося .gitlab-ci.yml с директивами only и зависимостями стадий.
Что дальше?
Осталось не озвученным требование разрешать отдельные задачи только некоторым пользователям. На данном этапе стало понятно, как это можно реализовать: нужен REST API, который можно будет запросить через curl с передачей переменных
GITLAB_USER_ID
и GITLAB_USER_EMAIL
. Создание такого REST API выходит за рамки данной статьи.В приведённых примерах скрипт, проверяющий зависимости, хранится в
.gitlab-ci.yml
. Это очень неудобно, если проектов много и нужно что-то поправить, например, если появится новое окружение для qa или окружений для pre-production станет больше. Мы это решили с помощью вынесения скриптов в один внешний скрипт, который не хранится в каждом репозитории, а устанавливается на машины с раннерами.Такому скрипту доступно несколько переменных среды. На основе этих переменных скрипт принимает решение, какой вид задачи запущен, по файлам от предыдущих стадий проверяет, можно ли запускать эту задачу. Если нужно, проверяет доступ для пользователя через внешний REST-сервис. Скрипт содержит в себе инструкции, которые нужно выполнить для задачи и после их успешного выполнения создаёт файлы, на которые будет реагировать следующая задача.
Обычно в пайплайне не так много вариаций задач, наш скрипт знает о трёх:
- инструкции для сборки,
- инструкции для разворачивания,
- инструкции approve и not approve.
Инструкции тоже получают переменные среды и могут подстраиваться под конкретную задачу. Так как вариантов сборки и развёртывания в окружениях великое множество, да и количество проектов в GitLab тоже у всех отличается, то приводить реализацию такого скрипта считаю излишним. Впрочем, если есть вопросы — давайте обсудим в комментариях.
Вместо заключения
Надеюсь, что данная статья раскроет для вас новые интересные возможности GitLab CI и даст отправную точку для реализации собственных крутых пайплайнов.
P.S. Не забывайте про проверку
.gitlab-ci.yml
по адресу https://мой-гитлаб/ci/lint
. Поможет сэкономить время!