Kiedy coś nie działa
Awarie, które naprawdę się zdarzyły, a większość z nich wygląda na inny problem, niż jest.
Server nie startuje
The database has N pending migration(s)
Schemat jest za obrazem, a temu Serverowi nie powiedziano, że wolno mu to
naprawić. Ustaw MIGRATE_ON_START=true w .env — po zrobieniu kopii — albo
zastosuj migracje samodzielnie przed startem.
To mówi też świeża instalacja, jeśli przełącznik jest wyłączony: pusta baza ma wszystkie migracje oczekujące.
Kończy pracę, narzekając na Forwarded
TRUSTED_PROXY_NETWORKS jest nieustawione. Server odmawia startu, dopóki nie
wskażesz mu, czyim informacjom o adresie odwiedzającego ma wierzyć. none to
pełna odpowiedź dla Servera osiąganego bezpośrednio.
Nazywa sieć, której nie napisałeś
Napisałeś CIDR z ustawionymi bitami hosta — 172.28.0.5/24. .NET normalizuje to
do 172.28.0.0/24 bez słowa, co zamienia „jedną maszynę” w „całą pracownię”,
więc Server odmawia i nazywa adres, który powinieneś był napisać.
preflight.sh łapie to wcześniej.
No storage is configured
STORAGE_KIND jest puste albo z literówką. Wartości to postgres, filesystem
albo s3.
Nazywa ustawienie magazynu, które jest puste
filesystem potrzebuje STORAGE_PATH; s3 potrzebuje STORAGE_ENDPOINT,
STORAGE_BUCKET, STORAGE_ACCESS_KEY i STORAGE_SECRET_KEY — wszystkich
czterech. Server odmawia startu, nazywając to, którego mu zabrakło, a
preflight.sh odmawia jeszcze wcześniej. Przy STORAGE_KIND=postgres żadne
z nich nie jest czytane i dlatego wszystkie są puste.
API odpowiada 404, a jesteś pewien ścieżki
/health nie jest endpointem zdrowia. Jest nim /api/v1/health.
Każda instalacja serwuje API pod /api/v1, a Server odpowiada 404 w gołym
korzeniu celowo — żeby Client wskazany na złą ścieżkę został poprawiony,
a nie obsłużony. Monitor odpytujący /health zgłasza instalację jako
niedostępną, choć ta serwuje bez zarzutu.
Przez dołączony nginx jest gorzej
/health nie jest tam nawet 404. location / kieruje na Client, który dla
każdej nieznanej ścieżki serwuje index.html, więc curl https://twoja.domena/health zwraca 200 i stronę HTML. To router aplikacji,
nie API.
/admin odpowiada 404 mimo poprawnego tokenu
I tak ma być. /api/v1/admin/** wymaga, żeby żądanie dotarło na własny
interfejs pętli zwrotnej Servera, a:
- przez nginx dociera jako brama mostka — 404;
- przez opublikowane
127.0.0.1:8080też dociera jako brama mostka, bo tak działa opublikowany port — 404.
Każda odmowa to celowo to samo 404, więc nic nie odróżnia złego tokenu od złej trasy. Nie 403: 403 potwierdziłoby, że endpoint tam jest i że wywołujący trafił jedną z dwóch połówek.
Wchodzi się tak:
docker compose exec -T server aj-admin statusJeśli odpowie AJ_Admin__Token is not set in this container, token był pusty,
gdy kontener startował. Ustaw go w .env i odtwórz Server.
Drugą przyczynę zamknięcia tej powierzchni widać w innym komunikacie. Server wypisuje go w logu przy starcie:
AJ_Admin__Token is the well-known development token, so /admin is closed.To instalacja z wartością, którą dostarcza stos deweloperski,
admin-token-development-only — jest opublikowana, więc nie jest żadnym tokenem.
Wpisz do .env własny sekret i odtwórz Server.
preflight.sh odrzuca dokładnie tę wartość, więc stos podnoszony przez
make up nigdy nie dochodzi do tego komunikatu. Wymienienie jej wprost to
jedyny sposób, żeby ją wyłapać: jest dość długa, żeby przejść kontrolę długości.
Konsekwencje sięgają zresztą dalej niż samo hasło: scripts/maintenance.sh nie
potrafi ani otworzyć, ani zamknąć instalacji, której /admin jest zamknięty, co
zatrzymuje na tym kroku backup.sh --quiesce, update.sh i restore.sh.
Nikt nie może administrować instalacją
Zrestartuj Server. Przy każdym starcie sprawdza on, czy w instalacji jest
jeszcze aktywne nadanie systemowe z kluczem system:administrator —
czyjekolwiek, nie tylko na koncie zakładanym przy pierwszym starcie. Jeśli nie
ma żadnego, przywraca kontu admin uprawnienia z roli admin:
docker compose restart serverW logu pojawia się wtedy Nobody administered this installation, so admin has been granted system:administrator again. Its password is unchanged. W tym
ostatnim zdaniu mieści się całe zastrzeżenie: restart przywraca uprawnienia, a
nie hasło. Jeśli hasła też nikt nie zna, ustaw je poleceniem
aj-admin password.
Nadania zrobionego ręcznie Server nigdy nie nadpisze. Jeżeli konto admin
ma już własne nadanie systemowe, które nie daje praw administratora — przycięte
do krótszej listy albo przestawione na zaproszone — zostaje ono bez zmian, a
log mówi grant system:administrator by hand. To jedyny przypadek, którego
restart nie naprawia.
Dziś dojście do tego stanu wymaga już bazy starszej niż sama odmowa albo edycji
wprost w bazie: odebranie instalacji ostatniego nadania z
system:administrator — przez jego usunięcie, przepisanie bez tego klucza albo
przestawienie na zaproszone — kończy się odpowiedzią 403 z kodem
grant.administrator.last. aj-admin nie ma żadnego polecenia do nadań i
dlatego jedyną drogą jest restart.
Nic nie jest oceniane
Od najczęstszej przyczyny:
- Runner nie został zatwierdzony. Jego log mówi
waiting: this Runner has not been approved yeti będzie to mówił zawsze. Zatwierdź go w panelu pod Runnery. - Obrazy sandboxa nie istnieją. Runner szuka wkompilowanych domyślnych
algojudge/lang-*:local, które ma tylko host deweloperski.compose.yamlustawia wszystkie cztery zmienneAJ_Sandbox__Image__*zREGISTRYiRUNNER_TAG; jeśli je nadpisałeś, sprawdźdocker images. - Demon jest starszy niż Docker Engine 26. Paczka jest rozpakowywana we
wspólnym wolumenie Runnerów, a każdy kontener sędziego montuje stamtąd jej
podkatalog — montowaniem
subpath, które pojawiło się w Engine 26 / API 1.45 (kwiecień 2024) i w Podmanie 5. Runner odmawia oceniania i mówi o tym, zamiast oceniać na niczym../scripts/preflight.shodmawia wcześniej niż pierwsze zgłoszenie, adocker version --format '{{.Server.APIVersion}}'podaje tę liczbę. Zaktualizuj demona; poniżej tego progu nie ma rozwiązania zastępczego. - Wolumen pamięci podręcznej nie jest tym, o którym powiedziano Runnerowi. Każdy Runner dostaje nazwę wolumenu, o który ma poprosić demona, i kończy pracę przy starcie, gdy demon takiego wolumenu nie ma — jest to więc Runner, który nigdy się nie rejestruje, a nie taki, który źle ocenia. Dwa polecenia z wpisu Checker zawodzi na każdym zgłoszeniu poniżej wypisują to, co Runner dostał, i to, czym dysponuje demon.
- Tagi. Runner z ustawionym
RUNNER_TAGSwypada z puli ogólnej, a praca bez tagów idzie do puli ogólnej. Puste znaczy pulę ogólną po obu stronach. Tagi są czytane raz, przy pierwszej rejestracji — późniejsza zmiana zmiennej nie robi nic, a zmienia je ponowna rejestracja.
Każde zlecenie zawodzi z carries no /usr/local/bin/aj-shim
Runner mówi to o jednym z czterech obrazów językowych i ma rację: aj-shim jest
tym, co czyni oceniany przebieg mierzalnym, więc obraz sprzed jego powstania nie
oceni niczego.
Host ma starą kopię, a docker compose pull jej nie wymieni: obrazy językowe
to wartości przekazywane Runnerowi, a nie usługi, więc Compose ich nie dotyka.
./scripts/pull.sh
docker compose restart runner-1 # każdy runner-N, który uruchamiaszRestart to prosta odpowiedź i nigdy nie jest błędem
Runner pobiera swoje obrazy językowe przy starcie, więc restart jest też tym, co je przynosi.
Pobranie ręczne też działa: działający Runner przyjmuje podmieniony obraz bez restartu, bo to, co o obrazie pamięta, jest zapisane pod jego identyfikatorem, a pobrany obraz ma inny identyfikator.
Do 2026-09-16 ta strona mówiła coś przeciwnego — że restart jest konieczny, bo Runner bada każdy obraz raz i pamięta odpowiedź przez całe życie procesu. To było zmierzone i było prawdą o Runnerze, który zapisywał to pod nazwą obrazu. Klucz się od tego czasu zmienił.
Runner z 2026-09-16 lub nowszy w ogóle tego nie spotka: pobiera wszystkie
cztery przy starcie, bezwarunkowo, a jeśli nie może — kończy pracę zamiast się
zarejestrować. make up i scripts/update.sh też je pobierają, więc zwykle są
już właściwe, zanim Runner spojrzy.
Każde zlecenie zawodzi z predates the input arriving as a descriptor
Przyczyna i lekarstwo są te same co wyżej: pobierz cztery obrazy, a potem zrestartuj każdy Runner. Wejście testu nie jest montowane do kontenera — Runner trzyma je w pamięci i podaje shimowi deskryptor przez gniazdo — więc shim sprzed tej zmiany otworzyłby gniazdo tak, jakby było plikiem, i zgłosiłby przebieg bez pomiaru. Runner woli odmówić, bo to zdanie, na które operator może zareagować.
Test kończy się na nothing opened the run's output channel
test 03: nothing opened the run's output channel within 30sKontener, który miał ocenić ten test, nie wystartował. Runner czeka trzydzieści sekund, aż ktokolwiek otworzy wyjście przebiegu, i wtedy mówi o tym wprost, zamiast zgłosić pustą odpowiedź — a ta byłaby błędnym werdyktem przypisanym zgłoszeniu, które nigdy nie ruszyło. To awaria infrastruktury, więc zlecenie wraca do kolejki, zamiast upaść, aż wyczerpią się jego doręczenia.
Własny komunikat demona o kontenerze, którego nie dało się uruchomić, stoi w logu Runnera linię wyżej i zwykle nazywa przyczynę wprost:
docker compose logs runner-1 | tail -40Dwie przyczyny, od najczęstszej:
- Obraz językowy nie startuje na tym hoście.
docker imagespokazuje, czy wszystkie cztery są na miejscu; kontener, który ginie naOCI runtime create failed, nie otworzy niczego. - Katalog roboczy nie potrafi trzymać nazwanego potoku. Kanał to FIFO
i gniazdo uniksowe, a nie bajty, więc system plików, który nie tworzy ani
jednego, ani drugiego, kończy się tutaj. Zwykłą drogą do tego jest wpis
tmpfs:przy usłudze runnera: należy on do przestrzeni montowań samego Runnera, a demon robi obok własny pusty katalog.
Na Dockerze Desktop pamięć podręczna nie ma dobrego miejsca pod /var
/var w jego maszynie wirtualnej jest zamontowany jako noexec, więc checker
zbudowany do pamięci podręcznej w tym miejscu nie da się uruchomić, a jego
kontener ginie przy starcie — i to jest jedna z dróg, którymi test trafia tutaj.
Trzymaj pamięć podręczną w nazwanym wolumenie, bo to właśnie dostarcza ten stos.
Checker albo interactor, którego własnego kanału nikt nie otworzył, czyta się inaczej: wraca jako zepsuty checker, a nie z powyższym zdaniem. Przyczyna jest ta sama, a zaczynać należy od wpisu poniżej.
Checker zawodzi na każdym zgłoszeniu albo ocenia wobec pustki
Zgłoszenia do zadania, którego paczka niesie checker, wracają jako awarie infrastruktury — często opisane tak, jakby to checker autora się nie zbudował.
Pamięć podręczna nie jest tą, do której Runner zapisuje. Paczka jest rozpakowywana, a jej checker budowany raz, w pamięci podręcznej, i kontener sędziego montuje jedno i drugie — więc demon musi sięgnąć po dokładnie te bajty, które przygotował Runner.
Pamięcią podręczną jest wolumen algojudge_runner-cache, do którego demon sięga
po nazwie, więc nie ma tu czego pomylić; ten wpis dotyczy wtedy prawie zawsze
Runnera wskazanego ręcznie gdzie indziej.
Oba obrazy Runnerów są distroless, więc docker compose exec nie ma ani
env, ani powłoki, w której dałoby się je uruchomić. Odczytaj kontener
z zewnątrz:
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$(docker compose ps -q runner-1)" | grep AJ_Cache__
docker volume inspect algojudge_runner-cacheRunner, który w ogóle nie widzi swojej pamięci podręcznej, odmawia startu i
mówi o AJ_Cache__Volume. Ten wpis dotyczy przypadku węższego: pamięć podręczna
istnieje, ale nie jest tą, do której Runner zapisuje.
Nic zewnętrznego nie jest oceniane
Lista jest inna, bo zwykłe przyczyny tutaj nie występują. Dwie pierwsze opisuje Zewnętrzny sędzia i obie zostawiają pustą kolejkę przy kontenerze, który wygląda zdrowo: ocenianie zewnętrzne jest w tej instalacji wyłączone oraz zewnętrzny Runner nie został zatwierdzony.
Pozostałe trzy:
- Zadanie nie powstało jako zewnętrzne. Musi mieć typ archiwum i znacznik zewnętrznego; zadanie typowane lokalnie nigdy nie trafia do Runnera, który przekazuje dalej.
propswersji nie podaje numeru zadania w archiwum. To zostaje odrzucone po nazwie, zanim cokolwiek opuści tę instalację.- Tagi, dokładnie jak przy każdym Runnerze, czytane raz przy pierwszej rejestracji.
Runner restartuje się co kilka sekund
restart: unless-stopped zamienia każdą odmowę przy starcie w pętlę,
a preflight.sh większości z nich nie zobaczy, bo z konfiguracją wszystko jest
w porządku. Czytaj log:
docker compose logs external-runner | tail -20
docker compose logs runner-1 | tail -5Konto w systemie oceniającym jest puste albo błędne. Zewnętrzny Runner
odmawia już przy czytaniu konfiguracji — przed kluczem tożsamości, przed
rejestracją — i nazywa ustawienie. Przy włączonym profilu preflight.sh odmawia
wcześniej, więc stos podnoszony skryptami nigdy tu nie dochodzi. Spacja na
początku albo na końcu hasła nie jest przyczyną: hasło idzie dalej dokładnie
tak, jak je zapisano, i to celowo, bo obcięcie spacji raz już zepsuło logowanie.
the Server refused with 403: runner.nonce.unknown to inna usterka o tym
samym objawie i dotyczy obu Runnerów: ten obraz jest starszy niż Server, u
którego się rejestruje. Pojawia się dopiero od drugiego startu, bo to wolumen
tożsamości sprawia, że Server rozpoznaje klucz, a odmowy rejestracji Runner nie
ponawia. Pobieraj oba obrazy z tego samego wydania, zamiast pozwalać ruchomym
tagom się rozjechać — dlaczego muszą chodzić razem, mówi Aktualizacja i
wycofanie.
AJ_Runner__TestsAtOnce w treści odmowy należy do Runnera oceniającego: nie
wystartuje z większą liczbą torów, niż jego zbiór ma procesorów. Kryją się tu dwie
pułapki.
Szerokość to jedna wartość dla wszystkich Runnerów. RUNNER_TESTS_AT_ONCE
trafia do obu usług z tej samej kotwicy, więc jeden wąski RUNNER_2_CPUSET to
pętla na runner-2, podczas gdy runner-1 ocenia bez zarzutu — czytaj log
tego, który się zapętlił, a nie tego, który działa.
Runner bez cpusetu czyta całą maszynę, a oba zbiory dostarczamy puste.
Zostawienie pustego zbioru nie rozluźnia sprawdzenia: szerokość cztery na
maszynie o dwóch procesorach zostanie odrzucona, czy zbiór jest wąski, czy go
nie ma. preflight.sh dochodzi do obu odpowiedzi, zanim cokolwiek wystartuje:
odmawia szerokości większej niż liczba procesorów hosta, a w każdym zapisanym
cpusecie liczy procesory i zestawia je z szerokością.
Nazwane ustawienie odpytywania albo dzierżawy to czwarta przyczyna: Runner
sprawdza swoje odstępy nawzajem, zanim cokolwiek zrobi. Żadnej z nich nie da się
osiągnąć wartościami, które dostarcza ten stos — biorą się z własnego .env.
AJ_Poll__WaitSeconds powyżej 300 jest odrzucane, bo 300 to własny sufit
Servera, a Runner odróżnia trzymane pobranie pracy od natychmiastowej odpowiedzi
po czasie, jaki minął — czytałby więc każde trzymane jako puste. Żeby odpytywać
rzadziej, służy AJ_Poll__MaxSeconds.
Zewnętrzny Runner odnawia trzymane dzierżawy na własnym zegarze, co ćwiartkę
dzierżawy przyznanej przez Server, więc z tego, jak często pyta archiwum, nie
wynika nic o tym, czy dzierżawa wygaśnie. Przy starcie odmawia dzierżawy, która
nie przeżywa AJ_External__PendingTimeoutSeconds, dzierżawy powyżej sufitu
Servera wynoszącego 3600 sekund oraz AJ_Poll__WaitSeconds powyżej 300.
Requested CPUs are not available
Demon odmawia utworzenia kontenera, którego cpuset nazywa procesor
nieistniejący na tym hoście, więc ta przyczyna zatrzymuje
docker compose up -d --wait z częścią stosu w ruchu, zamiast zostawić Runnera
w pętli restartów.
Oba cpusety dostarczamy puste, co oznacza wszystkie procesory hosta, więc taki
zbiór ktoś zapisał ręcznie. lscpu -p=CPU,CORE wypisuje procesory, które
istnieją, a preflight.sh nazywa te, o które prosi RUNNER_n_CPUSET, a których
host nie ma — osobno dla każdego Runnera, zanim cokolwiek wystartuje.
Archiwum twierdzi, że takie konto nie istnieje
Sprawdź nazwę użytkownika w samym archiwum — to ta sama, którą podaje się przy
logowaniu. Wyszukiwanie odmawia wprost, gdy identyfikator użytkownika wynosi 0,
bo dla nieznanej nazwy archiwum odpowiada właśnie 0, a 0 daje się sparsować
jako liczbę: zanim ta odmowa powstała, każde odpytanie po cichu nie zwracało
niczego.
Permission denied (os error 13) w Runnerze
Dwie różne przyczyny dają to jedno zdanie, a ono nie nazywa żadnej z nich:
komunikat przychodzi z głębi klienta HTTP albo z warstwy sandboxa, bez ścieżki
i bez numeru. preflight.sh sprawdza obie.
Pierwsza: gniazdo demona. DOCKER_GID nie zgadza się z grupą, do której
należy gniazdo.
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock alpine stat -c '%g' /var/run/docker.sockNa Docker Desktop to 0; na hoście linuksowym to grupa docker. preflight.sh
uruchamia dokładnie to i ostrzega przy niezgodności — pytając kontener, a nie
Twoją powłokę, bo pod Docker Desktop i WSL gniazdo widziane przez tę powłokę nie
jest tym, które obsługuje demon. Usługa Runnera działa jako root, więc jest to
przyczyną tylko tam, gdzie ktoś usunął to z compose.yaml — wtedy Runner
zawodzi przy starcie i nigdy nie bierze żadnego zlecenia.
Druga: kontener zadania nie potrafi odczytać przestrzeni roboczej — co przy wolumenach jest mało prawdopodobne: demon zakłada wolumen z właścicielem root i prawami 0755, a to jest już poprawne. Runner zapisuje tam pliki zgłoszenia jako root, a każdy kontener zadania montuje ten katalog tylko do odczytu i czyta go jako uid 65534. Wolumen podmieniony ręcznie potrafi nie spełnić tej drugiej połowy, więc gniazdo działa bez zarzutu, Runner rejestruje się, bierze zlecenie — i każde bez wyjątku zawodzi natychmiast, co właśnie odróżnia te dwie przyczyny.
docker volume inspect -f '{{.Mountpoint}}' algojudge_runner-1-workPamięć podręczna ma te same dwie połowy, z tego samego powodu: Runner
rozpakowuje tam każdą paczkę jako root, a kontener sędziego czyta ją jako 65534.
scripts/preflight.sh sprawdza obie — zapisując jako root i odczytując z
powrotem jako 65534.
Każde zlecenie zawodzi, a Runner napisał coś o cgroup
O limicie czasu rozstrzyga czas procesora, odczytany z cgroup, w której uruchamiany jest sandbox. Runner, który nie potrafi jej odczytać, nie ocenia bez niej — to komunikat ze startu, a nie z pojedynczego zlecenia:
docker compose logs runner-1 | head -40| Co mówi | Co zrobić |
|---|---|
| cgroup w wersji 1 | host wstaje z hierarchią hybrydową. Dopisz systemd.unified_cgroup_hierarchy=1 do wiersza poleceń jądra i zrestartuj maszynę |
| sterownik cgroup, którego nie zna | docker info --format '{{.CgroupDriver}}' musi wypisać cgroupfs albo systemd. Cokolwiek innego znaczy, że cgroup są wyłączone |
| nie potrafi odczytać hierarchii | ktoś zmienił compose.yaml: brakuje montażu /sys/fs/cgroup, cgroup: host albo user: "0:0" w usłudze runnera |
Oba sterowniki cgroup działają i żaden nie wymaga przestawiania demona. Wpis
native.cgroupdriver w /etc/docker/daemon.json nie szkodzi i niczego nie
daje.
Werdykty są poprawne, ale nie ma pomiaru pamięci
Zdarza się to wyłącznie na hoście ze sterownikiem cgroup systemd i Runner
mówi o tym na poziomie ERROR przy każdym starcie. Jedna slice obsługuje tam
wszystkie przebiegi jednego toru, więc szczyt bierze się z wyzerowania
memory.peak — interfejsu jądra, który pojawił się w Linuksie 6.12, a więc
już nie w Ubuntu 24.04, za to w Debianie 13. O limicie czasu rozstrzyga czas
procesora, więc każdy werdykt pozostaje w mocy, a szczyt samego ocenianego
zgłoszenia też nie cierpi: ten czyta się z cgroup założonej świeżo na ten
przebieg. Brakuje szczytu przebiegów, które nie są niczyim zgłoszeniem —
kompilacji, programu weryfikującego, interaktora.
uname -rNa starszym jądrze albo się z tym pogódź, albo daj demonowi sterownik
cgroupfs, gdzie każdy szczyt pochodzi ze świeżej cgroup i nie wymaga niczego
poza Linuksem 5.19:
// /etc/docker/daemon.json
{ "exec-opts": ["native.cgroupdriver=cgroupfs"] }nginx nie startuje
Uruchom ten sam test co CI:
docker run --rm --add-host server:127.0.0.1 --add-host client:127.0.0.1 \
-v "$PWD/nginx/algojudge.conf:/etc/nginx/conf.d/default.conf:ro" \
-v "$PWD/nginx/snippets:/etc/nginx/snippets:ro" \
-v "$PWD/certs:/etc/nginx/certs:ro" \
nginx:1.30-alpine nginx -t--add-host jest potrzebne, bo nginx rozwiązuje każdy upstream przy parsowaniu
konfiguracji, więc poza siecią Compose zawodzi z host not found in upstream "server:8080" na konfiguracji, która jest zupełnie poprawna.
Zdarzyły się tu naprawdę dwa takie przypadki: zduplikowany
proxy_read_timeout, bo include wstawia dyrektywę do bloku włączającego,
a nginx odrzuca duplikaty wprost, zamiast nadpisywać; oraz brakujący certyfikat,
bo certs/ było puste.
PostgreSQL nie startuje po aktualizacji
Wersja 18 przeniosła miejsce danych. PGDATA to
/var/lib/postgresql/18/docker, a wolumen należy o poziom wyżej, do
/var/lib/postgresql. Każdy poradnik napisany przed 18 mówi
/var/lib/postgresql/data, a zamontowanie tego sprawia, że kontener odmawia
startu.
compose.yaml robi to poprawnie. Wersja główna jest przypięta celowo —
nieprzypięty postgres:latest przechodzący na 18 to sposób, w jaki to
znaleziono.
docker compose ps nie pokazuje zdrowia Runnera
Oba Runnery mają pustą kolumnę zdrowia i mają ją zawsze; w żadnym z nich nie
jest to usterka. Obraz Runnera nie deklaruje testu zdrowia, a obraz zewnętrznego nie może
go mieć — nie ma powłoki, nie ma portu, nie ma o co zapytać. Dlatego
docker compose up --wait uznaje obu za gotowych, gdy tylko działają, a działają
także wtedy, gdy jeden z nich nie potrafi się zalogować. Czytaj ich logi.
Stos stoi, a przeglądarka nic nie pokazuje
- Ostrzeżenie o certyfikacie jest oczekiwane, jeśli uruchomiłeś
render-tls.sh: jest samopodpisany. - Pusta strona i 404 na
/assets/…: obraz Client wymieniono bez Servera albo proxy cache'ujeindex.html. Jest serwowany zno-storedokładnie dlatego. - Strona przerwy technicznej: Server jest w oknie. Uruchom
./scripts/maintenance.sh status, a jeśli tak być nie powinno —off.
Skrypt mówi, że działa inny
another AlgoJudge maintenance script is running (pid 1234, lock: …/state/algojudge.lock.d)Jeśli pid 1234 naprawdę działa, poczekaj. Jeśli nie, następne uruchomienie samo
przejmie blokadę i to powie — blokada nigdy nie zostaje zakleszczona na stałe.
Żeby wyczyścić ją ręcznie, usuń state/algojudge.lock.d.
Kopia nie jest kompletną kopią
backup.sh ostrzega przy każdym uruchomieniu, gdy STORAGE_KIND nie jest
postgres, a .meta obok każdego zrzutu zapisuje to jako
coverage=INCOMPLETE. Jeśli odtworzysz sam taki zrzut, dostaniesz instalację,
której wiersze wskazują na bajty, których nie ma — co objawia się jako 503 przy
każdym pobraniu, bez niczego, co nazwałoby przyczynę.
Odtwarzanie wyglądało na udane i nic nie zmieniło
Przeczytaj state/restore.log.
Ta awaria zdarzyła się naprawdę, przy pisaniu repozytorium wdrożeniowego:
wywołanie Compose zawiodło, jego jednolinijkowy błąd zaraportowano jako
ostrzeżenie pg_restore, a odtwarzanie nie zrobiło zupełnie nic, podczas gdy
każda kontrola po nim przechodziła na danych, których nikt nawet nie tknął.
restore.sh bierze tożsamość każdej tabeli przed i porównuje ją po:
--clean --if-exists nadaje każdej odtworzonej tabeli nową, a odtwarzanie, które
nic nie zrobiło, zostawia stare tam, gdzie były. Ale prawdziwym testem jest ten,
który to wyłapał: najpierw coś zmień, potem odtwórz, potem sprawdź, że zmiana
zniknęła.