Kubernetes mu Docker Compose mu: Küçük Projeler için Hangisi Daha İyi?

Bir proje başladığında masanın başında herkes toplanır ve kaçınılmaz soru gelir: “Bunu Kubernetes üzerinde mi koşturalım?” O soru genellikle iyiniyetle sorulur, ama çoğu zaman yanlış sorudur. Yıllar içinde onlarca ekiple çalışırken gördüğüm en yaygın hata, küçük bir uygulama için Kubernetes kurarak hem zaman hem de para harcamak oldu. Öte yandan tam tersi hata da var: Büyümeye başlayan bir ürünü Docker Compose ile yönetmeye çalışmak. Bu yazıda ikisi arasındaki gerçek farkı, gerçek senaryolar üzerinden konuşacağız.

Önce Bir Gerçeği Kabul Edelim

Kubernetes harika bir araç. Ama her araç gibi, doğru iş için kullanıldığında harika. Bir vidayı çekiçle çakabilirsiniz, ama bu iyi bir fikir değildir.

Docker Compose, tek bir makinede birden fazla container’ı orkestre etmek için tasarlanmış, son derece sade bir araçtır. YAML dosyası yazar, docker compose up dersiniz, iş biter. Kubernetes ise dağıtık sistemler için tasarlanmış, production-grade bir orkestrasyon platformudur. İkisi arasındaki fark sadece özellik seti değil, zihinsel model ve operasyonel yük açısından da büyüktür.

Küçük projeler için “hangisi” sorusunu cevaplamadan önce, “küçük proje” ile ne kastettiğimizi netleştirmeliyiz.

Küçük Proje Ne Demek?

Benim tanımım şu: Tek bir ekip tarafından geliştirilen, 5-10’dan az servisi olan, kullanıcı sayısı henüz binleri bulmamış, 1-3 sunucu üzerinde çalışabilecek bir uygulama.

Bu tanıma giren projeler için Docker Compose genellikle doğru seçimdir. Ama bazı istisnalar var, bunlara da geleceğiz.

Docker Compose’un Gerçek Gücü

Docker Compose’u küçümseyenler genellikle onu tam anlamıyla kullanmamışlardır. Sade bir docker-compose.yml ile production’a çok yakın bir ortam kurabilirsiniz.

Tipik bir web uygulaması için şu yapıyı düşünelim:

version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
      - REDIS_URL=redis://cache:6379/0
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped
    volumes:
      - ./logs:/app/logs

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  cache:
    image: redis:7-alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./certbot/conf:/etc/letsencrypt
    depends_on:
      - app
    restart: unless-stopped

volumes:
  postgres_data:

Bu yapı; uygulama, veritabanı, cache ve reverse proxy’den oluşan tam bir stack’i tek bir dosyayla yönetiyor. Deployment için gereken komutlar da son derece sade:

# İlk kurulum
docker compose pull
docker compose up -d

# Güncelleme
docker compose pull app
docker compose up -d --no-deps app

# Log takibi
docker compose logs -f app

# Sadece uygulama container'ını yeniden başlat
docker compose restart app

Bunları Kubernetes’te yapmak için en az 5-6 farklı manifest dosyası yazmanız, kubectl komutlarını öğrenmeniz ve büyük ihtimalle bir ingress controller kurmanız gerekir.

Kubernetes’in Gerçek Karmaşıklığı

Kubernetes’e geçmeye karar verdiğinizde sizi bekleyen şeyleri açıkça konuşalım. Aynı uygulamayı Kubernetes’e taşıdığınızda minimum şunları yazmanız gerekir:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myregistry/myapp:v1.2.3
          ports:
            - containerPort: 8000
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: myapp-secrets
                  key: database-url
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 5

Bu sadece deployment manifest’i. Bunun yanına Service, Ingress, Secret, ConfigMap, PersistentVolumeClaim ve HorizontalPodAutoscaler eklemeniz gerekiyor. Veritabanı için StatefulSet yazmalısınız ya da managed bir servis kullanmalısınız.

Ayrıca şunları da göz önünde bulundurmanız gerekiyor:

  • Ingress Controller: Nginx Ingress, Traefik veya başka bir seçenek kurmanız gerekiyor
  • Cert Manager: TLS sertifikalarını yönetmek için ayrı bir kurulum
  • Storage Class: Persistent volume’lar için storage altyapısı
  • RBAC: Kim neye erişebilir, servis hesapları nasıl yapılandırılacak
  • Monitoring: Prometheus, Grafana veya benzeri bir stack

Bunların hiçbiri kötü değil. Ama 2 servisten oluşan bir startup uygulaması için tüm bunları kurup yönetmek, asıl işinizden çok zaman çalır.

Gerçek Dünya Senaryosu: Bir SaaS Startup’ı

Bir e-ticaret startup’ıyla çalışmıştım. Ekip 3 backend geliştirici ve 1 DevOps’tan oluşuyordu. Kubernetes konusunda hevesliydiler ve GKE üzerinde bir cluster kurmuşlardı.

İlk 6 ay boyunca uygulamalarını değil, Kubernetes’i yönetiyorlardı. Node upgrade’leri, pod’ların OOMKilled olması, ingress sertifika sorunları, persistent volume claim’lerin bağlanmaması… Her hafta en az bir Kubernetes kaynaklı olay vardı.

Sonunda Docker Compose’a geçtik. Tek bir DigitalOcean Droplet üzerinde (16 GB RAM, 8 vCPU) tüm stack’lerini çalıştırmaya başladılar. Arıza oranı düştü, deployment süresi 20 dakikadan 3 dakikaya indi, aylık altyapı maliyeti 800 dolardan 120 dolara geriledi.

O startup şu an 50.000 aktif kullanıcıya hizmet veriyor ve hala aynı setup’ı kullanıyor. Kubernetes’e geçmeyi düşünüyorlar, ama şimdi gerçekten ihtiyaçları olduğu için.

Docker Compose ile Production: Doğru Yapıldığında

Docker Compose’u production’da güvenle kullanmak için birkaç kritik nokta var.

Güncelleme Stratejisi

Zero-downtime deployment için basit bir script:

#!/bin/bash
# deploy.sh

set -e

IMAGE_TAG=${1:-latest}
SERVICE_NAME=${2:-app}

echo "Yeni image çekiliyor: $IMAGE_TAG"
docker compose pull $SERVICE_NAME

echo "Container güncelleniyor..."
docker compose up -d --no-deps $SERVICE_NAME

echo "Health check bekleniyor..."
sleep 10

CONTAINER_ID=$(docker compose ps -q $SERVICE_NAME)
HEALTH=$(docker inspect --format='{{.State.Health.Status}}' $CONTAINER_ID 2>/dev/null || echo "running")

if [ "$HEALTH" = "healthy" ] || [ "$HEALTH" = "running" ]; then
    echo "Deployment başarılı!"
else
    echo "Container sağlıklı değil, rollback yapılıyor..."
    docker compose rollback $SERVICE_NAME
    exit 1
fi

Environment Yönetimi

Farklı ortamlar için override dosyaları kullanın:

# Geliştirme ortamı
docker compose -f docker-compose.yml -f docker-compose.dev.yml up

# Production ortamı
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# docker-compose.prod.yml
version: "3.9"

services:
  app:
    image: myregistry/myapp:${APP_VERSION}
    deploy:
      resources:
        limits:
          memory: 1G
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"

  db:
    volumes:
      - /data/postgres:/var/lib/postgresql/data

Yedekleme Otomasyonu

#!/bin/bash
# backup.sh - Crontab'a eklenecek

BACKUP_DIR="/backups/postgres"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7

mkdir -p $BACKUP_DIR

docker compose exec -T db pg_dump -U user myapp | 
    gzip > "$BACKUP_DIR/backup_$DATE.sql.gz"

# Eski yedekleri temizle
find $BACKUP_DIR -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete

echo "Yedekleme tamamlandı: backup_$DATE.sql.gz"

Kubernetes’i Ne Zaman Seçmelisiniz?

Kubernetes’i küçük projeler için önermekten kaçınsam da bazı durumlar gerçekten Kubernetes gerektirir.

Birden fazla sunucuya dağıtmanız gerekiyorsa: Uygulamanızın tek bir makinenin kapasitesini aşması ve yatay olarak ölçeklenmesi gerekiyorsa Kubernetes burada ciddi avantaj sağlar. Docker Swarm da bu işi yapabilir ama Kubernetes ekosistemi çok daha olgunlaşmış durumda.

Ekibiniz zaten Kubernetes biliyor ve CI/CD altyapınız buna göre kurulmuşsa: Sıfırdan öğrenme maliyeti olmadan Kubernetes kullanıyorsanız, küçük projeler için de Kubernetes tercih edilebilir.

Compliance ve güvenlik gereksinimleri varsa: Pod Security Policies, Network Policies, RBAC gibi özellikler kurumsal güvenlik gereksinimlerini karşılamak için Docker Compose’dan çok daha iyi araçlar sunar.

Managed Kubernetes kullanıyorsanız: EKS, GKE veya AKS gibi managed servisler, operasyonel yükü önemli ölçüde azaltır. Eğer zaten bu servislere erişiminiz varsa ve ücretini ödüyorsanız, neden kullanmayasınız?

Basit bir karar ağacı şöyle işleyebilir:

  • Tek sunucu yeterli mi? Docker Compose
  • Birden fazla sunucu ama ekip Kubernetes bilmiyor mu? Docker Swarm veya öğrenme süreci hesaba katılarak Kubernetes
  • Kurumsal ortam, compliance gereksinimleri, büyük ekip mi? Kubernetes

Kubernetes’e Geçiş: Doğru Zaman

Docker Compose’dan Kubernetes’e geçişi planlarken şu sinyalleri bekleyin:

  • Uygulamanız tek bir sunucunun CPU veya RAM limitine yaklaşıyor
  • Birden fazla bölgede veya datacenter’da çalışmanız gerekiyor
  • Farklı servisler için bağımsız ölçekleme ihtiyacı doğuyor
  • Ekibiniz birden fazla ortamı (staging, production, regional) yönetmek zorunda kalıyor
  • CI/CD pipeline’ınız karmaşıklaşıyor ve deployment koordinasyonu zorlaşıyor

Geçiş sırasında Kompose aracı işinize yarayabilir:

# docker-compose.yml'i Kubernetes manifest'lerine dönüştür
kompose convert -f docker-compose.yml -o k8s/

# Doğrudan Kubernetes'e uygula
kompose up

# Hangi dosyaların üretildiğini gör
ls k8s/
# app-deployment.yaml
# app-service.yaml
# db-deployment.yaml
# db-persistentvolumeclaim.yaml

Kompose mükemmel değil, üretilen dosyaları mutlaka gözden geçirin, ama migration sürecini hızlandırır.

Yerel Geliştirme İçin Hibrit Yaklaşım

Bazı ekipler production’da Kubernetes kullanırken geliştirme ortamında Docker Compose kullanır. Bu oldukça mantıklı bir yaklaşım.

# Geliştirici makinesinde
docker compose up -d  # Hızlı başlangıç, kolay debug

# CI/CD pipeline'ında
kubectl apply -f k8s/  # Production'a yakın ortam

# Production'da
helm upgrade --install myapp ./charts/myapp 
    --namespace production 
    --values values.prod.yaml

Bu hibrit modelde Docker Compose dosyanız hem geliştirme ortamı olarak hem de Kubernetes manifest’leri için referans belge olarak işlev görür. Servisler, environment variable’lar ve bağımlılıklar açısından iki ortam arasındaki tutarlılığı korumak önemli.

Monitoring: Her İki Durumda da Gerekli

Ne kullandığınızdan bağımsız olarak, production sistemleri izlenmek zorunda. Docker Compose ortamında basit bir monitoring stack’i şöyle kurulabilir:

# docker-compose.monitoring.yml
version: "3.9"

services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
    volumes:
      - grafana_data:/var/lib/grafana
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
    restart: unless-stopped

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8080:8080"
    restart: unless-stopped

volumes:
  prometheus_data:
  grafana_data:
# Monitoring stack'ini başlat
docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d

Bu basit kurulum size container metrikleri, uygulama performansı ve kaynak kullanımı hakkında oldukça iyi bir görünürlük sağlar.

Maliyet Karşılaştırması: Gerçek Rakamlar

Küçük bir SaaS uygulaması için yaklaşık maliyet hesabı yapalım. Bunu tablo yerine somut senaryolarla anlatayım.

Docker Compose ile: Tek bir sunucu üzerinde çalışan stack için Hetzner CX41 (8 vCPU, 16 GB RAM) aylık 24 Euro civarı. Yedekleme için obje depolama ekleyin, aylık 5 Euro daha. Toplam 30 Euro civarı.

Self-managed Kubernetes ile: Minimum 3 node (control plane dahil) gereksinimi vardır. Aynı Hetzner’da 3 adet CX31 (2 vCPU, 8 GB RAM) aylık 45 Euro, artı load balancer için 6 Euro. Toplam 51 Euro. Üstelik operasyonel yük çok daha fazla.

Managed Kubernetes ile (GKE/EKS/AKS): Minimum uygulanabilir cluster için aylık 150-300 dolar arasında değişir. Control plane ücreti, node maliyeti, load balancer ve storage ücretleri birleşince küçük projeler için ciddi bir rakam ortaya çıkar.

Rakamlar değişebilir, ama mesaj net: Küçük projeler için Kubernetes hem öğrenme hem de finansal açıdan pahalıdır.

Sonuç

Yıllarca Kubernetes yönetimi yaptıktan sonra şunu net olarak söyleyebilirim: Kubernetes, gerçekten büyük ölçekli sorunları çözmek için var. Ve o sorunları çözerken harikadır.

Ama küçük projelerde Kubernetes kullanmak, pirinç pişirmek için fırın sıcaklığını hesaplamakla uğraşmak gibi bir şey. Pilav da pişersiniz sonunda, ama neden?

Docker Compose’u hafife almayın. Doğru yapılandırıldığında, sağlam bir monitoring stack’iyle ve iyi bir deployment script’iyle, yüz binlerce kullanıcıya hizmet veren sistemleri başarıyla yönetebilirsiniz. Basitlik bir zaaf değil, bir güçtür.

Projeniz gerçekten büyüdüğünde, Kubernetes’e geçiş yapın. O zaman hazır olacaksınız çünkü sorunlarınızı bileceksiniz ve Kubernetes tam olarak o sorunları çözmek için orada olacak. Ama o güne kadar, docker compose up -d sizi çok daha mutlu edecek.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir