Ağ Altyapısı Çöküşünde Felaket Kurtarma: BGP ve DNS Failover Senaryoları
Sabah 03:47’de telefonun çalması, hiçbir sysadmin’in duymak istemediği şeydir. “Sitemize ulaşamıyoruz, tüm kullanıcılar etkileniyor” mesajı geldiğinde, eğer önceden hazırlanmadıysanız o gece çok uzun geçecek demektir. Ağ altyapısı çöküşleri, disk arızaları veya uygulama hatalarından çok daha sinsi bir yapıya sahiptir çünkü etki alanı geniş, hata noktaları dağınık ve kurtarma süreci genellikle birden fazla katmanı kapsar. Bu yazıda BGP failover ve DNS failover senaryolarını gerçek dünya pratikleriyle ele alacağız.
Ağ Felaketinin Anatomisi
Bir ağ altyapısı çöküşü tek bir noktadan kaynaklanmıyor gibi görünse de genellikle birkaç temel senaryoya indirgenir:
- Upstream ISP arızası: Transit sağlayıcınızın hatası, sizin tüm internet bağlantınızı koparır
- BGP oturumu düşmesi: Router’larınız arasındaki BGP session’ları kapanır, route’lar çekilir
- DNS altyapısı çöküşü: Authoritative DNS sunucularınız yanıt vermez, kullanıcılar sitenize ulaşamaz
- DDoS sonrası blackhole: Upstream sağlayıcı sizi blackhole’a alır, siz de erişilemez olursunuz
- Veri merkezi fiziksel arıza: Güç, soğutma veya fiber kesintisi tüm aktif ekipmanı etkiler
Bu senaryoların her biri farklı kurtarma mekanizması gerektirir. BGP failover genellikle routing katmanını, DNS failover ise uygulama erişilebilirliği katmanını korur.
BGP Failover: Temel Kavramlar ve Hazırlık
BGP failover’ı anlamak için önce neden birden fazla upstream bağlantısı gerektiğini netleştirmek lazım. Multihoming, yani birden fazla ISP’ye bağlı olmak, tek noktadan bağımlılığı ortadan kaldırır. Ama sadece iki kablo takmak yetmez; BGP politikalarını doğru yapılandırmak gerekir.
AS Path ve Local Preference Yapılandırması
Gerçek bir senaryoda şunu düşünelim: İki farklı ISP’niz var. ISP-A birincil, ISP-B yedek. Normal koşullarda tüm trafiği ISP-A üzerinden yönlendiriyorsunuz, ISP-A düştüğünde otomatik olarak ISP-B devreye girmeli.
# Cisco IOS benzeri yapılandırma (FRRouting/Quagga için de geçerli)
# /etc/frr/bgpd.conf
router bgp 65001
bgp router-id 203.0.113.1
neighbor 198.51.100.1 remote-as 65100
neighbor 198.51.100.1 description ISP-A-PRIMARY
neighbor 198.51.100.1 soft-reconfiguration inbound
neighbor 203.0.114.1 remote-as 65200
neighbor 203.0.114.1 description ISP-B-BACKUP
neighbor 203.0.114.1 soft-reconfiguration inbound
address-family ipv4 unicast
network 192.0.2.0/24
neighbor 198.51.100.1 route-map ISP-A-IN in
neighbor 198.51.100.1 route-map ISP-A-OUT out
neighbor 203.0.114.1 route-map ISP-B-IN in
neighbor 203.0.114.1 route-map ISP-B-OUT out
exit-address-family
route-map ISP-A-IN permit 10
set local-preference 200
route-map ISP-B-IN permit 10
set local-preference 100
route-map ISP-A-OUT permit 10
route-map ISP-B-OUT permit 10
Bu yapılandırmada ISP-A üzerinden gelen route’lara local-preference 200, ISP-B’den gelenlere 100 veriyoruz. BGP her zaman daha yüksek local-preference değerini tercih eder, dolayısıyla ISP-A aktif olduğu sürece trafik oradan akar.
BGP Session Monitoring Script’i
BGP oturumlarını izlemek ve düşme anında otomatik aksiyon almak için bir monitoring script’i şart:
#!/bin/bash
# /usr/local/bin/bgp_monitor.sh
BGP_NEIGHBORS=("198.51.100.1" "203.0.114.1")
ALERT_EMAIL="[email protected]"
LOG_FILE="/var/log/bgp_monitor.log"
SLACK_WEBHOOK="https://hooks.slack.com/services/XXXXX"
check_bgp_session() {
local neighbor=$1
local status=$(vtysh -c "show bgp neighbor $neighbor" 2>/dev/null | grep "BGP state" | awk '{print $4}' | tr -d ',')
echo $status
}
send_alert() {
local message=$1
echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" >> $LOG_FILE
# Slack bildirimi
curl -s -X POST $SLACK_WEBHOOK
-H 'Content-type: application/json'
-d "{"text":"🚨 BGP ALARM: $message"}" > /dev/null
# Email bildirimi
echo "$message" | mail -s "BGP Session Alert" $ALERT_EMAIL
}
for neighbor in "${BGP_NEIGHBORS[@]}"; do
status=$(check_bgp_session $neighbor)
if [ "$status" != "Established" ]; then
send_alert "BGP session DOWN - Neighbor: $neighbor, Status: $status"
# Failover script'ini tetikle
/usr/local/bin/bgp_failover.sh $neighbor
fi
done
Otomatik Failover Script’i
#!/bin/bash
# /usr/local/bin/bgp_failover.sh
FAILED_NEIGHBOR=$1
PRIMARY_ISP="198.51.100.1"
BACKUP_ISP="203.0.114.1"
LOG_FILE="/var/log/bgp_failover.log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}
if [ "$FAILED_NEIGHBOR" == "$PRIMARY_ISP" ]; then
log "Primary ISP DOWN. Activating backup route via $BACKUP_ISP"
# Backup ISP local-preference'ını yükselt
vtysh << EOF
configure terminal
route-map ISP-B-IN permit 10
set local-preference 300
end
clear ip bgp $BACKUP_ISP soft in
write memory
EOF
log "Failover completed. Traffic shifted to backup ISP."
# Recovery script'i background'da başlat
/usr/local/bin/bgp_recovery_monitor.sh &
elif [ "$FAILED_NEIGHBOR" == "$BACKUP_ISP" ]; then
log "Backup ISP DOWN. Primary still active, no action needed."
log "Monitoring primary ISP health..."
fi
DNS Failover: Stratejiler ve Uygulamalar
BGP failover routing katmanını korurken, DNS failover uygulama erişilebilirliğini sağlar. İki farklı yaklaşım var: passive DNS failover ve active health-check based DNS failover.
GeoDNS ve Anycast Temelli Yaklaşım
Büyük ölçekli yapılarda Anycast DNS kullanmak, tek nokta arızasını neredeyse imkansız kılar. Bind9 ile temel bir Anycast kurulumu:
# /etc/bind/named.conf.options
options {
directory "/var/cache/bind";
# Anycast IP adresimiz
listen-on { 192.0.2.53; 127.0.0.1; };
listen-on-v6 { 2001:db8::53; ::1; };
# Recursive query'leri kapat (authoritative için)
recursion no;
# DNSSEC
dnssec-validation auto;
# Rate limiting - DDoS koruması
rate-limit {
responses-per-second 10;
window 5;
log-only no;
};
# Transfer izinleri
allow-transfer {
198.51.100.0/24; # Secondary nameserver subnet
};
# Minimal responses
minimal-responses yes;
};
# Health check zone
zone "healthcheck.example.com" {
type master;
file "/etc/bind/zones/healthcheck.example.com.zone";
allow-query { any; };
};
Aktif Sağlık Kontrolü ile DNS Failover
PowerDNS ile health-check tabanlı failover için bir Python script’i:
#!/usr/bin/env python3
# /usr/local/bin/dns_failover_manager.py
import requests
import subprocess
import time
import logging
from datetime import datetime
logging.basicConfig(
filename='/var/log/dns_failover.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
POWERDNS_API = "http://localhost:8081/api/v1"
POWERDNS_KEY = "gizli-api-anahtari"
ZONE = "example.com."
ENDPOINTS = {
"primary": {
"ip": "203.0.113.10",
"health_url": "http://203.0.113.10/health",
"weight": 100
},
"secondary": {
"ip": "198.51.100.20",
"health_url": "http://198.51.100.20/health",
"weight": 0
}
}
def check_endpoint_health(url, timeout=5):
try:
response = requests.get(url, timeout=timeout)
return response.status_code == 200
except Exception as e:
logging.warning(f"Health check failed for {url}: {e}")
return False
def update_dns_record(name, ip_address, ttl=30):
headers = {
"X-API-Key": POWERDNS_KEY,
"Content-Type": "application/json"
}
data = {
"rrsets": [{
"name": f"{name}.{ZONE}",
"type": "A",
"ttl": ttl,
"changetype": "REPLACE",
"records": [{"content": ip_address, "disabled": False}]
}]
}
response = requests.patch(
f"{POWERDNS_API}/servers/localhost/zones/{ZONE}",
json=data,
headers=headers
)
if response.status_code == 204:
logging.info(f"DNS record updated: {name} -> {ip_address}")
return True
else:
logging.error(f"DNS update failed: {response.text}")
return False
def main():
current_active = "primary"
consecutive_failures = 0
FAILURE_THRESHOLD = 3
logging.info("DNS Failover Manager started")
while True:
primary_healthy = check_endpoint_health(ENDPOINTS["primary"]["health_url"])
secondary_healthy = check_endpoint_health(ENDPOINTS["secondary"]["health_url"])
if not primary_healthy:
consecutive_failures += 1
logging.warning(f"Primary endpoint unhealthy. Consecutive failures: {consecutive_failures}")
if consecutive_failures >= FAILURE_THRESHOLD and current_active == "primary":
if secondary_healthy:
logging.critical("Initiating DNS FAILOVER to secondary!")
success = update_dns_record("www", ENDPOINTS["secondary"]["ip"])
if success:
current_active = "secondary"
consecutive_failures = 0
else:
logging.critical("BOTH endpoints unhealthy! No failover possible!")
else:
if current_active == "secondary" and consecutive_failures == 0:
logging.info("Primary recovered. Failing back...")
update_dns_record("www", ENDPOINTS["primary"]["ip"])
current_active = "primary"
consecutive_failures = 0
time.sleep(10)
if __name__ == "__main__":
main()
Düşük TTL Stratejisi ve Tuzakları
DNS failover’ın çalışması için TTL değerlerini felaket anından önce düşürmeniz gerekir. Ama burada önemli bir noktaya dikkat etmek lazım: TTL’yi düşürmek DNS sunucularınıza gelen sorgu yükünü artırır.
# TTL'yi kademeli olarak düşürmek için bir script
# Felaket beklenen durumlarda (planlı bakım vs.) kullanılır
#!/bin/bash
# /usr/local/bin/lower_ttl.sh
ZONE="example.com"
PDNS_API="http://localhost:8081/api/v1"
PDNS_KEY="gizli-api-anahtari"
# Mevcut TTL'yi kaydet
CURRENT_TTL=$(dig +short SOA $ZONE | awk '{print $7}')
echo "Current TTL: $CURRENT_TTL" >> /var/log/ttl_changes.log
echo "$(date) - Original TTL: $CURRENT_TTL" >> /var/log/ttl_changes.log
# TTL'yi kademeli olarak düşür: 3600 -> 300 -> 60
for ttl in 300 60; do
echo "Setting TTL to $ttl seconds..."
curl -s -X PATCH "$PDNS_API/servers/localhost/zones/$ZONE."
-H "X-API-Key: $PDNS_KEY"
-H "Content-Type: application/json"
-d "{
"rrsets": [{
"name": "www.$ZONE.",
"type": "A",
"ttl": $ttl,
"changetype": "REPLACE",
"records": [{"content": "203.0.113.10", "disabled": false}]
}]
}"
echo "TTL set to $ttl, waiting for propagation..."
sleep $((ttl * 2))
done
echo "TTL lowering complete. Current effective TTL: 60 seconds"
Gerçek Dünya Senaryosu: Çift ISP Arızası
Geçmişte yaşanan bir senaryoyu aktarayım. Bir e-ticaret firmasında çalışıyorduk, Black Friday öncesi gece ISP-A fiber kesintisi yaşadı. BGP failover otomatik devreye girdi, ISP-B trafiği devraldı. Güzel. Ama kimsenin hesaba katmadığı şey şuydu: ISP-B’nin bant genişliği limiti ISP-A’nın yarısıydı ve Black Friday trafiği bu limiti aştı. Sonuç: kısmi erişim, yüksek gecikme, sepet abandon oranında patlama.
Bu tür senaryolar için kapasite planlaması şarttır. BGP failover testini gerçekçi trafik yüküyle yapmak gerekir.
Kapasite Testi ve Failover Doğrulama
#!/bin/bash
# /usr/local/bin/failover_test.sh
# Planlı failover testi - üretim saatlerinde ÇALIŞTIRILMAZ
TEST_LOG="/var/log/failover_test_$(date +%Y%m%d_%H%M%S).log"
PRIMARY_GATEWAY="198.51.100.1"
BACKUP_GATEWAY="203.0.114.1"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $TEST_LOG
}
# Pre-test kontrolleri
log "=== FAILOVER TEST BAŞLIYOR ==="
log "Primary BGP durumu:"
vtysh -c "show bgp summary" | grep $PRIMARY_GATEWAY | tee -a $TEST_LOG
log "Mevcut routing tablosu (ilk 20 satır):"
vtysh -c "show ip route" | head -20 | tee -a $TEST_LOG
# Trafik istatistiklerini kaydet
log "Mevcut trafik istatistikleri:"
ifstat -i eth0 1 5 | tee -a $TEST_LOG
# Simüle edilmiş failover
log "Primary ISP simulate ediliyor - BGP oturumu kapatılıyor..."
vtysh -c "configure terminal" -c "router bgp 65001" -c "neighbor $PRIMARY_GATEWAY shutdown"
sleep 5
log "Failover sonrası routing durumu:"
vtysh -c "show ip route" | head -20 | tee -a $TEST_LOG
log "Backup ISP BGP durumu:"
vtysh -c "show bgp summary" | grep $BACKUP_GATEWAY | tee -a $TEST_LOG
# Erişilebilirlik testi
log "Dış erişilebilirlik testi:"
for target in "8.8.8.8" "1.1.1.1" "208.67.222.222"; do
result=$(ping -c 3 -W 2 $target 2>&1 | tail -1)
log "Ping $target: $result"
done
# Recovery
log "Primary ISP geri açılıyor..."
vtysh -c "configure terminal" -c "router bgp 65001" -c "no neighbor $PRIMARY_GATEWAY shutdown"
sleep 10
log "=== TEST TAMAMLANDI ==="
log "Test logu: $TEST_LOG"
DNS Önbellek Zehirlenmesi Sonrası Kurtarma
Felaket senaryoları arasında DNS önbellek zehirlenmesi de var. Bu durumda sadece failover değil, aktif temizleme gerekir.
#!/bin/bash
# /usr/local/bin/dns_cache_flush.sh
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a /var/log/dns_emergency.log
}
log "DNS önbellek temizleme başlatıldı"
# BIND9 için
if systemctl is-active --quiet bind9; then
log "BIND9 önbelleği temizleniyor..."
rndc flush
rndc reload
log "BIND9 flush tamamlandı"
fi
# PowerDNS Recursor için
if systemctl is-active --quiet pdns-recursor; then
log "PowerDNS Recursor önbelleği temizleniyor..."
rec_control wipe-cache .
log "PowerDNS Recursor flush tamamlandı"
fi
# Unbound için
if systemctl is-active --quiet unbound; then
log "Unbound önbelleği temizleniyor..."
unbound-control flush_zone .
unbound-control flush_bogus
log "Unbound flush tamamlandı"
fi
# Sistem DNS önbelleği (systemd-resolved)
if systemctl is-active --quiet systemd-resolved; then
log "systemd-resolved önbelleği temizleniyor..."
systemd-resolve --flush-caches
log "systemd-resolved flush tamamlandı"
fi
log "Tüm DNS önbellekleri temizlendi"
# Doğrulama
log "DNS doğrulama:"
dig +short example.com A | tee -a /var/log/dns_emergency.log
dig +short example.com A @8.8.8.8 | tee -a /var/log/dns_emergency.log
Felaket Kurtarma Planı: Runbook Şablonu
Tüm bu teknik önlemlerin yanında bir runbook olmadan hiçbir şey işe yaramaz. Gece 03:47’de panik içinde nereye bakacağınızı bilmiyorsanız, en iyi script’ler bile işe yaramaz.
Runbook’unuzda mutlaka şunlar bulunmalı:
- Triage süreci: Sorunun tam olarak ne olduğunu anlamak için ilk 5 dakikada yapılacaklar
- Eskalasyon matrisi: Kim ne zaman aranır, yetki sınırları nelerdir
- Rollback prosedürleri: Her değişikliği geri almak için adım adım talimatlar
- İletişim şablonları: Müşterilere, yönetime ve teknik ekibe ne söyleyeceğiniz
- Test edilmiş kontaklar: ISP NOC numaraları, upstream sağlayıcı acil hatları, domain registrar destek
Runbook’un PDF versiyonunu yazıcıdan çıkarıp fiziksel olarak saklayın. Ağ çöktüğünde online wiki’ye ulaşamazsınız.
Test ve Simülasyon Takvimi
Felaket kurtarma planı test edilmemiş bir plandır. Yılda en az iki kez planlı failover testi yapın:
- Aylık: BGP monitoring script çıktılarını gözden geçirme, DNS TTL değerlerini doğrulama
- Üç ayda bir: Düşük trafikli gece yarısı saatinde kısmi failover testi, backup ISP üzerinden trafik akışını doğrulama
- Altı ayda bir: Tam failover simülasyonu, RTO ve RPO değerlerini ölçme, ekip tatbikatı
- Yıllık: Tam DR senaryosu, yeni personelin katılımı, runbook güncellemesi
Her testin ardından bir post-mortem yazın. Sadece ne yanlış gitti değil, ne doğru çalıştı da belgelenmeli. Bu belgeler ileride ekibe katılacak mühendisler için paha biçilmez olacak.
Sonuç
BGP ve DNS failover, ağ altyapısı felaket kurtarmanın iki temel direğidir. BGP failover routing katmanında çalışarak trafik akışını yönlendirirken, DNS failover uygulama katmanında kullanıcıların doğru sunuculara ulaşmasını sağlar. İkisi birbirini tamamlar; sadece birini uygulamak yeterli değildir.
Gerçek bir felaket anında soğukkanlı kalabilmek için önceden hazırlık şarttır. Script’lerinizi yazın, test edin, runbook’unuzu hazırlayın ve en önemlisi düzenli olarak tatbikat yapın. O gece 03:47 geldiğinde, hazır olduğunuz için şükredeceksiniz.
Sorularınız veya kendi senaryolarınız varsa yorumlarda paylaşabilirsiniz. Özellikle çok veri merkezli BGP anycast yapılandırmaları veya cloud-hybrid DNS failover senaryoları hakkında ayrı yazılar da planlıyorum.
