Çoklu Bölge Felaket Kurtarma: Aktif-Aktif Mimari Tasarımı ve Uygulaması

Felaket kurtarma planı yaparken çoğu ekip “aktif-pasif” mimariye yerleşir ve bir şeyler ters gittiğinde o pasif bölgenin gerçekten çalışıp çalışmadığını test etmeden aylar geçirir. Sonra gerçek bir felaket anında “failover” yapmaya çalışırsın ve sistemin yarısının ayağa kalkmadığını görürsün. Aktif-aktif mimari tam da bu problemi ortadan kaldırmak için var: her bölge sürekli trafik alıyor, her bölge sürekli test ediliyor, çünkü üretimde çalışıyor.

Aktif-Aktif Mimarinin Temelleri

Aktif-aktif, basitçe şu anlama geliyor: birden fazla veri merkezi veya bulut bölgesi aynı anda üretim trafiği alıyor ve her biri diğerinin yedeği olabiliyor. Bir bölge çöktüğünde trafik otomatik olarak diğer bölgelere yönlendiriliyor, kullanıcılar neredeyse hiç kesinti yaşamıyor.

Bunu başarabilmek için çözmen gereken üç temel sorun var:

  • Veri senkronizasyonu: İki bölgedeki veritabanları tutarlı kalmalı
  • Trafik yönetimi: DNS ve load balancer katmanı akıllıca davranmalı
  • Uygulama durumu: Stateless tasarım veya distributed session yönetimi şart

Gerçek dünyadan bir örnek verelim. E-ticaret platformu işlettiğini varsay. İstanbul ve Frankfurt’ta iki AWS region’ın var. Bir kullanıcı İstanbul üzerinden sipariş başlatıyor, ödeme adımında Frankfurt’a yönleniyor. Eğer veri senkronizasyonun yoksa kullanıcı sepetini kaybeder. Bu yüzden mimari kararlar çok kritik.

Altyapı Tasarımı

DNS Katmanı ile Global Trafik Yönetimi

İlk katman DNS seviyesinde başlıyor. AWS Route 53 veya Cloudflare gibi global DNS servisleri health check ile entegre trafik yönlendirmesi yapabiliyor.

# Route 53 health check oluşturma (AWS CLI)
aws route53 create-health-check 
  --caller-reference "region-eu-west-1-$(date +%s)" 
  --health-check-config '{
    "IPAddress": "52.10.20.30",
    "Port": 443,
    "Type": "HTTPS",
    "ResourcePath": "/health",
    "FullyQualifiedDomainName": "api-eu.example.com",
    "RequestInterval": 10,
    "FailureThreshold": 2,
    "MeasureLatency": true
  }'

# Latency-based routing policy ile record oluşturma
aws route53 change-resource-record-sets 
  --hosted-zone-id Z1234567890ABC 
  --change-batch '{
    "Changes": [{
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "api.example.com",
        "Type": "A",
        "SetIdentifier": "eu-west-1",
        "Region": "eu-west-1",
        "TTL": 30,
        "ResourceRecords": [{"Value": "52.10.20.30"}],
        "HealthCheckId": "abcd1234-5678-90ab-cdef"
      }
    }]
  }'

TTL değerini 30 saniyeye çekmen çok önemli. Normal koşullarda 300-3600 saniye olan TTL, failover anında ciddi sorunlara yol açar. Düşük TTL maliyeti biraz daha yüksek DNS sorgusu demek, ama buna değer.

Load Balancer Konfigürasyonu

Her bölgede kendi load balancer’ın çalışmalı. Nginx veya HAProxy ile sağlık kontrolü yapan bir yapı kurabilirsin:

# /etc/nginx/nginx.conf - Aktif-Aktif Load Balancer Konfigürasyonu
upstream backend_pool {
    least_conn;
    
    server app-node-1:8080 weight=3 max_fails=2 fail_timeout=10s;
    server app-node-2:8080 weight=3 max_fails=2 fail_timeout=10s;
    server app-node-3:8080 weight=3 max_fails=2 fail_timeout=10s;
    
    keepalive 32;
}

server {
    listen 443 ssl;
    server_name api.example.com;
    
    # Health check endpoint'i
    location /health {
        access_log off;
        return 200 '{"status":"healthy","region":"eu-west-1","timestamp":"$time_iso8601"}';
        add_header Content-Type application/json;
    }
    
    location / {
        proxy_pass http://backend_pool;
        proxy_set_header X-Region "eu-west-1";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_connect_timeout 2s;
        proxy_read_timeout 30s;
        proxy_next_upstream error timeout http_502 http_503;
        proxy_next_upstream_tries 2;
    }
}

proxy_next_upstream direktifi çok kritik. Bir backend node cevap vermediğinde Nginx otomatik olarak pool’daki bir sonraki node’a geçiyor, kullanıcı hata görmüyor.

Veritabanı Senkronizasyonu

Bu kısım aktif-aktif mimarinin en zorlu parçası. İki bölgede aynı anda yazma işlemi kabul eden veritabanı konfigürasyonu kurmak ciddi teknik bilgi gerektiriyor.

PostgreSQL ile Multi-Master Replikasyon

PostgreSQL için Patroni + Pgpool-II veya BDR (Bi-Directional Replication) kullanabilirsin. Daha pratik bir yol olarak CockroachDB veya YugabyteDB gibi distributed SQL veritabanlarını tercih edebilirsin. Ama eğer PostgreSQL’den ayrılmak istemiyorsan:

# Patroni konfigürasyonu - /etc/patroni/config.yml
cat > /etc/patroni/config.yml << 'EOF'
scope: postgres-cluster
namespace: /db/
name: node-eu-west-1

restapi:
  listen: 0.0.0.0:8008
  connect_address: 10.0.1.10:8008

etcd3:
  hosts:
    - 10.0.1.10:2379
    - 10.0.2.10:2379
    - 10.0.3.10:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    synchronous_mode: true
    synchronous_mode_strict: false
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        wal_level: logical
        max_wal_senders: 10
        max_replication_slots: 10
        synchronous_commit: remote_apply

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 10.0.1.10:5432
  data_dir: /var/lib/postgresql/14/main
  authentication:
    replication:
      username: replicator
      password: "güçlü-parola-buraya"
    superuser:
      username: postgres
      password: "süper-güçlü-parola"
EOF

systemctl enable patroni
systemctl start patroni

Redis Cluster ile Session Senkronizasyonu

Uygulama session’larını her iki bölgede de geçerli tutmak için Redis Cluster kullanman gerekiyor:

# Redis Cluster kurulumu ve cross-region replikasyon
# /etc/redis/redis-cluster.conf

cat > /etc/redis/redis-cluster.conf << 'EOF'
port 6379
cluster-enabled yes
cluster-config-file /etc/redis/nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 10.0.1.20
cluster-announce-port 6379
cluster-announce-bus-port 16379

# Cross-region replikasyon için
replicaof no one
replica-read-only no
replica-lazy-flush yes

# Güvenlik
requirepass "redis-cluster-parola-2024"
masterauth "redis-cluster-parola-2024"

# Persistence
appendonly yes
appendfsync everysec
save 900 1
save 300 10
EOF

# Cluster oluşturma (her iki bölgedeki node'ları dahil ederek)
redis-cli --cluster create 
  10.0.1.20:6379 10.0.1.21:6379 10.0.1.22:6379 
  10.0.2.20:6379 10.0.2.21:6379 10.0.2.22:6379 
  --cluster-replicas 1 
  -a "redis-cluster-parola-2024"

Uygulama Katmanı Konfigürasyonu

Uygulamanın aktif-aktif mimariye uyum sağlaması için stateless olması idealdir. Eğer Docker ve Kubernetes kullanıyorsan:

# kubernetes/deployment-multi-region.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service
  namespace: production
  labels:
    app: api-service
    region: eu-west-1
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: api-service
  template:
    metadata:
      labels:
        app: api-service
        version: "2.1.0"
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: api-service
      containers:
        - name: api
          image: registry.example.com/api-service:2.1.0
          ports:
            - containerPort: 8080
          env:
            - name: REGION
              valueFrom:
                fieldRef:
                  fieldPath: metadata.labels['region']
            - name: DB_PRIMARY_HOST
              value: "postgres-primary.eu-west-1.internal"
            - name: DB_REPLICA_HOST
              value: "postgres-replica.eu-west-1.internal"
            - name: REDIS_CLUSTER_NODES
              value: "redis-1:6379,redis-2:6379,redis-3:6379"
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"

Monitoring ve Alerting Kurulumu

Aktif-aktif sistemleri izlemek pasif yedekten çok daha karmaşık. Her iki bölgeni de eş zamanlı izlemen ve aralarındaki gecikmeyi takip etmen gerekiyor.

#!/bin/bash
# /usr/local/bin/multi-region-health-check.sh
# Her dakika çalışacak şekilde cron'a ekle

REGIONS=("eu-west-1" "us-east-1")
ENDPOINTS=("https://api-eu.example.com/health" "https://api-us.example.com/health")
SLACK_WEBHOOK="https://hooks.slack.com/services/XXXXX/YYYYY/ZZZZZ"
ALERT_THRESHOLD_MS=500
LOG_FILE="/var/log/multi-region-health.log"

check_region_health() {
  local region=$1
  local endpoint=$2
  
  # Response time ve HTTP kodu ölç
  response=$(curl -s -o /dev/null -w "%{http_code}:%{time_total}" 
    --connect-timeout 5 
    --max-time 10 
    "$endpoint")
  
  http_code=$(echo $response | cut -d: -f1)
  response_time_s=$(echo $response | cut -d: -f2)
  response_time_ms=$(echo "$response_time_s * 1000" | bc | cut -d. -f1)
  
  timestamp=$(date '+%Y-%m-%d %H:%M:%S')
  
  if [ "$http_code" != "200" ]; then
    message="KRITIK: $region bölgesi erişilemiyor! HTTP: $http_code"
    echo "$timestamp - $message" >> $LOG_FILE
    curl -s -X POST $SLACK_WEBHOOK 
      -H 'Content-type: application/json' 
      -d "{"text":"🔴 $message","channel":"#ops-alerts"}"
    return 1
  fi
  
  if [ "$response_time_ms" -gt "$ALERT_THRESHOLD_MS" ]; then
    message="UYARI: $region yüksek gecikme: ${response_time_ms}ms"
    echo "$timestamp - $message" >> $LOG_FILE
    curl -s -X POST $SLACK_WEBHOOK 
      -H 'Content-type: application/json' 
      -d "{"text":"🟡 $message","channel":"#ops-alerts"}"
  fi
  
  echo "$timestamp - $region OK: ${response_time_ms}ms" >> $LOG_FILE
  return 0
}

# Replikasyon gecikmesini kontrol et
check_replication_lag() {
  lag=$(psql -h postgres-primary.eu-west-1.internal 
    -U monitor_user -d postgres 
    -t -c "SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::INT;" 2>/dev/null)
  
  if [ -n "$lag" ] && [ "$lag" -gt 30 ]; then
    message="UYARI: Replikasyon gecikmesi ${lag} saniye!"
    curl -s -X POST $SLACK_WEBHOOK 
      -H 'Content-type: application/json' 
      -d "{"text":"⚠️ $message","channel":"#ops-alerts"}"
  fi
}

for i in "${!REGIONS[@]}"; do
  check_region_health "${REGIONS[$i]}" "${ENDPOINTS[$i]}"
done

check_replication_lag

Felaket Kurtarma Testi: Game Day Senaryoları

Aktif-aktif mimarinin en güzel yanı, test etmesinin aktif-pasif’e göre çok daha kolay olması. Ama yine de düzenli “game day” tatbikatları yapman şart.

Chaos Engineering ile Bölge Testi

#!/bin/bash
# /usr/local/bin/chaos-region-test.sh
# DIKKAT: Sadece staging ortamında veya planlı maintenance window'da çalıştır

REGION_TO_TEST="eu-west-1"
TEST_DURATION=300  # 5 dakika
NOTIFICATION_CHANNEL="#ops-game-day"
SLACK_WEBHOOK="https://hooks.slack.com/services/XXXXX/YYYYY/ZZZZZ"

notify() {
  curl -s -X POST $SLACK_WEBHOOK 
    -H 'Content-type: application/json' 
    -d "{"text":"$1","channel":"$NOTIFICATION_CHANNEL"}"
}

echo "=== GAME DAY: $REGION_TO_TEST Bölge Kesinti Testi ==="
notify "🧪 GAME DAY başlıyor: $REGION_TO_TEST bölgesi $TEST_DURATION saniye devre dışı bırakılıyor"

# Baseline metrikleri kaydet
BASELINE_RPS=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_requests_total[1m])" | jq '.data.result[0].value[1]')
echo "Baseline RPS: $BASELINE_RPS"

# Route 53'ten sağlık kontrolünü başarısız yap (simülasyon için)
# Gerçekte network ACL veya güvenlik grubu kuralıyla trafiği kes
aws ec2 modify-network-acl-entry 
  --network-acl-id acl-$REGION_TO_TEST 
  --rule-number 100 
  --protocol -1 
  --rule-action deny 
  --ingress 
  --cidr-block 0.0.0.0/0

sleep 30  # DNS propagation için bekle

# Diğer bölgenin tüm trafiği aldığını doğrula
OTHER_REGION_RPS=$(curl -s "http://prometheus-us:9090/api/v1/query?query=rate(http_requests_total[1m])" | jq '.data.result[0].value[1]')
echo "Failover sonrası RPS (US): $OTHER_REGION_RPS"
notify "📊 Failover metrikleri - Önceki RPS: $BASELINE_RPS, Mevcut RPS (US): $OTHER_REGION_RPS"

# Test süresince bekle
echo "Test devam ediyor... $TEST_DURATION saniye"
sleep $TEST_DURATION

# Bölgeyi geri getir
aws ec2 modify-network-acl-entry 
  --network-acl-id acl-$REGION_TO_TEST 
  --rule-number 100 
  --protocol -1 
  --rule-action allow 
  --ingress 
  --cidr-block 0.0.0.0/0

sleep 60  # Trafiğin normalize olması için bekle

notify "✅ GAME DAY tamamlandı: $REGION_TO_TEST yeniden aktif. Raporlar hazırlanıyor..."
echo "Test tamamlandı. Logları /var/log/game-day-$(date +%Y%m%d).log dosyasında inceleyebilirsin."

RTO ve RPO Gerçek Dünya Değerleri

Aktif-aktif mimaride teori ile pratik arasında fark oluyor. Beklentileri doğru yönetmek için şu gerçekçi rakamları göz önünde bulundur:

  • RTO (Kurtarma Süresi): DNS TTL + health check süresi + load balancer timeout = genellikle 30-90 saniye
  • RPO (Veri Kaybı Toleransı): Senkron replikasyon ile sıfıra yakın, asenkron ile saniyeler mertebesinde
  • Yazma çakışmaları: Multi-master kurulumda conflict resolution mekanizman olmalı
  • Network gecikme etkisi: Bölgeler arası latency uygulamanın SLA’sını doğrudan etkiler

Cross-region veritabanı yazma işlemleri için dikkat etmen gereken konu şu: eğer her iki bölgede de aynı kayda eş zamanlı yazılıyorsa “last write wins” veya uygulama seviyesinde conflict resolution stratejin olmalı. Bu tasarım aşamasında netleştirilmesi gereken bir karar.

Yaygın Hatalar ve Çözümleri

Aktif-aktif kurarken sıklıkla karşılaşılan sorunlar ve çözümleri:

  • Split-brain sendromu: Bölgeler birbirini göremez olduğunda her ikisi de primary sanır kendini. Quorum mekanizması ve fencing tokenleri bu sorunu çözer.
  • Asimetrik trafik: Bir bölge diğerinden çok daha fazla trafik alıyorsa weighted routing politikası güncellenmeli.
  • Replikasyon lag’i artması: Yoğun yazma dönemlerinde replika geri kalabilir. Yazma işlemlerini primary’ye yönlendirip okuma yükünü dağıtmak gerekebilir.
  • İkinci bölgenin “ısınmaması”: Az trafik alan bölgede JVM gibi runtime’lar optimize edilmemiş olabilir. Yapay trafik ile warm-up mekanizması kurulabilir.
  • Deployment tutarsızlıkları: Her iki bölgeye aynı anda deployment yapmayı sağlayan CI/CD pipeline’ı olmalı.

Sonuç

Aktif-aktif mimari, tek başına bir “kurulum ve unut” çözümü değil. Her gün test edilen, her gün optimize edilen, sürekli izlenen bir sistemdir. Bu yüzden ekibinin bu mimariyi anlayıp sahiplenmesi, araçlar kadar önemli.

Başlamak için şu adımları izlemen mantıklı olur: önce uygulamayı stateless hale getir, ardından DNS ve load balancer katmanını kur, sonra veritabanı replikasyonunu ekle ve en son olarak düzenli game day tatbikatları başlat. Her adımı üretim benzeri bir ortamda test etmeden bir sonrakine geçme.

Aktif-aktif’in gerçek değeri yalnızca felaket anında ortaya çıkmıyor. Normal çalışma sürecinde yük dengeleme, düşük latency ve sıfır-kesintili deployment gibi faydaları da getiriyor. Bu mimariyi kurmak için harcanan emek, ilk ciddi kesinti senaryosunda kendini fazlasıyla geri kazandırıyor.

Bir yanıt yazın

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