CoreDNS Corefile Yapılandırması ve Plugin Sistemi

Kubernetes ortamında DNS sorunlarıyla boğuşmaya başladığınızda, bir noktada CoreDNS’in Corefile’ına bakmak zorunda kalırsınız. İlk bakışta sade görünen bu yapılandırma dosyası, aslında oldukça güçlü bir plugin mimarisinin üzerine kurulu. Ben de bu yazıda CoreDNS’i salt teorik anlatmak yerine, gerçekte karşılaşılan senaryolar üzerinden anlatmaya çalışacağım.

Corefile’ın Anatomisi

CoreDNS’in kalbi Corefile’dır. Nginx’in nginx.conf‘u ne ise CoreDNS için Corefile odur. Yapı şöyle özetlenebilir: her blok bir sunucu tanımıdır ve bu sunucu hangi port/protokol üzerinde hangi zone’ları dinleyeceğini belirtir.

<zone> [<port>] {
    <plugin> [<seçenekler>]
    <plugin> [<seçenekler>]
}

Basit bir örnek:

. {
    forward . 8.8.8.8 8.8.4.4
    cache 30
    log
    errors
}

Bu yapılandırma şunu söylüyor: “Nokta (.) yani tüm domainler için gelen sorguları 8.8.8.8 ve 8.8.4.4’e yönlendir, 30 saniyelik önbellek kullan, logla ve hataları göster.” Gayet okunabilir.

Ama işler genellikle bu kadar sade kalmaz.

Plugin Sistemi Nasıl Çalışır?

CoreDNS’in en önemli özelliği plugin tabanlı mimarisinin olması. Bind9 gibi monolitik bir yapı yerine, her işlev ayrı bir plugin olarak tanımlanmış. Bu mimari beraberinde hem esneklik hem de dikkat edilmesi gereken bazı önemli detaylar getiriyor.

Plugin zinciri ve sıralama: Corefile’daki plugin sırası değil, CoreDNS’in derleme zamanında belirlenen plugin sırası önemlidir. Yani siz Corefile’da cache‘i forward‘dan önce yazarsanız bu bir hata üretmez ama davranış sizi şaşırtabilir. Plugin’ler her zaman önceden tanımlı sırayla çalışır.

Resmi plugin sırasına göz atmak için:

coredns -plugins

Bu komut mevcut binary’ye derlenmiş tüm plugin’leri listeler. Production ortamında hangi plugin’lerin mevcut olduğunu bilmek, “neden çalışmıyor” sorularının yarısını yanıtlar.

Kubernetes’te Varsayılan Corefile

Kubernetes’te kube-system namespace’inde çalışan CoreDNS’in varsayılan Corefile’ına bakalım:

kubectl get configmap coredns -n kube-system -o yaml

Çıktı genellikle şuna benzer:

.:53 {
    errors
    health {
       lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
       pods insecure
       fallthrough in-addr.arpa ip6.arpa
       ttl 30
    }
    prometheus :9153
    forward . /etc/resolv.conf {
       max_concurrent 1000
    }
    cache 30
    loop
    reload
    loadbalance
}

Her satırın ne işe yaradığını tek tek geçelim:

  • errors: DNS sorgularındaki hataları stderr’e yazar
  • health: /health endpoint’i üzerinden liveness probe’a yanıt verir, lameduck 5s ise pod kapanırken 5 saniye boyunca sağlıklı olmadığını bildirerek graceful shutdown sağlar
  • ready: Tüm plugin’ler hazır olana kadar /ready endpoint’i 503 döner
  • kubernetes: Kubernetes servis ve pod DNS’ini yönetir, pods insecure ile pod IP’lerinden reverse DNS yapılabilir
  • prometheus: :9153 portunda metrics endpoint açar
  • forward: Cluster dışı sorgular için upstream DNS belirtir
  • cache: TTL bazlı önbellek, burada 30 saniye
  • loop: Sonsuz döngü tespiti, varsa CoreDNS crash’ler (kasıtlı)
  • reload: Corefile değişikliklerini otomatik algılar
  • loadbalance: Round-robin DNS load balancing

Gerçek Senaryo 1: Şirket İçi Domain Yönlendirmesi

Diyelim ki şirketinizde internal.sirket.com.tr domaini için ayrı bir DNS sunucunuz var ve bu sunucu 192.168.1.10 adresinde çalışıyor. Kubernetes pod’larının bu domain’e erişebilmesi lazım ama geri kalan trafiğin normal forward’a gitmesini istiyorsunuz.

.:53 {
    errors
    health
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
        ttl 30
    }
    forward internal.sirket.com.tr 192.168.1.10 {
        policy sequential
        health_check 5s
    }
    forward . /etc/resolv.conf {
        max_concurrent 1000
    }
    cache 30
    loop
    reload
    loadbalance
}

Burada dikkat edilmesi gereken nokta: iki forward direktifi var. CoreDNS en spesifik zone’u önce eşleştirir. internal.sirket.com.tr için gelen sorgular 192.168.1.10’a giderken geri kalanlar sistem DNS’ine gider.

Bunu ConfigMap olarak uygulamak için:

kubectl edit configmap coredns -n kube-system

Değişiklikler reload plugin’i sayesinde otomatik algılanır, pod’ları yeniden başlatmanız gerekmez. Ama değişiklik sonrası log’ları izlemek yine de iyi bir alışkanlık:

kubectl logs -n kube-system -l k8s-app=kube-dns -f

Gerçek Senaryo 2: Hosts Plugin ile Statik Kayıt

Bazı durumlarda DNS sunucusuna kayıt eklemek mümkün olmayabilir veya geçici bir çözüm gerekebilir. hosts plugin’i tam bu noktada devreye giriyor.

.:53 {
    errors
    health
    ready
    hosts /etc/coredns/customhosts {
        192.168.10.50  eski-sistem.internal
        192.168.10.51  migration-db.internal
        fallthrough
    }
    kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
        ttl 30
    }
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}

fallthrough direktifi burada kritik. Eğer hosts dosyasında eşleşme bulunamazsa sorgu bir sonraki plugin’e geçsin istiyorsanız fallthrough şart. Yoksa NXDOMAIN döner.

Hosts dosyasını ConfigMap’e eklemek için:

kubectl create configmap custom-hosts 
  --from-literal=customhosts="192.168.10.50 eski-sistem.internaln192.168.10.51 migration-db.internal" 
  -n kube-system

Ardından CoreDNS deployment’ına bu configmap’i volume olarak bağlarsınız.

Gerçek Senaryo 3: Rewrite Plugin ile DNS Manipülasyonu

Migration süreçlerinde ya da test ortamlarında rewrite plugin’i hayat kurtarır. Örneğin api.eskisite.com sorgularını api.yenisite.com‘a yönlendirmek istiyorsunuz:

.:53 {
    errors
    rewrite name api.eskisite.com api.yenisite.com
    rewrite name regex (.*).test.internal {1}.staging.cluster.local answer auto
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}

İkinci rewrite kuralına dikkat: regex ile pattern matching yapıyoruz. servis1.test.internal sorgusu servis1.staging.cluster.local‘e rewrite ediliyor. answer auto ise cevap paketindeki ismi de otomatik olarak orijinal sorguya dönüştürür, aksi halde client kafası karışabilir.

Rewrite kurallarını test etmek için:

kubectl run -it --rm dns-test --image=busybox --restart=Never -- nslookup api.eskisite.com

Cache Plugin’in İncelikleri

Cache plugin göründüğünden daha fazla seçenek sunar. Şöyle bir yapılandırma düşünün:

.:53 {
    errors
    cache {
        success 9984 3600
        denial 9984 5
        prefetch 10 1m 10%
        serve_stale 1h
    }
    forward . 8.8.8.8 8.8.4.4
    log
}
  • success 9984 3600: Başarılı yanıtlar için 9984 entry kapasiteli, max 3600 saniyelik cache
  • denial 9984 5: NXDOMAIN ve SERVFAIL yanıtları için sadece 5 saniye cache (negative caching’i kısa tutmak iyi pratik)
  • prefetch 10 1m 10%: Son 1 dakikada 10’dan fazla sorgulan ve TTL’nin %10’una gelen kayıtlar proaktif olarak yenilenir
  • serve_stale 1h: Upstream cevap veremezse 1 saate kadar eski cache verisi döndürülür

serve_stale özellikle upstream DNS’in unstable olduğu ortamlarda çok değerli. Ziraat Bankası DNS’ini mi forward ettiniz ve o an arızalı? En azından önbelleğinizdeki verilerle ayakta kalırsınız.

Log Plugin ile Detaylı İzleme

Üretim ortamında DNS sorgularını loglamak başlangıçta masum görünür ama yüksek trafikli ortamlarda log hacmi sizi boğabilir. Seçici loglama şöyle yapılır:

.:53 {
    errors
    log . {
        class denial error
    }
    log internal.sirket.com.tr
    forward . /etc/resolv.conf
    cache 30
}

Birinci log direktifi sadece NXDOMAIN ve hata yanıtlarını loglar. İkincisi ise sadece belirli zone için tüm sorguları loglar. Bu şekilde gürültüyü azaltıp önemli olana odaklanabilirsiniz.

Log formatını özelleştirmek de mümkün:

.:53 {
    log . {
        format combined
        class all
    }
    forward . /etc/resolv.conf
    cache 30
}

Health Check ve Metrics

CoreDNS’i production’da izlemek için iki endpoint kritik:

# Liveness probe
curl http://localhost:8080/health

# Readiness probe
curl http://localhost:8181/ready

# Prometheus metrics
curl http://localhost:9153/metrics

Prometheus plugin ile toplanan önemli metrikler:

  • coredns_dns_requests_total: Toplam sorgu sayısı, tip ve protokole göre
  • coredns_dns_responses_total: Yanıt tiplerine göre (NOERROR, NXDOMAIN, SERVFAIL vb.)
  • coredns_cache_hits_total / coredns_cache_misses_total: Cache etkinliği
  • coredns_forward_healthcheck_failures_total: Upstream DNS sağlık sorunları
  • coredns_dns_request_duration_seconds: Sorgu gecikme histogramı

Grafana’da CoreDNS dashboard’u kurmak için dashboard ID 14981’i kullanabilirsiniz, Kubernetes ortamları için oldukça kapsamlı.

Çoklu Server Block Kullanımı

Bazen farklı portlarda veya farklı zone’lar için ayrı sunucu blokları tanımlamak gerekir:

cluster.local:53 {
    kubernetes cluster.local {
        pods insecure
    }
    cache 30
    errors
    log
}

.:53 {
    forward . 8.8.8.8 {
        except cluster.local
    }
    cache 60
    errors
}

.:5353 {
    # Internal test sorguları için ayrı port
    hosts {
        192.168.99.100 test.local
        fallthrough
    }
    forward . /etc/resolv.conf
}

Her blok bağımsız bir listener oluşturur. Bu yapı özellikle split-horizon DNS gereksinimleri olan ortamlarda işe yarar. Hem internal hem external sorguların farklı şekilde yanıtlanması gerektiğinde bu yaklaşım oldukça temiz bir çözüm sunar.

Corefile Doğrulama ve Test

Değişiklik yapmadan önce Corefile’ı doğrulamak için:

# Binary ile doğrulama
coredns -conf /etc/coredns/Corefile -validate

# Kubernetes'te ConfigMap değişikliği sonrası CoreDNS pod'larını izle
kubectl rollout status deployment/coredns -n kube-system

# DNS sorgu testi
kubectl run dnsutils --image=gcr.io/kubernetes-e2e-test-images/dnsutils:1.3 
  --restart=Never -it --rm -- dig @10.96.0.10 kubernetes.default.svc.cluster.local

dig çıktısında ANSWER SECTION kısmında kayıt görünüyorsa her şey yolunda demektir. AUTHORITY SECTION boşsa ve sadece ADDITIONAL SECTION doluysa yönlendirme doğru çalışıyor ama yanıt yetkili değil demektir ki bu çoğu senaryoda normaldir.

Daha kapsamlı test için:

# SERVFAIL kontrolü
dig +short @10.96.0.10 olmayan-servis.cluster.local
# Boş veya NXDOMAIN beklenir

# Upstream çalışıyor mu?
dig +short @10.96.0.10 google.com
# IP adresi görünmeli

Sık Karşılaşılan Sorunlar

Loop detection crash: CoreDNS’in loop plugin’i döngü tespit edince kendini öldürür. Bu genellikle node’un /etc/resolv.conf dosyasının CoreDNS’in kendi IP’sine işaret ettiğinde olur. systemd-resolved kullanan Ubuntu sistemlerde 127.0.0.53’e yönlendirme bu sorunu tetikleyebilir.

Çözüm için:

forward . 8.8.8.8 8.8.4.4 {
    # /etc/resolv.conf yerine explicit IP
}

NXDOMAIN fırtınası: Uygulamalar var olmayan servisleri ısrarla sorguladığında negative caching devreye girmezse CoreDNS ve upstream DNS ezilir. Cache denial süresini artırmak ve uygulama tarafında retry backoff eklemek bu sorunu çözer.

Yüksek bellek kullanımı: Cache büyüklüğünü gereksiz yere artırmak bellek sorununa yol açar. 9984 entry varsayılan değeri çoğu cluster için yeterlidir. Gerçekten yüksek sorgu hacmi varsa horizontal scaling tercih edilmeli.

Sonuç

CoreDNS’in Corefile yapılandırması, ilk bakışta sade görünse de altında ciddi bir esneklik yatıyor. Plugin sistemi sayesinde neredeyse her DNS senaryosunu karşılayabiliyorsunuz: static host kaydından Kubernetes servis discovery’sine, DNS rewrite’tan split-horizon yapılandırmasına kadar.

Benim önerim şu: önce varsayılan Corefile’ı iyi anlayın, her direktifin ne işe yaradığını bilin. Sonra ihtiyaca göre ekleme yapın. Gereksiz plugin eklemek hem performansı etkiler hem de debug süreçlerini zorlaştırır. CoreDNS log’larını ve metric’lerini düzenli izleyin, özellikle coredns_forward_healthcheck_failures_total arttığında upstream DNS sorunlarını hemen fark edersiniz.

DNS sorunları genellikle gecenin köründe ortaya çıkar ve baskı altında debug etmek zorunda kalırsınız. Corefile’ı önceden iyi yapılandırıp test etmek, o anlar için kendinize yapabileceğiniz en iyi iyiliktir.

Bir yanıt yazın

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