- PVSM.RU - https://www.pvsm.ru -

GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности

GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности - 1
Эта статья продолжает первую часть [1], содержащую подробное описание нашего пайплайна:

image
… и рассказывает о проблемах, с которыми мы столкнулись для его реализации, и их решении.

Итак, я остановился на том, что созданный .gitlab-ci.yml [2] не позволяет реализовать пайплайн в полной мере, поскольку GitLab CI не предоставляет директив для разделения задач по пользователям и для описания зависимостей выполнения задач от статуса выполнения других задач, а также не позволяет разрешить модификацию .gitlab-ci.yml только для отдельных пользователей.

1. Защита .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.

2. Переменные для скрипта задачи

Вторая проблема, которую получилось решить довольно легко, — переменные среды GITLAB_USER_ID и GITLAB_USER_EMAIL для скрипта задачи с идентификатором пользователя и его почтой. Эти переменные можно использовать, чтобы определить, может ли пользователь запустить задачу. Реализовано как решение тикета #21825 [6], принято в основную ветку (upstream) и доступно в GitLab CI начиная с версии 8.12:
GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности - 3

3. Зависимости между стадиями

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

То есть, во-первых, автоматические задачи запускаются только в момент создания пайплайна, а во-вторых, нельзя сделать такой процесс, когда успешное выполнение ручной задачи запустило бы автоматические задачи, расположенные дальше в пайплайне. Документация [7] по сути описывает поведение автоматических задач. Ручные же задачи живут «сами по себе» и могут быть запущены в любой момент, независимо от статуса выполнения задач на предыдущих стадиях.

На этот счёт есть несколько тикетов, где предлагается изменить поведение ручных и автоматических задач:

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

Артефакт задачи [11] — это файлы, указанные в директиве artifact, которые будут доступны (после успешного завершения задачи) всем остальным задачам на последующих стадиях. Тут, правда, есть свои подводные камни: файлы со всех задач стадии будут доступны на дальнейших стадиях и удалить что-то из этого набора нельзя. В то же время файлы артефакта задачи недоступны в других задачах той же стадии.

Рассмотрим подробнее на двух примерах. Сначала на примере стадий testing и staging:

GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности - 4

По описанию пайплайна, задачи развёртывания на окружения тестировщиков (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:

GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности - 5

Задачи 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:

GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности - 6

После выполнения задачи NOT approve следующая за ней задача deploy to production завершается с ошибкой:

GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности - 7

Исходники:

Что дальше?

Осталось не озвученным требование разрешать отдельные задачи только некоторым пользователям. На данном этапе стало понятно, как это можно реализовать: нужен REST API, который можно будет запросить через curl с передачей переменных GITLAB_USER_ID и GITLAB_USER_EMAIL. Создание такого REST API выходит за рамки данной статьи.

В приведённых примерах скрипт, проверяющий зависимости, хранится в .gitlab-ci.yml. Это очень неудобно, если проектов много и нужно что-то поправить, например, если появится новое окружение для qa или окружений для pre-production станет больше. Мы это решили с помощью вынесения скриптов в один внешний скрипт, который не хранится в каждом репозитории, а устанавливается на машины с раннерами.

Такому скрипту доступно несколько переменных среды [15]. На основе этих переменных скрипт принимает решение, какой вид задачи запущен, по файлам от предыдущих стадий проверяет, можно ли запускать эту задачу. Если нужно, проверяет доступ для пользователя через внешний REST-сервис. Скрипт содержит в себе инструкции, которые нужно выполнить для задачи и после их успешного выполнения создаёт файлы, на которые будет реагировать следующая задача.

Обычно в пайплайне не так много вариаций задач, наш скрипт знает о трёх:

  1. инструкции для сборки,
  2. инструкции для разворачивания,
  3. инструкции approve и not approve.

Инструкции тоже получают переменные среды и могут подстраиваться под конкретную задачу. Так как вариантов сборки и развёртывания в окружениях великое множество, да и количество проектов в GitLab тоже у всех отличается, то приводить реализацию такого скрипта считаю излишним. Впрочем, если есть вопросы — давайте обсудим в комментариях.

Вместо заключения

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

GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности - 8

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/