Ç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.
