Unbound ile Anycast DNS Yapılandırması

Büyük ölçekli bir DNS altyapısı kurarken en çok zorlanan konulardan biri, coğrafi olarak dağıtık sunucuları tek bir IP adresi üzerinden yönetilebilir hale getirmektir. Anycast bu sorunu çözen en zarif yöntemlerden biri ve Unbound ile birleştiğinde ciddi bir güç ortaya çıkıyor. Bu yazıda teorik anlatımdan çok, gerçek bir kurulum senaryosu üzerinden ilerleyeceğiz.

Anycast DNS Nedir ve Neden Unbound?

Anycast, aynı IP adresinin birden fazla fiziksel konumda duyurulması prensibine dayanır. BGP yönlendirme protokolü üzerinden çalışan bu yaklaşımda, istemci paketi gönderdiğinde ağ altyapısı otomatik olarak en yakın (en düşük maliyetli) sunucuya yönlendirir. DNS için bu yaklaşım son derece güçlüdür çünkü hem yük dengeleme hem de coğrafi yönlendirme hem de yüksek erişilebilirlik tek seferde çözülmüş olur.

Unbound’u bu senaryoda tercih etmemizin birkaç somut sebebi var. Birincisi, DNSSEC doğrulamasını kutudan çıkar çıkmaz destekliyor. İkincisi, cache yönetimi BIND’a kıyasla çok daha öngörülebilir davranıyor. Üçüncüsü ise yapılandırma dosyaları okunabilir ve modüler, büyük kurulumları yönetmek için ideal.

Altyapı Gereksinimleri

Bu kurulum için aşağıdaki bileşenlere ihtiyacınız var:

  • En az iki ayrı lokasyonda sunucu (örneğin İstanbul ve Ankara veri merkezleri)
  • Her iki lokasyonda da BGP konuşabilen bir router ya da yazılım tabanlı BGP daemon (BIRD veya FRRouting)
  • Anycast IP bloğu için bir ASN ve IP prefix’i
  • Her sunucuda Unbound kurulu olacak

Ağ topolojisi açısından şöyle düşünebilirsiniz: 203.0.113.0/24 bloğunuz var ve bu bloğun içinden 203.0.113.53 adresini anycast DNS IP’si olarak kullanacaksınız. Her iki lokasyon da bu /24’ü BGP üzerinden duyuruyor.

Unbound Kurulumu

Debian/Ubuntu tabanlı sistemlerde kurulum oldukça basit:

apt update && apt install -y unbound unbound-anchor

# DNSSEC root key'i güncelle
unbound-anchor -a /var/lib/unbound/root.key

# Servis durumunu kontrol et
systemctl status unbound

RHEL/Rocky Linux sistemler için:

dnf install -y unbound

# Root hints dosyasını indir
curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache

systemctl enable --now unbound

Anycast IP Adresini Loopback Arayüzüne Bağlama

Anycast kurulumunun püf noktası burada başlıyor. Her sunucuda anycast IP adresini loopback arayüzüne ya da ayrı bir dummy arayüze atamanız gerekiyor. Bu arayüz üzerinden servis duyurulacak, BGP bu arayüzün durumuna göre prefix’i duyurup duyurmamaya karar verecek.

# Dummy modülünü yükle
modprobe dummy

# Kalıcı hale getir
echo "dummy" >> /etc/modules-load.d/dummy.conf

# Anycast arayüzü oluştur
ip link add anycast0 type dummy
ip addr add 203.0.113.53/32 dev anycast0
ip link set anycast0 up

# Systemd-networkd ile kalıcı yapılandırma
cat > /etc/systemd/network/10-anycast0.netdev << 'EOF'
[NetDev]
Name=anycast0
Kind=dummy
EOF

cat > /etc/systemd/network/10-anycast0.network << 'EOF'
[Match]
Name=anycast0

[Address]
Address=203.0.113.53/32

[Link]
RequiredForOnline=no
EOF

systemctl restart systemd-networkd

Unbound Yapılandırması

Şimdi Unbound’u bu anycast IP üzerinden dinleyecek şekilde yapılandıralım. Büyük kurulumlar için yapılandırmayı modüler tutmak önemli, bu yüzden conf.d dizin yapısı kullanacağız.

# Ana yapılandırma dosyası
cat > /etc/unbound/unbound.conf << 'EOF'
server:
    # Anycast IP ve standart DNS portunu dinle
    interface: 203.0.113.53
    interface: 127.0.0.1
    port: 53

    # Performans ayarları - modern donanım için
    num-threads: 4
    msg-cache-slabs: 8
    rrset-cache-slabs: 8
    infra-cache-slabs: 8
    key-cache-slabs: 8

    # Cache boyutları
    msg-cache-size: 512m
    rrset-cache-size: 1024m

    # DNSSEC doğrulama
    auto-trust-anchor-file: "/var/lib/unbound/root.key"

    # Erişim kontrolü - recursive sorgu için
    access-control: 127.0.0.0/8 allow
    access-control: 10.0.0.0/8 allow
    access-control: 172.16.0.0/12 allow
    access-control: 192.168.0.0/16 allow
    access-control: 0.0.0.0/0 refuse

    # Root hints
    root-hints: "/etc/unbound/root.hints"

    # Güvenlik ayarları
    hide-identity: yes
    hide-version: yes
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: yes
    harden-large-queries: yes

    # Prefetch ile cache ısınması
    prefetch: yes
    prefetch-key: yes

    # Log ayarları
    logfile: "/var/log/unbound/unbound.log"
    log-queries: no
    log-replies: no
    verbosity: 1

    # SO_REUSEPORT aktif et
    so-reuseport: yes

    # TCP ve buffer ayarları
    tcp-upstream: no
    outgoing-range: 8192
    num-queries-per-thread: 4096

include: "/etc/unbound/conf.d/*.conf"
EOF

mkdir -p /var/log/unbound /etc/unbound/conf.d
chown unbound:unbound /var/log/unbound

BGP ile Prefix Duyurusu – FRRouting Yapılandırması

FRRouting (FRR) açık kaynak BGP implementasyonu için günümüzde en yaygın tercih. Sunucuda doğrudan BGP konuşturmak için kuruyoruz.

# FRR kurulumu (Debian/Ubuntu)
curl -s https://deb.frrouting.org/frr/keys.asc | apt-key add -
echo "deb https://deb.frrouting.org/frr $(lsb_release -s -c) frr-stable" > /etc/apt/sources.list.d/frr.list
apt update && apt install -y frr frr-pythontools

# BGP daemon'ı aktif et
sed -i 's/bgpd=no/bgpd=yes/' /etc/frr/daemons
systemctl restart frr

FRR BGP yapılandırması için vtysh üzerinden:

vtysh << 'EOF'
configure terminal

router bgp 65001
 bgp router-id 203.0.113.1
 bgp log-neighbor-changes

 neighbor 198.51.100.1 remote-as 65000
 neighbor 198.51.100.1 description "Upstream Router Istanbul"
 neighbor 198.51.100.1 password BGP_SECRET_PASS

 address-family ipv4 unicast
  network 203.0.113.0/24
  neighbor 198.51.100.1 activate
  neighbor 198.51.100.1 soft-reconfiguration inbound
 exit-address-family

ip route 203.0.113.0/24 Null0

exit
write memory
EOF

Sağlık Kontrolü ve Otomatik BGP Geri Çekme

Bu kısım anycast kurulumunun en kritik bölümü. Unbound servis dışı kaldığında ya da sağlıksız hale geldiğinde, o lokasyonun BGP prefix’ini otomatik olarak geri çekmesi gerekiyor. Aksi halde istemciler sağlıksız bir sunucuya yönlendirilmeye devam eder.

Bunun için basit ama etkili bir sağlık kontrolü scripti yazalım:

cat > /usr/local/bin/dns-healthcheck.sh << 'SCRIPT'
#!/bin/bash

ANYCAST_IP="203.0.113.53"
TEST_DOMAIN="example.com"
FRR_ASN="65001"
UPSTREAM_NEIGHBOR="198.51.100.1"
LOG_FILE="/var/log/unbound/healthcheck.log"
STATE_FILE="/var/run/dns-anycast-state"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

check_dns() {
    # dig ile sorgu yap, 2 saniye timeout
    result=$(dig @${ANYCAST_IP} ${TEST_DOMAIN} A +time=2 +tries=1 2>&1)
    if echo "$result" | grep -q "NOERROR|NXDOMAIN"; then
        return 0
    fi
    return 1
}

withdraw_prefix() {
    log "WARN: DNS sağlıksız, prefix geri çekiliyor"
    vtysh -c "configure terminal" 
          -c "router bgp ${FRR_ASN}" 
          -c "address-family ipv4 unicast" 
          -c "no network 203.0.113.0/24" 
          -c "exit-address-family" 
          -c "exit" 
          -c "write memory" 2>/dev/null
    echo "withdrawn" > "$STATE_FILE"
}

announce_prefix() {
    log "INFO: DNS sağlıklı, prefix duyuruluyor"
    vtysh -c "configure terminal" 
          -c "router bgp ${FRR_ASN}" 
          -c "address-family ipv4 unicast" 
          -c "network 203.0.113.0/24" 
          -c "exit-address-family" 
          -c "exit" 
          -c "write memory" 2>/dev/null
    echo "announced" > "$STATE_FILE"
}

# Mevcut durumu oku
current_state=$(cat "$STATE_FILE" 2>/dev/null || echo "unknown")

if check_dns; then
    if [ "$current_state" != "announced" ]; then
        announce_prefix
    fi
else
    if [ "$current_state" != "withdrawn" ]; then
        withdraw_prefix
    fi
fi
SCRIPT

chmod +x /usr/local/bin/dns-healthcheck.sh

# Cron ile her 30 saniyede bir çalıştır
cat > /etc/cron.d/dns-healthcheck << 'EOF'
* * * * * root /usr/local/bin/dns-healthcheck.sh
* * * * * root sleep 30 && /usr/local/bin/dns-healthcheck.sh
EOF

Lokasyon Bazlı Yapılandırma Farklılıkları

Anycast kurulumunda her lokasyonun yapılandırması neredeyse aynı olacak, ancak bazı ince farklılıklar olabilir. Bunu yönetmek için bir yapılandırma şablonu sistemi kullanmak mantıklı:

# Lokasyon tanımlayıcısı - her sunucuda farklı
cat > /etc/unbound/conf.d/local-identity.conf << 'EOF'
server:
    # Bu sunucunun coğrafi kimliği (CHAOS sorguları için)
    # identity: "istanbul-dc1"

    # Lokasyona özel forwarder (isteğe bağlı)
    # forward-zone:
    #     name: "internal.company.com"
    #     forward-addr: 10.1.0.53

    # Lokasyona özel erişim kontrolü
    access-control: 10.100.0.0/16 allow  # Istanbul iç ağ
EOF

Ankara lokasyonu için aynı dosyada sadece subnet adresi değişecek:

# Ankara DC'ye özel yapılandırma
cat > /etc/unbound/conf.d/local-identity.conf << 'EOF'
server:
    access-control: 10.200.0.0/16 allow  # Ankara iç ağ
EOF

Performans Testi ve Doğrulama

Kurulum tamamlandıktan sonra doğrulama adımları kritik önem taşıyor.

# Anycast IP'ye DNS sorgusu at
dig @203.0.113.53 google.com A +stats

# Hangi sunucudan cevap geldiğini anlayabilmek için traceroute
traceroute 203.0.113.53

# DNSSEC doğrulamasını test et
dig @203.0.113.53 dnssec-failed.org A

# Cache durumunu sorgula
unbound-control stats_noreset | grep -E "total.|cache."

# Bağlantı sayısı ve sorgu istatistikleri
unbound-control stats | grep "total.queries"

# Sorgu gecikmesini ölç - 1000 sorgu gönder
for i in $(seq 1 1000); do
    dig @203.0.113.53 example.com A +time=1 +tries=1 > /dev/null 2>&1
done

# unbound-control ile detaylı istatistik
unbound-control stats_noreset

Gerçek Dünya Senaryosu: E-Ticaret Platformu Örneği

Yakın zamanda üzerinde çalıştığım bir e-ticaret platformunda bu yapıyı hayata geçirdik. Platformun yaklaşık 2 milyon günlük aktif kullanıcısı vardı ve DNS çözümleme süreleri performans metrikleri içinde ciddi bir yer tutuyordu.

İstanbul ve İzmir’de birer, Ankara’da iki node olmak üzere toplam dört Unbound sunucusu anycast üzerinden çalışmaya başladı. Anycast öncesinde ortalama DNS çözümleme süresi 45ms civarındaydı. Anycast sonrasında bu 8ms’ye düştü. Nedenini anlamak kolay: İstanbul kullanıcıları artık her seferinde Ankara’daki tek DNS sunucusuna gitmek yerine yanı başlarındaki lokasyona bağlanıyor.

Bir ölçüm yapmak isteyenler için basit bir script:

#!/bin/bash
# DNS gecikme ölçüm aracı
DOMAINS=("google.com" "youtube.com" "twitter.com" "github.com")
DNS_SERVER="203.0.113.53"
ITERATIONS=10

for domain in "${DOMAINS[@]}"; do
    total=0
    for i in $(seq 1 $ITERATIONS); do
        latency=$(dig @${DNS_SERVER} ${domain} A +stats 2>&1 | grep "Query time" | awk '{print $4}')
        total=$((total + latency))
    done
    avg=$((total / ITERATIONS))
    echo "Domain: ${domain} | Ortalama gecikme: ${avg}ms"
done

Dikkat Edilmesi Gereken Noktalar

BGP convergence süresi: Bir node’un prefix’ini çekip başka bir node’un devralması anlık değil. BGP convergence süresi yapılandırmanıza bağlı olarak 30 saniye ile birkaç dakika arasında sürebilir. Bu süre zarfında o lokasyona gelen DNS sorguları başarısız olabilir. Bu yüzden DNS client’larda retry mekanizması mutlaka aktif olmalı.

Asimetrik ağ: Anycast ile gelen sorgu bir lokasyona gidebilir, ama bazı nadir durumlarda cevap farklı bir yoldan geçebilir. UDP tabanlı DNS için bu genellikle sorun yaratmaz ama TCP DNS (büyük cevaplar, DoT) için dikkat edin.

Cache tutarsızlığı: Her lokasyonun kendi cache’i var. Bu istemciler arasında farklı TTL davranışları görebilirsiniz. Production ortamında bunu kabul etmek gerekiyor; anycast ile tutarlı cache mümkün değil.

Monitoring: Her node’u ayrı ayrı izlemeniz şart. Prometheus + Unbound exporter kombinasyonu bu iş için birebir:

# Unbound Prometheus exporter kurulumu
wget https://github.com/letsencrypt/unbound_exporter/releases/latest/download/unbound_exporter-linux-amd64
chmod +x unbound_exporter-linux-amd64
mv unbound_exporter-linux-amd64 /usr/local/bin/unbound_exporter

# unbound-control socket erişimi için
cat > /etc/systemd/system/unbound-exporter.service << 'EOF'
[Unit]
Description=Unbound Prometheus Exporter
After=unbound.service

[Service]
ExecStart=/usr/local/bin/unbound_exporter 
    -unbound.host unix:///var/run/unbound/unbound.ctl 
    -web.listen-address=":9167"
Restart=always
User=unbound

[Install]
WantedBy=multi-user.target
EOF

systemctl enable --now unbound-exporter

Rate limiting: Anycast yapısında bir node’un önüne gelen trafik ani olarak artabilir. Unbound’un kendi rate limiting özelliğini aktif etmek iyi bir uygulama:

# /etc/unbound/conf.d/ratelimit.conf
cat > /etc/unbound/conf.d/ratelimit.conf << 'EOF'
server:
    # Saniyede 1000 sorgu limiti
    ratelimit: 1000
    ratelimit-size: 4m
    ratelimit-factor: 10
    ratelimit-for-domain: example.com 100
EOF

systemctl reload unbound

Sonuç

Unbound ile Anycast DNS yapısı kurmak görünürde karmaşık ama adım adım ilerlediğinizde son derece anlaşılır bir mimari ortaya çıkıyor. Kritik nokta şu: Ağ katmanı (anycast/BGP) ve uygulama katmanı (Unbound) birbirinden bağımsız ama birbirini tamamlayan şekilde çalışıyor. BGP prefix yönetimini Unbound’un sağlık durumuna bağladığınız anda sistem kendini yönetebilir hale geliyor.

Bu yapının size katacağı en büyük değer, coğrafi dağıtıklık ile yüksek erişilebilirliği aynı anda elde etmeniz. Tek bir DNS sunucusuna bağlı kalmak, özellikle Türkiye’nin farklı şehirlerindeki kullanıcıları düşündüğünüzde ciddi gecikme farklılıkları yaratıyor. Anycast bu farkı minimize ediyor.

Başlarken iki node ile küçük tutun, monitoring altyapısını çok erkenden kurun ve BGP convergence davranışını test ortamında iyice anlayın. Production’a geçmeden önce bir node’u kapatıp trafiğin ne kadar sürede diğerine geçtiğini bizzat gözlemleyin. O test size sistemin davranışı hakkında hiçbir dökümanın veremeyeceği kadar değerli bilgi verecektir.

Bir yanıt yazın

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