Veröffentlicht am

Kubernetes CPU Throttling finden und beheben

Teilen:
Authors

Kubernetes High CPU Troubleshooting: Performance-Probleme diagnostizieren und loesen

TL;DR

  • CPU-Throttling ist die haeufigste Ursache fuer langsame Anwendungen in Kubernetes -- Pods werden gedrosselt, sobald sie ihr CPU-Limit erreichen.
  • kubectl top pods zeigt die aktuelle CPU-Nutzung, aber nicht das Throttling. Dafuer brauchen Sie Prometheus-Metriken oder cgroup-Dateien.
  • CPU Requests bestimmen die Scheduling-Garantie, CPU Limits die harte Obergrenze. Falsch gesetzte Limits verursachen mehr Probleme als sie loesen.
  • QoS-Klassen (Guaranteed, Burstable, BestEffort) beeinflussen, welche Pods bei Node-Ueberlastung zuerst verdraengt werden.
  • Starten Sie mit grosszuegigen Limits, beobachten Sie die tatsaechliche Nutzung, und optimieren Sie dann -- nicht umgekehrt.

Wie CPU-Zuweisung in Kubernetes funktioniert

Bevor Sie CPU-Probleme debuggen koennen, muessen Sie verstehen, wie Kubernetes CPU verwaltet. CPU wird in "Millicores" gemessen: 1 CPU = 1000m. Ein Container mit cpu: 500m bekommt die Haelfte eines CPU-Kerns.

Es gibt zwei Einstellungen:

resources:
  requests:
    cpu: 250m      # Garantierte CPU -- Scheduler reserviert das
  limits:
    cpu: 500m      # Harte Obergrenze -- Container wird gedrosselt

Requests: Der Scheduler stellt sicher, dass der Node mindestens so viel CPU frei hat. Ihr Pod wird nur auf Nodes platziert, die genug freie Requests haben.

Limits: Die harte Obergrenze. Erreicht der Container sein Limit, wird er vom Linux-Kernel gedrosselt (Throttling). Er wird nicht gekillt (das passiert nur bei Memory), sondern einfach langsamer.

Symptom 1: Anwendung ist langsam (CPU Throttling)

Das Problem erkennen

Ihre Anwendung antwortet langsam, obwohl kubectl top niedrige CPU-Werte zeigt. Das ist das typische Zeichen fuer CPU-Throttling: Der Container wird regelmaessig gedrosselt, aber die durchschnittliche CPU-Nutzung sieht normal aus.

# Aktuelle CPU-Nutzung pruefen
kubectl top pods -n production --sort-by=cpu

# Ausgabe:
# NAME                       CPU(cores)   MEMORY(bytes)
# api-server-7f8b9-x4k2l    245m         128Mi
# worker-5d4f8b-n9m3p        180m         256Mi

245m sieht harmlos aus, wenn das Limit bei 500m liegt. Aber: kubectl top zeigt den Durchschnitt der letzten Minute. Wenn Ihr Container in Bursts arbeitet (z.B. bei jedem Request kurz 500m braucht), wird er staendig gedrosselt, obwohl der Durchschnitt niedrig ist.

Throttling mit Prometheus erkennen

Die entscheidende Metrik heisst container_cpu_cfs_throttled_periods_total:

# Throttling-Rate pro Container (letzte 5 Minuten)
sum(rate(container_cpu_cfs_throttled_periods_total{namespace="production"}[5m])) by (pod, container)
/
sum(rate(container_cpu_cfs_periods_total{namespace="production"}[5m])) by (pod, container)
* 100

Das Ergebnis ist ein Prozentwert. Alles ueber 25% ist problematisch und bedeutet, dass Ihr Container in einem Viertel aller Scheduling-Perioden gedrosselt wird.

# Konkrete CPU-Nutzung vs. Limit
sum(rate(container_cpu_usage_seconds_total{namespace="production", pod=~"api-server.*"}[5m])) by (pod)
/
sum(kube_pod_container_resource_limits{namespace="production", pod=~"api-server.*", resource="cpu"}) by (pod)
* 100

Throttling ohne Prometheus pruefen

Wenn Sie kein Prometheus haben, koennen Sie die cgroup-Dateien direkt lesen:

# In den Container exec'en
kubectl exec -it api-server-7f8b9-x4k2l -n production -- /bin/sh

# cgroup v2: Throttling-Statistiken lesen
cat /sys/fs/cgroup/cpu.stat
# usage_usec 1234567890
# user_usec 1000000000
# system_usec 234567890
# nr_periods 50000
# nr_throttled 12500        <-- Anzahl gedrosselter Perioden
# throttled_usec 30000000   <-- Gesamte gedrosselte Zeit in Mikrosekunden

# cgroup v1 (aeltere Cluster):
cat /sys/fs/cgroup/cpu/cpu.stat

Wenn nr_throttled staendig steigt, wird Ihr Container aktiv gedrosselt.

Fix: CPU-Limits erhoehen oder entfernen

# Vorher: Zu enges Limit
resources:
  requests:
    cpu: 250m
  limits:
    cpu: 500m

# Nachher: Grosszuegigeres Limit
resources:
  requests:
    cpu: 250m
  limits:
    cpu: "1"       # 1 voller CPU-Kern

Viele Teams entscheiden sich inzwischen dafuer, CPU-Limits ganz zu entfernen und nur Requests zu setzen. Der Grund: CPU-Throttling schadet mehr als es nuetzt. Ein Container ohne CPU-Limit kann bursten, wenn der Node freie Kapazitaet hat, und wird nur gedrosselt, wenn andere Container ihre Requests brauchen.

# Nur Requests, kein Limit
resources:
  requests:
    cpu: 250m
  # Kein CPU-Limit -- Container kann bursten

Beachten Sie: Ohne CPU-Limits erhaelt der Pod die QoS-Klasse "Burstable" statt "Guaranteed". Mehr dazu im Abschnitt zu QoS-Klassen.

Symptom 2: Pods werden evicted (Node-Ueberlastung)

Das Problem erkennen

Wenn ein Node ueberlastet ist, beginnt Kubernetes Pods zu verdraengen (Eviction). Das sehen Sie in den Events:

# Node-Auslastung pruefen
kubectl top nodes

# Ausgabe:
# NAME          CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
# node-01       3800m        95%    12Gi            85%
# node-02       3200m        80%    10Gi            71%
# node-03       3900m        97%    14Gi            99%

# Events fuer Evictions pruefen
kubectl get events --field-selector reason=Evicted -A --sort-by='.lastTimestamp'

Ursache: Noisy Neighbors

In einem Shared Cluster koennen einzelne Pods andere verdraengen. Ein Pod mit requests: 100m und limits: 4000m (oder ohne Limit) kann theoretisch 4 CPU-Kerne beanspruchen. Wenn mehrere solche Pods auf dem gleichen Node landen, wird der Node ueberlastet.

# Die groessten CPU-Verbraucher finden
kubectl top pods -A --sort-by=cpu | head -20

# Resource Requests vs. tatsaechliche Nutzung vergleichen
kubectl get pods -n production -o custom-columns=\
NAME:.metadata.name,\
CPU_REQ:.spec.containers[0].resources.requests.cpu,\
CPU_LIM:.spec.containers[0].resources.limits.cpu

Fix: Resource Quotas und Limit Ranges

Schuetzen Sie Namespaces mit ResourceQuotas:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: cpu-quota
  namespace: production
spec:
  hard:
    requests.cpu: "8"       # Maximal 8 CPU-Kerne Requests im Namespace
    limits.cpu: "16"        # Maximal 16 CPU-Kerne Limits

LimitRange setzt Defaults fuer Container, die keine Resources angeben:

apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-defaults
  namespace: production
spec:
  limits:
    - type: Container
      default:
        cpu: 500m
      defaultRequest:
        cpu: 100m
      max:
        cpu: "2"
      min:
        cpu: 50m

Symptom 3: Pods im Pending-Status (nicht schedulebar)

Wenn Ihre CPU Requests hoeher sind als die verfuegbare Kapazitaet, bleiben Pods im Pending-Status:

kubectl get pods -n production | grep Pending

kubectl describe pod pending-pod-xyz -n production
# Events:
# Warning  FailedScheduling  Insufficient cpu

Fix: Cluster skalieren oder Requests anpassen

# Verfuegbare CPU pro Node anzeigen
kubectl describe nodes | grep -A 5 "Allocated resources"

# Oder detaillierter:
kubectl get nodes -o custom-columns=\
NAME:.metadata.name,\
CPU_CAP:.status.capacity.cpu,\
CPU_ALLOC:.status.allocatable.cpu

Wenn alle Nodes voll sind, brauchen Sie entweder mehr Nodes (Cluster Autoscaler) oder muessen die Requests reduzieren. Lesen Sie dazu den Artikel zu Kubernetes Autoscaling.

QoS-Klassen verstehen

Kubernetes ordnet jeden Pod einer Quality-of-Service-Klasse zu. Diese bestimmt die Eviction-Reihenfolge bei Node-Ueberlastung:

QoS-KlasseBedingungEviction-Prioritaet
GuaranteedRequests = Limits (fuer alle Container)Zuletzt evicted
BurstableRequests gesetzt, aber ungleich LimitsMittlere Prioritaet
BestEffortKeine Requests/Limits gesetztZuerst evicted
# Guaranteed QoS
resources:
  requests:
    cpu: 500m
    memory: 256Mi
  limits:
    cpu: 500m         # Gleich wie Request
    memory: 256Mi     # Gleich wie Request

# Burstable QoS
resources:
  requests:
    cpu: 250m
    memory: 128Mi
  limits:
    cpu: 500m         # Hoeher als Request
    memory: 256Mi

# BestEffort QoS (nicht empfohlen fuer Production)
# Keine resources-Sektion
# QoS-Klasse eines Pods anzeigen
kubectl get pod my-pod -n production -o jsonpath='{.status.qosClass}'

Fuer produktionskritische Workloads empfehlen wir mindestens Burstable, besser Guaranteed. BestEffort-Pods werden bei der ersten Node-Ueberlastung verdraengt.

cgroup v2: Was sich aendert

Neuere Kubernetes-Versionen (ab 1.25+) nutzen cgroup v2 statt v1. Das aendert einige Details beim CPU-Management:

# Pruefen, ob cgroup v2 aktiv ist
kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.containerRuntimeVersion}'

# Auf dem Node selbst:
stat -f /sys/fs/cgroup/
# cgroup v2: "cgroup2fs"
# cgroup v1: "tmpfs"

Wesentliche Aenderungen mit cgroup v2:

  • CPU-Burst: Container koennen kurzfristig ueber ihr Limit bursten, wenn der Node Kapazitaet hat (Feature Gate CPUManagerPolicyBeta).
  • PSI (Pressure Stall Information): Genauere Metriken fuer CPU-Druck auf dem Node.
  • Unified Hierarchy: CPU und Memory werden in einer gemeinsamen Hierarchie verwaltet.

Right-Sizing: Die richtige Groesse finden

Schritt 1: Aktuelle Nutzung beobachten

Lassen Sie Ihre Workloads mindestens eine Woche mit grosszuegigen Limits laufen und beobachten Sie die tatsaechliche Nutzung:

# 95. Perzentil der CPU-Nutzung (letzte 7 Tage)
quantile_over_time(0.95,
  rate(container_cpu_usage_seconds_total{namespace="production", pod=~"api-server.*"}[5m])[7d:]
) * 1000

Schritt 2: Requests auf das 95. Perzentil setzen

# Empfehlung: Request = P95, Limit = P99 oder 2x Request
# P95 CPU-Nutzung: 180m
# P99 CPU-Nutzung: 350m

# Ergebnis:
# requests.cpu: 200m  (P95 aufgerundet)
# limits.cpu: 400m    (P99 aufgerundet oder 2x Request)

Schritt 3: VPA nutzen (optional)

Der Vertical Pod Autoscaler kann Empfehlungen automatisch berechnen:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api-server-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-server
  updatePolicy:
    updateMode: "Off"     # Nur Empfehlungen, kein automatisches Update
  resourcePolicy:
    containerPolicies:
      - containerName: api
        minAllowed:
          cpu: 100m
        maxAllowed:
          cpu: "2"
# VPA-Empfehlungen abrufen
kubectl get vpa api-server-vpa -n production -o yaml | grep -A 20 recommendation

Fuer eine detailliertere Anleitung zum Zusammenspiel von VPA, HPA und Cluster Autoscaler lesen Sie den Autoscaling-Guide.

App-Level CPU-Probleme finden

Nicht immer liegt das Problem bei Kubernetes. Manchmal ist die Anwendung selbst ineffizient:

Java: GC-Overhead

# Heap-Dump erstellen
kubectl exec api-server-7f8b9-x4k2l -n production -- jcmd 1 GC.heap_info

# Thread-Dump fuer CPU-Hotspots
kubectl exec api-server-7f8b9-x4k2l -n production -- jcmd 1 Thread.print
# JVM-Flags fuer Container setzen
env:
  - name: JAVA_OPTS
    value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:ActiveProcessorCount=2"

Node.js: Event Loop Blocking

# CPU-Profiling mit Node.js
kubectl exec api-server-7f8b9-x4k2l -n production -- node --prof /app/index.js

Allgemein: Process-Profiling im Container

# Top-Prozesse im Container
kubectl exec api-server-7f8b9-x4k2l -n production -- top -bn1 -o %CPU | head -15

# Oder mit ps
kubectl exec api-server-7f8b9-x4k2l -n production -- ps aux --sort=-%cpu | head -10

Monitoring-Setup fuer CPU-Probleme

Richten Sie diese Prometheus-Alerts ein, um CPU-Probleme fruehzeitig zu erkennen:

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: cpu-alerts
  namespace: monitoring
spec:
  groups:
    - name: cpu-alerts
      rules:
        - alert: HighCPUThrottling
          expr: |
            sum(rate(container_cpu_cfs_throttled_periods_total{namespace="production"}[5m])) by (pod, container)
            /
            sum(rate(container_cpu_cfs_periods_total{namespace="production"}[5m])) by (pod, container)
            > 0.25
          for: 15m
          labels:
            severity: warning
          annotations:
            summary: "Container {{ $labels.pod }}/{{ $labels.container }} wird zu mehr als 25% gedrosselt"

        - alert: HighNodeCPU
          expr: |
            instance:node_cpu_utilisation:rate5m > 0.90
          for: 30m
          labels:
            severity: critical
          annotations:
            summary: "Node {{ $labels.instance }} hat ueber 90% CPU-Auslastung"

        - alert: CPURequestsOvercommit
          expr: |
            sum(kube_pod_container_resource_requests{resource="cpu"}) by (node)
            /
            sum(kube_node_status_allocatable{resource="cpu"}) by (node)
            > 1.0
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "Node {{ $labels.node }}: CPU Requests uebersteigen die verfuegbare Kapazitaet"

Fuer ein umfassendes Monitoring-Setup lesen Sie den Artikel zu Kubernetes Monitoring und Observability.

Checkliste: Kubernetes High CPU Troubleshooting

# 1. Ueberblick verschaffen
kubectl top nodes
kubectl top pods -A --sort-by=cpu | head -20

# 2. Betroffenen Pod identifizieren
kubectl top pods -n production --sort-by=cpu

# 3. Resource-Konfiguration pruefen
kubectl get pod my-pod -n production -o jsonpath='{.spec.containers[0].resources}'

# 4. QoS-Klasse pruefen
kubectl get pod my-pod -n production -o jsonpath='{.status.qosClass}'

# 5. Node-Kapazitaet pruefen
kubectl describe node my-node | grep -A 10 "Allocated resources"

# 6. Events pruefen (Evictions, FailedScheduling)
kubectl get events -n production --sort-by='.lastTimestamp' | grep -i cpu

# 7. Throttling pruefen (im Container)
kubectl exec my-pod -n production -- cat /sys/fs/cgroup/cpu.stat

Verwandte Artikel


CPU-Probleme kosten Performance und Geld. Wenn Sie Unterstuetzung beim Right-Sizing Ihrer Kubernetes-Cluster brauchen oder wiederkehrende Performance-Probleme haben, helfen wir Ihnen gerne. Kontaktieren Sie uns fuer eine kostenlose Erstberatung.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen