Kubernetes cluster’ında bir pod’un tüm node kaynaklarını tükettiğini ve yanındaki kritik servislerin çöktüğünü gece 2’de fark etmek… Bu senaryoyu yaşadıysanız, kaynak limitlerinin ne kadar önemli olduğunu zaten biliyorsunuzdur. Yaşamadıysanız, bu yazıyı okuduktan sonra yarın sabah production’a uygulayacaksınız.
Kubernetes’te kaynak yönetimi, stabil ve öngörülebilir bir cluster işletmenin temel taşıdır. Request ve limit kavramlarını doğru anlamadan yapılan deploymentlar, er ya da geç ciddi problemlere yol açar. Bu yazıda bu iki kavramı derinlemesine inceleyeceğiz, gerçek dünya senaryolarıyla pekiştireceğiz.
Request ve Limit Nedir?
Kubernetes’te her container için iki temel kaynak parametresi tanımlarsınız: requests ve limits.
Requests (İstek): Container’ın çalışması için garantilenmiş minimum kaynak miktarıdır. Kubernetes scheduler, pod’u yerleştireceği node’u seçerken bu değeri baz alır. Yani “bu container en az bu kadar kaynağa ihtiyaç duyar” diyorsunuz.
Limits (Sınır): Container’ın kullanabileceği maksimum kaynak miktarıdır. Container bu sınırı aşmaya çalıştığında Kubernetes müdahale eder. CPU için throttling uygulanır, memory için ise container sonlandırılır (OOMKilled).
Bu iki değer arasındaki fark kritiktir. Request, scheduler için önemlidir. Limit ise runtime davranışını belirler.
CPU ve Memory Birimleri
CPU değerleri millicore cinsinden ifade edilir:
- 1000m = 1 CPU core
- 500m = 0.5 CPU core
- 250m = 0.25 CPU core
- 100m = 0.1 CPU core
Memory değerleri ise şu birimlerle ifade edilir:
- Ki = Kibibyte (1024 bytes)
- Mi = Mebibyte (1024 Ki)
- Gi = Gibibyte (1024 Mi)
- M = Megabyte (1000 bytes) – Ki/Mi ile karıştırmayın!
Temel Kaynak Tanımlaması
En basit haliyle bir deployment’a kaynak tanımlamak şu şekilde görünür:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: nginx:1.25
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
Bu örnekte container başına minimum 128Mi memory ve 250m CPU garantilenmiş, maksimum ise 256Mi memory ve 500m CPU ile sınırlandırılmıştır.
Scheduler Nasıl Karar Verir?
Diyelim ki 3 node’lu bir cluster’ınız var ve her node’da 4Gi memory mevcut. 2 Gi memory request’i olan bir pod deploy etmek istiyorsunuz. Scheduler bu pod’u yerleştirmek için hangi node’da 2Gi boş memory olduğuna bakar. Buradaki “boş” kavramı önemli: Scheduler, fiziksel kullanımı değil, mevcut pod’ların request toplamını baz alır.
Mevcut durumu görmek için şu komutları kullanabilirsiniz:
# Node'lardaki allocatable kaynakları göster
kubectl describe nodes | grep -A 5 "Allocatable:"
# Her node için kaynak kullanımını özetle
kubectl get nodes -o custom-columns=
"NAME:.metadata.name,
CPU-ALLOCATABLE:.status.allocatable.cpu,
MEM-ALLOCATABLE:.status.allocatable.memory"
# Namespace bazında kaynak kullanımını gör
kubectl top pods -n production --sort-by=memory
QoS Sınıfları: Gözardı Edilmemesi Gereken Konu
Kubernetes, kaynak tanımlamalarınıza göre pod’larınıza otomatik olarak bir QoS (Quality of Service) sınıfı atar. Bu sınıf, sistemde bellek baskısı oluştuğunda hangi pod’ların önce öldürüleceğini belirler.
Guaranteed
Request ve limit değerleri eşit olduğunda atanır. En yüksek önceliğe sahiptir, sistem baskı altındayken en son öldürülür.
apiVersion: v1
kind: Pod
metadata:
name: kritik-veritabani
spec:
containers:
- name: postgres
image: postgres:15
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "512Mi"
cpu: "500m"
Burstable
Request tanımlanmış ama limit farklı (veya sadece biri tanımlı) olduğunda atanır. Orta önceliğe sahiptir.
apiVersion: v1
kind: Pod
metadata:
name: api-server
spec:
containers:
- name: api
image: myapp:latest
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
BestEffort
Hiç kaynak tanımlanmadığında atanır. En düşük önceliğe sahiptir, sistem baskı altındayken ilk öldürülen bunlar olur. Production’da hiçbir zaman bu sınıfta pod bırakmayın.
LimitRange ile Namespace Bazlı Varsayılanlar
Her deployment’a tek tek kaynak tanımlamak hem zahmetlidir hem de gözden kaçabilir. LimitRange objesi, namespace seviyesinde varsayılan değerler ve sınırlar belirlemenizi sağlar.
apiVersion: v1
kind: LimitRange
metadata:
name: default-limitrange
namespace: development
spec:
limits:
- default:
cpu: "500m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "2000m"
memory: "2Gi"
min:
cpu: "50m"
memory: "64Mi"
type: Container
Bu konfigürasyonla development namespace’ine deploy edilen herhangi bir container, kaynak tanımlaması olmasa bile otomatik olarak varsayılan değerleri alır. Aynı zamanda hiçbir container 2Gi memory veya 2 CPU’dan fazla talep edemez.
LimitRange’i uyguladıktan sonra test etmek için:
# LimitRange'i uygula
kubectl apply -f limitrange.yaml
# Kaynak tanımı olmayan bir pod deploy et
kubectl run test-pod --image=nginx -n development
# Pod'un aldığı kaynakları kontrol et
kubectl get pod test-pod -n development -o yaml | grep -A 10 "resources:"
# LimitRange detaylarını gör
kubectl describe limitrange default-limitrange -n development
ResourceQuota ile Namespace Toplam Sınırları
LimitRange container başına sınır koyarken, ResourceQuota tüm namespace için toplam kaynak sınırı belirler. Çok tenant’lı (multi-tenant) cluster’larda ekipler arası adil kaynak dağılımı için vazgeçilmezdir.
apiVersion: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: production
spec:
hard:
requests.cpu: "10"
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
pods: "50"
services: "20"
persistentvolumeclaims: "30"
Bu konfigürasyonla production namespace’indeki tüm pod’ların toplam CPU request’i 10 core’u, toplam memory request’i 20Gi’yi geçemez. Aynı zamanda namespace’de maksimum 50 pod çalışabilir.
Mevcut quota kullanımını kontrol etmek için:
kubectl describe resourcequota production-quota -n production
Çıktı şuna benzer:
Name: production-quota
Namespace: production
Resource Used Hard
-------- ---- ----
limits.cpu 8500m 20
limits.memory 16Gi 40Gi
pods 23 50
requests.cpu 4200m 10
requests.memory 8Gi 20Gi
Gerçek Dünya Senaryosu: E-ticaret Platformu
Bir e-ticaret platformu yönettiğinizi düşünün. Frontend, backend API, cache ve veritabanından oluşan bir stack’iniz var. Her servis için farklı kaynak profili gerekir.
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: ecommerce
spec:
replicas: 3
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: ecommerce-frontend:v2.1
ports:
- containerPort: 3000
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "1000m"
env:
- name: NODE_ENV
value: "production"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-api
namespace: ecommerce
spec:
replicas: 5
selector:
matchLabels:
app: backend-api
template:
metadata:
labels:
app: backend-api
spec:
containers:
- name: api
image: ecommerce-api:v3.0
ports:
- containerPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "2000m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-cache
namespace: ecommerce
spec:
replicas: 1
selector:
matchLabels:
app: redis-cache
template:
metadata:
labels:
app: redis-cache
spec:
containers:
- name: redis
image: redis:7-alpine
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
Bu senaryoda frontend container’ları burst edebilir (yoğun trafik anlarda CPU limitine kadar çıkabilir), backend API’ler ise daha geniş bir burst aralığına sahiptir. Redis için Guaranteed QoS sağlamak mantıklı olabilir çünkü cache’in aniden ölmesi tüm sistemi etkiler.
Kaynak Değerlerini Nasıl Belirlemeliyim?
En sık sorulan soru budur ve maalesef “şu kadar koy” diye bir cevabı yoktur. Ancak sistematik bir yaklaşım izleyebilirsiniz.
İlk deployment için tahmini değerlerle başlayın: Uygulamanın türüne göre başlangıç değerleri belirleyin. Bir Node.js API için 128Mi-256Mi memory request makul bir başlangıçtır.
Metrics toplamak için Vertical Pod Autoscaler (VPA) recommendation modunu kullanın:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: backend-api-vpa
namespace: ecommerce
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: backend-api
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: api
minAllowed:
cpu: "100m"
memory: "128Mi"
maxAllowed:
cpu: "4"
memory: "4Gi"
updateMode: "Off" ile VPA sadece tavsiye verir, değişiklik yapmaz. Bir süre sonra önerileri görmek için:
kubectl describe vpa backend-api-vpa -n ecommerce
kubectl top ile gerçek zamanlı kullanımı izleyin:
# Pod bazında CPU ve memory kullanımı
kubectl top pods -n ecommerce
# Container bazında detaylı gösterim
kubectl top pods -n ecommerce --containers
# Sürekli izlemek için watch ile
watch -n 5 kubectl top pods -n ecommerce
Yaygın Hatalar ve Çözümleri
Limit koymadan request koymak
Request tanımlanmış ama limit yok ise container teorik olarak tüm node kaynaklarını tüketebilir. Her zaman ikisini birlikte tanımlayın.
Çok düşük memory limit (OOMKilled)
# OOMKilled olan pod'ları tespit et
kubectl get pods -n production | grep OOMKilled
# Pod'un sonlanma nedenini detaylı gör
kubectl describe pod <pod-name> -n production | grep -A 5 "Last State:"
# Önceki container'ın çıkış kodunu kontrol et
# Exit code 137 = OOMKilled
kubectl get pod <pod-name> -n production -o jsonpath=
'{.status.containerStatuses[0].lastState.terminated.exitCode}'
OOMKilled görüyorsanız memory limit’iniz gerçek kullanımın altındadır. Yüzde 20-30 headroom bırakarak limit değerini artırın.
CPU throttling gözardı etmek
CPU throttling, OOMKilled gibi dramatik değildir; uygulamanız yavaşlar ama çalışmaya devam eder. Bu yüzden fark edilmesi daha zordur. Prometheus ve Grafana kullanıyorsanız şu metriği izleyin: container_cpu_cfs_throttled_seconds_total
Throttling oranı yüzde 25’i geçiyorsa CPU limitinizi artırmayı düşünün.
Request/Limit oranını çok açık tutmak
Bazı ekipler çok küçük request, çok büyük limit tanımlar. Örneğin 100m request, 4000m limit. Bu durumda pod kolayca yerleştirilir ama çalışırken node’u boğabilir. Genel kural olarak limit/request oranını 2-3x içinde tutmaya çalışın. Kritik servisler için 1x (Guaranteed) tercih edin.
Namespace Başlangıç Konfigürasyonu: Production Ready Template
Yeni bir namespace açtığınızda uygulamanız gereken minimum konfigürasyon:
apiVersion: v1
kind: Namespace
metadata:
name: new-project
labels:
environment: production
---
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: new-project
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "4"
memory: "4Gi"
min:
cpu: "50m"
memory: "64Mi"
- type: Pod
max:
cpu: "8"
memory: "8Gi"
- type: PersistentVolumeClaim
max:
storage: "50Gi"
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: namespace-quota
namespace: new-project
spec:
hard:
requests.cpu: "8"
requests.memory: "16Gi"
limits.cpu: "16"
limits.memory: "32Gi"
pods: "40"
services: "15"
secrets: "30"
configmaps: "30"
persistentvolumeclaims: "20"
Bu template’i bir baseline olarak kullanın ve projenin ihtiyaçlarına göre değerleri ayarlayın. Kaynaklara ihtiyaç duydukça quota’yı artırmak, baştan sınırsız bırakmaktan çok daha güvenlidir.
Monitoring ve Alerting
Kaynak limitlerini tanımlamak yetmez; sürekli izlemek gerekir. Prometheus kullanıyorsanız şu alertleri tanımlamanızı öneririm:
# Prometheus alert kuralları örneği
groups:
- name: kubernetes-resources
rules:
- alert: PodMemoryUsageHigh
expr: |
(container_memory_working_set_bytes /
container_spec_memory_limit_bytes) > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "Pod memory kullanimi limit'in %85'ini asti"
description: "{{ $labels.pod }} pod'u memory limit'inin %85'ini kullaniyor"
- alert: CPUThrottlingHigh
expr: |
rate(container_cpu_cfs_throttled_seconds_total[5m]) /
rate(container_cpu_cfs_periods_total[5m]) > 0.25
for: 10m
labels:
severity: warning
annotations:
summary: "CPU throttling yuksek"
description: "{{ $labels.pod }} %25'in uzerinde CPU throttling yasaniyor"
- alert: NamespaceQuotaAlmostFull
expr: |
kube_resourcequota{type="used"} /
kube_resourcequota{type="hard"} > 0.80
for: 5m
labels:
severity: warning
annotations:
summary: "Namespace quota %80'i asti"
Sonuç
Kubernetes’te kaynak yönetimi, cluster sağlığının ve uygulama güvenilirliğinin temel belirleyicisidir. Özetleyecek olursak:
Requests, scheduler’a rehberlik eder ve container’a garanti edilmiş kaynağı ifade eder. Limits ise runtime’da uygulanan üst sınırdır ve aşılması durumunda CPU throttling veya memory OOMKill ile sonuçlanır.
QoS sınıflarını anlayarak kritik servislerinizi Guaranteed, normal servislerinizi Burstable olarak yapılandırın. BestEffort pod’larını production’da bırakmayın. LimitRange ile namespace’e varsayılan değerler atayın, kimse kaynak tanımını unutmasın. ResourceQuota ile ekipler arası adil kaynak dağılımı sağlayın.
Değerleri belirlerken önce gözlemleyin, VPA recommendation modunu ve kubectl top’u aktif kullanın, sonra bilinçli kararlar alın. Throttling ve OOMKilled metriklerini Prometheus’ta izleyin ve alert kuralları tanımlayın.
En önemli kural: Hiçbir production pod’unuzu kaynak tanımı olmadan çalıştırmayın. Bugün aldığınız bu önlem, sizi gece 2’deki o acı verici telefon görüşmesinden kurtarabilir.