Отладка пайплайна: типичные ошибки
Разбираем самые частые причины красных пайплайнов и как их быстро находить.
Отладка пайплайна — поиск причины, по которой джоба не запустилась, зависла или упала, по статусу, логам и конфигурации.
Рано или поздно красный пайплайн встретит каждый: это штатная часть работы с CI. Важно не то, что пайплайн падает, а сколько времени уходит на поиск причины. Опытный инженер не «всматривается» в полотно лога — он действует по протоколу: сначала смотрит статус джобы, потом этап, на котором всё сломалось, и только затем читает строки. Эта дисциплина экономит часы, потому что 90% инцидентов укладываются в несколько повторяющихся сценариев.
Главная ловушка новичка — начинать отладку с конца, с последней красной строки лога, тогда как реальная причина может лежать в самом начале: джоба вообще не получила раннер, или пайплайн не создался из-за YAML. Поэтому правило номер ноль звучит так: сначала пойми, на каком уровне отказ — пайплайн не создан, джоба не стартовала или джоба упала в процессе. Каждому уровню соответствует свой набор причин и свой инструмент диагностики; ниже разберём их по порядку, от самых «ранних» к самым «поздним».
Джоба висит в pending
Если джоба не стартует, дело почти всегда в раннере: нет онлайн-раннера, не совпали tags, исчерпаны минуты shared-раннеров или раннер не принимает джобы без тегов. Проверьте Settings → CI/CD → Runners: есть ли активный раннер с нужными тегами. Это причина номер один зависших пайплайнов.
Разберём механику теговой системы, потому что именно она генерирует львиную долю pending-инцидентов. Когда вы пишете джобе tags: [docker, linux], GitLab ищет раннер, у которого есть оба этих тега. Если ни один раннер не подходит, джоба будет ждать вечно — никакой ошибки не возникнет, статус просто застынет на pending. Зеркальная проблема: специфичные раннеры по умолчанию настроены брать только тегированные джобы, поэтому джоба без тегов может зависнуть рядом с вполне живым раннером. Включите для раннера опцию «Run untagged jobs» или явно проставьте теги — и затор рассосётся.
Вторая частая причина — исчерпание минут: на SaaS-инстансе у бесплатного тарифа есть месячный лимит compute-минут, и когда он кончается, джобы встают в pending без понятного объяснения (признак — пайплайн зелёный в начале месяца и стабильно pending к концу). Полезно держать в голове короткий чек-лист pending-диагностики.
pending? проверь по порядку: 1) есть ли вообще онлайн-раннер? (Settings -> CI/CD -> Runners) 2) совпадают ли tags джобы и раннера? 3) берёт ли раннер untagged-джобы? 4) не кончились ли compute-минуты? 5) не упёрлись ли в лимит concurrent?
Ошибка синтаксиса YAML
YAML чувствителен к отступам (только пробелы, не табы) и структуре. Один лишний пробел — и пайплайн вообще не создастся с ошибкой invalid YAML. Лучшая профилактика — встроенный редактор CI/CD → Editor: он валидирует файл и показывает merged YAML. Не редактируйте сложный пайплайн вслепую.
Отдельно стоит понимать разницу между двумя классами YAML-ошибок. Первый — синтаксический: сломан сам формат (таб вместо пробела, незакрытая кавычка, неверный отступ списка). Такой файл GitLab отвергает целиком, и в интерфейсе вы увидите красную плашку «pipeline failed to create» ещё до запуска любой джобы. Второй класс — семантический: формат корректен, но конфигурация бессмысленна (джоба ссылается через needs на несуществующую джобу, stage не объявлен в списке stages, два ключа конфликтуют). Эти ошибки ловит именно линтер редактора, и они коварнее, потому что глазами файл выглядит «нормальным».
Практический приём: для сложных пайплайнов используйте вкладку Full configuration (merged YAML) — она разворачивает все include, extends и якоря и показывает, как джоба выглядит после всех наследований. Очень часто баг прячется в том, что унаследованное значение перекрыло ваше или, наоборот, не применилось.
Ошибки прав и доступа
Деплой-джоба упала с permission denied? Частые причины: protected-переменная недоступна на незащищённой ветке, у токена недостаточно прав, неверный kubeconfig. Помните, что protected-секреты приходят только в пайплайны защищённых веток — на feature-ветке их просто нет, и команда логина падает.
Этот сценарий стоит прочувствовать, потому что он порождает самый запутанный симптом: «джоба падает в feature-ветке, но работает в main». Причина не в коде и не в раннере, а в модели безопасности GitLab. Protected-переменные умышленно не передаются в пайплайны незащищённых веток — это защита от того, чтобы автор форка не вытащил продакшен-секрет через echo $SECRET. Поэтому «точно заданная» переменная в feature-ветке оказывается пустой строкой, и команда логина в реестр или кластер падает с отказом доступа.
Диагностика проста: напечатайте длину секрета (echo ${#TOKEN}), но никогда само значение. Длина ноль на feature-ветке и ненулевая в main — диагноз поставлен, дело в protected. Лечение зависит от намерения: либо джоба и не должна деплоить с feature-веток (ограничьте через rules), либо переменную надо открыть шире (снять флаг protected, осознавая риск).
Проблемы кеша
Симптомы: «работает локально, падает на CI» или старые версии зависимостей. Часто виноват кеш с плохим ключом — он не инвалидируется при смене пакетов. Способ диагностики: временно отключить кеш и проверить, что джоба зеленеет на чистом окружении. Если да — проблема в ключе кеша.
Кеш — двуликий инструмент: он ускоряет пайплайн, но именно он чаще всего делает падения невоспроизводимыми. Корень зла — статический ключ. Если ключ кеша не зависит от lock-файла, то при обновлении зависимостей GitLab подложит в джобу старый node_modules, в котором уже нет новых пакетов или, наоборот, остались удалённые. Получается «призрачное» состояние: ни ваш коммит, ни чистая установка такого не дадут, а CI стабильно падает. Поэтому правильный ключ — это хеш lock-файла: меняется зависимость → меняется ключ → кеш пересобирается с нуля.
Золотое правило отладки кеша: кеш не должен быть источником истины. Любая джоба обязана работать и при полностью пустом кеше — он лишь ускоряет, но не «дополняет» окружение. Быстрый тест — кнопка Clear runner caches или временное переименование ключа: если после очистки джоба упала, значит, она нелегально зависела от мусора в кеше.
Чтение логов
Лог джобы — главный источник истины. Ищите первую красную строку: остальное обычно следствие. Полезные приёмы: добавить set -x в начало script, чтобы видеть каждую выполняемую команду, и временно вставить echo переменных (не секретных!), чтобы проверить, какие значения реально пришли.
debug-job:
script:
- set -x # печатать каждую команду
- echo "branch=$CI_COMMIT_BRANCH source=$CI_PIPELINE_SOURCE"
- ./build.shПри чтении лога держите в голове разницу между симптомом и причиной. Последние строки лога — это почти всегда симптом: упавший процесс, ненулевой код возврата, оборванный вывод. Причина же — первая аномалия сверху: команда, которая не нашла файл, переменная, которая пришла пустой, шаг установки, который тихо предупредил об ошибке и поехал дальше. Привычка читать лог сверху вниз, а не снизу вверх, отделяет десятиминутную отладку от двухчасовой. Когда лог слишком многословен, помогает временно поднять детализацию точечно: обернуть в трассировку только подозрительный кусок, а для переменных печатать не значение, а его признаки — длину, факт пустоты, — чтобы не утопить секрет в логе.
Как работает под капотом
GitLab разделяет два момента отказа: создание пайплайна (ошибки YAML, правила, отсутствие джоб) и выполнение джобы (код, права, окружение). Первый виден сразу при push как «pipeline failed to create». Второй — это ненулевой код возврата команды внутри джобы. Понимание, на каком этапе проблема, сужает поиск вдвое: либо чините конфиг, либо — содержимое команд.
Углубим эту модель до трёх стадий, потому что между «созданием» и «выполнением» есть промежуточная — постановка в очередь. Сначала GitLab валидирует YAML и решает, какие джобы создавать (тут отрабатывают rules). Затем созданные джобы ждут раннера — это стадия pending, где живут теги и минуты. И только потом раннер разворачивает контейнер, восстанавливает кеш, клонирует репозиторий и запускает script. Каждая красная джоба «сломалась» ровно на одной из этих стадий, и определение стадии — половина отладки.
Частые ошибки
- Долго искать баг в логах, тогда как джоба вообще не стартовала (pending) — сначала смотрите статус и раннеры.
- Редактировать YAML без валидатора и ловить ошибки структуры на проде.
- Печатать секреты в
echoдля отладки — они утекут в лог (даже masking не панацея). - Читать лог снизу вверх и чинить симптом вместо первой красной строки сверху.
Итоги
- Pending → проблема с раннером (онлайн, теги, минуты); failed → проблема в команде или правах.
- YAML чините через встроенный редактор с валидацией и merged-просмотром.
set -xи аккуратныеecho(без секретов) ускоряют диагностику логов.- «Падает на feature, работает на main» — почти всегда protected-переменные; кеш только ускоряет.