С чего начать разбираться в этом всём
Когда я впервые увидел .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, думал, что это какая-то магия. Но на самом деле — просто набор понятных концепций, которые логично складываются вместе. Главное — не бояться экспериментировать, читать логи и понимать, что за каждой директивой стоит простая идея: сделать жизнь разработчика чуть проще.
И помните: даже самый сложный пайплайн начинался с одной простой задачи — «а давайте автоматизируем запуск тестов». Остальное пришло со временем.
