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-Typ | Spot-tauglich? | Hinweise |
|---|---|---|
| Stateless HTTP APIs | Ja | Genug Replicas fahren und PDBs setzen |
| Batch Jobs | Ja | Checkpoints nutzen; bei Unterbrechung neu starten |
| CI-Runner | Ja | Job-Level-Retry kümmert sich um Eviction |
| Dev/Staging | Ja | Offensichtlich |
| Stateful Databases | Nein | On-Demand oder managed RDS/Aurora nutzen |
| Leader-Elected Controller | Vorsicht | Mehrere 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.
| Metrik | Vorher | Nachher | Änderung |
|---|---|---|---|
| Monatliche EKS-Ausgaben | 48.200 $ | 28.900 $ | -40 % |
| Node-Anzahl (prod) | 32 | 18 | -44 % |
| Durchschn. CPU-Auslastung | 14 % | 46 % | +3,3x |
| Durchschn. Memory-Auslastung | 22 % | 61 % | +2,8x |
| Spot-Anteil | 0 % | 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:
- OpenCost installieren. Zahlen pro Namespace bekommen.
- Die zehn teuersten Workloads per VPA-Empfehlungen right-sizen.
- Karpenter mit aktivierter Konsolidierung ausrollen.
- Stateless und Batch Workloads auf Spot verschieben.
- ResourceQuotas und LimitRanges auf jedem Namespace setzen.
- 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.