Indeks dziennikaDockup / notatka terenowa
Note / build-fails-with-no-logs

Build zakończony niepowodzeniem bez logów: jak uzyskać dane wyjściowe

Build zakończony niepowodzeniem bez logów oznacza, że awaria wystąpiła, zanim rozpoczął się właściwy build. Poznaj cztery etapy, na których może do tego dojść, dowiedz się, jak je odróżnić i jak uzyskać dane wyjściowe z każdego z nich.

„Build zakończony niepowodzeniem”. Bez stack trace, bez błędu kompilatora, bez żadnych danych wyjściowych. Build zakończony niepowodzeniem bez logów to najmniej pomocny komunikat, jaki może wygenerować platforma. Zwykle oznacza on coś konkretnego i wartego zrozumienia: awaria nastąpiła zanim uruchomił się element generujący logi.

Build nie jest jednym krokiem. Składa się z czterech etapów, a awarie na każdym z nich wyglądają inaczej.

Cztery etapy

1. Pobieranie kodu źródłowego. Platforma klonuje repozytorium z użyciem określonego ref. 2. Przygotowanie builda. Ustala, jak wykonać build — na podstawie Dockerfile, buildpacka lub wykrytego frameworka. 3. Uruchamianie builda. Wykonywane są Twoje polecenia. To jedyny etap, który generuje oczekiwane dane wyjściowe. 4. Pakowanie. Wynik zostaje przekształcony w obraz gotowy do uruchomienia.

Jeśli nie masz absolutnie żadnych logów, awaria wystąpiła na etapie 1 lub 2. Twój build w ogóle się nie uruchomił, więc nie mógł niczego wypisać.

Etap 1: kod nie został pobrany

Objawy to całkowita cisza i szybka awaria — zwykle w czasie krótszym niż piętnaście sekund.

Najczęstsze przyczyny, w kolejności:

  • Branch nie istnieje. Usługa jest skonfigurowana do wdrażania master, ale repozytorium zmieniło nazwę na main. Taka sytuacja kończy się natychmiast i zwykle nie daje praktycznie żadnych informacji.
  • Cofnięto dostęp. Token lub instalacja aplikacji, które działały w zeszłym miesiącu, zostały usunięte albo repozytorium przeniesiono do organizacji, w której przyznane uprawnienie już nie obowiązuje.
  • Repozytorium jest prywatne, a połączenie wygasło. Sytuacja wygląda tak samo jak wyżej — platforma otrzymuje odpowiedź 404 zamiast 403, ponieważ właśnie tak dostawcy Git odpowiadają w przypadku prywatnych repozytoriów, których nie możesz zobaczyć.
  • Nie można pobrać submodułu. Główne repozytorium zostaje sklonowane, ale submoduł korzystający z adresu SSH kończy się błędem, ponieważ środowisko builda nie ma odpowiedniego klucza.

Szybki test: czy platforma pokazuje hash commita dla nieudanego wdrożenia? Jeśli nie, kod nie został pobrany i nic w Twoim Dockerfile nie ma tu znaczenia.

Etap 2: platforma nie wie, jak wykonać build

Ten etap również przebiega bez logów, ponieważ nie wybrano jeszcze żadnego polecenia builda.

  • Brak Dockerfile w miejscu określonym w konfiguracji. dockerfilePath wskazuje ścieżkę, która została zmieniona.
  • Monorepo bez określonego katalogu głównego. Platforma sprawdza katalog główny repozytorium, a Twoja usługa znajduje się w apps/api.
  • Nie znaleziono niczego podczas wykrywania. Brak rozpoznanego manifestu, więc żaden buildpack nie został dopasowany.
  • Nie można sparsować Dockerfile. Błąd składni w pierwszym wierszu powoduje awarię, zanim zostanie uruchomiona jakakolwiek warstwa.

Etap 3: tutaj znajdziesz logi

Jeśli widzisz częściowe dane wyjściowe, które nagle się urywają, jesteś na etapie 3. Dwie najczęstsze przyczyny dotyczą zasobów, a nie kodu:

Brak pamięci. Build zakończony przez mechanizm OOM nie ma możliwości wyświetlenia informacji o przyczynie. Log po prostu urywa się w środku kroku. Buildy TypeScript, webpack i Vite w dużych codebase’ach regularnie napotykają ten problem. Wskazówką jest to, że ten sam commit działa poprawnie na Twoim laptopie, który ma więcej pamięci niż builder.

Przekroczenie limitu czasu. Build, który przekroczy limit platformy, zostaje zakończony. Objaw jest taki sam: dane wyjściowe urywają się, zamiast zakończyć się normalnie.

Obie sytuacje wyglądają jak „brak logów”, jeśli awaria nastąpi odpowiednio wcześnie.

Etap 4: build się powiódł, ale nie można go spakować

To rzadka i specyficzna sytuacja: build zakończył się powodzeniem, ale artefakt jest nieprawidłowy. Przyczyną może być obraz bez CMD lub ENTRYPOINT, niezgodność architektury albo obraz zbyt duży dla limitu platformy.

Kolejność diagnostyki

# 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 w ostatnim wyniku to najszybszy sposób na zlokalizowanie awarii. Wdrożenie, które spędziło 0,4 sekundy na klonowaniu, a następnie zakończyło się niepowodzeniem, zakończyło się na etapie 1. Jeśli build trwał dziewięćdziesiąt sekund, a potem się zatrzymał, problem dotyczy etapu 3 — najprawdopodobniej pamięci.

Jak uzyskać dane wyjściowe, gdy ich nie ma

Trzy techniki, uporządkowane według nakładu pracy:

Odtwórz lokalnie ograniczenia środowiska. Nie chodzi o pytanie „czy działa na moim komputerze” — wykonaj build z taką samą ilością pamięci, jaką ma builder:

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

Jeśli w ten sposób odtworzysz awarię, znajdziesz jej przyczynę — to problem z pamięcią, a nie coś tajemniczego.

Zwiększ szczegółowość logów builda. Większość narzędzi builda domyślnie nie informuje dokładnie o tym, co za chwilę doprowadzi do ich zakończenia.

# 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

Sama linia z NODE_OPTIONS jest warta wypróbowania — build Node, który kończy się bez komunikatu, bardzo często napotyka limit heapu, a jego zwiększenie naprawia buildy, które nie wygenerowały żadnych informacji diagnostycznych.

Wykonaj bisekcję Dockerfile. Zakomentuj wszystko po kroku, na którym występuje awaria, i dodaj znaczniki RUN echo "reached step N". To rozwiązanie może wydawać się prymitywne, ale działa, gdy zawiodą wszystkie inne metody.

Co ogranicza problemy tego typu

Dwie rzeczy mają większe znaczenie niż jakakolwiek technika debugowania.

Strumieniowanie logów zamiast ich podsumowywania. Jeśli dane wyjściowe pojawiają się dopiero po zakończeniu builda, build zakończony w trakcie działania nie wygeneruje niczego, ponieważ podsumowanie jest zapisywane na końcu. Strumieniowanie oznacza, że w momencie awarii masz log zawierający wszystko aż do chwili jej wystąpienia.

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

Etapy z nazwami i pomiarem czasu. „Build zakończony niepowodzeniem” to jedna informacja. „Klonowanie: 0,4 s, build: niepowodzenie po 94 s” wystarczy, aby pominąć trzy z czterech opisanych wyżej przyczyn, bez czytania czegokolwiek więcej.

Najczęściej zadawane pytania

Dlaczego mój build w ogóle nie generuje logów? Ponieważ awaria nastąpiła, zanim uruchomiły się polecenia builda — zwykle podczas pobierania kodu źródłowego albo ustalania sposobu wykonania builda. Żaden z tych etapów nie generuje danych wyjściowych builda.

Dlaczego lokalnie build działa, a na platformie nie? Najczęściej przyczyną jest pamięć. Twój komputer ma jej więcej niż builder. Odtwórz sytuację za pomocą docker build --memory=2g, aby to potwierdzić, zanim zaczniesz szukać gdzie indziej.

Co oznacza log, który urywa się w środku kroku? Proces został zakończony, zamiast zakończyć działanie w kontrolowany sposób. Dwie najbardziej prawdopodobne przyczyny to brak pamięci i przekroczenie limitu czasu builda. OOM killer nie daje procesowi możliwości wyjaśnienia, co się stało.

Czy potrzebuję Dockerfile? Niekoniecznie — platformy mogą wykrywać popularne typy projektów i wykonywać build bez niego. Jednak nieudane wykrywanie samo w sobie prowadzi do cichej awarii bez logów, dlatego jawny Dockerfile eliminuje całą klasę niejasności.