Пайплайны Gitlab CI: моя коллекция граблей
Привет, Хабр! Я Евгений Малышев, SRE-инженер в Купере (так теперь называется СберМаркет). Моя основная задача — это надежная работа сервисов фронтенда, и немалую роль в этом играют правильно построенные пайплайны CI/CD. В этом нам помогает Gitlab CI. В компании мы широко используем этот инструмент для создания общих шаблонов для сервисов на различных языках. На уровне отдельного репозитория легко расширить или настроить шаблонные джобы и добавить свои.
До этого у меня был опыт с Jenkins и Azure Devops, так что Gitlab CI мне показался довольно простым: есть стадии, есть правила запуска джоб с shell-подобным синтаксисом, да и скрипты джоб тоже используют bash-интерпретатор. Но в процессе близкого знакомства не раз возникали ситуации, когда поднимается то одна бровь, то обе, а то и руки в праведном гневе. Заходите посмотреть, какую коллекцию граблей собрал я.
Весь код с примерами граблей можно посмотреть в репозитории.
allow_failure это тоже не failure
Начнем с простого. Нам нужно выстроить зависимость двух джоб:
first_job:
script: ./run_stuff.sh
dependent_job:
script: ./run_more_stuff.sh
needs: first_job
Элементарно же? Теперь допустим, что first_job
может завершаться неуспешно — пускай это будут нестрогие тесты, которые завершаются как warning
и не фейлят весь пайплайн. Для этого мы включаем allow_failure
. Есть соблазн использовать это и в более сложных кейсах.
Допустим, нам требуется выстроить какую-то логику запуска зависимой задачи, которая выходит за возможности rules
. Казалось бы, вот он механизм, можно просто увести в warning
первую джобу и тогда зависимая на запустится!
first_job:
script: ./check_if_we_need_dependent_job.sh || echo "Nothing to do, skipping dependent job"
allow_failure: true
dependent_job:
script: ./run_more_stuff.sh
needs: first_job
Но нет, это неправильные пчелы и неправильный nёёd
. В этом случае зависимая задача все равно запустится. Что делать? Собирать нужные переменные артефактом из первой задачи через dotenv report и проверять их в зависимой задаче. Не очень красиво, но переживём.
Пляски по старинным граблям с set -e
Тот факт, что джобы в Gitlab выполняются в bash
, должно помогать разработчикам, освоившим этот универсальный инструмент, который, как суперклей, помогает соединить вместе самые разные инструменты в *nix-системе. Однако, тут можно наткнуться на куда более старые грабли, заложенные уже разработчиками bash
:
check_condition:
script: |
set -e
./check_condition.sh && echo 'Условие проверено, продолжаем!'
Вот казалось бы, у нас есть скрипт check_condition.sh
, мы его выполняем и в зависимости от кода возврата либо продолжаем, либо джоба завершается неуспешно. Почему неуспешно? А для этого мы устанавливаем флаг Errexit командой set -e
, чтобы любая неуспешная команда в скрипте приводила к ошибке всего скрипта.
Но вот в чем загвоздка, у этого флага есть множество исключений и наличие &&
или ||
это одно из них. Поэтому, если логика джобы строится на кодах ошибки, лучше обрабатывать это явным образом, а не расчитывать на поведение шелла. Здесь все тонкости set -e
разобраны очень подробно. Также эти грабли описаны в документации по дебагу пайплайнов.
Исправить такую конструкцию можно, поместив конструкцию с && в дочернюю оболочку с помощью скобок:
check_condition:
script: |
set -e
(./check_condition.sh && echo 'Условие проверено, продолжаем!')
В этом случае Gitlab корректно обработает код возврата из дочерней оболочки и джоба завершится неуспешно, как и задумывалось.
Кстати, указывать явным образом set -e
было необязательно: в шелл-раннерах Gitlab по-умолчанию установлены флаги set -eo pipefail
, хотя в документации об этом не упоминается.
Благодаря этому можно подобрать еще одни грабли наподобие таких:
cat application.log | grep 'этого_там_точно_нет' | tee -a found.log
grep
не просто ничего не найдет, но еще и уронит джобу. Ой.
Грабли известные
Многие грабли описаны в документации, но все же читают инструкции, когда уже все сломалось, да?
Как пример, шишек можно набить и на оформлении многострочных скриптов. Здесь Gitlab даёт и широкие возможности, чтобы выстрелить себе в ногу:
multiline_script_job:
script:
- for i in {1..10}
- do echo $i # здесь мы ждем 10 строк с числами 1..10
- done # но получаем ошибку
Да, такие конструкции нельзя разносить по элементам YAML-списка (кстати, почему?). Ну да ладно, сделаем другим способом:
multiline_script_job:
script: >
for i in {1..10}; do
echo $i
done
Но здесь сработала магия баша при обработке переносов строки внутри цикла. В других случаях можно напороться на такое:
If you use the
- >
folded YAML multiline block scalar to split long commands, additional indentation causes the lines to be processed as individual commands.
А если по-русски, то:
Если использовать сворачивание многострочного YAML-блока с помощью элемента
>
, то излишние отступы приведут к тому, что такие линии будут восприняты, как выделенные переносами строк, до и после линии с отступами.
Поиграться и понять, как это работает, поможет сайт https://yaml-multiline.info
Итак, пример:
script: >
RESULT=$(curl --silent
--header
"Authorization: Bearer $CI_JOB_TOKEN"
"${CI_API_V4_URL}/job"
)
Что, кстати, ровно то же самое, что:
script:
RESULT=$(curl --silent
--header
"Authorization: Bearer $CI_JOB_TOKEN"
"${CI_API_V4_URL}/job"
)
Выведет ошибку из-за лишних переносов строки:
$ RESULT=$(curl --silent # collapsed multi-line command
curl: no URL specified!
curl: try 'curl --help' or 'curl --manual' for more information
/bin/bash: line 149: --header: command not found
/bin/bash: line 150: https://gitlab.example.com/api/v4/job: No such file or directory
Это можно исправить:
script: >
RESULT=$(curl --silent
--header
"Authorization: Bearer $CI_JOB_TOKEN"
"${CI_API_V4_URL}/job"
script: >
RESULT=$(curl --silent \
--header \
"Authorization: Bearer $CI_JOB_TOKEN" \
"${CI_API_V4_URL}/job")
А в случае, если все переносы строк нужно сохранить, используется литерал |
script: |
echo 'это всё'
echo 'отдельные'
echo 'команды'
Ну и разумеется, можно просто отделять отдельные команды переносами строки:
script:
echo 'так'
echo 'тоже'
echo 'можно'
Что в итоге?
Gitlab CI неспроста так популярна (возможностей море) и продолжает развиваться, обрастая новыми фичами. Некоторые из которых, возможно, баги, но что поделать. Даже о низкий порог входа можно запнуться. Главное, чтобы можно было быстро освоиться и использовать все эти широкие возможности. Надеюсь, моя коллекция вам в этом немного помогла :)
Tech-команда Купера (ex СберМаркет) ведет соцсети с новостями и анонсами. Если хочешь узнать, что под капотом высоконагруженного e-commerce, следи за нами в Telegram и на YouTube. А также слушай подкаст «Для tech и этих» от наших it-менеджеров.