Jak debugować CPU w Kubernetes bez zgadywania
- data
- kategoria
- Observability
- także w
- Capacity & Performance
- czytanie
- 3 min / 659 słów
Problemy z CPU w Kubernetes bardzo często zaczynają się od jednego wykresu:
CPU usage
i jednego pytania:
czy CPU jest za wysokie?
To za mało.
Wysokie usage może być całkowicie zdrowe. Niskie usage może współistnieć z throttlingiem. Umiarkowane usage może ukrywać contention i długie oczekiwanie runnable tasków.
Dlatego diagnostyka CPU powinna mieć stałą kolejność.
Nie zaczynamy od:
może zwiększmy limit
Tylko od rozdzielenia czterech różnych zjawisk:
usage
throttling
contention
pressure
1. Najpierw ustal, ile CPU workload faktycznie dostał
Podstawowa metryka to CPU usage.
W Prometheus bardzo często będzie to:
container_cpu_usage_seconds_total
To counter.
Nie pokazuje „aktualnego CPU” bezpośrednio. Pokazuje skumulowany CPU time zużyty przez kontener.
Jeżeli licznik wzrósł w ciągu jednej sekundy o:
1.0
to workload zużył około:
1 CPU-second
czyli średnio:
1 CPU
w tym przedziale.
Typowo używamy więc:
rate(container_cpu_usage_seconds_total[5m])
Jeżeli wynik wynosi:
0.5
to workload zużywa średnio:
0.5 CPU
czyli:
500m
Jeżeli:
2.4
to około:
2.4 CPU
To pierwszy fakt, którego potrzebujemy:
ile CPU rzeczywiście wykonano?
2. Porównaj usage z requestem i limitem, ale nie wyciągaj jeszcze wniosków
Załóżmy:
request = 500m
limit = 2 CPU
usage = 1.4 CPU
To oznacza:
usage > request
ale nadal:
usage < limit
Nie ma w tym nic podejrzanego.
Workload po prostu burstuje powyżej requestu.
Request nie jest sufitem.
Znacznie ciekawsza sytuacja:
request = 500m
limit = 1 CPU
usage ≈ 1 CPU
Tu workload może dobijać do limitu.
Ale samo usage nadal nie mówi, czy rzeczywiście występuje throttling.
Musimy zejść poziom niżej.
3. Sprawdź throttling
Jeżeli workload ma CPU limit, sprawdzamy statystyki cgroup.
W cgroup v2:
cpu.stat
może zawierać:
usage_usec
user_usec
system_usec
nr_periods
nr_throttled
throttled_usec
Najważniejsze dla throttlingu:
nr_throttled
throttled_usec
Jeżeli:
nr_throttled
rośnie, workload regularnie wykorzystuje dostępny CPU bandwidth.
Jeżeli:
throttled_usec
również szybko rośnie, kernel faktycznie blokuje dalsze wykonywanie z powodu wyczerpanej quota.
Conceptually:
task runnable
|
v
quota exhausted
|
v
throttled
To bardzo konkretna diagnoza.
Nie:
CPU chyba za wysokie
tylko:
workload jest zatrzymywany przez CPU bandwidth controller
4. Brak throttlingu nie oznacza braku problemu CPU
Załóżmy:
nr_throttled = 0
Czy możemy powiedzieć:
CPU jest zdrowe
Nie.
Workload może nie mieć limitu:
cpu.max = max ...
i nadal cierpieć przez contention.
Przykład:
node capacity = 8 CPU
runnable demand = 16 CPU
Nikt nie musi być throttlowany.
Po prostu część tasków czeka w runqueue.
To prowadzi do następnego pytania:
czy workload czeka na CPU?
5. Sprawdź CPU PSI
Tutaj wchodzi:
cpu.pressure
Na poziomie systemu:
/proc/pressure/cpu
albo na poziomie cgroup:
cpu.pressure
Interesuje nas przede wszystkim:
some
Przykład:
some avg10=18.00 avg60=8.50 avg300=3.20 total=...
avg10=18 oznacza w przybliżeniu, że przez około 18% ostatniego krótkiego okna co najmniej jeden task doświadczał CPU stall.
Czyli:
workload miał runnable pracę,
ale nie dostał CPU od razu
To już jest sygnał contention.
Usage i PSI odpowiadają na inne pytania:
usage:
ile CPU dostałem?
PSI:
jak często brak CPU mnie blokował?
6. Interpretuj usage i PSI razem
Najciekawsze są kombinacje.
Wysokie usage, niskie PSI
CPU usage = wysokie
CPU PSI = niskie
Workload intensywnie wykorzystuje CPU, ale nie czeka znacząco na scheduler.
To może być zdrowy CPU-bound workload.
Wysokie usage, wysokie PSI
CPU usage = wysokie
CPU PSI = wysokie
CPU pracuje intensywnie i istnieje więcej runnable demand niż dostępnej capacity.
To klasyczny contention.
Umiarkowane usage, wysokie PSI
To ciekawszy przypadek.
Może oznaczać, że workload ma ograniczony zestaw CPU albo contention występuje lokalnie, mimo że cały node nie wygląda na mocno obciążony.
Sam średni utilization node'a może wtedy być mylący.
7. Jeśli PSI rośnie, sprawdź runqueue latency
PSI mówi:
ktoś czeka
ale nie mówi dokładnie:
jak długo konkretny task czeka na scheduler
Do tego potrzebujemy obserwacji schedulera.
Kluczowe zdarzenia to:
sched_wakeup
sched_switch
Conceptually:
sched_wakeup
|
| task staje się runnable
|
v
... czeka ...
|
v
sched_switch
|
| task zostaje running
Różnica czasu między tymi momentami daje nam:
runqueue latency
Przykład:
wakeup: 12:00:00.100
running: 12:00:00.107
czyli:
7 ms scheduler wait
To już bezpośrednia odpowiedź na pytanie:
czy latency aplikacji rośnie dlatego,
że task czeka na CPU?
8. kubectl top to tylko początek
Polecenie:
kubectl top pod
jest wygodne.
Ale pokazuje przede wszystkim zagregowane usage.
Możemy zobaczyć:
NAME CPU
api-123 780m
I tyle.
Nie wiemy z tego:
czy Pod jest throttlowany
czy czeka w runqueue
czy ma wysoki PSI
czy usage jest stabilne czy burstowe
Dlatego kubectl top jest dobrym narzędziem do szybkiego sprawdzenia:
kto aktualnie zużywa CPU
ale słabym narzędziem do odpowiedzi:
dlaczego aplikacja ma problem z CPU
9. Minimalny workflow diagnostyczny
Załóżmy symptom:
p99 latency wzrosło
Nie zaczynaj od zmiany requestów czy limitów.
Idź kolejno.
Krok 1
Sprawdź:
CPU usage
Pytanie:
ile CPU workload faktycznie zużywa?
Krok 2
Sprawdź:
request
limit
Pytanie:
czy usage zbliża się do limitu?
Krok 3
Sprawdź:
cpu.stat
Pytanie:
czy workload jest throttlowany?
Jeżeli tak:
problem = bandwidth limit
Krok 4
Jeżeli throttlingu nie ma, sprawdź:
cpu.pressure
Pytanie:
czy runnable taski czekają na CPU?
Jeżeli PSI rośnie:
problem = contention
Krok 5
Jeżeli potrzebujesz dokładności:
sched_wakeup
sched_switch
Pytanie:
jak długo taski czekają w runqueue?
10. Przykład: dwa podobne symptomy, dwie różne przyczyny
Workload A
usage = 1 CPU
limit = 1 CPU
nr_throttled szybko rośnie
throttled_usec szybko rośnie
CPU PSI = niskie
Diagnoza:
CPU throttling
Kernel ogranicza workload do skonfigurowanego bandwidthu.
Workload B
usage = 1 CPU
limit = brak
nr_throttled = 0
CPU PSI some = wysokie
runqueue latency = wysokie
Diagnoza:
CPU contention
Workload nie jest ograniczany własną quota.
Nie dostaje CPU, bo scheduler ma za dużo runnable pracy.
Na wykresie:
CPU usage = 1
oba workloady mogą wyglądać identycznie.
Przyczyna problemu jest kompletnie inna.
11. Nie zaczynaj diagnostyki od procentów
Popularny dashboard:
CPU / request = 180%
albo:
CPU / limit = 95%
jest użyteczny jako sygnał.
Ale nie jest diagnozą.
180% request może być całkowicie zdrowe.
95% limit może działać bez problemu.
50% node CPU może współistnieć z lokalnym contention.
Dobra diagnostyka nie pyta więc:
jaki procent pokazuje dashboard?
tylko:
czy proces dostał CPU?
jeśli nie:
dlaczego?
Model mentalny
Najprostszy workflow:
symptom
|
v
CPU usage
|
v
request / limit
|
v
cpu.stat
|
+--> throttling?
| |
| v
| quota problem
|
v
CPU PSI
|
+--> pressure?
|
v
contention
|
v
runqueue latency
Każda warstwa odpowiada na inne pytanie:
usage:
ile CPU dostałem?
cpu.stat:
czy kernel zatrzymał mnie przez limit?
PSI:
czy brak CPU opóźniał runnable pracę?
runqueue latency:
jak długo konkretnie czekałem?
Dopiero po przejściu tej ścieżki warto zmieniać konfigurację.
Bo „problem z CPU” może oznaczać kilka zupełnie różnych mechanizmów, a każdy z nich wymaga innego rozwiązania.