Build eșuat fără loguri: cum obții output
Un build eșuat fără loguri înseamnă că eroarea s-a produs înainte ca build-ul să înceapă. Află care sunt cele patru etape în care se poate întâmpla, cum să le diferențiezi și cum să obții output din fiecare.
„Build eșuat.” Fără stack trace, fără eroare de compilator, fără niciun output. Un build eșuat fără loguri este cel mai puțin util mesaj pe care îl poate produce o platformă și, de obicei, indică ceva concret, care merită înțeles: eroarea s-a produs înainte ca procesul care generează loguri să înceapă.
Un build nu este un singur pas. Sunt patru, iar fiecare eșuează în mod diferit.
Cele patru etape
1. Preluarea codului sursă. Platforma clonează repository-ul la un ref. 2. Pregătirea build-ului. Determină cum să facă build-ul — Dockerfile, buildpack sau un framework detectat automat. 3. Rularea build-ului. Sunt executate comenzile tale. Aceasta este singura etapă care produce output-ul pe care îl aștepți. 4. Împachetarea. Rezultatul este transformat într-o imagine care poate fi rulată.
Dacă nu ai deloc loguri, eroarea s-a produs în etapa 1 sau 2. Build-ul tău nu a rulat niciodată, deci nu avea cum să afișeze ceva.
Etapa 1: codul tău nu a fost preluat
Simptomele sunt tăcerea totală și o eroare rapidă — de obicei în mai puțin de cincisprezece secunde.
Cauze frecvente, în ordinea probabilității:
- Branch-ul nu există. Un serviciu configurat să facă deploy pe
masterîn cazul unui repository redenumit înmain. Eșecul apare instantaneu și mesajul oferă foarte puține informații. - Accesul a fost revocat. Tokenul sau instalarea aplicației care funcționa luna trecută a fost eliminată ori repository-ul a fost mutat într-o organizație în care permisiunea nu se mai aplică.
- Repository-ul este privat, iar conexiunea a expirat. Situația este similară cu cea de mai sus; platforma primește un 404, nu un 403, deoarece acesta este răspunsul furnizat de Git providers pentru repository-urile private pe care nu le poți vedea.
- Un submodul nu poate fi preluat. Repository-ul principal este clonat, dar un submodul care folosește un URL SSH eșuează, deoarece mediul de build nu are cheia necesară.
Verificarea rapidă: platforma afișează un hash de commit pentru deployment-ul eșuat? Dacă nu, codul nu a fost preluat niciodată, iar nimic din Dockerfile nu este relevant.
Etapa 2: nu știe cum să facă build-ul
Și aici totul este tăcut, deoarece încă nu a fost aleasă nicio comandă de build.
- Nu există niciun Dockerfile în locația configurată. Un
dockerfilePathindică un path care a fost mutat. - Un monorepo fără root. Platforma verifică root-ul repository-ului, iar serviciul tău se află în
apps/api. - Detectarea nu a găsit nimic. Nu există niciun manifest recunoscut, așa că niciun buildpack nu s-a potrivit.
- Un Dockerfile care nu poate fi interpretat. O eroare de sintaxă pe linia 1 produce eșecul înainte ca orice layer să ruleze.
Etapa 3: aici există loguri
Dacă vezi output parțial care se oprește brusc, ești în etapa 3, iar cele mai frecvente două cauze țin de resurse, nu de cod:
Memorie insuficientă. Un build oprit de OOM reaper nu apucă să afișeze motivul. Logul se oprește pur și simplu la mijlocul unui pas. Build-urile TypeScript, webpack și Vite pe codebase-uri mari ajung frecvent în această situație, iar indiciul este că același commit se construiește fără probleme pe laptopul tău, care are mai multă memorie decât builder-ul.
Timeout. Un build care depășește limita platformei este oprit. Simptomul este același: output-ul se oprește, în loc să se încheie.
Ambele situații arată ca un „build fără loguri” dacă eroarea apare suficient de devreme.
Etapa 4: build-ul a reușit, dar nu poate fi împachetat
O situație rară și specifică: build-ul a reușit, dar artefactul nu este corect. De exemplu, o imagine fără CMD sau ENTRYPOINT, o incompatibilitate de arhitectură ori o imagine prea mare pentru limita platformei.
Ordinea diagnosticării
# 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 din ultimul output este cea mai rapidă metodă de a localiza eroarea. Un deployment care a petrecut 0,4 secunde la clonare și apoi s-a încheiat cu eșec a avut o problemă în etapa 1. Unul care a petrecut nouăzeci de secunde la build și apoi s-a oprit are o problemă în etapa 3, cel mai probabil legată de memorie.
Cum obții output când nu există
Trei tehnici, în ordinea efortului necesar:
Reproduce local aceeași limitare. Nu „se construiește pe mașina mea” — construiește-l folosind aceeași cantitate de memorie pe care o are builder-ul:
docker build --memory=2g --memory-swap=2g -t test .
Dacă reproduci eroarea, ai găsit cauza, iar aceasta ține de memorie, nu de ceva misterios.
Fă build-ul mai verbose. Majoritatea build tool-urilor sunt implicit tăcute în privința lucrului care urmează să le oprească.
# 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
Linia cu NODE_OPTIONS merită încercată separat — un build Node care moare fără zgomot se lovește foarte des de limita heap-ului, iar creșterea acesteia repară build-uri care nu au produs niciun diagnostic.
Fă bisect pe Dockerfile. Comentează tot ce urmează după pasul care eșuează și adaugă marcaje RUN echo "reached step N". Este o metodă rudimentară, dar funcționează când nimic altceva nu dă rezultate.
Ce reduce acest tip de problemă
Două lucruri sunt mai importante decât orice tehnică de depanare.
Loguri transmise în timp real, nu loguri rezumate. Dacă output-ul apare doar după terminarea build-ului, un build oprit nu produce nimic, deoarece sumarul este scris la final. Transmiterea în timp real înseamnă că logul disponibil în momentul opririi conține tot ce s-a întâmplat până atunci.
dockup logs my-project/my-api --build --follow
Etape denumite și cronometrate. „Build eșuat” reprezintă un singur bit de informație. „Clone: 0,4 s, build: eșuat după 94 s” este suficient pentru a elimina trei dintre cele patru cauze de mai sus fără să mai citești nimic.
Întrebări frecvente
De ce build-ul meu nu produce deloc loguri? Pentru că a eșuat înainte ca comenzile tale de build să ruleze — de obicei la preluarea codului sursă sau la determinarea modului de build. Niciuna dintre aceste etape nu produce output de build.
De ce build-ul reușește local, dar nu pe platformă?
Cel mai adesea, cauza este memoria. Mașina ta are mai multă memorie decât builder-ul. Reproduce cu docker build --memory=2g pentru confirmare, înainte să cauți în altă parte.
Ce înseamnă un log care se oprește la mijlocul unui pas? Procesul a fost oprit, nu s-a încheiat normal. Lipsa memoriei sau un timeout de build sunt cele două posibilități, iar OOM killer nu îi oferă procesului șansa să explice ce s-a întâmplat.
Am nevoie de un Dockerfile? Nu neapărat — platformele pot detecta tipuri comune de proiecte și pot face build fără unul. Însă eșecul detectării este, la rândul său, un eșec tăcut, fără loguri, așa că un Dockerfile explicit elimină o întreagă categorie de ambiguități.
