Loading...
Alle Artikel
Kubernetes · 8 min read

Kubernetes-Kostenoptimierung: Reduzieren Sie Ihre Cloud-Rechnung um 40 %

Ein systematischer Ansatz zur Senkung Ihrer Kubernetes-Ausgaben: Right-Sizing mit VPA, Karpenter-Konsolidierung, Spot-Workloads, Namespace-Quotas und Showback mit OpenCost.

Warum Ihr Cluster Geld verbrennt

Das dreckige Geheimnis der Kubernetes-Ökonomie: Die meisten Cluster laufen real bei 15–25 % Auslastung, werden aber so abgerechnet, als wären sie am Anschlag. Wir haben im letzten Jahr dutzende Produktions-Cluster für Kunden auditiert, und das Muster ist identisch: Requests auf das Doppelte oder Dreifache dessen gesetzt, was der Pod je verbraucht, eine Flotte großer On-Demand-Nodes und eine Rechnung, die schneller wächst als das Team. Ein typisches Engagement senkt die Ausgaben im ersten Monat um 35–45 %, ohne eine einzige Zeile Application Code zu ändern.

Dieser Beitrag ist das Playbook, das wir nutzen. Er ist in der Reihenfolge sortiert, in der Sie vorgehen sollten – von den Änderungen mit den größten Einsparungen bis zu den Long-Tail-Optimierungen, die zählen, sobald das tief hängende Obst gepflückt ist.

Messen, bevor Sie schneiden

Sie können nicht optimieren, was Sie nicht zuordnen können. Bevor Sie einen einzigen Request anfassen, installieren Sie etwas, das Ihnen Kostentransparenz pro Namespace und Workload liefert. 2026 zählen zwei Optionen:

  • OpenCost – das CNCF-Projekt, das aus Kubecost hervorging. Open Source, herstellerneutral, integriert mit Prometheus.
  • Kubecost – der kommerzielle Build derselben Codebase mit Hosting-Optionen, Alerts und Team-Features.

Beides lässt sich mit Helm in etwa einer Stunde deployen:

helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm upgrade --install opencost opencost/opencost \
  --namespace opencost --create-namespace \
  --set opencost.exporter.defaultClusterId=prod-eu-west-1 \
  --set opencost.prometheus.internal.enabled=true

Auf Ihr Prometheus zeigen lassen, 24 Stunden auf Daten warten, dann ein Grafana-Dashboard mit Kosten pro Namespace, Idle-Kosten und Pod-Effizienz (tatsächlich / requested) bauen. Ein Namespace unter 30 % Effizienz ist ein Ziel.

Schritt 1: Requests und Limits richtig dimensionieren

Überhöhte CPU- und Memory-Requests sind der größte einzelne Kostentreiber, den wir sehen. Engineers kopieren requests aus einem alten Chart, multiplizieren mit einem Sicherheitsfaktor „nur für den Fall" und pushen. Der Cluster provisioniert pflichtschuldig Nodes, um diese Requests zu erfüllen.

VPA im Recommendation-Modus verwenden

Aktivieren Sie VPA nicht im Auto-Modus auf Stateful Workloads – er startet Pods neu, um sie neu zu dimensionieren. Nutzen Sie den Off-Modus für Empfehlungen ohne Mutation:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api-vpa
  namespace: prod
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
      - containerName: "*"
        minAllowed:
          cpu: 50m
          memory: 64Mi
        maxAllowed:
          cpu: 2
          memory: 4Gi

Nach einer Woche fragen Sie die VPA-Empfehlungen mit kubectl get vpa api-vpa -o yaml ab und vergleichen sie mit den aktuellen Requests. Die Differenz ist Ihre Einsparung.

Faustregeln

  • CPU-Requests sollten auf das p95 des realen Verbrauchs zielen. CPU ist komprimierbar – der Pod wird gedrosselt, nicht gekillt, wenn er Requests überschreitet.
  • Memory-Requests sollten auf p99 oder das beobachtete Maximum zielen. Memory ist nicht komprimierbar – das Limit zu treffen bedeutet OOMKill.
  • CPU-Limits sollten in der Regel nicht gesetzt sein. CPU-Limits unterhalb der Node-Kapazität verursachen Throttling, selbst wenn der Host idle ist. Setzen Sie sie nur für Workloads, die Sie aus Abrechnungs- oder Fairness-Gründen deckeln wollen.
  • Memory-Limits sollten Requests entsprechen, für vorhersagbares Verhalten. Das gibt dem Pod eine Guaranteed QoS Class.

Ein Pod mit sinnvoller Resource-Config

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      containers:
        - name: api
          image: ghcr.io/example/api:1.12.0
          resources:
            requests:
              cpu: 150m
              memory: 256Mi
            limits:
              memory: 256Mi
          readinessProbe:
            httpGet: { path: /ready, port: 8080 }
          livenessProbe:
            httpGet: { path: /live, port: 8080 }

Schritt 2: Cluster Autoscaler durch Karpenter ersetzen

Wenn Sie auf EKS noch Cluster Autoscaler mit vorab erstellten Node Groups nutzen, liegt Geld auf dem Tisch. Karpenter schaut auf Pending Pods, wählt den günstigsten passenden Instance-Typ und provisioniert ihn in rund 30 Sekunden. Außerdem konsolidiert er: Wird eine Node untergenutzt, verschiebt Karpenter die Pods auf eine kleinere Instance und terminiert die alte.

Minimaler NodePool mit gemischt Spot und On-Demand, capacity-optimized Allocation und aktivierter Konsolidierung:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general
spec:
  template:
    spec:
      requirements:
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ["m7i", "m7a", "c7i", "r7i"]
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values: ["nano", "micro", "small"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      expireAfter: 720h
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30s
  limits:
    cpu: "2000"
    memory: 4000Gi

Zwei Details zählen. Erstens: Wer sowohl amd64 als auch arm64 erlaubt, lässt Karpenter Graviton-Nodes wählen, wenn ein Workload sie unterstützt – Graviton ist typischerweise 15–20 % günstiger pro vCPU. Zweitens: WhenEmptyOrUnderutilized ist der neue Konsolidierungsmodus, der den Cluster aktiv schrumpft, nicht nur wenn eine Node komplett leer ist.

Schritt 3: Die richtigen Workloads auf Spot

Spot-Instances sind 60–90 % günstiger als On-Demand. Die Angst gilt der Unterbrechung: AWS gibt Ihnen zwei Minuten Vorwarnung und nimmt die Node zurück. Diese Angst ist 2026 meist überzogen – Interruption Rates in den gängigen Familien (m7i, c7i, r7i) liegen oft unter 5 % pro Monat.

Was auf Spot gehört

Workload-TypSpot-tauglich?Hinweise
Stateless HTTP APIsJaGenug Replicas fahren und PDBs setzen
Batch JobsJaCheckpoints nutzen; bei Unterbrechung neu starten
CI-RunnerJaJob-Level-Retry kümmert sich um Eviction
Dev/StagingJaOffensichtlich
Stateful DatabasesNeinOn-Demand oder managed RDS/Aurora nutzen
Leader-Elected ControllerVorsichtMehrere Replicas auf verschiedene Zonen verteilen

Verfügbarkeit mit PodDisruptionBudgets schützen

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  minAvailable: 50%
  selector:
    matchLabels:
      app: api

Kombiniert mit Topology Spread, damit Replicas nicht alle auf derselben Spot-Node landen:

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: ScheduleAnyway
    labelSelector:
      matchLabels:
        app: api

Schritt 4: Die Workloads skalieren, nicht nur die Nodes

Node-Autoscaling ist nötig, aber nicht hinreichend. Wenn ein Deployment um 3 Uhr nachts 20 Replicas hat, obwohl 4 reichen würden, bezahlen Sie Idle-Pods. HPA auf CPU-Basis ist der Default, aber die echten Einsparungen stecken in Custom Metrics.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 30
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "200"
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 25
          periodSeconds: 60

Für ereignisgetriebene Workloads (Queue Consumer, Kafka Processors) ist KEDA die richtige Antwort. Es skaliert auf externe Metriken wie SQS-Tiefe, Kafka-Lag oder Postgres-Row-Count – inklusive Scale-to-Zero.

Schritt 5: Namespace-Quotas und LimitRanges

Geben Sie jedem Team ein Budget. Ohne Quotas frisst ein fehlverhaltendes Team den ganzen Cluster und Ihre ganze Rechnung.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-growth-quota
  namespace: team-growth
spec:
  hard:
    requests.cpu: "40"
    requests.memory: 80Gi
    limits.memory: 80Gi
    pods: "100"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
  namespace: team-growth
spec:
  limits:
    - type: Container
      default:
        cpu: 200m
        memory: 256Mi
      defaultRequest:
        cpu: 100m
        memory: 128Mi
      max:
        cpu: "4"
        memory: 8Gi

Der LimitRange ist hier der stille Held – er gibt jedem Pod, der Requests und Limits vergisst, sinnvolle Defaults, damit sich kein „unlimited" einschleicht.

Schritt 6: Showback und Chargeback

Die letzten 10 % Einsparung kommen aus kulturellem Druck, nicht aus Konfiguration. Sobald OpenCost reale Zahlen liefert, schicken Sie jedem Engineering-Team eine wöchentliche Kosten-Mail. Kein Alert, kein Dashboard – eine ruhige Zusammenfassung im Stil „Euer Namespace hat letzte Woche 4.213 $ gekostet, hier sind die 5 teuersten Workloads". Teams optimieren den eigenen Code, sobald die Zahl sichtbar und ihnen zugeordnet ist.

Eine einfache PromQL-Abfrage für die Kosten pro Namespace:

sum by (namespace) (
  kubecost_namespace_monthly_cost{cluster="prod-eu-west-1"}
)

In einen geplanten Grafana-Report oder ein kleines Python-Skript verdrahten und jeden Montag versenden.

Ein echtes Vorher/Nachher

Hier ein redigiertes Ergebnis aus einem jüngeren Kundenmandat – ein Series-B-SaaS auf EKS mit rund 400 Pods über Prod-, Staging- und Dev-Cluster.

MetrikVorherNachherÄnderung
Monatliche EKS-Ausgaben48.200 $28.900 $-40 %
Node-Anzahl (prod)3218-44 %
Durchschn. CPU-Auslastung14 %46 %+3,3x
Durchschn. Memory-Auslastung22 %61 %+2,8x
Spot-Anteil0 %65 %-

Das gesamte Projekt dauerte sechs Wochen. Zwei davon Karpenter-Rollout, zwei Right-Sizing der 40 teuersten Workloads und zwei Quota-Policy und Showback-Aufbau.

Die Kurz-Checkliste

Wenn Sie in diesem Quartal sonst nichts tun:

  1. OpenCost installieren. Zahlen pro Namespace bekommen.
  2. Die zehn teuersten Workloads per VPA-Empfehlungen right-sizen.
  3. Karpenter mit aktivierter Konsolidierung ausrollen.
  4. Stateless und Batch Workloads auf Spot verschieben.
  5. ResourceQuotas und LimitRanges auf jedem Namespace setzen.
  6. Wöchentliche Kosten-Mails an die Team-Leads einrichten.

Nächste Schritte

Kostenoptimierung ist kein einmaliges Projekt – Cluster-Drift ist real, und ein sauberer Cluster wird binnen eines Quartals wieder unordentlich, wenn man ihn nicht ehrlich hält. Teams, die effizient bleiben, behandeln es wie Security: automatisierte Checks in CI, regelmäßige Audits, ein benannter Owner. Wenn Sie Unterstützung wünschen, dieses Playbook auf Ihren eigenen Cluster anzuwenden, nehmen Sie Kontakt auf.

abgelegt unter
kuberneteskubernetescloudcost-optimizationfinops
mit uns arbeiten

Soll unser Team Ihrer Infrastruktur helfen?

talk to an engineerFree 30-min discovery callBook
close