Späť na blog

Kubernetes p99 špičky bez OOM: Diagnostika cgroup v2 memory.high cez PSI

p99 skočí z 80ms na 3s. CPU je v pohode. RSS je pod limitom. Nikto nedostal OOMKilled. A predsa je služba v krátkych vlnách “zaseknutá”.

Toto je typický cgroup v2 problém: ste throttlení pamäťovým tlakom, nie zabití pamäťovým limitom. Najčastejšie ide o kombináciu:

  • memory.high (soft limit / backpressure)
  • reclaim stalls (direct reclaim / refault stormy)
  • PSI (Pressure Stall Information), ktoré takmer nikto negrafuje

Ak pozeráte iba RSS a OOMKilly, miniete root cause.

Testované na: Kubernetes 1.29–1.31, containerd 1.7, Linux 6.1–6.6 (cgroup v2), systemd 252+.

Prečo to v roku 2026 bolí viac

cgroup v2 je default na moderných distro. Platform tímy navyše často zapnú “skorý backpressure” (systemd slices alebo kubelet/runtime QoS), aby chránili nody pred tvrdým OOM. Výsledok: viac incidentov, kde latencia padá bez jedného OOMKillu.

Ak máte SLO na latenciu, memory.high je rovnako “reálny limit” ako CPU throttling.

Incident (anonymizovaný)

Máme latency‑senzitívne API (Go) v multi‑tenant Kubernetes clustri. Po refreshi node poolu začali padať alerty:

  • p99 > 2s v burstoch 30–90s
  • 5xx nízke, ale klientské timeouty stúpli
  • node CPU < 50%, pod RSS ~70% limitu
  • žiadne restarty, žiadne OOMKilly, žiadny očividný GC spike

Blast radius: ~20% trafficu (requesty na pody schedulované na podmnožinu nodov).

Constraint: nemohli sme len “pridať pamäť nodom”; kapacita bola napnutá a chceli sme presnú opravu.

Timeline

  • T-0: alert na p99 latenciu; dashboardy ukazujú CPU ok, pamäť “ok”.
  • T+5m: app metriky ukazujú handler time stabilný; rastie queue time (thready stoja).
  • T+15m: v pode memory.events rýchlo zvyšuje high.
  • T+25m: memory.pressure ukazuje výrazný stall time počas špičky.
  • T+35m: na postihnutých nodoch /proc/pressure/memory skáče presne s latenciou.
  • T+50m: mitigácia: prehodiť workload do Guaranteed QoS (request==limit) + mierne zvýšiť limit.
  • T+90m: p99 sa stabilizuje; PSI padá; high prestane rásť pri steady trafficu.

Mechanizmus: čo sa naozaj stalo

memory.max vás zabije. memory.high vás spomalí.

V cgroup v2 máte dve “čiary”:

  • memory.max: hard strop; po prekročení príde OOM (v cgroup alebo globálne).
  • memory.high: soft strop; po prekročení kernel spustí reclaim a môže throttliť alokácie.

Keď cgroup prekročí memory.high, kernel začne reclaimovať a vie spomaliť alokácie. Proces neumrie; len trávi čas v reclaim pathoch.

Prečo latencia exploduje a CPU vyzerá v pohode

Zaseknutý thread často nevyzerá ako “CPU problém”, lebo je blokovaný na:

  • reclaim práci
  • IO vyvolanom refaultami (podľa storage a cache patternu)
  • allocator stalloch

Ten čas je reálna latencia, ale nie nutne “busy CPU”.

PSI je truth serum

PSI meria “koľko času boli tasky zaseknuté kvôli tlaku” (memory/CPU/IO). V tomto incidente PSI ukázalo koreláciu okamžite: keď p99 vyskočilo, memory PSI vyskočilo.

Runbook: potvrdenie memory.high reclaim stallov

Čo skontrolovať ako prvé

  1. Používate cgroup v2?

V pode:

stat -fc %T /sys/fs/cgroup
# cgroup2fs == v2
  1. Je memory.high nastavené (nie “max”)?
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.high
cat /sys/fs/cgroup/memory.current

Ak je memory.high nižšie ako memory.max, pod môže byť throttlený skôr, než dostane OOMKilled.

  1. Kernel zaznamenal throttling eventy?
cat /sys/fs/cgroup/memory.events

Hľadajte, či high rastie počas incidentu.

Ako potvrdiť hypotézu

A. PSI na úrovni podu:

cat /sys/fs/cgroup/memory.pressure

Uvidíte napr.:

some avg10=0.25 avg60=0.12 avg300=0.05 total=123456789
full avg10=0.03 avg60=0.01 avg300=0.00 total=987654

Interpretácia:

  • some = % času, keď aspoň jeden task stál kvôli memory tlaku
  • full = % času, keď stáli všetky non-idle tasky (prakticky “všetko je stuck”)

Pre latency‑senzitívne služby je už pár % some problém. Sustained full je typicky katastrofa.

B. Korelácia s latenciou (rýchly loop):

while true; do
  date
  cat /sys/fs/cgroup/memory.current
  cat /sys/fs/cgroup/memory.events
  cat /sys/fs/cgroup/memory.pressure
  echo
  sleep 5
done

Hľadáte:

  • memory.current blízko/nad memory.high
  • high counter rastie
  • PSI some/full rastie
  • p99 rastie v tom istom čase

C. Node-level PSI (globálny tlak):

Na node (SSH alebo kubectl debug node/...):

cat /proc/pressure/memory
cat /proc/pressure/io

Ak skáče aj node-level PSI, máte problém s headroomom nody (kube-reserved/system-reserved príliš malé) plus podový memory.high.

Bezpečné mitigácie

  1. Presuňte kritický workload do Guaranteed QoS (request == limit).
  2. Mierne zvýšte memory limit, ak steady-state + normálne špičky idú príliš blízko k backpressure.
  3. Dočasne znížte alokačný rate / fan-out:
    • znížiť concurrency
    • cap na in-flight requesty
    • znížiť per-request buffering
  4. Drainnite pár “hot” nodov, ak problém vidíte iba na časti fleet.

Rizikové mitigácie (môžu ublížiť clustru)

  1. Globálne vypnutie memory.high (napr. na kubepods.slice) bez pochopenia dôvodu.
  2. Drop caches (echo 3 > /proc/sys/vm/drop_caches) – často zhorší refault/IO.
  3. Restarty podov ako “fix” – maskujú mechanizmus a vedia vyvolať connection storm.

Čo sme zmenili (konkrétne)

1) Guaranteed QoS + reálny headroom

Predtým (Burstable, tesné):

resources:
  requests:
    memory: "512Mi"
  limits:
    memory: "768Mi"

Potom (Guaranteed + headroom):

resources:
  requests:
    memory: "1Gi"
  limits:
    memory: "1Gi"

2) Odstránenie náhodného MemoryHigh clampu na node

Našli sme systemd drop-in, ktorý aplikoval MemoryHigh na kubepods.slice príliš agresívne.

Diff (ilustratívny):

# /etc/systemd/system/kubepods.slice.d/10-memoryhigh.conf

[Slice]
-MemoryHigh=85%
+MemoryHigh=infinity

Node ochranu sme nechali primárne na Kubernetes eviction thresholdoch.

3) PSI a high eventy ako explicitný alert/runbook signál

Pridali sme guardrails pre:

  • rate high eventov (ak to scrapujete)
  • node-level PSI (minimálne ako on-call diagnostiku)

Ako verifikovať (merateľné signály)

  1. memory.events prestane rýchlo zvyšovať high:
watch -n 2 'cat /sys/fs/cgroup/memory.events'
  1. PSI ostane nízke počas steady trafficu:
watch -n 2 'cat /sys/fs/cgroup/memory.pressure'
  1. Latencia sa zotaví bez restartov:
  • p99 späť na baseline
  • PSI bez špičiek
  1. Node-level headroom drží:
  • /proc/pressure/memory nešpičkuje vo veľkom
  • menej eviction eventov v kubelet logoch

Prevencia / guardrails

Kontrakty (čo vynucujeme)

  • Latency‑kritické služby musia byť Guaranteed (alebo explicitne schválené ako Burstable).
  • Memory headroom budget:
    • držať memory.current < ~70–80% z memory.max pri peak loade (service-specific)
  • PSI budget:
    • sustained full ~0
    • sustained some v nízkych jednotkách %

Alerty, ktoré stoja za to

  • node memory PSI some avg10 nad thresholdom X minút
  • high event rate > 0 pre kritické pody
  • zvýšený refault/major fault rate (ak máte dostupné metriky)

Súvisiace čítanie

Súvisiace články

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "Kubernetes p99 špičky bez OOM: Diagnostika cgroup v2 memory.high cez PSI". https://www.michal-drozd.com/sk/blog/cgroup-v2-memory-high-psi-kubernetes/ (Publikované 25. októbra 2025).