← Natrag
Tehnologija

Master supervisor: GPU mutex lock i 60s watchdog za 8 kritičnih servisa

RiNET Engineering· 22. lipnja 2026.· 6 min čitanja· 20 pregleda
Master supervisor: GPU mutex lock i 60s watchdog za 8 kritičnih servisa
🎧 Poslušaj članak

Kad netko pomisli da je Kubernetes liveness probe dovoljna garancija za produkcijski rad na single GPU node-u, taj ili nije vidio što se događa kad CUDA context eksplodira u facu ili je prodavač cloud rješenja. Na jednom GPU-u, u jednom fizičkom trenutku, može se izvršavati točno jedan CUDA kontekst. To nije preporuka, to je hardversko ograničenje. Ako dvije instance nekog servisa istovremeno povuku cudaSetDevice(), jedna će dobiti segfault, druga će visiti u neodređenom stanju, a GPU memorija će ostati zaključana dok netko ne povuče nvidia-smi reset. Zato smo u RiNET-u uveli nešto što zovemo master supervisor: mehanizam koji ne pita jel sve ok, nego garantira da samo jedan proces uopće može dirati GPU.

Naš lock file na /var/run/rinet-gpu.lock nije puki fajl. To je mutex implementiran preko file descriptor-a, s atomičnim create i O_EXCL flagom. Ako drugi proces pokuša kreirati taj fajl dok prvi drži lock, dobit će EEXIST i odmah exit s kodom 75. Nema retryja, nema backoffa, nema pokušaja da se pregovara s GPU-om. Lock se oslobađa samo kad proces koji ga drži završi — normalno ili kroz naš watchdog. Ali što ako proces padne, a fajl ostane? Tu dolazi stale lock cleanup: ako je lock stariji od 6 sati, supervisor ga briše i diže alert. Šest sati je prag koji smo izvukli iz analize realnih incidenata — ni jedan legitiman posao na GPU-u ne traje duže od 4 sata uz maksimalnu konfiguraciju. Sve preko toga je ili memory leak ili mrtvi proces.

Sam lock nije dovoljan. Trebaš znati što se događa s onim tko drži lock. Zato smo implementirali per-service health check koji se izvršava svakih 60 sekundi. To nije HTTP GET na /healthz, to je provjera da li proces koji drži lock i dalje postoji u proces tablici, da li mu je PID živ, da li je otvorio GPU file descriptor preko /dev/nvidia*. Ako je PID živ ali nema otvoren GPU descriptor, to znači da je proces u deadlocku — vrti se u petlji, al ne može dohvatiti GPU. U tom slučaju supervisor šalje SIGKILL, ne SIGTERM, i restartira servis. SIGTERM bi čekao graceful shutdown koji se nikad neće dogoditi jer proces čeka na GPU koji je već zaglavio.

Auto-restart pattern kod nas nije jednostavno podizanje novog containera. Kad supervisor ubije proces, on prvo oslobodi lock, zatim čisti CUDA cache preko nvidia-smi -r, pa tek onda diže novi proces. To košta 3-4 sekunde, al garantira da novi proces neće naslijediti pokvareni CUDA context. Ako restart padne dva puta unutar 5 minuta, supervisor prelazi u backoff mode: čeka 30 sekundi, pa 60, pa 120. Nakon trećeg faila, servis se označava kao degraded i više se ne restartira automatski. To je namjerno — bolje da servis ne radi nego da vrti krugove i troši GPU na beskorisne restartove.

Telegram alert je zadnja linija. Kad supervisor detektira fail, šalje poruku koja sadrži: ime servisa, PID, vrijeme trajanja locka, zadnji health check status, i dump zadnjih 20 linija iz loga. To nije generička notifikacija "servis X je pao". To je forenzički podatak koji omogućava inženjeru da u 10 sekundi shvati jel problem u kodu, u GPU driveru ili u memory leaku. Poruka ide u dedicated kanal na Telegramu, s tagom @channel, i očekuje se da netko reagira unutar 5 minuta. Ako nema reakcije, supervisor eskalira na SMS prema dežurnom inženjeru.

Zašto Kubernetes liveness probe nije dovoljan? Zato što liveness probe provjerava je li proces živ, a ne je li zdrav. Proces može biti živ, ali visiti u D stanju (uninterruptible sleep) dok čeka na GPU. liveness probe će dobiti timeout, Kubernetes će restartati pod, ali lock na GPU-u ostaje jer stari proces još nije umro. Novi pod će pokušati dohvatiti GPU, naletjeti na lock i pasti. To se vrti u beskonačnoj petlji dok netko ručno ne očisti lock. Mi smo to vidjeli u produkciji: jedan servis je triggerao 47 restartova u 3 sata, GPU je bio blokiran, a Kubernetes je samo mirno vrtio logove "Back-off restarting failed container". Liveness probe ne zna za GPU lock, ne zna za CUDA kontekst, ne zna za stale lock cleanup. To je alat za web servere, ne za GPU workloadove.

Ovaj pristup razvijamo na rinet.one — za detalje slobodno se javi.

Naš supervisor je napisan u Go-u, kompajliran kao statički binary, bez ikakvih dependencyja. Ne koristi libc za file operacije, ne koristi glibc za signal handling. Sve je syscall direktno. To znači da radi i na minimalnim container slikama, čak i na scratch bazi. Lock mehanizam koristi flock() na file descriptoru, što je atomično i radi na svim Linux filesystemima. Nema opasnosti od race conditiona jer flock() osigurava da samo jedan proces drži lock. Ako proces padne, kernel automatski oslobađa file descriptor, a time i lock. Stale lock cleanup služi samo kao sigurnosna mreža za slučaj da kernel ne oslobodi descriptor — što se događa na starim kernel verzijama ili kad je filesystem mountan s noac.

Watchdog timer od 60 sekundi nije proizvoljan. To je najveće vrijeme koje jedan CUDA kernel smije izvršavati na našim GPU-ovima bez da blokira druge operacije. Ako kernel traje duže od 60 sekundi, supervisor šalje signal procesu i započinje cleanup. Ako proces u roku od 10 sekundi ne odgovori na signal, supervisor ga ubija. To sprječava situaciju u kojoj jedan dugotrajni kernel blokira cijeli GPU za sve ostale servise. Imali smo slučaj gdje je ML model sa 128 layer-a pokretao inference koji je trajao 4 minute i 20 sekundi — watchdog ga je prekinuo, a model je optimiziran da koristi half-precision i batch size 1. Sad inference traje 12 sekundi.

Failover mehanizam je implementiran na razini supervisor arhitekture. Imamo tri instance supervisor-a na tri različita noda, ali samo jedna drži GPU lock. Ako primarni supervisor padne, sekundarni detektira da lock nije osvježen u zadnjih 90 sekundi (60s watchdog + 30s grace period) i preuzima kontrolu. Preuzimanje uključuje: provjeru da li je GPU u reset stanju, pokretanje nvidia-persistenced, i zatim kreiranje novog lock fajla. Sve to traje manje od 5 sekundi. Klijenti koji koriste GPU ne vide prekid duži od jednog timeouta na API pozivu. Ovo nije high availability u smislu zero-downtime, ali za single GPU arhitekturu je maksimum koji se može postići bez dupliciranja hardvera.

Implementacija je prošla kroz tri iteracije. Prva verzija je koristila PID file i periodični check, ali nije handleala slučaj kad proces promijeni PID nakon fork-a. Druga verzija je dodala file descriptor tracking, ali je imala bug u stale lock cleanupu koji je brisao lock i kad je proces još bio živ. Treća verzija, koja je sad u produkciji, koristi kombinaciju flock(), inotify za detekciju promjena na lock fajlu, i per-process cgroup tracking. Ako proces padne, cgroup ga automatski ubija, a inotify detektira promjenu i oslobađa lock. Sve je testirano na 10.000 simuliranih failova, s nulom lažnih pozitiva.

Ono što sam naučio iz ovog projekta je da se pouzdanost ne gradi na alatu nego na razumijevanju hardverskih ograničenja. Kubernetes liveness probe je koristan za web servere i API-je, ali za GPU workloadove trebaš nešto što razumije što je CUDA context, što je GPU memory, što je device reset. Master supervisor nije puki watchdog — to je sistematski pristup koji garantira da GPU nikad nije u stanju u kojem ga nitko ne može koristiti. I da, pišem ovo dok jedan od naših servisa upravo prolazi kroz watchdog restart na GPU nodeu u drugoj vremenskoj zoni. Supervisor je poslao alert, lock je očišćen, servis je ponovno pokrenut za 4.2 sekunde. Nikad nisam morao ustati od stola.

Ako te zanima ovakva arhitektura ili gradiš nešto slično za svoju instituciju — javi se preko rinet.one.

watchdogGPU lockservice supervisorfailoverhigh availabilityRi.NETsovereign AIAI operativni sustavCroatian AItehnologija

Zanima vas ovakva AI tehnologija?

RiNET gradi sovereign AI rjesenja za javni sektor i poduzeca - od civic intelligence do automatizacije nabave.

Kontaktirajte nas →
0 komentara

Komentari