OOM Killer
Există un scenariu pe care orice administrator de sistem îl întâlnește mai devreme sau mai târziu: sistemul rămâne fără memorie, swap-ul e plin, și niciun proces nu eliberează RAM. În acest punct, kernelul Linux activează un mecanism de ultimă instanță numit Out of Memory Killer - sau simplu, OOM Killer. Rolul său este brutal de simplu: alege unul sau mai multe procese și le termină forțat pentru a elibera memorie și a menține sistemul funcțional.
Cum ajunge sistemul în stare OOM
Linux folosește implicit o strategie numită memory overcommit - permite proceselor să aloce mai multă memorie decât există fizic disponibil, bazându-se pe faptul că majoritatea proceselor nu folosesc simultan toată memoria alocată. De exemplu, când un proces apelează malloc() pentru 1GB, kernelul îi promite acea memorie fără să o aloce efectiv - paginile fizice sunt alocate abia când procesul scrie în ele (lazy allocation).
Această strategie funcționează bine în practică, dar în scenarii extreme - un proces care începe să consume rapid memorie, mai multe procese care cresc simultan, sau un leak de memorie - sistemul poate ajunge în situația în care promisiunile depășesc cu mult realitatea. Când swap-ul e plin și RAM-ul e epuizat, kernelul nu mai poate onora alocările și OOM Killer intră în acțiune.
Cum alege OOM Killer victima
OOM Killer nu termină procese la întâmplare. Kernelul calculează pentru fiecare proces un oom_score între 0 și 1000, unde un scor mai mare înseamnă o probabilitate mai mare de a fi ales ca victimă. Algoritmul ia în considerare mai mulți factori:
- Consumul de memorie - procesele care folosesc mult RAM au scor mai mare
- Durata de rulare - procesele nou-pornite au scor ușor mai mare decât cele vechi
- Procesele copil - un proces cu mulți copii care consumă memorie are scor mai mare
- Prioritatea - procesele cu prioritate mare au scor ușor mai mic
Scopul algoritmului este să elibereze cât mai multă memorie cu un singur proces terminat, minimizând impactul asupra sistemului.
Verifici scorul OOM al oricărui proces:
cat /proc/PID/oom_score
unde PID este ID-ul procesului. Pentru a vedea scorurile tuturor proceselor, sortate descrescător:
for proc in /proc/[0-9]*/; do pid=$(basename $proc) score=$(cat $proc/oom_score 2>/dev/null) name=$(cat $proc/comm 2>/dev/null) echo "$score $pid $name" done | sort -rn | head -20
Cum identifici dacă OOM Killer a acționat
Primul semn că OOM Killer a acționat este de obicei un serviciu care dispare fără urmă sau un server care devine brusc iresponsiv. Kernelul înregistrează întotdeauna acțiunile OOM Killer în log-urile de sistem.
Pe sisteme cu systemd:
journalctl -k | grep -i "oom\|killed process\|out of memory"
sau pentru log-urile kernel din ultima pornire:
journalctl -k -b 0 | grep -i oom
Pe sisteme fără systemd sau dacă preferi dmesg:
dmesg | grep -i "oom\|killed"
Un mesaj tipic OOM arată astfel:
Out of memory: Killed process 1234 (nginx) total-vm:524288kB, anon-rss:102400kB, file-rss:4096kB
Mesajul include numele procesului, PID-ul și câtă memorie folosea în momentul terminării. Înainte de acest mesaj vei găsi de obicei un dump complet al stării memoriei în momentul evenimentului, util pentru diagnostic.
Controlul comportamentului OOM
oom_score_adj
Fiecare proces are un fișier /proc/PID/oom_score_adj care permite ajustarea scorului OOM calculat de kernel. Valoarea poate fi între -1000 și 1000:
- -1000 înseamnă că procesul este complet imun la OOM Killer
- 0 înseamnă comportament implicit
- 1000 înseamnă că procesul va fi primul ales ca victimă
Verifici valoarea curentă pentru un proces:
cat /proc/PID/oom_score_adj
Modifici valoarea pentru un proces care rulează deja:
echo -500 > /proc/PID/oom_score_adj
Protejarea proceselor critice
Dacă rulezi servicii critice - un server de baze de date, un server web principal, un daemon de monitorizare - poți reduce semnificativ șansa ca OOM Killer să le termine setând un oom_score_adj negativ.
Pentru procese gestionate de systemd, adaugi în fișierul de serviciu (/etc/systemd/system/nume-serviciu.service sau prin systemctl edit):
[Service] OOMScoreAdjust=-500
Reîncarci configurația și repornești serviciul:
systemctl daemon-reload systemctl restart nume-serviciu
Valoarea -500 reduce semnificativ șansa de a fi ales, fără să acorde imunitate completă. Imunitatea completă (-1000) e rezervată proceselor cu adevărat critice - dacă protejezi prea multe procese, OOM Killer nu va mai putea elibera suficientă memorie și sistemul va îngheța în loc să termine un proces.
Procesele cu imunitate implicită
Kernelul acordă automat imunitate (-1000) câtorva procese esențiale pentru funcționarea sistemului, cum ar fi procesele din spațiul kernel. Procesul init (PID 1, systemd sau echivalentul) are de asemenea protecție implicită.
Comportamentul la OOM: panic sau kill
Implicit, când kernelul detectează o situație OOM, activează OOM Killer. Există însă un parametru care schimbă acest comportament și provoacă un kernel panic (oprire imediată a sistemului) în loc să termine procese:
cat /proc/sys/vm/panic_on_oom
Valoarea 0 înseamnă că OOM Killer e activ (implicit). Valoarea 1 sau 2 provoacă kernel panic. Pe servere de producție unde consecințele unui serviciu terminat sunt mai grave decât o repornire, kernel panic + watchdog poate fi preferabil - sistemul repornește controlat în loc să ruleze într-o stare degradată.
# Activezi kernel panic la OOM (temporar) sysctl vm.panic_on_oom=1 # Permanent în /etc/sysctl.conf echo "vm.panic_on_oom=1" >> /etc/sysctl.conf
Un parametru companion este kernel.panic, care setează numărul de secunde după care sistemul repornește automat după un panic:
kernel.panic=10
Overcommit: controlul alocărilor de memorie
Comportamentul de overcommit al kernelului este controlat de vm.overcommit_memory:
| Valoare | Comportament |
|---|---|
| 0 | Overcommit euristic (implicit) - kernelul permite overcommit rezonabil |
| 1 | Overcommit fără limite - orice alocare este acceptată |
| 2 | Fără overcommit - alocările sunt limitate la RAM + swap |
Modul 2 previne complet situațiile OOM prin refuzarea alocărilor care ar depăși memoria disponibilă, dar poate cauza erori de tip Cannot allocate memory în aplicații care se bazează pe comportamentul standard de overcommit (multe programe C, JVM, etc.).
# Verifici modul curent cat /proc/sys/vm/overcommit_memory # Modifici temporar sysctl vm.overcommit_memory=2 # Permanent echo "vm.overcommit_memory=2" >> /etc/sysctl.conf
Când overcommit_memory=2, limita totală de memorie este controlată de vm.overcommit_ratio (implicit 50), care reprezintă procentul din RAM fizic adăugat la swap pentru calculul limitei:
limita = swap + (overcommit_ratio / 100) * RAM_fizic
Prevenirea situațiilor OOM
OOM Killer este ultima linie de apărare, nu o soluție. Dacă sistemul tău activează OOM Killer regulat, problema reală este că rulezi mai multe servicii decât poate susține RAM-ul disponibil.
Monitorizarea proactivă este primul pas - urmărești consumul de memorie și primești alerte înainte de a ajunge în situație critică:
# Verifici rapid memoria disponibilă free -h # Urmărești evoluția în timp real watch -n 5 free -h
Limitele de memorie per container sunt esențiale dacă rulezi Docker - fără limite, un container poate consuma tot RAM-ul disponibil și declanșa OOM Killer pentru alte procese. Adaugi în docker-compose.yml:
services: aplicatie: mem_limit: 512m memswap_limit: 512m
earlyoom este un daemon din spațiul utilizator care monitorizează memoria și termină procese înainte ca kernelul să ajungă în stare OOM critică, oferind un control mai fin și notificări:
# Ubuntu/Debian apt install earlyoom # Arch Linux pacman -S earlyoom systemctl enable --now earlyoom
earlyoom acționează când memoria disponibilă scade sub un prag configurabil (implicit 10%) și poate fi configurat să trimită notificări sau să execute un script înainte de a termina procese.
Diagnosticare post-OOM
Când OOM Killer a acționat, e important să înțelegi de ce pentru a preveni recurența. Colectezi informațiile complete din log-uri:
journalctl -k -b -1 | grep -A 50 "Out of memory"
-b -1 se referă la boot-ul anterior (util dacă sistemul s-a restartat după OOM). Dump-ul kernel include consumul de memorie per proces în momentul evenimentului, ceea ce îți permite să identifici care proces a consumat RAM excesiv.
Dacă vrei să simulezi o situație OOM în mod controlat pentru testare (într-un mediu de test, nu pe producție):
# Alocă memorie rapid până la epuizare - folosește cu MARE grijă python3 -c "x = []; [x.append(' ' * 10**6) for _ in range(10000)]"
Resurse suplimentare
- Swap pe Linux - gestionarea memoriei virtuale, partiții și fișiere swap
- zram și zswap - alternative moderne la swap-ul clasic pe disc