CoreDNS ile Split Horizon DNS Yapılandırması
Prodüksiyonda DNS yönetimi, kulağa basit gelen ama iş karmaşık ortamlara gelince sizi gerçekten zorlayan bir alan. Özellikle hem iç ağ hem dış dünyaya hizmet veren bir altyapınız varsa, aynı domain adının farklı IP adreslerini döndürmesini istediğiniz durumlar kaçınılmaz hale geliyor. İşte tam burada Split Horizon DNS devreye giriyor ve CoreDNS bu işi son derece zarif bir şekilde çözüyor.
Bu yazıda gerçek bir senaryoyu ele alacağız: sirket.local ve sirket.com domainleri için iç ağdan gelen sorgulara farklı, dış ağdan gelen sorgulara farklı yanıt veren bir CoreDNS yapısı kuracağız. Kubernetes ortamı için de özel bir bölüm var, o kısmı atlamayın.
Split Horizon DNS Nedir ve Neden CoreDNS?
Split Horizon, ya da bazı kaynaklarda “Split-Brain DNS” olarak da geçen bu yaklaşım, aynı DNS adının sorgulandığı konuma göre farklı yanıtlar döndürmesi üzerine kuruludur. Klasik senaryo şöyle: app.sirket.com adresi dışarıdan sorgulandığında load balancer’ınızın public IP’sini döndürür, iç ağdan sorgulandığında ise direkt olarak uygulama sunucusunun iç IP’sini. Bu sayede iç trafik gereksiz yere internete çıkıp geri dönmez, gecikme azalır, güvenlik artar.
BIND ile bu işi yıllarca yaptım. Çalışıyor, stabil, ama konfigürasyon yönetimi bir süre sonra iç karartıcı bir hal alıyor. CoreDNS ise Go ile yazılmış, plugin tabanlı, cloud-native bir DNS sunucusu. Kubernetes’in varsayılan DNS çözümleyicisi olması boşuna değil. Corefile syntax’ı net, anlaşılır ve versiyon kontrolüne çok daha kolay entegre oluyor.
Ortam Hazırlığı ve Kurulum
Anlatacaklarımı Ubuntu 22.04 üzerinde test ettim ama RHEL/Rocky Linux için de gerekli notları düşeceğim.
# CoreDNS'in son sürümünü indirin
# https://github.com/coredns/coredns/releases adresinden güncel versiyona bakın
wget https://github.com/coredns/coredns/releases/download/v1.11.1/coredns_1.11.1_linux_amd64.tgz
tar -xvzf coredns_1.11.1_linux_amd64.tgz
sudo mv coredns /usr/local/bin/
sudo chmod +x /usr/local/bin/coredns
# Servis kullanıcısı ve dizin yapısı
sudo useradd -r -s /bin/false coredns
sudo mkdir -p /etc/coredns/zones
sudo chown -R coredns:coredns /etc/coredns
RHEL tabanlı sistemlerde SELinux ile uğraşmak gerekebilir. CoreDNS binary’sine ve konfigürasyon dizinine doğru SELinux context’i atamayı unutmayın:
# RHEL/Rocky Linux için SELinux context ayarı
sudo semanage fcontext -a -t bin_t '/usr/local/bin/coredns'
sudo restorecon -v /usr/local/bin/coredns
# 53 portunu non-root kullanıcı için açmak
sudo setcap cap_net_bind_service=+ep /usr/local/bin/coredns
Systemd servis dosyasını oluşturalım:
sudo tee /etc/systemd/system/coredns.service > /dev/null <<EOF
[Unit]
Description=CoreDNS DNS Server
Documentation=https://coredns.io
After=network.target
[Service]
User=coredns
Group=coredns
ExecStart=/usr/local/bin/coredns -conf /etc/coredns/Corefile
Restart=on-failure
RestartSec=5
LimitNOFILE=1048576
# Güvenlik sertleştirme
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/etc/coredns
[Install]
WantedBy=multi-network.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable coredns
Corefile Yapısı ve Split Horizon Mantığı
CoreDNS’in çalışma mantığını kavramak için önce Corefile syntax’ını anlamak şart. Her “server block” hangi zone’ı, hangi portta dinleyeceğini tanımlar. Split Horizon için kritik olan view plugin’i değil (o ticari versiyonda), bunun yerine farklı IP adreslerini dinleyen ayrı server block’ları ve acl plugin kombinasyonunu kullanmak.
Senaryo şu: İki network arayüzümüz var. eth0 internal ağa bakıyor (192.168.1.0/24), eth1 ise dış yönlendiriciye. İç kullanıcılar için app.sirket.com -> 10.10.1.50 döndüreceğiz, dış dünya için aynı adres -> 203.0.113.10 döndürecek.
Temel Corefile iskeletini oluşturalım:
# /etc/coredns/Corefile
# İÇ AĞ için server block - sadece internal interface'i dinler
(internal_common) {
log
errors
health 192.168.1.1:8080
ready
prometheus 192.168.1.1:9153
}
# DIŞ AĞ için server block - sadece external interface'i dinler
(external_common) {
log
errors
}
# Internal network - sirket.com zone'u
sirket.com:53 {
import internal_common
# Sadece iç ağ sorgularını kabul et
acl {
allow net 192.168.0.0/16
allow net 10.0.0.0/8
allow net 172.16.0.0/12
block
}
file /etc/coredns/zones/internal/sirket.com.db
# İç ağda çözülemeyen sorgular için fallback
forward . 8.8.8.8 8.8.4.4
cache 300
}
# External network - aynı domain, farklı zone dosyası
sirket.com:5353 {
import external_common
file /etc/coredns/zones/external/sirket.com.db
cache 3600
}
Burada bir gerçeklik paylaşmak istiyorum: CoreDNS’de aynı port üzerinde view benzeri bir IP tabanlı ayrım yapmak için ya iki ayrı port kullanıyorsunuz (ve önüne bir yönlendirici koyuyorsunuz) ya da iki ayrı IP üzerinde aynı portu dinliyorsunuz. Ben genellikle ikinci yöntemi tercih ediyorum.
# Gerçek ortam için önerilen yaklaşım
# eth0: 192.168.1.1 (internal)
# eth1: 203.0.113.1 (external)
sirket.com:53 {
bind 192.168.1.1
file /etc/coredns/zones/internal/sirket.com.db
log
errors
cache 300
acl {
allow net 192.168.0.0/16
allow net 10.0.0.0/8
block
}
}
sirket.com:53 {
bind 203.0.113.1
file /etc/coredns/zones/external/sirket.com.db
log
errors
cache 3600
}
# Diğer tüm sorgular için upstream
. {
forward . 8.8.8.8 1.1.1.1
cache 300
log
}
Zone Dosyalarını Oluşturmak
Şimdi asıl iş. İki ayrı zone dosyası oluşturacağız.
# Dizin yapısını hazırlayalım
sudo mkdir -p /etc/coredns/zones/internal
sudo mkdir -p /etc/coredns/zones/external
Internal zone dosyası – burada özel IP’leri kullanıyoruz:
; /etc/coredns/zones/internal/sirket.com.db
$ORIGIN sirket.com.
$TTL 300
@ IN SOA ns1.sirket.com. hostmaster.sirket.com. (
2024010101 ; Serial
3600 ; Refresh
900 ; Retry
604800 ; Expire
300 ) ; Minimum TTL
; Name servers
@ IN NS ns1.sirket.com.
@ IN NS ns2.sirket.com.
ns1 IN A 192.168.1.1
ns2 IN A 192.168.1.2
; Internal A records - özel IP'ler
@ IN A 10.10.1.10
www IN A 10.10.1.10
app IN A 10.10.1.50
api IN A 10.10.1.51
db IN A 10.10.2.10
mail IN A 10.10.1.20
; İç ağa özgü servisler - dışarıdan erişilemeyen
gitlab IN A 10.10.3.10
jenkins IN A 10.10.3.11
grafana IN A 10.10.3.20
kibana IN A 10.10.3.21
; MX records
@ IN MX 10 mail.sirket.com.
External zone dosyası – dışarıya açık public IP’ler:
; /etc/coredns/zones/external/sirket.com.db
$ORIGIN sirket.com.
$TTL 3600
@ IN SOA ns1.sirket.com. hostmaster.sirket.com. (
2024010101 ; Serial
86400 ; Refresh
7200 ; Retry
604800 ; Expire
3600 ) ; Minimum TTL
; Name servers - public IP'ler
@ IN NS ns1.sirket.com.
@ IN NS ns2.sirket.com.
ns1 IN A 203.0.113.1
ns2 IN A 203.0.113.2
; External A records - public IP'ler
@ IN A 203.0.113.10
www IN A 203.0.113.10
app IN A 203.0.113.10
api IN A 203.0.113.11
; Mail sunucusu
mail IN A 203.0.113.20
@ IN MX 10 mail.sirket.com.
; TXT records (SPF, DKIM vb.)
@ IN TXT "v=spf1 ip4:203.0.113.20 ~all"
Kubernetes Ortamında Split Horizon
Kubernetes kullanıyorsanız, CoreDNS zaten cluster’ınızda çalışıyor ve ConfigMap üzerinden yönetiliyor. Buradaki senaryo biraz farklı: cluster içindeki pod’ların hem .cluster.local zone’unu hem de dış domainleri düzgün çözmesini, ama iç servislere erişirken iç IP’leri kullanmasını istiyorsunuz.
# Mevcut CoreDNS ConfigMap'i alın
kubectl get configmap coredns -n kube-system -o yaml > coredns-configmap-backup.yaml
# Düzenlemek için
kubectl edit configmap coredns -n kube-system
Kubernetes için önerilen ConfigMap yapısı:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
# Kubernetes cluster.local zone'u
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
# İç domain için özel forward - kendi DNS sunucunuza
# sirket.local sorgularını iç DNS'e yönlendir
forward sirket.local 192.168.1.1 {
policy sequential
health_check 5s
}
# sirket.com için de iç DNS sunucusunu kullan
# (Split Horizon'dan internal cevaplar gelecek)
forward sirket.com 192.168.1.1 {
policy sequential
health_check 5s
}
# Geri kalan her şey için public DNS
forward . 8.8.8.8 1.1.1.1 {
max_concurrent 1000
}
prometheus :9153
cache 30
loop
reload
loadbalance
}
Bu ConfigMap değişikliğini uyguladıktan sonra CoreDNS pod’larının otomatik reload etmesini bekleyin. reload plugin sayesinde genellikle pod restart’a gerek kalmıyor.
ACL ve Güvenlik Sertleştirmesi
Split Horizon kurulumunda güvenlik kritik. Yanlış yapılandırma, iç ağ bilgilerinin dışarıya sızmasına neden olabilir. ACL plugin’ini detaylı ele alalım:
sirket.com:53 {
bind 192.168.1.1
acl {
# RFC 1918 private adreslere izin ver
allow net 10.0.0.0/8
allow net 172.16.0.0/12
allow net 192.168.0.0/16
# IPv6 link-local
allow net fe80::/10
# Loopback her zaman izin
allow net 127.0.0.0/8
allow net ::1/128
# Geri kalan her şeyi engelle
block
}
file /etc/coredns/zones/internal/sirket.com.db
log . "{remote} - {type} {name} {class} {proto} {size} {rcode} {duration}"
errors
cache 300
}
İzleme ve Debug
Prodüksiyona almadan önce her şeyin doğru çalıştığını test etmek için kullandığım komutlar:
# CoreDNS config syntax kontrolü
coredns -conf /etc/coredns/Corefile -dns.port 1053 &
# İç ağ sorgusunu simüle et - internal IP'yi sorgula
dig @192.168.1.1 app.sirket.com A
# Dış ağ sorgusunu simüle et
dig @203.0.113.1 app.sirket.com A
# Zone transfer deneyin - engellenmeli
dig @192.168.1.1 sirket.com AXFR
# Prometheus metrikleri kontrol
curl -s http://192.168.1.1:9153/metrics | grep coredns_dns_requests_total
# CoreDNS loglarını takip et
sudo journalctl -u coredns -f
# Kubernetes'te CoreDNS pod logları
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100 -f
Gerçek bir prodüksiyon ortamında karşılaştığım klasik sorun: Zone dosyasında serial numarayı güncellemeyi unutmak. CoreDNS file plugin’i default olarak 60 saniyede bir zone dosyasını kontrol eder ama cache nedeniyle değişiklikler hemen yansımayabilir. Serial numarayı her değişiklikte artırmak zorunlu, aksi halde secondary DNS sunucuları (varsa) güncel zone’u almaz.
# Zone dosyasını değiştirdikten sonra reload zorlamak için
sudo systemctl reload coredns
# veya
sudo kill -SIGUSR1 $(pgrep coredns)
# Zone'un doğru yüklenip yüklenmediğini test et
dig @localhost version.bind TXT CH +short
Yüksek Erişilebilirlik için İki Düğümlü Yapı
Tek bir CoreDNS instance’ı prodüksiyonda yeterli değil. En basit HA yaklaşımı iki sunucu, keepalived ile VIP yönetimi:
# Her iki sunucuya da CoreDNS kurun, sonra keepalived
sudo apt install keepalived -y
# Master sunucu için keepalived.conf
sudo tee /etc/keepalived/keepalived.conf > /dev/null <<EOF
vrrp_script check_coredns {
script "dig @127.0.0.1 health.coredns.local +time=2 +tries=1 || exit 1"
interval 5
fall 2
rise 2
}
vrrp_instance DNS_VIP {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass dns-secret-2024
}
virtual_ipaddress {
192.168.1.100/24
}
track_script {
check_coredns
}
}
EOF
sudo systemctl enable keepalived --now
Bu yapıda istemciler her zaman 192.168.1.100 adresine sorgu atar. Master düştüğünde keepalived VIP’i backup sunucuya taşır, kesinti birkaç saniyeyle sınırlı kalır.
Sık Karşılaşılan Sorunlar
Prodüksiyonda gördüğüm ve zaman kaybettiren hatalar:
SERVFAIL yanıtları: Zone dosyasındaki syntax hatası en yaygın sebep. named-checkzone aracıyla zone dosyasını CoreDNS bağımsız olarak doğrulayabilirsiniz.
Yanlış bind adresi: CoreDNS default olarak tüm interface’leri dinler. bind direktifini unutursanız internal zone’unuz dışarıya da yanıt verebilir.
Cache tutarsızlığı: İç ve dış cevaplar arasında geçiş yaparken test ederken +nocache flagini kullanın: dig @192.168.1.1 app.sirket.com +nocache
Kubernetes’te ndots sorunu: Cluster içi pod’lar kısa hostname’leri çözerken fazla sorgu atıyor olabilir. Pod spec’inde dnsConfig ile ndots değerini düşürmek yardımcı olur.
# Gerçek zamanlı sorgu sayısını izle
watch -n 2 "curl -s http://192.168.1.1:9153/metrics | grep 'coredns_dns_requests_total'"
# Hangi sorgular SERVFAIL alıyor?
sudo journalctl -u coredns | grep SERVFAIL | tail -20
Sonuç
CoreDNS ile Split Horizon kurulumu, doğru anlaşıldığında BIND’a göre çok daha yönetilebilir bir hal alıyor. bind direktifi ve zone dosyası ayrımı sayesinde iç-dış trafik yönetimi temiz kalıyor, Corefile’ın okunabilirliği konfigürasyon hatalarını azaltıyor.
Kritik noktalara dikkat çekmek istiyorum: Zone dosyalarının serial numaralarını ihmal etmeyin, ACL bloklarını daima “block” ile kapatın, ve Prometheus metriklerini mutlaka izleme sisteminize entegre edin. DNS sorunları sessiz sedasız yayılır, fark ettiğinizde yarım saatlik bir şey haline gelmiş olur.
Kubernetes ortamındakilere özellikle şunu söylemeliyim: CoreDNS ConfigMap’inizi GitOps akışınıza dahil edin. Elle yapılan değişiklikler cluster yeniden oluşturmalarında kaybolur ve “DNS neden çalışmıyor” sorusunun cevabını saat 03:00’da aramak istemezsiniz.
