Konrad Kowalski (rootsher)Principal Platform & Reliability Architect010110110111101101101010101011101100100100101101

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:

text
CPU usage

i jednego pytania:

text
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:

text
może zwiększmy limit

Tylko od rozdzielenia czterech różnych zjawisk:

text
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:

text
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:

text
1.0

to workload zużył około:

text
1 CPU-second

czyli średnio:

text
1 CPU

w tym przedziale.

Typowo używamy więc:

promql
rate(container_cpu_usage_seconds_total[5m])

Jeżeli wynik wynosi:

text
0.5

to workload zużywa średnio:

text
0.5 CPU

czyli:

text
500m

Jeżeli:

text
2.4

to około:

text
2.4 CPU

To pierwszy fakt, którego potrzebujemy:

text
ile CPU rzeczywiście wykonano?

2. Porównaj usage z requestem i limitem, ale nie wyciągaj jeszcze wniosków

Załóżmy:

text
request = 500m
limit   = 2 CPU
usage   = 1.4 CPU

To oznacza:

text
usage > request

ale nadal:

text
usage < limit

Nie ma w tym nic podejrzanego.

Workload po prostu burstuje powyżej requestu.

Request nie jest sufitem.

Znacznie ciekawsza sytuacja:

text
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:

text
cpu.stat

może zawierać:

text
usage_usec
user_usec
system_usec
nr_periods
nr_throttled
throttled_usec

Najważniejsze dla throttlingu:

text
nr_throttled
throttled_usec

Jeżeli:

text
nr_throttled

rośnie, workload regularnie wykorzystuje dostępny CPU bandwidth.

Jeżeli:

text
throttled_usec

również szybko rośnie, kernel faktycznie blokuje dalsze wykonywanie z powodu wyczerpanej quota.

Conceptually:

text
task runnable
     |
     v
quota exhausted
     |
     v
throttled

To bardzo konkretna diagnoza.

Nie:

text
CPU chyba za wysokie

tylko:

text
workload jest zatrzymywany przez CPU bandwidth controller

4. Brak throttlingu nie oznacza braku problemu CPU

Załóżmy:

text
nr_throttled = 0

Czy możemy powiedzieć:

text
CPU jest zdrowe

Nie.

Workload może nie mieć limitu:

text
cpu.max = max ...

i nadal cierpieć przez contention.

Przykład:

text
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:

text
czy workload czeka na CPU?

5. Sprawdź CPU PSI

Tutaj wchodzi:

text
cpu.pressure

Na poziomie systemu:

text
/proc/pressure/cpu

albo na poziomie cgroup:

text
cpu.pressure

Interesuje nas przede wszystkim:

text
some

Przykład:

text
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:

text
workload miał runnable pracę,
ale nie dostał CPU od razu

To już jest sygnał contention.

Usage i PSI odpowiadają na inne pytania:

text
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

text
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

text
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:

text
ktoś czeka

ale nie mówi dokładnie:

text
jak długo konkretny task czeka na scheduler

Do tego potrzebujemy obserwacji schedulera.

Kluczowe zdarzenia to:

text
sched_wakeup
sched_switch

Conceptually:

text
sched_wakeup
    |
    | task staje się runnable
    |
    v
    ... czeka ...
    |
    v
sched_switch
    |
    | task zostaje running

Różnica czasu między tymi momentami daje nam:

text
runqueue latency

Przykład:

text
wakeup:  12:00:00.100
running: 12:00:00.107

czyli:

text
7 ms scheduler wait

To już bezpośrednia odpowiedź na pytanie:

text
czy latency aplikacji rośnie dlatego,
że task czeka na CPU?

8. kubectl top to tylko początek

Polecenie:

bash
kubectl top pod

jest wygodne.

Ale pokazuje przede wszystkim zagregowane usage.

Możemy zobaczyć:

text
NAME       CPU
api-123    780m

I tyle.

Nie wiemy z tego:

text
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:

text
kto aktualnie zużywa CPU

ale słabym narzędziem do odpowiedzi:

text
dlaczego aplikacja ma problem z CPU

9. Minimalny workflow diagnostyczny

Załóżmy symptom:

text
p99 latency wzrosło

Nie zaczynaj od zmiany requestów czy limitów.

Idź kolejno.

Krok 1

Sprawdź:

text
CPU usage

Pytanie:

text
ile CPU workload faktycznie zużywa?

Krok 2

Sprawdź:

text
request
limit

Pytanie:

text
czy usage zbliża się do limitu?

Krok 3

Sprawdź:

text
cpu.stat

Pytanie:

text
czy workload jest throttlowany?

Jeżeli tak:

text
problem = bandwidth limit

Krok 4

Jeżeli throttlingu nie ma, sprawdź:

text
cpu.pressure

Pytanie:

text
czy runnable taski czekają na CPU?

Jeżeli PSI rośnie:

text
problem = contention

Krok 5

Jeżeli potrzebujesz dokładności:

text
sched_wakeup
sched_switch

Pytanie:

text
jak długo taski czekają w runqueue?

10. Przykład: dwa podobne symptomy, dwie różne przyczyny

Workload A

text
usage = 1 CPU
limit = 1 CPU

nr_throttled szybko rośnie
throttled_usec szybko rośnie

CPU PSI = niskie

Diagnoza:

text
CPU throttling

Kernel ogranicza workload do skonfigurowanego bandwidthu.

Workload B

text
usage = 1 CPU
limit = brak

nr_throttled = 0

CPU PSI some = wysokie
runqueue latency = wysokie

Diagnoza:

text
CPU contention

Workload nie jest ograniczany własną quota.

Nie dostaje CPU, bo scheduler ma za dużo runnable pracy.

Na wykresie:

text
CPU usage = 1

oba workloady mogą wyglądać identycznie.

Przyczyna problemu jest kompletnie inna.


11. Nie zaczynaj diagnostyki od procentów

Popularny dashboard:

text
CPU / request = 180%

albo:

text
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:

text
jaki procent pokazuje dashboard?

tylko:

text
czy proces dostał CPU?

jeśli nie:
dlaczego?

Model mentalny

Najprostszy workflow:

text
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:

text
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.