← Kursa Dön
📄 Text · 18 min

Blue-Green ve Canary Deployment Stratejileri

Giriş — "Deploy Etmek" Neden Bu Kadar Stresli?

Cuma akşamı saat 18:00. Yeni feature hazır, testler geçiyor, PR onaylandı. "Deploy edelim" diyorsun. Herkes sessizleşiyor. Birileri "Cuma deploy yapılır mı?" diyor. Neden? Çünkü deploy = risk. Kullanıcılar o an sistemde, bir şey bozulursa herkes etkilenir, geri almak vakit alır.

Deployment stratejileri tam da bu sorunu çözer. Uygulamanın yeni versiyonunu güvenli, kontrollü ve geri alınabilir şekilde canlıya alma yöntemleridir. Doğru stratejiyi seçersen Cuma akşamı bile deploy edebilirsin — gerçekten.

Bu derste üç temel stratejiyi (Rolling Update, Blue-Green, Canary) detaylıca öğrenecek, Spring Boot + Docker ile uygulayacak ve production-ready deployment pipeline kurmanın temellerini atacağız.


Deployment'ın Riskleri

Bir deployment sırasında iki ana risk vardır:

1. Downtime (Kesinti): Eski versiyon kapanır, yeni versiyon ayağa kalkar. Aradaki boşlukta kullanıcılar sisteme erişemez. Saniyeler bile olsa, e-ticaret sitesinde binlerce lira kayıp demek.

2. Hatalı Release: Yeni versiyon bir bug içeriyor. Testlerde yakalanamayan bir edge case, production verisinde patlıyor. Tüm kullanıcılar etkileniyor.

RiskKötü Senaryoİyi Strateji
Downtime5 dakika kesinti, 10K istek kayıpZero-downtime deployment
Hatalı releaseTüm kullanıcılar bug'ı görüyorKademeli rollout + hızlı rollback
Geri alma zorluğuDB migration geri alınamıyorBlue-Green + backward compatible changes
Config hatasıYeni env variable eksik, uygulama başlamıyorHealth check + otomatik rollback

Analoji — Restoran Mutfak Yenileme

Deployment stratejilerini anlamak için bir restoran düşün. Mutfağı yeniliyorsun ama müşterilere hizmet vermeye devam etmen lazım.

Recreate (Naif Yaklaşım): Restoranı kapat, mutfağı yenile, tekrar aç. Basit ama müşteriler gitti.

Rolling Update: Mutfağın bir tezgâhını yenile, o sırada diğer tezgâhlarda yemek yapmaya devam et. Kapasite biraz düşer ama hizmet kesilmez.

Blue-Green: Yan binada ikinci bir mutfak kur. Her şey hazır olduğunda müşterileri yeni mutfağa yönlendir. Sorun olursa eski mutfağa geri dön — 10 saniyede.

Canary: Yeni mutfakta sadece 2 masaya servis yap. Şikâyet gelmezse 5 masaya çık, sonra 10, sonra herkes yeni mutfaktan yesin.


Strateji 1: Rolling Update

Nasıl Çalışır?

Rolling update, instance'ları (pod, container, server) tek tek günceller. 4 instance varsa:

Zaman 0:  [v1] [v1] [v1] [v1]    ← Tüm instance'lar eski
Zaman 1:  [v2] [v1] [v1] [v1]    ← İlk instance güncellendi
Zaman 2:  [v2] [v2] [v1] [v1]    ← İkincisi güncellendi
Zaman 3:  [v2] [v2] [v2] [v1]    ← Üçüncüsü güncellendi
Zaman 4:  [v2] [v2] [v2] [v2]    ← Tümü yeni versiyonda

Her adımda bir instance kapatılır, yerine yeni versiyonu olan instance ayağa kaldırılır. Load balancer, kapalı instance'a trafik göndermez.

Avantajlar ve Dezavantajlar

AvantajDezavantaj
Zero-downtime (doğru yapılırsa)Geçiş sırasında v1 ve v2 aynı anda çalışır
Ekstra kaynak gerektirmezAPI uyumsuzluğu varsa sorun çıkar
Basit, Kubernetes'te varsayılanRollback yavaş (tekrar tek tek geri al)

Ne Zaman Kullanılır?

Rolling update, backward compatible değişiklikler için idealdir. Yeni versiyon, eski versiyonla aynı veritabanı şemasını ve API kontratını kullanıyorsa sorunsuz çalışır. Küçük ve sık deploy yapan ekipler için varsayılan tercih budur.

⚠️ Dikkat: Rolling update sırasında v1 ve v2 aynı anda request alır. Eğer v2'de bir API field'ı kaldırdıysan veya DB'de kolon sildiysen, v1 instance'ları patlayabilir. Her zaman backward compatible değişiklik yap.


Strateji 2: Blue-Green Deployment

Nasıl Çalışır?

Blue-Green deployment'ta iki tam kopya ortam vardır. Biri aktif (kullanıcılara hizmet veriyor), diğeri pasif (bekleme modunda).

                    ┌─────────────┐
                    │ Load Balancer│
                    └──────┬──────┘
                           │
              ┌────────────┼────────────┐
              │                         │
       ┌──────▼──────┐          ┌──────▼──────┐
       │  BLUE (v1)  │          │  GREEN (v2) │
       │  ● Aktif    │          │  ○ Pasif    │
       │  4 instance │          │  4 instance │
       └─────────────┘          └─────────────┘

Deploy süreci:

  1. Green ortama yeni versiyonu deploy et

  2. Green'de testleri çalıştır (smoke test, health check)

  3. Load balancer'ı Green'e yönlendir → Switch!

  4. Sorun varsa → Load balancer'ı Blue'ya geri yönlendir → Instant Rollback!

  5. Her şey yolundaysa → Blue'yu sonraki deploy için hazırla

Avantajlar ve Dezavantajlar

AvantajDezavantaj
Anında rollback — saniyeler içinde2x kaynak maliyeti — iki ortam
Zero-downtime, temiz geçişVeritabanı paylaşımı karmaşık
Production ortamında test imkânıState'li uygulamalarda zor (session, cache)

Spring Boot + Docker ile Blue-Green Örneği

Basit bir Spring Boot uygulaması ve Docker Compose ile blue-green deploy yapalım:

// src/main/java/com/example/demo/HealthController.java

@RestController
public class HealthController {

    @Value("${app.version:unknown}")
    private String appVersion;

    @GetMapping("/info")
    public Map<String, String> info() {
        return Map.of(
            "version", appVersion,
            "timestamp", Instant.now().toString()
        );
    }
}
# docker-compose.yml
version: '3.8'

services:
  app-blue:
    image: myapp:1.0.0
    container_name: app-blue
    ports:
      - "8081:8080"
    environment:
      - APP_VERSION=1.0.0
      - SPRING_PROFILES_ACTIVE=production
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  app-green:
    image: myapp:2.0.0
    container_name: app-green
    ports:
      - "8082:8080"
    environment:
      - APP_VERSION=2.0.0
      - SPRING_PROFILES_ACTIVE=production
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  nginx:
    image: nginx:alpine
    container_name: lb
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app-blue
      - app-green

NGINX konfigürasyonu — switch burada yapılır:

# nginx.conf
events { worker_connections 1024; }

http {
    upstream app_active {
        server app-blue:8080;   # Switch: "app-green:8080" yap
    }

    server {
        listen 80;
        location / {
            proxy_pass http://app_active;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

Switch yapmak için nginx.conf içindeki upstream'i app-green:8080 olarak değiştir ve docker exec lb nginx -s reload çalıştır. Rollback için tekrar app-blue:8080 yap. Gerçek ortamda bu işlem bir bash script'i ile otomatize edilir:

#!/bin/bash
# blue-green-switch.sh

ACTIVE_ENV=$1  # "blue" veya "green"

if [ "$ACTIVE_ENV" == "green" ]; then
    TARGET="app-green:8080"
else
    TARGET="app-blue:8080"
fi

# Yeni ortamın sağlığını kontrol et
HEALTH=$(curl -s "http://localhost:${TARGET%%:*}/actuator/health" | jq -r '.status')
if [ "$HEALTH" != "UP" ]; then
    echo "HATA: $TARGET sağlıklı değil! Switch iptal."
    exit 1
fi

# NGINX upstream güncelle ve reload
sed -i "s/server app-[a-z]*:8080/server $TARGET/" nginx.conf
docker exec lb nginx -s reload
echo "Switch tamamlandı! Aktif: $ACTIVE_ENV"

Strateji 3: Canary Deployment

Nasıl Çalışır?

Canary deployment, yeni versiyonu önce küçük bir kullanıcı grubuna sunar. Sorun yoksa kademeli olarak genişletir. Adı, madenlerde zehirli gazı erken tespit etmek için kullanılan kanarya kuşlarından gelir.

Aşama 1:  %5 trafik → v2    %95 trafik → v1
          ┌──┐               ┌──┬──┬──┬──┐
          │v2│               │v1│v1│v1│v1│
          └──┘               └──┴──┴──┴──┘
          Metrikler OK? ✅ → Aşama 2'ye geç

Aşama 2:  %20 trafik → v2   %80 trafik → v1
          ┌──┐               ┌──┬──┬──┐
          │v2│               │v1│v1│v1│
          └──┘               └──┴──┴──┘
          Metrikler OK? ✅ → Aşama 3'e geç

Aşama 3:  %100 trafik → v2
          ┌──┬──┬──┬──┐
          │v2│v2│v2│v2│
          └──┴──┴──┴──┘

Her aşamada metrikler izlenir: error rate, response time, CPU/memory. Anomali tespit edilirse otomatik rollback yapılır.

Avantajlar ve Dezavantajlar

AvantajDezavantaj
Minimum blast radiusKarmaşık setup — monitoring, trafik yönetimi
Gerçek production verisiyle testİki versiyon aynı anda çalışır
A/B testing ile kombine edilebilirTam rollout uzun sürer

Netflix, Google, Facebook gibi şirketler canary deployment'ı standart olarak kullanır. Milyonlarca kullanıcıya bir bug göndermektense, %1'ine göndermek çok daha az acı verir.


Kubernetes'te Deployment Stratejileri

Kubernetes, deployment stratejilerini native olarak destekler.

Rolling Update (Varsayılan)

# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # Aynı anda en fazla 1 ekstra pod
      maxUnavailable: 1   # Aynı anda en fazla 1 pod kapalı
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myapp:2.0.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /actuator/health
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /actuator/health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10

maxSurge: 1 → Güncelleme sırasında en fazla 5 pod olabilir (4+1). maxUnavailable: 1 → En fazla 1 pod aynı anda kapalı. Her zaman minimum 3 pod hizmet verir.

Blue-Green (K8s)

Kubernetes'te blue-green, iki ayrı Deployment + Service selector değişikliği ile yapılır:

# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-blue
spec:
  replicas: 4
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
        - name: myapp
          image: myapp:1.0.0
---
# service.yaml — Switch burada yapılır
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
    version: blue   # "green" yaparak switch et!
  ports:
    - port: 80
      targetPort: 8080

Switch: kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'

Rollback: kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"blue"}}}'

Canary (K8s)

Gerçek canary için Istio, Flagger veya Argo Rollouts kullanılır. Native K8s'te replica sayısı ile basit bir canary yapılabilir:

# v1: 3 replica, v2: 1 replica → yaklaşık %25 canary
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-stable
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      track: stable
  template:
    metadata:
      labels:
        app: myapp
        track: stable
    spec:
      containers:
        - name: myapp
          image: myapp:1.0.0
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
      track: canary
  template:
    metadata:
      labels:
        app: myapp
        track: canary
    spec:
      containers:
        - name: myapp
          image: myapp:2.0.0
---
# Service — track label olmadan, her ikisini de seçer
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
    - port: 80
      targetPort: 8080

💡 İpucu: Istio VirtualService ile %5, %10, %20 gibi ince ayar yapılabilir. Replica sayısı ile yapılan yöntem kaba ama basit — başlangıç için yeterli.


Health Check ile Deployment Doğrulama

Her stratejinin ortak noktası: yeni versiyon sağlıklı mı? Spring Boot Actuator bunu hazır sağlar:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics
  endpoint:
    health:
      show-details: when-authorized
      probes:
        enabled: true  # K8s liveness/readiness probes
  health:
    db:
      enabled: true
    diskSpace:
      enabled: true

Custom health indicator ile uygulamana özel kontroller ekleyebilirsin:

@Component
public class ExternalApiHealthIndicator implements HealthIndicator {

    private final RestClient restClient;

    public ExternalApiHealthIndicator(RestClient.Builder builder) {
        this.restClient = builder.baseUrl("https://payment-service.internal").build();
    }

    @Override
    public Health health() {
        try {
            var response = restClient.get()
                .uri("/health")
                .retrieve()
                .toEntity(String.class);

            if (response.getStatusCode().is2xxSuccessful()) {
                return Health.up()
                    .withDetail("paymentService", "reachable")
                    .build();
            }
            return Health.down()
                .withDetail("paymentService", "unhealthy")
                .build();
        } catch (Exception e) {
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
    }
}

Rollback Planı

Her deployment'ın bir rollback planı olmalı. Onsuz deploy etmek, paraşütsüz atlamak gibi.

Ne Zaman Rollback Yapılır?

  • Health check DOWN dönüyor

  • Error rate %5'i aşıyor (5xx hatalar)

  • Response time kabul edilemez seviyeye çıkıyor (p99 > 2s)

  • Kritik iş akışı bozuluyor (ödeme, login)

Strateji Bazında Rollback

StratejiRollback HızıNasıl?
Rolling UpdateYavaş (dakikalar)kubectl rollout undo deployment/myapp
Blue-GreenAnında (saniyeler)Load balancer'ı eski ortama çevir
CanaryHızlı (saniyeler)Canary instance'ı kapat

Deploy sonrası otomatik doğrulama scripti:

#!/bin/bash
# deployment-validator.sh

MAX_RETRIES=10
HEALTH_URL="http://localhost:8080/actuator/health"

for i in $(seq 1 $MAX_RETRIES); do
    HEALTH=$(curl -s $HEALTH_URL | jq -r '.status' 2>/dev/null)
    if [ "$HEALTH" == "UP" ]; then
        echo "✅ Deployment başarılı! (deneme $i/$MAX_RETRIES)"
        exit 0
    fi
    echo "⏳ Bekleniyor... (deneme $i/$MAX_RETRIES)"
    sleep 5
done

echo "❌ Deployment başarısız! Rollback başlatılıyor..."
# kubectl rollout undo deployment/myapp
exit 1

⚠️ Dikkat: Veritabanı migration'ları rollback'i zorlaştırır. Kolon silme gibi yıkıcı migration'ları deploy'dan ayrı yap. Önce kodu deploy et (eski kolonu kullanmayı bırak), sonra ayrı adımda kolonu sil. Buna expand and contract pattern denir.


Feature Flags + Canary Kombinasyonu

Feature flag'ler, kodun içinde açma-kapama düğmeleri oluşturur. Canary ile birleştirildiğinde çift katmanlı güvenlik sağlar.

@Service
public class FeatureFlagService {

    private final Map<String, FeatureFlag> flags = new ConcurrentHashMap<>();

    @PostConstruct
    void initFlags() {
        flags.put("new-payment-engine", new FeatureFlag(true, 10));
        flags.put("new-search-algo", new FeatureFlag(false, 0));
    }

    public boolean isEnabled(String flagName, String userId) {
        FeatureFlag flag = flags.get(flagName);
        if (flag == null || !flag.enabled()) {
            return false;
        }
        // Deterministic hash — aynı kullanıcı her zaman aynı sonucu alır
        int hash = Math.abs(userId.hashCode() % 100);
        return hash < flag.rolloutPercentage();
    }

    record FeatureFlag(boolean enabled, int rolloutPercentage) {}
}

Kullanımı:

@RestController
@RequestMapping("/api/payments")
public class PaymentController {

    private final FeatureFlagService featureFlags;
    private final OldPaymentService oldPaymentService;
    private final NewPaymentService newPaymentService;

    // constructor injection...

    @PostMapping
    public PaymentResult processPayment(@RequestBody PaymentRequest request,
                                        @AuthenticationPrincipal User user) {
        if (featureFlags.isEnabled("new-payment-engine", user.getId())) {
            return newPaymentService.process(request);   // %10 kullanıcı
        }
        return oldPaymentService.process(request);       // %90 kullanıcı
    }
}

Canary + Feature Flag kombinasyonu:

  1. Canary ile yeni versiyonu %5 kullanıcıya sun

  2. Feature flag ile yeni motoru bu %5'in %10'una aç (gerçekte %0.5)

  3. Metrikler OK → Flag'i %50'ye, canary'yi %20'ye çık

  4. Adım adım full rollout


Stratejileri Karşılaştırma

KriterRolling UpdateBlue-GreenCanary
KarmaşıklıkDüşükOrtaYüksek
MaliyetDüşükYüksek (2x kaynak)Orta
Rollback hızıYavaşAnındaHızlı
RiskOrtaDüşükÇok düşük
Zero-downtime
A/B test
En uygunKüçük-orta projelerKritik sistemlerBüyük ölçekli sistemler

Local Proje Kurulumu

Bu derste anlatılan örnekleri çalıştırmak için:

Gereksinimler: Java 17+, Docker ve Docker Compose, (opsiyonel) Minikube veya kind

Adımlar:

  1. Spring Initializr'dan proje oluştur: Web, Actuator dependency'leri

  2. Dockerfile ekle:

FROM eclipse-temurin:17-jre-alpine
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
  1. mvn clean package -DskipTests ile JAR oluştur

  2. docker build -t myapp:1.0.0 . ile image oluştur

  3. Bu dersteki docker-compose.yml ve nginx.conf dosyalarını kopyala

  4. docker compose up -d ile başlat

  5. curl http://localhost/info ile test et


Özet

  • Rolling Update, instance'ları tek tek günceller — basit, az kaynak, ama rollback yavaş ve geçiş sırasında iki versiyon aynı anda çalışır.

  • Blue-Green Deployment, iki paralel ortam tutar — anında rollback, zero-downtime, ama 2x kaynak maliyeti ve DB paylaşımı komplikasyonu var.

  • Canary Deployment, yeni versiyonu kademeli olarak yayar (%5 → %20 → %100) — minimum risk, gerçek production verisiyle test, ama karmaşık setup gerektirir.

  • Health check endpoint'leri (/actuator/health) her stratejide kritik — deployment doğrulama, otomatik rollback kararı, Kubernetes probes.

  • Feature flags, deployment'tan bağımsız feature kontrolü sağlar — canary ile birleştirildiğinde çift katmanlı güvenlik oluşturur.

  • Rollback planı olmadan deploy yapma — her strateji için geri alma prosedürün hazır olsun, DB değişikliklerini backward compatible tut.