С чего начать разбираться в этом всём
Когда я впервые увидел .gitlab-ci.yml, мне показалось, что это какой-то древний магический свиток. Сплошные stages, artifacts, непонятные правила... Но на самом деле всё гораздо проще — это просто рецепт: что, в каком порядке и как делать с твоим кодом.
Давайте разберём мой рабочий пайплайн по косточкам. Не как сухую документацию, а как реальный пример, который живёт у меня GitLab.
Что такое workflow и зачем он нужен
Это как швейцар у подъезда — решает, пускать ли пайплайн на работу. У меня настроено строго:
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "web"
when: always
- if: $CI_PIPELINE_SOURCE == "pipeline"
when: always
- when: never
Перевод на человеческий: запускайся только если я сам нажал кнопку «Run pipeline» в интерфейсе, или если тебя вызвал другой пайплайн. Всё остальное — от ворот поворот.
Зачем так? Чтобы случайно не запустить тяжёлые тесты при каждом пуше в ветку. Представьте: приходит джуниор, делает коммит — и бац, у вас 20 минут грузятся раннеры. Такой подход экономит и время, и нервы.
Этапы (stages) — конвейер из жизни
Пайплайн у меня разбит на пять этапов:
stages:
- get_version
- testing
- history_copy
- reports
- deploy
Это как сборка машины на заводе: сначала проверяешь номер кузова (версию), потом катаешь на тест-драйве (тесты), смотришь историю предыдущих поездок (история), составляешь отчёт механику (генерация отчёта), и только потом выкатываешь на продажу (публикация).
Важный нюанс: этапы выполняются строго по порядку, но внутри одного этапа задачи могут бежать параллельно. Это экономит кучу времени.
Переменные — чтобы не мучиться с сертификатами
В корпоративных сетях постоянно проблемы с сертификатами. У меня в пайплайне стоят две «костыльные» переменные:
variables:
GIT_SSL_NO_VERIFY: "1"
DOCKER_TLS_VERIFY: ""
Первая отключает проверку сертификатов при работе с гитом, вторая — для докера. Да, это снижает безопасность, но в изолированной внутренней сети без этого ничего не работает. Главное — никогда не использовать такие настройки в публичных проектах.
И ещё один важный момент: все секреты (ключи, токены) хранятся не в файле, а в настройках проекта (Settings → CI/CD → Variables). Там же ставим галочку «Маскировать переменную» — тогда в логах они покажутся как ***.
Как достать версию с удалённого сервера
Первая задача — get_soft_version. Её цель простая: узнать, с какой версией ПО мы сейчас работаем.
get_soft_version:
stage: get_version
tags:
- docker
image: docker:25.0.4-alpine3.19
script:
- mkdir -p ~/.ssh && chmod 0700 ~/.ssh
- echo "$SSH_KEY_DEV" > ~/.ssh/id_rsa
- echo "$known_hosts" > ~/.ssh/known_hosts
- chmod 0600 ~/.ssh/id_rsa
- apk add bash postgresql-client
- ssh dev-docker@$SERVER_IP_KPPO1 date
- docker context create remote --docker "host=ssh://dev-docker@$SERVER_IP_KPPO1"
- mkdir ./VER
- chmod +x ./version.sh
- ./version.sh
- cp ./.properties ./VER/.properties
artifacts:
paths:
- ./VER
Что здесь происходит:
-
Настраиваем SSH — создаём папку, копируем ключ из переменной окружения, ставим правильные права (
chmod 0600— без этого SSH откажется работать). -
Проверяем соединение простой командой
date— если сработало, значит ключ живой. -
Создаём удалённый докер-контекст. Это хитрый трюк: теперь все команды
dockerбудут лететь не на локальный раннер, а на удалённый сервер. -
Запускаем скрипт
version.sh, который определяет версию и пишет её в файл.properties. -
Копируем результат в папку
VERи сохраняем как артефакт.
Последний пункт критически важен. Без artifacts следующие задачи ничего не увидят — каждая задача живёт в своём изолированном контейнере. expire_in: 1 day ставлю, чтобы не засорять хранилище старыми версиями.
Тесты: не бойтесь упавших проверок
Вот тут начинается самое интересное — запуск тестов через Playwright:
docker_job:
stage: testing
tags:
- docker
image: registry.pdc.local.rielta/.../playwright:v1.50.0-noble-py3.12
before_script:
- source /venv/bin/activate
script:
- update-ca-certificates
- mkdir ./allure-current-results
- pytest -v --maxfail=100 --alluredir=./allure-current-results --browser firefox
allow_failure: true
artifacts:
when: always
paths:
- ./allure-current-results
expire_in: 1 day
Несколько моментов, на которые стоит обратить внимание:
Образ подобран под задачу. Вместо базового образа с питоном я использую специальный образ с предустановленным Playwright, браузерами и всеми зависимостями. Это экономит время и гарантирует воспроизводимость — тесты бегут в одинаковой среде каждый раз.
allow_failure: true — мой любимый трюк. Многие думают, что пайплайн должен быть всегда зелёным. Но на практике важнее получить отчёт, даже если тесты упали. С этой директивой пайплайн продолжит работу: соберёт историю, сгенерирует отчёт и опубликует его. Разработчик увидит не просто «упало», а конкретные скриншоты, логи и шаги воспроизведения. Это экономит кучу времени на отладку.
Артефакты сохраняются всегда (when: always). Даже проваленные тесты дают полезные данные — их тоже нужно сохранить.
История и отчёты: как не потерять контекст
Подтягиваем историю прошлых запусков
history_job:
stage: history_copy
tags:
- docker
image: storytel/alpine-bash-curl
script:
- curl -k --output ./artifacts.zip --header "PRIVATE-TOKEN:$TOKEN_ISSUE"
"https://gitlab.pdc.local.rielta/api/v4/projects/439/jobs/artifacts/master/download?job=pages"
- apk add unzip
- unzip ./artifacts.zip
- cp -r ./public/history/* ./allure-previous-results
allow_failure: true
artifacts:
paths:
- ./allure-previous-results
expire_in: 1 day
needs:
- docker_job
Зачем это нужно? Чтобы в отчёте видеть тренды: улучшается качество или, наоборот, появляются регрессии. Без истории ты видишь только текущий срез — а это как лечить пациента без истории болезни.
Команда curl качает артефакты из последнего успешного пайплайна на ветке master. Обрати внимание на параметр ?job=pages — он указывает, из какой именно задачи брать артефакты.
allow_failure: true здесь тоже критичен: при первом запуске истории ещё нет, и пайплайн не должен падать из-за этого.
Генерируем красивый отчёт в Allure
allure_job:
stage: reports
tags:
- docker
image: frankescobar/allure-docker-service:2.21.0-amd64
script:
- mkdir ./allure-results
- mkdir ./allure-results/history
- cp -r ./allure-previous-results/* ./allure-results/history || true
- cp -r ./allure-current-results/* ./allure-results
- cp ./VER/.properties ./allure-results/environment.properties
- allure generate -c ./allure-results -o ./allure-report
artifacts:
paths:
- ./allure-report
expire_in: 1 day
needs:
- job: docker_job
artifacts: true
- job: history_job
artifacts: true
- job: get_soft_version
artifacts: true
Самый вкусный момент — копирование .properties в environment.properties:
cp ./VER/.properties ./allure-results/environment.properties
Allure автоматически подхватывает этот файл и показывает информацию об окружении прямо в интерфейсе отчёта. Теперь любой разработчик сразу видит: «Ага, эти тесты прогонялись на версии 2.3.1 от 15 февраля».
Флаг artifacts: true в секции needs гарантирует, что все необходимые файлы подтянутся из предыдущих задач. Без него пайплайн сломается — он не будет знать, откуда брать данные.
Финальный аккорд: публикация и уведомления
Последняя задача — pages — делает две вещи одновременно.
Публикуем отчёт на GitLab Pages
GitLab Pages ищет контент только в папке public. Поэтому просто переносим туда сгенерированный отчёт:
mkdir public
mv ./allure-report/* public
Готово — отчёт доступен по адресу вида https://ваш-проект.pages.gitlab.io.
Создаём задачу с результатами
А ещё автоматически создаём задачу в трекере:
curl -k -X POST
-H "PRIVATE-TOKEN:$TOKEN_ISSUE"
-H "Content-Type: application/json"
--data '{
"title": "Тестирование сервиса '"$SERVICE_NAME"' (версия '"$VERSION"')",
"description": "'"$(tr 'n' ' ' < ./VER/.properties)"'",
"labels": ["test"]
}'
"https://gitlab.***.local.rielta/api/v4/projects/439/issues"
После каждого запуска пайплайна команда получает уведомление с:
-
Ссылкой на подробный отчёт
-
Версией протестированного сервиса
-
Статистикой по тестам
Это сильно повышает прозрачность процесса — теперь все видят, что тесты вообще запускались, и могут быстро оценить результат.
Что я вынес из этого опыта
Артефакты — не опция, а основа
Без правильной работы с артефактами пайплайн превращается в набор изолированных островков. Каждая задача должна чётко понимать: что она получает от предыдущих этапов и что передаёт следующим.
Не бойтесь allow_failure
Раньше я тоже думал, что упавший пайплайн — это провал. Но на практике часто важнее получить информацию об ошибке, чем просто «красный крестик». Особенно когда речь идёт о тестах — упавший тест с подробным отчётом ценнее, чем зелёный статус без контекста.
Версионируйте образы
Мой образ playwright:v1.50.0-noble-py3.12 — это не прихоть. Когда образ версионирован, ты точно знаешь, в какой среде бегут тесты. Никаких сюрпризов вроде «вчера работало, сегодня сломалось» из-за обновления базового образа.
Держите секреты в секрете
Никогда не храните ключи и токены в коде. Даже если репозиторий приватный — рано или поздно кто-то скопирует код, сделает форк или случайно зальёт в публичный гит. Переменные в настройках проекта — единственный правильный путь.
Итог
Когда начинал разбираться с GitLab CI/CD, думал, что это какая-то магия. Но на самом деле — просто набор понятных концепций, которые логично складываются вместе. Главное — не бояться экспериментировать, читать логи и понимать, что за каждой директивой стоит простая идея: сделать жизнь разработчика чуть проще.
И помните: даже самый сложный пайплайн начинался с одной простой задачи — «а давайте автоматизируем запуск тестов». Остальное пришло со временем.
Автор: Verstuk
