Dlaczego koszty Kubernetes wymykają się spod kontroli
Kubernetes obiecuje efektywność, elastyczność i automatyzację. Brzmi świetnie, prawda? Ale w praktyce dla wielu organizacji stał się swoistą „czarną dziurą" kosztową. Najnowsze dane branżowe z 2026 roku mówią wprost: ponad 68% organizacji przepłaca za Kubernetes o 20–40%, a niemal co drugi kontener wykorzystuje mniej niż jedną trzecią zarezerwowanych zasobów CPU i pamięci. To sporo zmarnowanych pieniędzy.
Problem narasta wraz ze skalą. Gdy klastry rosną, zespoły dodają kolejne node'y „na wszelki wypadek", deweloperzy ustawiają zawyżone requesty zasobów, a osierocone deploymenty i nieużywane namespace'y generują koszty miesiącami — i nikt tego nie zauważa. W efekcie rachunek za chmurę rośnie szybciej niż faktyczne obciążenie.
A identyfikacja źródeł marnotrawstwa w wielowarstwowej architekturze Kubernetes? Znacznie trudniejsza niż przy tradycyjnych maszynach wirtualnych.
W tym przewodniku pokażę konkretne, sprawdzone strategie obniżenia kosztów Kubernetes o 40–70% — od prawidłowego ustawiania requestów i limitów zasobów, przez konfigurację autoskalerów, po wykorzystanie instancji Spot i wdrożenie narzędzi do monitorowania kosztów. Każda sekcja zawiera gotowe konfiguracje YAML, więc możesz od razu zaadaptować je do swoich klastrów.
Główne źródła marnotrawstwa w Kubernetes
Zanim przejdziemy do rozwiązań, warto zrozumieć, skąd w ogóle bierze się to nadmierne wydawanie w klastrach Kubernetes. Z mojego doświadczenia — pięć źródeł pojawia się praktycznie zawsze.
Przewymiarowanie zasobów (overprovisioning)
To zdecydowanie najkosztowniejszy problem. Deweloperzy ustawiają requesty CPU i pamięci z ogromnym zapasem — co jest w sumie zrozumiałe, bo nikt nie chce być osobą, która spowodowała awarię o 2 w nocy przez OOM Kill. Ale te indywidualne decyzje „na wszelki wypadek" sumują się w skali setek podów do masowego, systemowego marnotrawstwa.
Typowy audyt w 2026 roku ujawnia, że 80–90% podów ma zawyżone requesty CPU i pamięci. Serio, to prawie każdy pod.
Bezczynne zasoby (idle resources)
Klastry deweloperskie i testowe działające 24/7, choć nikt nie pracuje w nocy ani w weekendy. Zapomniane Persistent Volume Claims, nieużywane Load Balancery, porzucone namespace'y — to wszystko generuje cichy, stały koszt, który łatwo przeoczyć.
Osierocone i „zombie" zasoby
Kiedy projekt się kończy, często zostają po nim zasoby: dyski, snapshoty, stare deploymenty, bazy danych niepodłączone do żadnego serwisu. Bez regularnych przeglądów potrafią działać miesiącami. Znam przypadek, gdzie zapomniany klaster testowy generował koszty przez ponad pół roku, zanim ktokolwiek to zauważył.
Nieefektywne bin-packing node'ów
Gdy requesty podów nie są dobrze dopasowane do pojemności node'ów, scheduler Kubernetes nie jest w stanie efektywnie „upakować" podów. Rezultat? Nowe node'y są uruchamiane, mimo że istniejące mają wolną pojemność — tyle że scheduler tego nie widzi, bo requesty „rezerwują" zasoby, które nie są faktycznie wykorzystywane. To trochę jak rezerwowanie całego stolika w restauracji dla jednej osoby.
Koszty ruchu sieciowego (egress)
Transfer danych między regionami, strefami dostępności, a szczególnie na zewnątrz chmury, generuje znaczące koszty. W architekturach mikroserwisowych, gdzie serwisy komunikują się intensywnie, te opłaty potrafią stanowić 10–15% całkowitego rachunku za klaster. Wielu administratorów dowiaduje się o tym dopiero na etapie analizy faktury.
Right-sizing: prawidłowe ustawianie requestów i limitów
Right-sizing to absolutny fundament optymalizacji kosztów Kubernetes — i szczerze mówiąc, to punkt, od którego zawsze warto zacząć. Kubernetes używa requestów do rezerwacji minimalnych zasobów na node'dzie (i podejmowania decyzji schedulingowych), a limitów do ograniczenia maksymalnego zużycia. Gdy requesty są zawyżone, node'y wyglądają na „pełne", mimo że pody faktycznie zużywają ułamek zarezerwowanych zasobów.
Jak działają requesty i limity
Kilka kluczowych zasad, które warto mieć z tyłu głowy:
- Limity CPU są egzekwowane przez throttling — kernel ogranicza dostęp kontenera do procesora, gdy zbliża się do limitu. To twardy limit, którego kontener nie może przekroczyć.
- Limity pamięci są egzekwowane przez OOM Kill — gdy kontener próbuje użyć więcej pamięci niż limit i kernel wykryje presję pamięciową, proces zostaje zabity. Bezlitośnie.
- Jeśli ustawisz limit, ale nie ustawisz requestu, Kubernetes automatycznie kopiuje wartość limitu jako request — co prowadzi do jeszcze większego marnotrawstwa. To częsty błąd, o którym wiele osób nie wie.
Praktyczna konfiguracja requestów i limitów
Poniżej przykład prawidłowo skonfigurowanego Deploymentu z realistycznymi requestami i limitami opartymi na faktycznym monitoringu:
# right-sized-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-service
template:
metadata:
labels:
app: api-service
spec:
containers:
- name: api
image: myapp/api:v2.1
resources:
requests:
cpu: "250m" # bazowane na P50 zużycia
memory: "256Mi" # bazowane na stabilnym zużyciu
limits:
cpu: "500m" # 2x request dla obsługi skoków
memory: "512Mi" # 2x request jako bufor bezpieczeństwa
Wymuszanie standardów przez LimitRange
Żeby zapobiec sytuacjom, w których deweloperzy wdrażają pody bez requestów (albo z absurdalnie zawyżonymi wartościami), warto użyć obiektu LimitRange:
# limitrange-defaults.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: default-resource-limits
namespace: production
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "2"
memory: "4Gi"
min:
cpu: "50m"
memory: "64Mi"
Kontrola budżetu przez ResourceQuota
Na poziomie namespace'u możesz ograniczyć łączne zużycie zasobów przez wszystkie pody. To taki „sufit" budżetowy dla zespołu:
# resource-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-alpha-quota
namespace: team-alpha
spec:
hard:
requests.cpu: "8"
requests.memory: "16Gi"
limits.cpu: "16"
limits.memory: "32Gi"
pods: "50"
persistentvolumeclaims: "10"
Dzięki temu zespół „alpha" nie przekroczy ustalonego budżetu zasobów — nawet jeśli postanowi wdrożyć dodatkowe serwisy w swoim namespace.
Autoskalowanie: HPA, VPA i Cluster Autoscaler
Kubernetes oferuje trzy mechanizmy autoskalowania, które działają na różnych poziomach infrastruktury. Dobrze skonfigurowane potrafią drastycznie obniżyć koszty. Źle skonfigurowane? No cóż, mogą je podnieść.
Horizontal Pod Autoscaler (HPA)
HPA automatycznie skaluje liczbę replik podów na podstawie zużycia CPU, pamięci lub metryk niestandardowych. To najczęściej używany autoskaler i idealny wybór dla bezstanowych serwisów webowych.
# hpa-configuration.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-service-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-service
minReplicas: 2
maxReplicas: 15
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 5 min przed skalowaniem w dół
policies:
- type: Percent
value: 25
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 50
periodSeconds: 60
Zwróć uwagę na sekcję behavior. Konfiguracja okna stabilizacji (stabilizationWindowSeconds) zapobiega nadmiernemu skalowaniu w dół po krótkotrwałych spadkach ruchu. Bez tego HPA potrafi zbyt agresywnie usuwać repliki, a potem przy kolejnym skoku obciążenia masz problem — za mało podów, za wolna odpowiedź, nerwowy PagerDuty.
Vertical Pod Autoscaler (VPA)
VPA automatycznie dostosowuje requesty i limity CPU oraz pamięci dla poszczególnych podów. Świetnie sprawdza się przy obciążeniach stanowych, gdzie skalowanie horyzontalne nie wchodzi w grę.
# vpa-configuration.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-service-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-service
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: api
minAllowed:
cpu: "100m"
memory: "128Mi"
maxAllowed:
cpu: "2"
memory: "4Gi"
controlledResources: ["cpu", "memory"]
Ważna uwaga: nie łącz VPA i HPA, gdy oba skalują na podstawie tych samych metryk (CPU/pamięć). To prosta droga do niestabilności. Jeśli potrzebujesz obu autoskalerów jednocześnie, skonfiguruj HPA na metryki niestandardowe (np. liczba żądań HTTP na sekundę), a VPA zostaw na CPU/pamięć.
Jeśli dopiero zaczynasz przygodę z VPA, zdecydowanie polecam ustawić updateMode: "Off". W tym trybie VPA tylko generuje rekomendacje, nie modyfikując podów. Dzięki temu zbierzesz dane i zobaczysz, co VPA chciałby zmienić, zanim pozwolisz mu działać automatycznie.
Cluster Autoscaler
Cluster Autoscaler operuje na poziomie node'ów — dodaje nowe, gdy pody nie mogą zostać zaschedulowane z powodu braku zasobów, i usuwa te niedostatecznie wykorzystywane. W połączeniu z HPA i VPA tworzy kompletny system autoskalowania od poziomu poda po infrastrukturę.
Kluczowe ustawienia Cluster Autoscalera, na które warto zwrócić uwagę:
- scale-down-utilization-threshold — node'y z wykorzystaniem poniżej tego progu (domyślnie 50%) są kandydatami do usunięcia
- scale-down-delay-after-add — czas oczekiwania po dodaniu node'a przed rozpoczęciem skalowania w dół (domyślnie 10 min)
- max-node-provision-time — maksymalny czas na uruchomienie nowego node'a
Instancje Spot w Kubernetes: oszczędności 60–90%
No dobrze, a teraz coś, co może naprawdę odmienić twój rachunek za chmurę. Instancje Spot (AWS), Spot VM (Azure) i Preemptible/Spot VM (GCP) oferują dostęp do niewykorzystanej pojemności chmury z rabatami 60–90%. Tak, dobrze czytasz — do 90% taniej.
Jest jednak haczyk. Dostawca może odebrać te zasoby z krótkim ostrzeżeniem: 2 minuty na AWS, 30 sekund do 2 minut na Azure i GCP. Ale Kubernetes (na szczęście) doskonale radzi sobie z tą sytuacją dzięki natywnym mechanizmom taints, tolerations i node affinity.
Konfiguracja Spot node pool z taintami
# Taint na node'ach Spot (ustawiany automatycznie przez cloud provider)
# node-type=spot:NoSchedule
# spot-tolerant-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
namespace: production
spec:
replicas: 8
selector:
matchLabels:
app: batch-processor
template:
metadata:
labels:
app: batch-processor
spec:
tolerations:
- key: "node-type"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 90
preference:
matchExpressions:
- key: node-type
operator: In
values:
- spot
- weight: 10
preference:
matchExpressions:
- key: node-type
operator: In
values:
- on-demand
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: batch-processor
topologyKey: kubernetes.io/hostname
containers:
- name: processor
image: myapp/batch:v1.5
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1"
memory: "2Gi"
Ta konfiguracja robi trzy rzeczy naraz:
- Toleracja taintu Spot — pod może działać na node'ach Spot
- Preferencja node affinity — scheduler preferuje node'y Spot (waga 90), ale w razie czego użyje On-Demand jako fallbacku (waga 10)
- Pod anti-affinity — repliki rozkładają się na różne node'y, więc jedno przerwanie Spot nie zabije ci wszystkich podów naraz
Kiedy używać instancji Spot, a kiedy nie
Idealne dla Spot: przetwarzanie batchowe, zadania CI/CD, bezstanowe API z wieloma replikami, trenowanie modeli ML (z checkpointingiem!), środowiska deweloperskie i testowe.
Nie nadają się na Spot: bazy danych, serwisy stanowe z pojedynczą repliką, systemy krytyczne bez redundancji. Tutaj nie warto ryzykować.
Sprawdzona strategia dla produkcji wygląda mniej więcej tak: 60–70% pojemności bazowej na instancjach Reserved/Savings Plans, 20–30% na Spot dla obciążeń tolerujących przerwania i 10% On-Demand jako bufor awaryjny. To daje dobre połączenie oszczędności z niezawodnością.
Monitorowanie kosztów: OpenCost i Kubecost
Bez widoczności kosztów na poziomie namespace'u, deploymentu i poda, optymalizacja jest jak strzelanie na ślepo. Szczerze? Nie da się optymalizować czegoś, czego nie mierzysz. W 2026 roku dwa narzędzia dominują ten segment: OpenCost (projekt CNCF, darmowy) i Kubecost (komercyjny, zbudowany na bazie OpenCost).
Instalacja OpenCost
OpenCost wymaga Prometheusa jako backendu do zbierania metryk. Instalacja jest dość prosta:
# 1. Instalacja Prometheus
helm install prometheus \
--repo https://prometheus-community.github.io/helm-charts prometheus \
--namespace prometheus-system --create-namespace \
--set prometheus-pushgateway.enabled=false \
--set alertmanager.enabled=false \
-f https://raw.githubusercontent.com/opencost/opencost/develop/kubernetes/prometheus/extraScrapeConfigs.yaml
# 2. Instalacja OpenCost
helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm repo update
helm install opencost opencost/opencost \
--namespace opencost --create-namespace
# 3. Dostęp do dashboardu
kubectl port-forward -n opencost svc/opencost 9090:9090
OpenCost rozbija koszty na poziomie deploymentu, poda, node'a, serwisu, namespace'u i etykiet niestandardowych. Wspiera AWS, Azure i GCP — integrując się z API rozliczeniowymi dostawców w celu dynamicznego wyceniania zasobów.
Instalacja Kubecost
# Instalacja Kubecost
helm repo add kubecost https://kubecost.github.io/cost-analyzer/
helm repo update
kubectl create namespace kubecost
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost
# Dostęp do dashboardu
kubectl port-forward -n kubecost svc/kubecost-cost-analyzer 9090:9090
Kubecost rozszerza OpenCost o kilka naprawdę przydatnych funkcji: uwzględnianie rabatów i Reserved Instances w kalkulacji kosztów, alerty i governance, widok wielu klastrów w jednym panelu oraz rekomendacje optymalizacyjne. Jeśli twoja organizacja korzysta z wynegocjowanych rabatów (a na pewno powinna), Kubecost da ci dokładniejszy obraz rzeczywistych kosztów.
Kluczowe metryki do monitorowania
Niezależnie od wybranego narzędzia, w 2026 roku najskuteczniejsze zespoły FinOps regularnie śledzą te wskaźniki:
- Koszt per namespace — pozwala alokować koszty do konkretnych zespołów
- Koszt per aplikacja — identyfikuje najdroższe serwisy (często zaskakujące wyniki)
- Wykorzystanie CPU i pamięci vs. requesty — ujawnia overprovisioning
- Procent bezczynnych zasobów — bezpośredni miernik marnotrawstwa
- Koszt per żądanie/transakcja — łączy koszty z wartością biznesową, co jest kluczowe dla rozmów z zarządem
Praktyki FinOps dla zespołów Kubernetes
Narzędzia to tylko część rozwiązania. Trwała optymalizacja kosztów wymaga zmian organizacyjnych i procesowych — czyli podejścia FinOps. I tu zaczyna się najtrudniejsza część, bo chodzi o zmianę kultury, nie technologii.
Obowiązkowe tagowanie zasobów
Zasada jest brutalna w swojej prostocie: jeśli zasób nie jest otagowany, nie da się go zoptymalizować. Wymuś stosowanie etykiet na każdym deploymencie:
# Wymagane etykiety na każdym zasobie Kubernetes
metadata:
labels:
app: "api-gateway"
team: "platform"
env: "production"
cost-center: "CC-1234"
owner: "jan.kowalski"
Użyj narzędzi takich jak OPA (Open Policy Agent) lub Kyverno, aby automatycznie blokować deploymenty bez wymaganych etykiet na etapie admission control. Tak — blokować, nie tylko ostrzegać. Ostrzeżenia ludzie ignorują.
Harmonogramowanie środowisk nieprodukcyjnych
Środowiska deweloperskie i testowe zazwyczaj nie potrzebują działać 24/7. To chyba najbardziej oczywista optymalizacja, a mimo to zaskakująco wiele firm jej nie stosuje. Skalowanie tych środowisk do zera w nocy i w weekendy może przynieść 65–70% oszczędności na tych klastrach.
Użyj CronJobów lub narzędzi takich jak kube-downscaler do automatycznego skalowania — skonfiguruj raz i zapomnij.
Regularne przeglądy kosztów
Skuteczny cykl optymalizacji w 2026 roku wygląda mniej więcej tak:
- Ciągły — automatyczne narzędzia identyfikują i eliminują oczywiste marnotrawstwo (osierocone zasoby, puste namespace'y)
- Tygodniowy — zespół platformowy przegląda trendy kosztowe podczas regularnych spotkań operacyjnych
- Kwartalny — głęboka analiza wzorców, korekta zobowiązań Reserved Instances/Savings Plans, rewizja całej strategii
Shift-left: koszty w pipeline CI/CD
Integracja danych kosztowych w narzędziach, z których deweloperzy i tak korzystają — pipeline'ach CI/CD, dashboardach monitoringowych, workflow'ach GitOps — pomaga budować świadomość kosztową od samego początku. Zamiast informować zespoły o przekroczeniu budżetu po fakcie, pokaż im szacowany koszt deploymentu przed wdrożeniem na produkcję. To naprawdę zmienia podejście ludzi do requestów zasobów.
Podsumowanie: plan działania
Optymalizacja kosztów Kubernetes to nie jednorazowy projekt, ale ciągły proces. Wymaga połączenia automatyzacji, monitoringu, governance i (co może najważniejsze) zmiany kultury organizacyjnej. Oto konkretny plan wdrożenia, który sprawdza się w praktyce:
- Tydzień 1–2: Wdróż OpenCost lub Kubecost i uzyskaj widoczność kosztów per namespace i deployment — bez tego wszystko inne to zgadywanie
- Tydzień 3–4: Przeprowadź audyt requestów i limitów — użyj VPA w trybie
Offdo zbierania rekomendacji - Miesiąc 2: Wdróż LimitRange i ResourceQuota, skonfiguruj HPA dla kluczowych serwisów
- Miesiąc 3: Dodaj Spot node pool dla obciążeń tolerujących przerwania, wdróż kube-downscaler dla środowisk nieprodukcyjnych
- Kwartał 2: Zintegruj koszty z pipeline CI/CD, wdróż polityki tagowania przez OPA/Kyverno
Organizacje, które konsekwentnie stosują te strategie, raportują redukcję kosztów Kubernetes o 40–70% — bez kompromisów w wydajności ani dostępności aplikacji. A to nie jest mała kwota, gdy mówimy o rachunkach za chmurę rzędu tysięcy (albo dziesiątek tysięcy) dolarów miesięcznie.
FAQ — Najczęściej zadawane pytania
Jak sprawdzić, ile kosztuje mój klaster Kubernetes?
Najprościej? Użyj narzędzi takich jak OpenCost (darmowy, projekt CNCF) lub Kubecost (freemium) — oba integrują się z API rozliczeniowymi AWS, Azure i GCP i rozbijają koszty na poziomie namespace'u, deploymentu i poda. Alternatywnie, natywne narzędzia dostawców (AWS Cost Explorer, Azure Cost Management, GCP Billing Reports) pokazują koszty na poziomie klastra, ale bez granularności na poziomie podów.
Czy mogę łączyć HPA i VPA jednocześnie?
Tak, ale pod jednym warunkiem — nie mogą skalować na podstawie tych samych metryk. Sprawdzona konfiguracja wygląda tak: HPA skaluje repliki na podstawie metryk niestandardowych (np. liczba żądań HTTP/s), a VPA dostosowuje requesty CPU i pamięci. Jeśli oba autoskalery reagują na CPU/pamięć jednocześnie, będą wchodzić w konflikt i powodować niestabilność. A tego nikt nie chce na produkcji.
Ile mogę zaoszczędzić, używając instancji Spot w Kubernetes?
Instancje Spot oferują rabaty 60–90% w porównaniu z ceną On-Demand. W praktyce organizacje z dobrze skonfigurowaną strategią Spot (dywersyfikacja typów instancji, wiele stref dostępności, graceful shutdown) raportują oszczędności 50–70% na kosztach compute dla obciążeń tolerujących przerwania. Klucz to właściwa konfiguracja — bez niej oszczędności mogą szybko zamienić się w problemy z dostępnością.
Jaka jest różnica między OpenCost a Kubecost?
OpenCost to darmowy, open-source'owy projekt CNCF oferujący solidny, podstawowy monitoring kosztów. Kubecost to komercyjny produkt (model freemium) zbudowany na bazie OpenCost, który dodaje: uwzględnianie rabatów i Reserved Instances, governance i alerty, widok wielu klastrów w jednym panelu, rekomendacje optymalizacyjne oraz wsparcie techniczne. Dla małych zespołów OpenCost zazwyczaj wystarczy. Większe organizacje często wybierają Kubecost ze względu na dokładniejsze dane kosztowe i lepsze raportowanie.
Jak zapobiec overprovisioningowi zasobów w Kubernetes?
Trzy kluczowe mechanizmy: (1) LimitRange wymusza domyślne i maksymalne wartości requestów/limitów na poziomie namespace'u, (2) ResourceQuota ogranicza łączne zużycie zasobów per namespace, (3) VPA w trybie recommendation analizuje faktyczne zużycie i sugeruje optymalne requesty. Uzupełnieniem jest polityka tagowania wymuszona przez OPA lub Kyverno, blokująca deploymenty bez wymaganych etykiet. To razem tworzy dość szczelną siatkę bezpieczeństwa kosztowego.