Kubernetes’te Pod Zamanlama Kontrolü: Affinity, Taint ve Toleration Kullanımı
Kubernetes cluster’ınız büyüdükçe, pod’ların hangi node üzerinde çalışacağını kontrol etme ihtiyacı da kaçınılmaz olarak ortaya çıkıyor. “Bu servis sadece SSD diskli node’larda çalışsın”, “GPU işleri sadece GPU node’larına gitsin”, “Frontend ve backend pod’ları aynı node’da olsun ki gecikme düşük olsun” gibi gereksinimler, gerçek dünya ortamlarında çok sık karşılaşılan durumlar. İşte tam bu noktada Kubernetes’in pod zamanlama mekanizmaları devreye giriyor: Node Affinity, Pod Affinity/Anti-Affinity, Taint ve Toleration.
Bu yazıda bu kavramları sadece teorik olarak değil, gerçek senaryolar üzerinden inceleyeceğiz. Hangi durumda ne kullanmalısınız, nasıl debug edersiniz, production’da nelere dikkat etmelisiniz, bunların hepsine bakacağız.
Temel Kavramlar: Scheduler Nasıl Çalışır?
Kubernetes scheduler, her yeni pod için uygun bir node bulmaya çalışır. Bu süreç iki aşamadan oluşur: Filtering (eleme) ve Scoring (puanlama). Filtering aşamasında uygun olmayan node’lar elenirken, scoring aşamasında kalan node’lar puanlanır ve en yüksek puanlı node seçilir.
Bizim bugün konuşacağımız mekanizmalar bu sürecin üzerine inşa ediliyor:
- Node Selector: En basit yöntem, label’a göre node seçimi
- Node Affinity: Node Selector’ın gelişmiş ve esnek hali
- Pod Affinity/Anti-Affinity: Pod’ların birbirine göre konumlandırılması
- Taint/Toleration: Node’ların belirli pod’ları reddetmesi
Node Selector: Başlangıç Noktası
Node Selector, zamanlama kontrolünün en basit yoludur. Bir pod’un belirli label’lara sahip node’larda çalışmasını istiyorsanız kullanırsınız.
# Önce node'larımıza label ekleyelim
kubectl label nodes worker-01 disk=ssd
kubectl label nodes worker-02 disk=hdd
kubectl label nodes worker-03 gpu=true
# Mevcut label'ları kontrol et
kubectl get nodes --show-labels
# node-selector-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: ssd-workload
spec:
nodeSelector:
disk: ssd
containers:
- name: app
image: nginx:latest
Node Selector işe yarıyor ama oldukça kısıtlı. Mesela “SSD diskli VEYA NVMe diskli node’larda çalışsın” gibi bir OR mantığı kuramazsınız. İşte bu yüzden Node Affinity var.
Node Affinity: Gelişmiş Node Seçimi
Node Affinity, iki temel türde gelir:
- requiredDuringSchedulingIgnoredDuringExecution: Pod mutlaka bu kurala uyan bir node’da çalışmalı, aksi halde Pending kalır
- preferredDuringSchedulingIgnoredDuringExecution: Tercih edilir ama zorunlu değil, uygun node yoksa başka yere de gidebilir
“IgnoredDuringExecution” kısmı şu anlama geliyor: Pod çalışmaya başladıktan sonra node’un label’ı değişse bile pod oradan kaldırılmaz. Gelecekte “RequiredDuringExecution” da gelecek ama şu an stable değil.
# node-affinity-required.yaml
apiVersion: v1
kind: Pod
metadata:
name: database-pod
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values:
- ssd
- nvme
- key: region
operator: In
values:
- eu-west
containers:
- name: postgres
image: postgres:14
Burada operator değerleri şunlar olabilir:
- In: Değer listede var mı?
- NotIn: Değer listede yok mu?
- Exists: Bu key var mı? (value önemli değil)
- DoesNotExist: Bu key yok mu?
- Gt: Büyüktür (sayısal karşılaştırma)
- Lt: Küçüktür (sayısal karşılaştırma)
Şimdi bir preferred örneği yapalım. Diyelim ki uygulamanız SSD’li node’ları tercih ediyor ama şart değil:
# node-affinity-preferred.yaml
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: disk
operator: In
values:
- ssd
- weight: 20
preference:
matchExpressions:
- key: zone
operator: In
values:
- zone-a
containers:
- name: web
image: nginx:latest
weight değeri 1-100 arasında olabilir ve scheduler bu ağırlıkları kullanarak node’ları puanlar. SSD diskli node’a 80 puan, zone-a’daki node’a 20 puan ekleniyor. İkisi de sağlanırsa o node 100 puan toplar.
Pod Affinity ve Anti-Affinity: Pod’ları Birbirlerine Göre Konumlandırma
Bazen pod’ların birbirinin yanına (affinity) veya birbirinden uzağa (anti-affinity) yerleştirilmesini isteriz.
Gerçek dünya senaryosu 1: Bir web uygulamanız ve cache servisiniz var. Cache servisinin web uygulamasıyla aynı node’da olmasını istiyorsunuz çünkü network gecikmesini minimize etmek istiyorsunuz.
# pod-affinity-cache.yaml
apiVersion: v1
kind: Pod
metadata:
name: cache-pod
labels:
app: cache
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-frontend
topologyKey: kubernetes.io/hostname
containers:
- name: redis
image: redis:7
topologyKey burada çok önemli. Bu değer node’ların hangi “düzlemde” gruplandığını belirtir:
- kubernetes.io/hostname: Aynı node (en dar kapsam)
- topology.kubernetes.io/zone: Aynı availability zone
- topology.kubernetes.io/region: Aynı region
Gerçek dünya senaryosu 2: Bir uygulamanın replika’larını farklı node’lara dağıtmak istiyorsunuz, böylece tek node çöktüğünde tüm replika’lar etkilenmiyor.
# anti-affinity-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: critical-app
spec:
replicas: 3
selector:
matchLabels:
app: critical-app
template:
metadata:
labels:
app: critical-app
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- critical-app
topologyKey: kubernetes.io/hostname
containers:
- name: app
image: myapp:v1
Bu konfigürasyonla her replika farklı bir node’a gidecek. Eğer uygun node sayısı replika sayısından azsa, fazla pod’lar Pending kalacak. Bunu önlemek için required yerine preferred kullanabilirsiniz.
Taint ve Toleration: Node’ları Kısıtlamak
Taint mekanizması tam tersi yönden çalışır. Affinity’de pod “ben buraya gitmek istiyorum” diyordu. Taint’te node “bana sadece şu tür pod’lar gelebilir” diyor.
Bir node’a taint eklemek şöyle yapılır:
# Node'a taint ekle
kubectl taint nodes worker-03 gpu=true:NoSchedule
# Taint formatı: key=value:effect
# Effect'ler:
# NoSchedule: Toleration'ı olmayan pod'lar buraya schedule edilmez
# PreferNoSchedule: Mümkünse schedule etme ama mecbur kalırsa eder
# NoExecute: Buraya schedule etme, buradaki uyumsuz pod'ları da taşı
# Taint'i kaldırmak için (sonuna - ekle)
kubectl taint nodes worker-03 gpu=true:NoSchedule-
# Mevcut taint'leri görmek
kubectl describe node worker-03 | grep Taint
Şimdi bir pod’un bu taint’li node’a gidebilmesi için toleration tanımlaması gerekiyor:
# gpu-pod-with-toleration.yaml
apiVersion: v1
kind: Pod
metadata:
name: gpu-training-job
spec:
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
containers:
- name: training
image: tensorflow/tensorflow:latest-gpu
resources:
limits:
nvidia.com/gpu: 1
Toleration’da operator değerleri:
- Equal: Key ve value eşleşmeli
- Exists: Sadece key eşleşmeli, value farklı olabilir
Bir pod tüm taint’leri tolere etmek istiyorsa şöyle yapabilirsiniz (dikkatli kullanın!):
tolerations:
- operator: "Exists"
NoExecute Taint: Çalışan Pod’ları Taşımak
NoExecute effect’i biraz daha agresif davranır. Sadece yeni pod’ların gelmesini engellemekle kalmaz, bu taint’i tolere edemeyen mevcut pod’ları da node’dan çıkarır.
Bu özellik özellikle node maintenance senaryolarında kullanışlıdır. Kubernetes’in kubectl drain komutu aslında arka planda NoExecute taint kullanır.
# Node'u bakıma almadan önce
kubectl drain worker-02 --ignore-daemonsets --delete-emptydir-data
# Bu komut arka planda şunu yapar:
kubectl taint nodes worker-02 node.kubernetes.io/unschedulable:NoExecute
# Bakım tamamlandı, node'u geri al
kubectl uncordon worker-02
NoExecute toleration’larında bir de tolerationSeconds özelliği var. Pod’un node’da ne kadar süre kalacağını belirler:
tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300
Bu konfigürasyon, node erişilemez olursa pod’un 300 saniye bekleyip sonra başka bir node’a taşınmasını sağlar. Production ortamlarda bu değeri düşük tutarsanız pod’lar çok hızlı taşınır, yüksek tutarsanız geçici network problemlerinde gereksiz restart’lar önlenir.
Gerçek Dünya Senaryosu: Multi-Tier Uygulama
Hepsini bir araya getirelim. Diyelim ki şöyle bir ortamınız var:
- 3 adet yüksek performanslı node (SSD, 32GB RAM) – database için
- 5 adet orta performanslı node – uygulama tier için
- 2 adet GPU node – ML işleri için
- 2 adet monitoring node – sadece monitoring stack için
# Node label'larını ve taint'leri ayarla
kubectl label nodes db-node-{1,2,3} tier=database disk=ssd
kubectl label nodes app-node-{1..5} tier=application
kubectl label nodes gpu-node-{1,2} tier=gpu accelerator=nvidia
kubectl label nodes mon-node-{1,2} tier=monitoring
# Monitoring node'larına taint ekle - sadece monitoring gelsin
kubectl taint nodes mon-node-1 dedicated=monitoring:NoSchedule
kubectl taint nodes mon-node-2 dedicated=monitoring:NoSchedule
# GPU node'larına taint ekle
kubectl taint nodes gpu-node-1 accelerator=nvidia:NoSchedule
kubectl taint nodes gpu-node-2 accelerator=nvidia:NoSchedule
Database Deployment:
# database-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgresql
spec:
replicas: 3
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: tier
operator: In
values:
- database
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- postgresql
topologyKey: kubernetes.io/hostname
containers:
- name: postgres
image: postgres:14
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
Prometheus monitoring stack (taint’li node’lara gitmesi için toleration ekliyoruz):
# prometheus-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
spec:
replicas: 2
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "monitoring"
effect: "NoSchedule"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: tier
operator: In
values:
- monitoring
containers:
- name: prometheus
image: prom/prometheus:latest
Debug ve Troubleshooting
Pod’unuz Pending durumunda kalıyorsa ilk yapılacak şey şu:
# Pod'un neden schedule edilemediğini anla
kubectl describe pod <pod-name> | grep -A 20 "Events:"
# Scheduler loglarına bak
kubectl logs -n kube-system -l component=kube-scheduler --tail=50
# Node'ların mevcut durumunu kontrol et
kubectl get nodes -o wide
kubectl describe node <node-name> | grep -E "Taints|Labels"
Sık karşılaşılan hata mesajları ve anlamları:
- 0/5 nodes are available: 5 node(s) didn’t match Pod’s node affinity/selector: Affinity kurallarına uyan node yok
- 0/5 nodes are available: 5 node(s) had untolerated taint: Taint’leri tolere edemeyen pod
- 0/3 nodes are available: 3 node(s) didn’t match pod anti-affinity rules: Anti-affinity kuralları çok kısıtlayıcı
# Belirli bir node üzerindeki tüm pod'ları gör
kubectl get pods --all-namespaces --field-selector spec.nodeName=worker-01
# Node'a hangi pod'ların schedule edilebildiğini simüle et
kubectl get events --sort-by=.metadata.creationTimestamp | grep -i "failed scheduling"
Sistem Taint’leri: Kubernetes’in Kendi Kullandıkları
Kubernetes’in otomatik olarak eklediği bazı taint’ler var, bunları bilmek önemli:
- node.kubernetes.io/not-ready: Node hazır değil
- node.kubernetes.io/unreachable: Node erişilemiyor
- node.kubernetes.io/memory-pressure: Bellek baskısı var
- node.kubernetes.io/disk-pressure: Disk baskısı var
- node.kubernetes.io/pid-pressure: PID baskısı var
- node.kubernetes.io/unschedulable: Node schedulable değil (cordon edilmiş)
- node.kubernetes.io/network-unavailable: Network yapılandırması yok
DaemonSet’ler bu system taint’lerin çoğunu otomatik olarak tolere eder, bu yüzden bir node probleme girdiğinde DaemonSet pod’ları hala çalışmaya devam eder.
Performans ve Best Practice’ler
Zamanlama kurallarını tasarlarken dikkat edilmesi gereken bazı noktalar var.
Çok kısıtlayıcı kurallardan kaçının. Required affinity ve required anti-affinity’yi aynı anda kullanmak, pod’ların hiç schedule edilememesine yol açabilir. Özellikle cluster küçüldüğünde (node’lar kapandığında) bu problem ortaya çıkar.
topologyKey seçimine dikkat edin. Zone bazlı anti-affinity kullanıyorsanız ve 3 zone’unuz varsa, 3’ten fazla replika istediğinizde bazı zone’lar birden fazla pod barındırmak zorunda kalacak. Bu durumda required yerine preferred kullanmak daha güvenli.
Label yönetimini standartlaştırın. Affinity kuralları label’lara dayanır, label yönetiminiz dağınıksa affinity kurallarınız da güvenilmez olur. Node label’larını GitOps sürecinize dahil edin.
Taint’leri belgelenmiş tutun. Hangi node’da hangi taint var ve neden, bunu bir yerde kaydedin. Aksi halde birkaç ay sonra “bu taint neden var?” sorusuyla karşılaşırsınız.
# Tüm node taint'lerini listele
kubectl get nodes -o json | jq '.items[] | {name: .metadata.name, taints: .spec.taints}'
# Tüm pod toleration'larını listele
kubectl get pods --all-namespaces -o json | jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace, tolerations: .spec.tolerations}'
Sonuç
Kubernetes’te pod zamanlama kontrolü, cluster’ınızı gerçekten verimli ve güvenilir hale getirmenin temel taşlarından biri. Node Affinity ile workload’larınızı doğru donanıma yönlendiriyorsunuz, Pod Anti-Affinity ile yüksek erişilebilirlik sağlıyorsunuz, Taint/Toleration ile ise node gruplarını mantıksal olarak izole ediyorsunuz.
Başlangıçta bu kavramlar karmaşık gelebilir ama temel mantığı oturttuğunuzda, “bu pod neden o node’a gitmedi?” sorusuna hızlıca cevap bulabiliyorsunuz. En iyi öğrenme yolu ise test cluster’ınızda farklı kombinasyonları denemek ve kubectl describe pod ile kubectl describe node komutlarını bol bol kullanmak.
Production ortamında bu mekanizmaları kullanmadan önce mutlaka staging’de test edin ve özellikle required affinity/anti-affinity kurallarının cluster boyutunuzla uyumlu olduğundan emin olun. Bir pod’un Pending’de kalması, bazen affinity kurallarının çok kısıtlayıcı olmasından kaynaklanır ve bunu gece 2’de debug etmek kimsenin isteyeceği bir durum değil.
