Kullanıcıların fiziksel konumlarına göre farklı DNS yanıtları döndürmek, hem performans hem de içerik yönetimi açısından kritik bir ihtiyaç haline geldi. Büyük CDN sağlayıcıları, yük dengeleyiciler ve global hizmet veren uygulamalar bu tekniğe günlük olarak güveniyor. PowerDNS, esnek backend mimarisi ve Lua scripting desteği sayesinde GeoDNS implementasyonu için son derece güçlü bir platform sunuyor. Bu yazıda sıfırdan bir GeoDNS altyapısı kuracağız, gerçek dünya senaryolarını ele alacağız ve production ortamına hazır bir yapılandırma oluşturacağız.
GeoDNS Nedir ve Ne Zaman Gereklidir
GeoDNS, gelen DNS sorgusunun kaynak IP adresine bakarak coğrafi konuma özgü yanıtlar döndüren bir mekanizmadır. Klasik DNS’ten farkı şudur: standart DNS her istemciye aynı IP adresini döndürürken, GeoDNS ile Türkiye’deki bir kullanıcıya Frankfurt sunucusunun IP’sini, ABD’deki bir kullanıcıya ise Virginia sunucusunun IP’sini verebilirsiniz.
Hangi senaryolarda buna ihtiyaç duyarsınız:
- Coğrafi içerik kısıtlaması: Belirli bölgelere farklı içerik sunmak
- Latency optimizasyonu: Kullanıcıyı en yakın sunucuya yönlendirmek
- Yük dağılımı: Trafiği bölgesel olarak dengelemek
- Failover: Bir bölgedeki sunucu çöktüğünde trafiği başka bölgeye kaydırmak
- Uyumluluk: GDPR gibi yasal gerekliliklere göre veri lokasyonu yönetimi
PowerDNS Kurulumu ve Ön Hazırlıklar
Önce temel bileşenleri kuralım. Bu rehberde Ubuntu 22.04 LTS kullanıyorum ama Debian ve RHEL tabanlı sistemlerde de benzer adımlar geçerli.
# PowerDNS Authoritative Server kurulumu
apt-get install -y pdns-server pdns-backend-sqlite3 pdns-backend-pipe
# GeoIP veritabanı için gerekli araçlar
apt-get install -y mmdb-bin libmaxminddb0 libmaxminddb-dev
# MaxMind GeoLite2 veritabanını indirmek için geoipupdate
apt-get install -y geoipupdate
MaxMind GeoLite2 veritabanı için ücretsiz hesap açmanız gerekiyor. Hesap açtıktan sonra lisans anahtarınızı /etc/GeoIP.conf dosyasına ekleyin:
# /etc/GeoIP.conf
AccountID YOUR_ACCOUNT_ID
LicenseKey YOUR_LICENSE_KEY
EditionIDs GeoLite2-Country GeoLite2-City GeoLite2-ASN
# Veritabanlarını güncelleyin
geoipupdate
# Veritabanlarının indirildiğini doğrulayın
ls -la /var/lib/GeoIP/
# GeoLite2-City.mmdb, GeoLite2-Country.mmdb dosyalarını görmelisiniz
# Test sorgusu
mmdblookup --file /var/lib/GeoIP/GeoLite2-Country.mmdb --ip 88.255.0.1
PowerDNS Temel Yapılandırması
PowerDNS’in ana konfigürasyon dosyasını düzenleyelim:
# /etc/powerdns/pdns.conf
local-address=0.0.0.0
local-port=53
daemon=yes
guardian=yes
# Log ayarları
log-dns-queries=yes
log-dns-details=yes
loglevel=4
# Backend ayarları - Lua backend'i etkinleştiriyoruz
launch=lua
lua-filename=/etc/powerdns/geodns.lua
# Performans ayarları
receiver-threads=4
distributor-threads=3
cache-ttl=20
query-cache-ttl=20
negquery-cache-ttl=60
# EDNS Client Subnet desteği - GeoDNS için kritik
edns-subnet-processing=yes
EDNS Client Subnet (ECS) desteği burada çok önemli. Cloudflare veya Google DNS gibi büyük resolver’lar sorguları kendi IP’lerinden gönderir. ECS olmadan tüm Türkiye trafiği sanki Cloudflare’in Dublin veri merkezinden geliyormuş gibi görünür. ECS ile gerçek istemci IP’si sorguya eklenerek iletilir.
Lua Backend ile GeoDNS Mantığı
PowerDNS’in Lua backend’i, DNS sorgularını işleyecek özel mantık yazmanıza olanak tanır. Bu bizim GeoDNS motorumuz olacak.
-- /etc/powerdns/geodns.lua
-- MaxMind GeoIP2 kütüphanesini yükle
local mmdb = require("mmdb")
-- Veritabanlarını açık tut (her sorgu için açıp kapatmak performansı öldürür)
local country_db = mmdb.open("/var/lib/GeoIP/GeoLite2-Country.mmdb")
local city_db = mmdb.open("/var/lib/GeoIP/GeoLite2-City.mmdb")
-- Bölgesel sunucu haritası
local geo_map = {
-- Türkiye ve Orta Doğu
["TR"] = "185.220.10.1",
["AZ"] = "185.220.10.1",
["GE"] = "185.220.10.1",
-- Batı Avrupa
["DE"] = "78.46.55.100",
["NL"] = "78.46.55.100",
["FR"] = "78.46.55.100",
["GB"] = "78.46.55.100",
-- Kuzey Amerika
["US"] = "104.21.30.200",
["CA"] = "104.21.30.200",
-- Asya Pasifik
["JP"] = "103.86.20.15",
["SG"] = "103.86.20.15",
["AU"] = "103.86.20.15",
-- Varsayılan (bilinmeyen bölgeler)
["default"] = "78.46.55.100"
}
-- IP adresinden ülke kodu çıkaran fonksiyon
function get_country(ip)
if not ip or ip == "" then
return "default"
end
local result = country_db:lookup(ip)
if result and result.country and result.country.iso_code then
return result.country.iso_code
end
return "default"
end
-- Sunucu IP'sini döndüren fonksiyon
function get_server_for_country(country_code)
local server = geo_map[country_code]
if server then
return server
end
return geo_map["default"]
end
Şimdi DNS yanıt fonksiyonunu yazalım. Bu fonksiyon her DNS sorgusunda çağrılır:
-- geodns.lua dosyasına devam
function dns_answer(dname, qtype, ip, edns_subnet)
local answers = {}
-- Önce EDNS subnet'ten IP al, yoksa sorgu kaynağını kullan
local lookup_ip = ip
if edns_subnet and edns_subnet ~= "" then
lookup_ip = edns_subnet
end
local country = get_country(lookup_ip)
local server_ip = get_server_for_country(country)
-- Sadece hedef domainimiz için yanıt üretelim
if dname == "app.ornek.com." then
if qtype == QType.A or qtype == QType.ANY then
answers[#answers + 1] = {
qtype = QType.A,
content = server_ip,
ttl = 60 -- GeoDNS için TTL düşük tutulmalı
}
end
elseif dname == "ornek.com." then
if qtype == QType.A or qtype == QType.ANY then
answers[#answers + 1] = {
qtype = QType.A,
content = server_ip,
ttl = 60
}
end
-- SOA kaydı
if qtype == QType.SOA or qtype == QType.ANY then
answers[#answers + 1] = {
qtype = QType.SOA,
content = "ns1.ornek.com. hostmaster.ornek.com. 2024010101 3600 900 604800 300",
ttl = 3600
}
end
end
return answers
end
Pipe Backend Alternatifi
Lua backend kullanmak istemiyorsanız ya da farklı bir dil tercih ediyorsanız, PowerDNS’in Pipe backend’ini kullanabilirsiniz. Bu yöntemde PowerDNS, sorgular için dış bir sürece yazı yazar ve cevap bekler. Python, Go veya herhangi bir dille yazabilirsiniz.
#!/usr/bin/env python3
# /etc/powerdns/geodns_pipe.py
import sys
import socket
import geoip2.database
import geoip2.errors
# Veritabanını bir kez aç
reader = geoip2.database.Reader('/var/lib/GeoIP/GeoLite2-Country.mmdb')
GEO_MAP = {
'TR': '185.220.10.1',
'DE': '78.46.55.100',
'NL': '78.46.55.100',
'US': '104.21.30.200',
'CA': '104.21.30.200',
'default': '78.46.55.100'
}
def get_country(ip):
try:
response = reader.country(ip)
return response.country.iso_code
except (geoip2.errors.AddressNotFoundError, ValueError):
return 'default'
def get_ip_for_country(country):
return GEO_MAP.get(country, GEO_MAP['default'])
# Pipe protokolü handshake
sys.stdout.write("OKtGeoDNS Backend v1.0n")
sys.stdout.flush()
for line in sys.stdin:
line = line.strip()
parts = line.split('t')
if not parts:
sys.stdout.write("FAILn")
sys.stdout.flush()
continue
query_type = parts[0]
if query_type == "Q":
# Format: Q <qname> <qclass> <qtype> <id> <remoteip> [localip] [ednssubnet]
if len(parts) < 6:
sys.stdout.write("ENDn")
sys.stdout.flush()
continue
qname = parts[1].lower()
qclass = parts[2]
qtype = parts[3]
qid = parts[4]
remote_ip = parts[5]
edns_subnet = parts[7] if len(parts) > 7 else remote_ip
# ECS varsa onu kullan
lookup_ip = edns_subnet if edns_subnet and edns_subnet != '0.0.0.0' else remote_ip
country = get_country(lookup_ip)
server_ip = get_ip_for_country(country)
if qname in ('app.ornek.com', 'ornek.com') and qtype in ('A', 'ANY'):
sys.stdout.write(f"DATAt{qname}t{qclass}tAt60t{qid}t{server_ip}n")
sys.stdout.write("ENDn")
sys.stdout.flush()
elif query_type == "AXFR":
sys.stdout.write("ENDn")
sys.stdout.flush()
else:
sys.stdout.write("ENDn")
sys.stdout.flush()
Pipe backend yapılandırması:
# /etc/powerdns/pdns.conf içinde pipe backend için
launch=pipe
pipe-command=/etc/powerdns/geodns_pipe.py
pipe-timeout=2000
Şehir Bazlı Yönlendirme
Ülke seviyesi kaba kalmaya başlarsa, özellikle büyük ülkelerde (ABD, Rusya, Çin gibi) şehir veya koordinat bazlı yönlendirme yapabilirsiniz. Aşağıdaki örnek, ABD’deki kullanıcıları kıyı bölgelerine göre ayırt ediyor:
-- Şehir/bölge bazlı yönlendirme
function get_us_region_server(ip)
local result = city_db:lookup(ip)
if not result then
return "104.21.30.200" -- Varsayılan ABD
end
local longitude = result.location and result.location.longitude
if not longitude then
return "104.21.30.200"
end
-- Boylama göre Doğu/Batı kıyısı ayrımı
if longitude < -100 then
-- Batı kıyısı (California, Oregon, Washington)
return "104.18.20.50" -- Los Angeles PoP
else
-- Doğu kıyısı ve orta ABD
return "104.21.30.200" -- Virginia PoP
end
end
Health Check ile Failover Entegrasyonu
GeoDNS’in en güçlü özelliklerinden biri, sağlık kontrolleriyle birleştirildiğinde otomatik failover sağlamasıdır. Aşağıdaki script, sunucuların sağlık durumunu periyodik olarak kontrol eder ve sonuçları bir dosyaya yazar. Lua backend bu dosyayı okuyarak yanıt üretir:
#!/bin/bash
# /usr/local/bin/geodns_healthcheck.sh
HEALTH_FILE="/var/lib/powerdns/server_health.json"
TIMEOUT=3
declare -A SERVERS=(
["tr"]="185.220.10.1"
["eu"]="78.46.55.100"
["us"]="104.21.30.200"
["ap"]="103.86.20.15"
)
declare -A BACKUPS=(
["tr"]="78.46.55.100"
["eu"]="104.21.30.200"
["us"]="78.46.55.100"
["ap"]="104.21.30.200"
)
result="{"
for region in "${!SERVERS[@]}"; do
primary="${SERVERS[$region]}"
backup="${BACKUPS[$region]}"
# HTTP health check (443 portuna TCP bağlantı kontrolü)
if nc -z -w "$TIMEOUT" "$primary" 443 2>/dev/null; then
active_ip="$primary"
status="healthy"
else
active_ip="$backup"
status="failover"
logger -t geodns "UYARI: $region bolgesi primary sunucusu ($primary) DOWN, failover: $backup"
fi
result+=""${region}":{"ip":"${active_ip}","status":"${status}"},"
done
# Son virgülü kaldır ve kapat
result="${result%,}}"
echo "$result" > "$HEALTH_FILE"
chmod 644 "$HEALTH_FILE"
Bu scripti cron ile dakikada bir çalıştırın:
# crontab -e
* * * * * /usr/local/bin/geodns_healthcheck.sh
Test ve Doğrulama
GeoDNS kurulumunu test etmek için birkaç farklı yöntem kullanın:
# Temel dig testi - local resolver kullanarak
dig @localhost app.ornek.com A
# EDNS subnet ile test - Türkiye IP'si simülasyonu
dig @localhost app.ornek.com A +subnet=88.255.0.1/24
# ABD IP'si ile test
dig @localhost app.ornek.com A +subnet=8.8.8.8/24
# Almanya IP'si ile test
dig @localhost app.ornek.com A +subnet=217.0.0.1/24
# Yanıt sürelerini karşılaştır
for subnet in "88.255.0.1/24" "8.8.8.8/24" "217.0.0.1/24"; do
echo "--- Subnet: $subnet ---"
dig @localhost app.ornek.com A +subnet=$subnet +short
done
# PowerDNS log takibi
journalctl -u pdns -f
# veya
tail -f /var/log/pdns.log
Dışarıdan test etmek için farklı coğrafi konumlardaki VPS’lerden ya da online araçlardan yararlanabilirsiniz:
# DNS propagasyonunu dünya genelinde test et
# dnschecker.org veya whatsmydns.net gibi siteler kullanılabilir
# nmap ile DNS scriptleri
nmap -sU -p 53 --script dns-service-discovery ns1.ornek.com
# Curl ile HTTP header kontrolü (sunucuların gerçekten farklı mı cevap verdiğini anlamak için)
curl -H "Host: app.ornek.com" http://185.220.10.1/healthcheck
curl -H "Host: app.ornek.com" http://78.46.55.100/healthcheck
TTL Stratejisi ve Önbellek Yönetimi
GeoDNS’de TTL değeri kritik bir parametre. Çok yüksek TTL koyarsanız, bir sunucu çöktüğünde müşterileriniz uzun süre eski IP’yi kullanmaya devam eder. Çok düşük TTL koyarsanız, DNS altyapınıza gereksiz yük bindirirsiniz.
Önerilen yaklaşım:
- Normal operasyon TTL: 60-300 saniye arası
- Planlı bakım öncesi: Bakımdan 2-3 saat önce TTL’i 30 saniyeye düşürün
- Bakım sonrası: TTL’i tekrar normal değerine çıkarın
- SOA kaydı TTL: Daha yüksek tutulabilir, genellikle 3600 saniye
Negative caching de önemli. Var olmayan bir kayıt için TTL çok düşük tutulursa, DoS saldırılarına karşı savunmasız kalabilirsiniz:
# pdns.conf içinde negative caching
negquery-cache-ttl=300
cache-ttl=60
Güvenlik Düşünceleri
GeoDNS altyapısı birkaç güvenlik açısından dikkat gerektiriyor:
Rate limiting DNS amplification saldırılarını önlemek için zorunlu:
# pdns.conf
# İstemci başına sorgu limiti
max-tcp-connections=20
# Paket boyutu limiti
udp-truncation-threshold=1220
# Recursive sorguları reddet (authoritative sunucu için)
recursor=no
DNSSEC desteği eklemek, yanıtlarınızın sahte olmamasını garanti eder. PowerDNS ile DNSSEC yapılandırması için alan adı başına anahtar oluşturmanız gerekiyor:
# DNSSEC anahtarı oluştur
pdnsutil add-zone-key ornek.com ksk active ecdsa256
# Zone'u imzala
pdnsutil rectify-zone ornek.com
# DS kaydını göster (registrar'a ekleyeceksiniz)
pdnsutil show-zone ornek.com | grep DS
İzleme ve Metriks
Production ortamında GeoDNS davranışını izlemek için Prometheus ve Grafana kombinasyonu işe yarar. PowerDNS, yerleşik web API’si ile metrikleri dışa aktarır:
# pdns.conf içinde API ve metrikleri etkinleştir
webserver=yes
webserver-address=127.0.0.1
webserver-port=8081
webserver-password=guclu_sifre_buraya
api=yes
api-key=api_anahtari_buraya
# Prometheus exporter ile metrik toplama
# pdns_exporter kurulumu
wget https://github.com/janeczku/powerdns-prometheus-exporter/releases/latest/download/pdns-exporter_linux_amd64.tar.gz
tar xzf pdns-exporter_linux_amd64.tar.gz
mv pdns-exporter /usr/local/bin/
# Servis olarak çalıştır
cat > /etc/systemd/system/pdns-exporter.service << EOF
[Unit]
Description=PowerDNS Prometheus Exporter
After=network.target
[Service]
ExecStart=/usr/local/bin/pdns-exporter -api-url http://127.0.0.1:8081/api/v1 -api-key api_anahtari_buraya
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now pdns-exporter
İzlemeniz gereken kritik metrikler:
- query-count: Toplam sorgu sayısı, ani düşüşler probleme işaret eder
- servfail-count: SERVFAIL yanıt sayısı, backend hatalarını gösterir
- latency: Yanıt gecikme süresi, 5ms üzeri araştırılmalı
- cache-hit-ratio: Önbellek hit oranı, %80 altındaysa TTL ayarlarını gözden geçirin
Gerçek Dünya Örneği: E-Ticaret Platformu
Bir e-ticaret müşterisi için kurduğumuz yapıyı paylaşayım. Türkiye, Almanya ve ABD’de sunucuları vardı. Önceden tek bir IP kullanıyorlardı ve Türk kullanıcılar ABD sunucusuna, oradan S3’ten resim çekiyordu. Ortalama sayfa yüklenme süresi 4.2 saniyeydi.
GeoDNS sonrasında:
- Türk kullanıcılar İstanbul CDN’ine yönlendirildi (0.8 saniye)
- Avrupa kullanıcıları Frankfurt’a yönlendirildi (1.1 saniye)
- ABD kullanıcıları Virginia’ya bağlandı (0.9 saniye)
Toplam altyapı maliyeti neredeyse aynı kaldı, çünkü zaten bu sunucular mevcuttu. Sadece trafik yönlendirme değişti. Müşteri dönüşüm oranı ise ortalama %12 arttı, ki bu rakam sayfa yüklenme süresiyle doğrudan ilişkili.
Sonuç
PowerDNS ile GeoDNS yapılandırması, ilk bakışta karmaşık görünse de adım adım ele alındığında yönetilebilir bir süreç. Temel noktalara dikkat etmeniz gerekiyor:
- Düşük TTL kullanın, GeoDNS’in hızlı failover yapabilmesi için
- ECS desteğini mutlaka etkinleştirin, aksi halde büyük resolver’lardan gelen trafiği doğru yönlendiremezsiniz
- Health check mekanizması olmadan GeoDNS yarım kalır
- GeoIP veritabanını düzenli güncelleyin, eski veritabanları yanlış yönlendirmeye neden olur
- Test sürecini ciddiye alın, farklı ülkelerden simülasyon yaparak her senaryoyu kontrol edin
Pipe backend ile Python yazmak tercih ettiğim yöntem, çünkü debug etmesi çok daha kolay. Lua backend ise daha performanslı ama hatayı bulmak bazen can sıkıcı oluyor. Production’a almadan önce hangi yöntemi seçerseniz seçin, kapsamlı bir test süreci geçirin. DNS hatalarının kullanıcıya etkisi anlıktır ve önbellek süresi boyunca devam eder, geri almak genellikle o kadar kolay olmaz.