Индекс на дневникаDockup / бележка от практиката
Note / build-fails-with-no-logs

Неуспешен build без логове: как да получите изход

Когато build завърши неуспешно без логове, това означава, че проблемът е възникнал преди самият build да започне. Научете кои са четирите етапа, на които това може да се случи, как да ги различите и как да получите изход от всеки от тях.

„Build неуспешен.“ Няма stack trace, няма compiler error, няма никакъв изход. Неуспешен build без логове е най-малко полезното съобщение, което една платформа може да покаже, и обикновено означава нещо конкретно, което си струва да разберете: проблемът е възникнал преди да стартира процесът, който генерира логове.

Build не е една стъпка. Той се състои от четири, като всеки етап може да се провали по различен начин.

Четирите етапа

1. Извличане на source кода. Платформата клонира repository-то ви при даден ref. 2. Подготовка на build-а. Тя определя как да извърши build-а — Dockerfile, buildpack или разпозната framework. 3. Изпълнение на build-а. Вашите команди се изпълняват. Това е единственият етап, който генерира изхода, който очаквате. 4. Packaging. Резултатът се превръща в runnable image.

Ако изобщо нямате логове, проблемът е възникнал в етап 1 или 2. Вашият build не е стартирал и следователно не е могъл да изведе нищо.

Етап 1: кодът ви изобщо не е получен

Симптомите са пълна тишина и бърз failure — обикновено за по-малко от петнадесет секунди.

Често срещани причини, подредени по вероятност:

  • Branch-ът не съществува. Service, конфигуриран да deploy-ва master, към repository, което е преименувано на main. Това се проваля незабавно и почти не показва информация.
  • Достъпът е отнет. Token-ът или app installation-ът, който е работил миналия месец, е премахнат, или repository-то е преместено в organisation, за която grant-ът вече не важи.
  • Repository-то е private и връзката е прекъсната. Случаят е същият като по-горе; платформата получава 404 вместо 403, защото така Git providers отговарят за private repository-та, които нямате право да виждате.
  • Submodule не може да бъде извлечен. Основното repository се клонира, но submodule, който използва SSH URL, се проваля, защото build environment-ът няма ключ за него.

Бързата проверка: показва ли платформата commit hash за неуспешния deployment? Ако не го показва, кодът изобщо не е бил получен и нищо във вашия Dockerfile няма отношение към проблема.

Етап 2: платформата не знае как да извърши build

Тук също няма изход, защото все още не е избрана build команда.

  • Няма Dockerfile на посоченото място. dockerfilePath сочи към път, който е бил променен.
  • Monorepo без root. Платформата проверява root директорията на repository-то, а вашият service се намира в apps/api.
  • При detection не е намерено нищо. Няма разпознат manifest, затова не е намерен подходящ buildpack.
  • Dockerfile не може да бъде parse-нат. Syntax error на първия ред води до failure, преди да е стартирал който и да е layer.

Етап 3: тук има логове

Ако виждате частичен изход, който внезапно спира, се намирате в етап 3 и двете най-чести причини са свързани с resources, а не с кода:

Недостатъчно memory. Build, прекратен от OOM reaper-а, не успява да изведе информация за причината. Логът просто спира по средата на стъпката. Build-ове с TypeScript, webpack и Vite върху големи codebase-и често се сблъскват с това, а показателният признак е, че същият commit се build-ва успешно на вашия laptop, който разполага с повече memory от builder-а.

Timeout. Build, който надвиши лимита на платформата, се прекратява. Симптомът е същият: изходът спира, вместо да завърши.

И двете причини изглеждат като „няма логове“, ако failure-ът настъпи достатъчно рано.

Етап 4: build-ът е успешен, но не може да бъде package-нат

Рядък и специфичен случай: build-ът е завършил успешно, но artefact-ът е неправилен. Image без CMD или ENTRYPOINT, несъответствие в architecture-та или image, който е прекалено голям за лимита на платформата.

Ред на диагностика

# Is there a commit hash? If not, stage 1.
dockup deployments my-project/my-api --json

# Build logs of the latest deployment, streamed as it goes
dockup logs my-project/my-api --build --follow

# The full record, including which stage took how long
dockup status my-project/my-api --json

stageTimings в последния изход е най-бързият начин да локализирате проблема. Deployment, който е прекарал 0.4 секунди в clone и след това е завършил с failure на етап 1. Ако build-ът е продължил деветдесет секунди и след това е спрял, проблемът е в етап 3, най-вероятно заради memory.

Как да получите изход, когато няма такъв

Три техники, подредени според необходимото усилие:

Възпроизведете ограничението локално. Не „build-ва ли се на моята машина“ — build-вайте със същото количество memory, с което разполага builder-ът:

docker build --memory=2g --memory-swap=2g -t test .

Ако това възпроизвежда failure-а, открили сте причината и тя е свързана с memory, а не с нещо мистериозно.

Направете build-а си по-подробен. Повечето build tools по подразбиране не дават достатъчно информация за проблема, който предстои да ги прекрати.

# Print progress so a truncated log still shows where it stopped
RUN npm ci --loglevel verbose
RUN NODE_OPTIONS="--max-old-space-size=3072" npm run build

Този ред с NODE_OPTIONS си струва да бъде изпробван самостоятелно — Node build, който спира безшумно, много често е ограничен от heap лимита, а увеличаването му решава build-ове, за които изобщо не е изведена диагностична информация.

Използвайте bisect на Dockerfile. Закоментирайте всичко след проблемната стъпка и добавете RUN echo "reached step N" markers. Груб подход, но работи, когато нищо друго не помага.

Как да намалите вероятността за този тип проблеми

Две неща са по-важни от всяка debugging техника.

Стриймвайте логовете вместо да използвате обобщени логове. Ако изходът се показва едва след приключването на build-а, build, който е прекратен, не показва нищо, защото summary-то се записва накрая. При streaming разполагате с лог до самия момент на прекратяването.

dockup logs my-project/my-api --build --follow

Използвайте етапи с имена и измерено време. „Build неуспешен“ е един bit информация. „Clone: 0.4s, build: failed after 94s“ е достатъчно, за да изключите три от четирите причини по-горе, без да четете нищо друго.

Често задавани въпроси

Защо build-ът ми изобщо не генерира логове? Защото failure-ът е настъпил, преди да се изпълнят build командите ви — обикновено при извличането на source кода или при определянето как да бъде извършен build-ът. Нито един от тези етапи не генерира build output.

Защо build-ът работи локално, но не и на платформата? Най-често причината е memory. Вашата машина разполага с повече memory от builder-а. Възпроизведете проблема с docker build --memory=2g, за да го потвърдите, преди да търсите другаде.

Какво означава лог, който спира по средата на стъпка? Процесът е бил прекратен, вместо да завърши нормално. Двете най-вероятни причини са недостатъчно memory или build timeout, а OOM killer-ът не дава на процеса възможност да обясни какво се е случило.

Нужен ли ми е Dockerfile? Не непременно — платформите могат да разпознават често срещани типове проекти и да извършват build без него. Но неуспешният detection сам по себе си е безшумен failure без логове, затова явният Dockerfile премахва цял клас неясноти.