Pierwsza instalacja
Od pustego katalogu do instalacji, która ocenia zgłoszenie.
Ustaw AJ_ADMIN_TOKEN przed pierwszym `up`
Dopóki ta wartość jest pusta, /api/v1/admin/** jest zamknięte — a to obejmuje
endpoint, który ustawia hasło administratora. Hasło konta admin, zakładanego
przy pierwszym starcie, to dwadzieścia losowych znaków, które nigdy nie
trafiają do logu, na ekran ani w żadne czytelne miejsce.
Instalacja uruchomiona bez tego tokenu nie ma administratora, na którego konto ktokolwiek mógłby wejść, a jedyne wyjście to skasować bazę i zacząć od nowa.
Obrazy, i dlaczego nowy może się nie pobrać
Osiem pakietów — algojudge-server, algojudge-client, algojudge-runner,
lang-gcc, lang-clang, lang-python, lang-pypy i
algojudge-external-runner — jest opublikowanych i publicznych. docker compose pull nie wymaga logowania do rejestru, z żadnej maszyny.
Pakiet GHCR utworzony pierwszym pushem jest jednak prywatny i żaden workflow
tego nie zmieni: ktoś z dostępem do pakietów organizacji przestawia go raz na
publiczny. Warto o tym wiedzieć, bo wraca z każdym nowym obrazem, a objawem jest
denied przy docker compose pull w skądinąd poprawnej instalacji.
Cztery obrazy językowe nie są usługami
docker compose pull pobiera sześć z ośmiu. lang-gcc, lang-clang,
lang-python i lang-pypy to wartości przekazywane Runnerowi, a nie usługi,
więc Compose ich nie dotyka: Runner pobiera obraz, gdy hosta go nie ma, a
scripts/update.sh pobiera wszystkie cztery przy aktualizacji instalacji.
1. Klon i konfiguracja
git clone https://github.com/AlgoJudge/AlgoJudge-Ops.git /opt/algojudge-ops
cd /opt/algojudge-ops
cp .env.example .env
chmod 600 .envTrzy wartości nie mają domyślnych i bez nich stos nie wystartuje. Każdy sekret generuj osobno: jeśli ten sam trafi w dwa miejsca, jeden wyciek kompromituje oba.
AJ_ADMIN_TOKEN= # openssl rand -base64 36
POSTGRES_PASSWORD= # openssl rand -base64 36, inny
RUNNER_WORK_DIR= # BEZWZGLĘDNA ścieżka na hoście, np. /srv/algojudge/runner-workRUNNER_WORK_DIR musi być bezwzględna, bo Runner podaje ten napis demonowi
Dockera, a ścieżka, której demon nie potrafi otworzyć, staje się pustym
katalogiem, nie błędem. Każde zgłoszenie ocenia się wtedy na pustce i żaden
test nie zawodzi w widoczny sposób. Właśnie dlatego preflight.sh odrzuca
ścieżkę względną.
Załóż ten katalog sam przed pierwszym uruchomieniem albo pozwól utworzyć go Compose:
sudo mkdir -p /srv/algojudge/runner-workCompose zakłada brakujący katalog montowany z hosta jako root, z prawami
0755 — i o to właśnie chodzi: Runner zapisuje tu jako root, a każdy kontener
zadania montuje ten katalog tylko do odczytu i czyta go jako uid 65534.
Katalog ręcznie zamknięty przed innymi przechodzi próbę Runnera, a nie przechodzi
próby zadania, i wtedy każde zgłoszenie kończy się komunikatem Permission denied (os error 13). preflight.sh sprawdza obie połowy — zapis jako root i odczyt
jako 65534 — i mówi, która zawiodła.
chown 65532:65532 też zadziała i nie jest potrzebny — usługa Runnera działa
jako root, żeby móc mierzyć. Psuje to dopiero katalog ręcznie zamknięty przed
innymi.
Jeszcze dwie wartości warto przeczytać przed pierwszym startem:
TRUSTED_PROXY_NETWORKSdecyduje, czyim informacjom o adresie odwiedzającego Server ma wierzyć, a bez tego Server nie wystartuje. Domyślnie wskazuje sieć Compose, w której stoi dołączony nginx. Musi to być adres sieci:172.28.0.5/24odrzucamy przy starcie, podając nazwę zmiennej i adres, który powinien się tam znaleźć. Za własnym proxy wpisz sieć tego proxy; jeśli proxy nie ma w ogóle,nonejest pełną odpowiedzią.DOCKER_GIDto grupa, do której należy gniazdo demona;preflight.shpowie, jeśli u Ciebie jest zła. Patrz Czego wymaga host.
2. Certyfikat
Umieść fullchain.pem i privkey.pem w certs/. Montujemy je tylko do
odczytu; nic tutaj nie wystawia certyfikatu, a odnawianiem zajmuje się nadal to,
czego już używasz.
/.well-known/acme-challenge/ jest serwowane na obu portach, więc wyzwanie
HTTP-01 nigdy nie zostanie przekierowane. Po odnowieniu:
docker compose exec nginx nginx -s reloadJeśli nie masz certyfikatu, a chcesz tylko rzucić okiem:
./scripts/render-tls.sh twoja.domenaSamopodpisany, i każda przeglądarka to powie. Jest po to, żeby pierwszy start dał działającą instancję z ostrzeżeniem, a nie nginx, który nie wstaje, i błąd, którego nikt nie powiąże z brakującym certyfikatem. Skrypt odmawia nadpisania certyfikatu, który już tam leży.
3. Start
./scripts/preflight.sh
docker compose up -d --waitpreflight.sh odmawia od razu i jednym zdaniem, zamiast w połowie podnoszenia
stosu: puste hasło, brakujący lub zbyt krótki token, CIDR z bitami hosta,
względny katalog pracy, katalog pracy, którego kontener zadania nie przeczyta,
cgroup v1 albo nieznany sterownik cgroup, .env śledzony przez Git. make up
uruchamia go najpierw, więc zwykłą drogę sprawdzamy niezależnie od tego, czy
ktoś pamięta, że ten skrypt istnieje.
Profil runner uruchamia cztery Runnery, od runner-1 do runner-4.
Cztery pasują do ośmiu rdzeni fizycznych; na mniejszej maszynie uruchom mniej,
bo więcej Runnerów niż rdzeni fizycznych nie przyspiesza oceniania, a psuje jego
rzetelność — tabelę ma Czego wymaga host.
4. Administrator
docker compose exec -it server aj-admin passwordPotem zaloguj się na https://twoja.domena/ jako admin.
Narzędzie prosi o hasło bez wyświetlania go i nigdy nie przyjmuje go jako
argumentu — argumenty widzi przez ps każdy inny proces, a do tego lądują
w historii powłoki. W skrypcie narzędzie czyta pierwszą linię standardowego
wejścia:
printf '%s' "$NEW_PASSWORD" | docker compose exec -T server aj-admin passwordObowiązuje polityka haseł, minimum dwanaście znaków, a odmowa niczego nie zmienia — stare hasło dalej działa, więc literówka nie zostawi instalacji bez wejścia. Komenda zdejmuje też blokadę konta: dziesięć nietrafionych prób to zwykle właśnie to, co poprzedza sięgnięcie po nią.
aj-admin działa wewnątrz kontenera Servera i czyta token ze środowiska tego
kontenera, więc token nigdy nie trafia do Twojej historii powłoki. To jedyna
wspierana droga do powierzchni operatora: /api/v1/admin/** odpowiada na
własnym interfejsie pętli zwrotnej Servera, a żądanie przez nginx — albo przez
opublikowane 127.0.0.1:8080 — dociera jako brama mostka i dostaje 404. To
zachowanie zmierzone, nie domysł.
5. Zatwierdź Runnery
Nowy Runner rejestruje się i czeka. To nie jest usterka i nie ma limitu czasu. Nic nie zostanie ocenione, dopóki administrator go nie zatwierdzi — i to właśnie powstrzymuje kogoś przed podpięciem własnej maszyny do Twojej instalacji.
W panelu: Runnery, i zatwierdź każdy z czterech, które się pojawiły. Dopóki
tego nie zrobisz, ich logi powtarzają waiting: this Runner has not been approved yet, za każdym razem z dłuższą przerwą — bo cała pracownia Runnerów
zarejestrowanych naraz zasypywałaby inaczej Server jedną falą pytań przez cały
czas, którego ktoś potrzebuje, żeby dojść do panelu. Niezatwierdzony Runner po
prostu stoi bezczynnie, a kolejkę niosą pozostałe, więc zapomniane zatwierdzenie
wygląda na wolną instalację, a nie na błąd.
Potem wyślij coś i zobacz, jak dostaje werdykt. Dopóki to się raz nie wydarzy, nie wiadomo, czy instalacja działa.
6. Opcjonalnie: harmonogram
./scripts/install-cron.sh --print # co by zainstalował
./scripts/install-cron.shNic nie planuje się samo. Co skrypt instaluje, a co celowo zostawia zakomentowane, opisuje Praca rutynowa i harmonogram.
Praca na obrazach zbudowanych lokalnie
Tak się tego stosu normalnie nie uruchamia — od tego są obrazy
opublikowane, które pobierze każdy. To jest sposób na pracę z kodem, który
nie jest wydany: ze zmianą w repozytorium produktowym albo z poprawką w
drodze do pull requesta. Zbuduj ją, otaguj tam, gdzie stałby obraz opublikowany,
a compose.yaml zadziała bez zmian. Ścieżkę po -f liczy się
względem katalogu, w którym stoisz, a nie względem kontekstu budowania — linia
Servera jako jedyna zawiera ścieżkę, więc to ją warto sprawdzić.
Buduj od nowa, zamiast sięgać po tag, który już masz: te obrazy są przypięte
ruchomym tagiem, więc stary lokalny :0 jest po cichu tym, co zbudowałeś
miesiąc temu.
docker build -f AlgoJudge-Server/AlgoJudge.Server/Dockerfile -t ghcr.io/algojudge/algojudge-server:0 AlgoJudge-Server
docker build -t ghcr.io/algojudge/algojudge-client:0 AlgoJudge-Client
docker build -t ghcr.io/algojudge/algojudge-runner:0 AlgoJudge-Runner
# Kontekstem jest `images`, nie `images/$lang`: wszystkie cztery budują w nim
# pośrednika pomiarowego z `images/shim`, więc musi się w nim znaleźć.
for lang in gcc clang python pypy; do
docker build -t "ghcr.io/algojudge/lang-$lang:0" \
-f "AlgoJudge-Runner/images/$lang/Dockerfile" AlgoJudge-Runner/images
done
# Tylko dla profilu sędziego zewnętrznego.
docker build -t ghcr.io/algojudge/algojudge-external-runner:0 AlgoJudge-External-Runnerupdate.sh wciąż potrzebuje rejestru, z którego pobiera. Aktualizację
i wycofanie ćwiczymy bez opublikowanego rejestru na lokalnym registry:2,
z REGISTRY=localhost:5000/algojudge.