Kubernetes'e Giriş — Pod, Service, Deployment Temelleri
Bir önceki derste Docker Swarm ile cluster kurulumunu, servis yönetimini ve rolling update'i öğrendik. Swarm basit ve etkili bir araç. Ama şimdi endüstri standardı olan, çok daha güçlü ve kapsamlı bir araçla tanışacağız: Kubernetes (kısaca K8s).
Havalimanı Analojisi
Bir havalimanı düşün. Yolcular (istekler) geliyor, farklı terminallere (servislere) yönlendirilmeleri gerekiyor. Uçaklar (pod'lar) kalktığında yerine yenisi gelmeli. Pist kapasitesi (kaynak) dolduğunda yeni pist (node) açılmalı. Kötü hava koşullarında (arıza) uçuşlar otomatik olarak başka havalimanlarına yönlendirilmeli. Tüm bunları yöneten merkezi kontrol kulesi — işte Kubernetes bu.
Google, kendi iç sistemlerinde 15 yıl boyunca milyarlarca container yönetti (Borg sistemi). Bu devasa deneyimin açık kaynak hali Kubernetes oldu. Bugün dünya genelinde production workload'larının büyük çoğunluğu Kubernetes üzerinde çalışıyor. Docker ile container'ları oluşturuyorsun, Kubernetes ile onları yönetiyorsun.
Kubernetes Mimarisi
Kubernetes'in mimarisini anlamak, onu etkin kullanmanın temelidir. İki ana katman var:
Control Plane (Yönetim Katmanı)
Control Plane, cluster'ın "beyni". Tüm kararlar burada alınır:
API Server: Kubernetes ile tüm iletişim buradan geçer. kubectl komutu yazdığında, aslında API Server'a REST isteği gönderiyorsun. Diğer tüm bileşenler de API Server üzerinden haberleşir.
etcd: Cluster'ın tüm durumunu saklayan dağıtık veritabanı. "3 web pod'u olmalı", "api servisi 80 portunda çalışıyor" gibi bilgiler burada tutulur. etcd'yi kaybedersen, cluster'ın tüm konfigürasyonunu kaybedersin — bu yüzden backup'ı kritik.
Scheduler: Yeni oluşturulan pod'ları uygun node'lara atar. Karar verirken node'ların kaynak durumunu, constraint'leri ve affinity kurallarını değerlendirir. "Bu pod'u en uygun yere koy" diyen akıllı yerleştirici.
Controller Manager: İstenen durum (desired state) ile mevcut durum (current state) arasındaki farkı sürekli kontrol eden mekanizma. "3 pod olmalı ama 2 var" → hemen yeni pod oluşturur. Bu reconciliation loop, Kubernetes'in kendi kendini iyileştirmesinin temelidir.
Worker Node (İşçi Node)
Worker node'lar asıl işin yapıldığı yerler — container'lar burada çalışır:
kubelet: Her node'da çalışan agent. API Server'dan aldığı talimatlarla pod'ları başlatır, durumlarını izler ve raporlar.
kube-proxy: Servis ağ kurallarını yönetir. Bir servise istek geldiğinde, bu isteği doğru pod'a yönlendiren kural setlerini (iptables/IPVS) yönetir.
Container Runtime: Container'ları çalıştıran yazılım — genellikle containerd veya CRI-O. Docker'ın kendisi değil, ama Docker'ın da arka planda kullandığı containerd.
Lokal Kubernetes Kurulumu
Kubernetes'i öğrenmek için bulut hesabına veya production cluster'a ihtiyacın yok. Lokal makinende çalıştırabilirsin. En kolay yollardan biri Docker Desktop:
# Docker Desktop kullanıyorsan:
# Settings → Kubernetes → Enable Kubernetes ✓
# Birkaç dakika bekle — tek node'lu cluster hazır
kubectl cluster-info
# Kubernetes control plane is running at https://127.0.0.1:6443Alternatif olarak Minikube veya Kind kullanabilirsin:
# Minikube
minikube start --driver=docker --memory=4096 --cpus=2
minikube dashboard # Güzel bir web arayüzü açılır
# Kind (Kubernetes in Docker) — çok hafif
kind create cluster --name my-clusterHepsinde sonuç aynı: lokal bir Kubernetes cluster'ın oluyor ve kubectl ile yönetebiliyorsun.
kubectl — Kubernetes'in CLI Aracı
kubectl, Kubernetes ile konuşmanın ana yolu. Docker CLI biliyorsan, mantığı benzer — ama komut yapısı farklı. Hadi temel komutlara bakalım:
# Cluster bilgisi
kubectl cluster-info
kubectl get nodes
# Kaynak listeleme
kubectl get pods # Pod'ları listele
kubectl get pods -o wide # Detaylı (IP, node bilgisi)
kubectl get services # Service'leri listele
kubectl get deployments # Deployment'ları listele
kubectl get all # Her şeyi listele
# Detaylı bilgi
kubectl describe pod my-pod # Pod'un tüm detayları
kubectl describe service my-service
# Loglar
kubectl logs my-pod # Pod logları
kubectl logs -f my-pod # Canlı takip
# Pod'a bağlanma
kubectl exec -it my-pod -- bash # Container'a shell aç💡 İpucu: kubectl yazmak uzun gelebilir. Çoğu kişi alias k=kubectl tanımlar ve sadece k get pods yazar.
Pod — En Küçük Birim
Pod, Kubernetes'in en küçük deploy birimi. Bir veya daha fazla container içerir. Docker'daki "container" kavramının bir adım üstü gibi düşün.
"Neden doğrudan container değil de Pod?" diye sorabilirsin. Çünkü bazen birbiriyle sıkı ilişkili container'lar var — aynı network namespace'ini, aynı storage'ı paylaşmaları gerekiyor. Pod, bu container'ları tek bir birim olarak yönetmenin yolu.
Ama pratikte çoğu pod tek bir container içerir. Basit bir pod tanımı:
# simple-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: web
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"Bu YAML'ı inceleyelim: apiVersion ve kind Kubernetes'e ne tür bir kaynak oluşturmak istediğini söylüyor. metadata altındaki labels çok önemli — Kubernetes'te her şey label'larla eşleştirilir. spec ise pod'un teknik detayları.
resources kısmına dikkat et:
requests— pod'un minimum ihtiyacı. Scheduler bu değere göre pod'u yerleştirir.limits— pod'un kullanabileceği maksimum kaynak. Aşarsa throttle edilir (CPU) veya öldürülür (memory).
Pod'u oluşturalım:
kubectl apply -f simple-pod.yaml
kubectl get pod web
# NAME READY STATUS RESTARTS AGE
# web 1/1 Running 0 30s⚠️ Önemli: Pod'ları doğrudan oluşturmak production'da önerilmez. Pod çökerse yenisi otomatik oluşturulmaz — elle müdahale gerekir. Bunun için Deployment kullanacağız.
Deployment — Pod'ların Akıllı Yöneticisi
Deployment, pod'ların istenen sayıda çalışmasını garantiler. Pod çökerse yenisini oluşturur, güncelleme istersen rolling update yapar, sorun çıkarsa rollback eder. Production'da her zaman Deployment kullanırsın, doğrudan Pod değil.
# web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 10Bu dosyada çok şey var, hadi parça parça inceleyelim:
replicas: 3 — "3 pod çalışsın." Biri çökerse Kubernetes otomatik yenisini başlatır.
selector ve template.metadata.labels — Deployment, hangi pod'ların kendisine ait olduğunu label'larla anlar. app: web label'ına sahip pod'lar bu Deployment tarafından yönetilir.
strategy — güncelleme stratejisi. maxSurge: 1 güncelleme sırasında en fazla 1 fazla pod olabilir, maxUnavailable: 0 hiç pod eksik olamaz. Yani her an en az 3 pod aktif.
livenessProbe ve readinessProbe — bunlar çok önemli health check mekanizmaları:
livenessProbe: "Bu container sağlıklı mı?" Başarısız olursa container restart edilir. Kilitlenmiş, yanıt vermeyen container'ları kurtarır.
readinessProbe: "Bu container trafik almaya hazır mı?" Başarısız olursa container Service'den çıkarılır — trafik gitmez ama restart edilmez. Başlangıçta veritabanı bağlantısı kuruluyor olabilir — hazır olana kadar trafik almasın.
# Deploy et
kubectl apply -f web-deployment.yaml
# Durumu izle
kubectl get deployment web
# NAME READY UP-TO-DATE AVAILABLE AGE
# web 3/3 3 3 45s
# Pod'ları gör
kubectl get pods -l app=web
# NAME READY STATUS RESTARTS AGE
# web-6d8f7b9c4d-abc12 1/1 Running 0 45s
# web-6d8f7b9c4d-def34 1/1 Running 0 45s
# web-6d8f7b9c4d-ghi56 1/1 Running 0 45sScaling ve Rolling Update
# Manuel ölçekleme
kubectl scale deployment web --replicas=5
# Image güncelleme (rolling update)
kubectl set image deployment/web nginx=nginx:1.26-alpine
# Güncelleme durumunu izle
kubectl rollout status deployment/web
# Rollback
kubectl rollout undo deployment/webOtomatik Ölçekleme (HPA)
Kubernetes'in en güçlü özelliklerinden biri Horizontal Pod Autoscaler — CPU veya memory kullanımına göre otomatik ölçekleme:
kubectl autoscale deployment web \
--min=2 \
--max=10 \
--cpu-percent=70Bu komut şunu söylüyor: "CPU kullanımı %70'i geçerse yeni pod ekle, %70'in altına düşerse pod kaldır. Minimum 2, maksimum 10 pod olsun." Gece trafiğin az olduğunda 2 pod yeterli, öğlen saatlerinde trafik artınca otomatik 8-10 pod'a çıkabilir.
Service — Stable Erişim Noktası
Pod'lar geçicidir — çöker, yeniden oluşturulur, farklı IP alır. Peki bir pod'a nasıl güvenilir şekilde erişeceksin? Service ile.
Service, pod'ların önüne sabit bir DNS adı ve IP koyar. Arkadaki pod'lar değişse bile, Service adresi hep aynı kalır.
ClusterIP (Varsayılan)
Sadece cluster içinden erişilebilir. Backend servisler için ideal:
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
type: ClusterIP
selector:
app: api
ports:
- port: 80
targetPort: 3000Cluster içinden erişim: http://api-service:80 veya http://api-service.default.svc.cluster.local:80
NodePort
Her node'un belirli bir portunda erişim sağlar. Development ve test için:
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080Erişim: http://NODE_IP:30080
LoadBalancer
Cloud provider'ın load balancer'ını otomatik oluşturur. Production'da en çok kullanılan tür:
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 80Cloud provider (AWS, GCP, Azure) otomatik olarak bir load balancer oluşturur ve external IP atar.
Namespace — Kaynak İzolasyonu
Namespace, cluster'ı mantıksal bölümlere ayırır. Farklı ortamlar veya takımlar için izolasyon sağlar:
kubectl create namespace staging
kubectl create namespace production
# Belirli namespace'e deploy
kubectl apply -f deployment.yaml -n staging
# Namespace'ler arası izolasyon
# staging'deki pod'lar production'daki servislere default olarak erişemezConfigMap ve Secret
Konfigürasyon ve hassas veriyi pod tanımından ayırmak için:
# ConfigMap — hassas olmayan konfigürasyon
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: "db-service"
LOG_LEVEL: "info"
---
# Secret — hassas veri (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
password: c3VwZXItc2VjcmV0 # echo -n "super-secret" | base64Pod'da kullanımı:
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password⚠️ Dikkat: Kubernetes Secret'ları varsayılan olarak sadece base64 ile encode edilir — şifrelenmez! Production'da etcd encryption veya Sealed Secrets gibi çözümler kullan.
Yaygın Hatalar ve Çözümleri
Pod "Pending" durumunda kalıyor: Genellikle kaynak yetersizliği. kubectl describe pod stuck-pod ile Events bölümüne bak. "Insufficient cpu" veya "Insufficient memory" görürsen, node ekle veya resource request'leri düşür.
Pod "CrashLoopBackOff": Container sürekli çöküp yeniden başlatılıyor. kubectl logs crashing-pod --previous ile önceki instance'ın loglarına bak. Yaygın sebepler: uygulama hatası, yanlış CMD, bağımlı servis hazır değil, memory limiti yetersiz.
Service'e erişilemiyor: Selector ile pod label'ları eşleşiyor mu kontrol et: kubectl get endpoints web-service. Endpoint listesi boşsa, selector yanlış.
Image pull hatası: Private registry kullanıyorsan imagePullSecrets tanımla:
kubectl create secret docker-registry my-registry \
--docker-server=myregistry.com \
--docker-username=user \
--docker-password=passGerçek Dünya Örneği: Full-Stack Uygulama Deploy
Öğrendiğimiz kavramları birleştirerek gerçekçi bir uygulama deploy edelim — Node.js API + PostgreSQL:
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: myapp
---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: myapp
type: Opaque
data:
password: bXlzdXBlcnNlY3JldA==
---
# db-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
namespace: myapp
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: "myapp"
- name: POSTGRES_USER
value: "myapp"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: pgdata-pvc
---
apiVersion: v1
kind: Service
metadata:
name: db
namespace: myapp
spec:
selector:
app: db
ports:
- port: 5432
---
# api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: myapp
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: myregistry/api:v1.0
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
value: "postgres://myapp:$(DB_PASSWORD)@db:5432/myapp"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: myapp
spec:
selector:
app: api
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pgdata-pvc
namespace: myapp
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10GiBu örnekte neler yaptığımıza bakalım. Önce bir namespace oluşturduk — myapp — tüm kaynaklarımız bu izole alanda yaşayacak. Sonra veritabanı şifresi için bir Secret tanımladık. PostgreSQL deployment'ı PersistentVolumeClaim ile kalıcı veri depolama sağlıyor — pod restart olsa bile veriler kaybolmaz. API deployment'ı 3 replica ile çalışıyor ve hem liveness hem readiness probe'ları var. Son olarak API servisi LoadBalancer tipiyle dış dünyaya açık.
Hepsini deploy etmek:
kubectl apply -f namespace.yaml
kubectl apply -f .
# Durumu kontrol et
kubectl get all -n myappBirkaç dakika içinde tüm pod'lar "Running" durumuna geçer ve API servisin external IP alır. Bu IP üzerinden API'na erişebilirsin.
Kubernetes vs Swarm: Pratik Karar
İki dersi de gördükten sonra şu karşılaştırmayı daha iyi anlayacaksın:
Swarm'da 5 dakikada cluster kurup servis deploy ettin. Kubernetes'te aynı iş 30-60 dakika sürdü ve çok daha fazla YAML yazdın. Ama Kubernetes sana otomatik ölçekleme (HPA), detaylı health check'ler, namespace izolasyonu, RBAC (rol bazlı erişim kontrolü), Ingress ile gelişmiş trafik yönetimi gibi özellikler sunuyor.
Genel kural: 5-10 servislik basit bir uygulama için Swarm yeterli ve çok daha hızlı. 10+ servisli, karmaşık ölçekleme ihtiyaçları olan, cloud-native bir uygulama için Kubernetes doğru seçim.
Ve şunu unutma: production'da Kubernetes'i kendin yönetmek yerine managed servisler kullan — AWS EKS, Google GKE, Azure AKS. Bunlar control plane'i senin için yönetir, sen sadece uygulamanı deploy edersin.
Bu Derste Ne Öğrendik?
Kubernetes Google'ın 15 yıllık container deneyiminin açık kaynak hali — endüstri standardı orchestrator
Control Plane (API Server, Scheduler, Controller Manager, etcd) cluster'ı yönetir; Worker Node'lar pod'ları çalıştırır
Pod en küçük birim, Deployment pod'ları yönetir (replica, rolling update, rollback)
Service pod'lara stable erişim sağlar: ClusterIP (iç), NodePort (test), LoadBalancer (production)
Health probe'ları (liveness, readiness) container sağlığını izler ve otomatik müdahale sağlar
HPA ile otomatik ölçekleme — trafik artınca pod ekle, azalınca kaldır
Namespace ile kaynak izolasyonu, ConfigMap ile konfigürasyon, Secret ile hassas veri yönetimi
Lokal geliştirmede Docker Desktop, Minikube veya Kind kullan
Sonraki derste Docker ve Kubernetes'in birlikte nasıl çalıştığını — geliştirmeden production'a tam workflow'u — öğreneceğiz.
AI Asistan
Sorularını yanıtlamaya hazır