Kubernetes ~4 Min.

Karpenter: Consolidation und Spot

Nodes schneller drehen, Kosten runter — ohne Überraschungen.

von PlatPrep

Der Cloud-Bill-Review im Quartal hatte eine Grafik, die jeder sofort verstand: Die Cluster-Nodes waren konstant zu etwa 25 Prozent ausgelastet. Zwanzig Nodes, von denen fünfzehn im Wesentlichen leer liefen. Cluster Autoscaler hatte sie hochgefahren und dann nicht wieder abgebaut, weil ein paar verteilte Pods verhinderten, dass einzelne Nodes komplett leer wurden.

Karpenters Consolidation-Feature ist für genau dieses Problem gebaut.

Anders denken: NodePools statt Node Groups

Der Grundunterschied zu Cluster Autoscaler: Karpenter arbeitet nicht mit vordefinierten Node Groups fester Instanzgröße, sondern provisioniert Nodes direkt aus Pod-Anforderungen. Ein NodePool beschreibt, was erlaubt ist — Instance-Familien, Architekturen, Capacity-Typen — und Karpenter wählt beim Scheduling-Bedarf die günstigste passende Instanz:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 5m

Pending Pods lösen direkt einen API-Call an den Cloud-Provider aus — kein Warten auf eine Auto-Scaling-Group. Neue Kapazität steht typischerweise in unter einer Minute bereit statt in mehreren.

Consolidation: der eigentliche Sparhebel

Consolidation heißt: Karpenter prüft kontinuierlich, ob Nodes leer sind oder ob ihre Pods auf weniger bzw. günstigere Nodes passen würden — und räumt dann um. WhenEmpty entfernt nur komplett leere Nodes, WhenEmptyOrUnderutilized packt aktiv um. Genau dieser zweite Modus hätte die fünfzehn 25-Prozent-Nodes vom Anfang auf vier, fünf gut gefüllte reduziert.

Der Preis: Umräumen bedeutet Pod-Evictions, und zwar laufend, nicht nur bei Upgrades. Workloads müssen das aushalten — PodDisruptionBudgets werden respektiert und sind damit Pflicht, genau wie korrekte Readiness Probes und Graceful Shutdown (dieselben Hausaufgaben wie bei Zero-Downtime-Upgrades). Für Workloads, die wirklich nicht unterbrochen werden dürfen — lange Batch-Jobs, Stateful-Sonderfälle — gibt es die Annotation karpenter.sh/do-not-disrupt: "true" am Pod. Und damit Consolidation nicht zur Unruhequelle wird, lassen sich Disruption-Budgets am NodePool definieren: maximal x Prozent der Nodes gleichzeitig, oder Zeitfenster, in denen gar nicht konsolidiert wird.

Consolidation ist dabei nur einer von mehreren Disruption-Gründen. Karpenter erkennt auch Drift: Ändert sich die NodePool-Definition oder das AMI in der EC2NodeClass, gelten bestehende Nodes als veraltet und werden kontrolliert ersetzt — Node-Konfiguration wird damit deklarativ wie alles andere im Cluster. Und mit expireAfter bekommst du eine maximale Node-Lebensdauer: Nach z. B. 30 Tagen wird jeder Node turnusmäßig ausgetauscht, was OS-Patching nebenbei erledigt und verhindert, dass sich “ewige” Nodes mit undokumentiertem Zustand ansammeln.

Was in der Praxis schiefgeht: Ein Team setzt do-not-disrupt großzügig auf alles, “zur Sicherheit”. Damit ist Consolidation faktisch tot, die Kosten bleiben hoch, und es merkt monatelang niemand. Die Annotation gehört auf begründete Ausnahmen, nicht in das Standard-Deployment-Template.

Spot ohne Drama

Spot-Instanzen kosten je nach Markt 60–90 Prozent weniger als On-Demand, können aber mit zwei Minuten Vorwarnung terminiert werden. Karpenter spielt hier seine Stärke aus: Statt “ich brauche m5.large als Spot” heißt die Anforderung “2 vCPU, 8 GB RAM, Spot bevorzugt” — und Karpenter wählt aus dem gesamten passenden Instance-Type-Spektrum. Je breiter die erlaubte Auswahl, desto besser die Spot-Verfügbarkeit und desto seltener Unterbrechungen, weil unbeliebtere Instanztypen seltener zurückgefordert werden. Wer die Requirements auf zwei Lieblingstypen einschränkt, sabotiert genau diesen Mechanismus.

Die Interruption-Behandlung läuft auf AWS über eine SQS-Queue, die EC2-Events (Spot-Warnung, Rebalance-Empfehlung, Wartung) empfängt. Kommt die Zwei-Minuten-Warnung, cordoned und drained Karpenter den Node sofort und beschafft parallel Ersatz. Zwei Minuten reichen für sauber gebaute Stateless-Workloads — nicht aber für Pods mit 300 Sekunden terminationGracePeriodSeconds oder langen In-Flight-Jobs. Spot-Tauglichkeit ist eine Workload-Eigenschaft, keine Infrastruktur-Einstellung.

Ein Setup, das sich bewährt hat

  • System-NodePool: On-Demand, mit Taint, nur für kritische Cluster-Komponenten (CoreDNS, Ingress, Monitoring). Hier wird nicht experimentiert.
  • Default-NodePool: Spot und On-Demand gemischt, breite Instance-Auswahl, WhenEmptyOrUnderutilized mit Disruption-Budget.
  • GPU-NodePool: eigene Requirements und Taints, damit nicht versehentlich ein Web-Service auf einer teuren GPU-Instanz landet.

Dazu gehört Messbarkeit: Ohne saubere Kosten-Attribution per Labels weißt du nicht, ob die Konsolidierung wirkt — wie das geht, steht in FinOps mit Labels und Showback.

Realistische Erwartung: 30–50 Prozent Ersparnis gegenüber einem unoptimierten Cluster-Autoscaler-Setup sind ein typischer Korridor — der größere Teil kommt meist aus Spot, der Rest aus Consolidation und besserem Bin-Packing. Der Setup-Aufwand liegt bei wenigen Tagen, der laufende Aufwand bei gelegentlichem Nachjustieren von Budgets und Requirements. Im Interview zählt weniger die Zahl als die Begründung: Wer erklären kann, warum breite Instance-Diversität die Spot-Stabilität erhöht und welche Workload-Voraussetzungen Consolidation braucht, zeigt, dass er das System verstanden hat. Schritt für Schritt aufgebaut wird das im Karpenter-Lernpfad.