← Kursa Dön
📄 Text · 40 min

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:6443

Alternatif 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-cluster

Hepsinde 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: 10

Bu 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          45s

Scaling 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/web

Otomatik Ö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=70

Bu 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: 3000

Cluster 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: 30080

Eriş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: 80

Cloud 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şemez

ConfigMap 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" | base64

Pod'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=pass

Gerç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: 10Gi

Bu ö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 myapp

Birkaç 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.