CoreDNS Plugin’leri: forward, cache, log ve health Kullanımı

Kubernetes cluster’larımda CoreDNS ile ilk ciddi tanışmam, production ortamında yaşanan garip bir DNS gecikme problemiyle oldu. Servisler bazen 2-3 saniye gecikmeli yanıt veriyordu, bazen hiç yanıt vermiyordu. O güne kadar CoreDNS’e “zaten çalışıyor, dokunma” gözüyle bakıyordum. Ama o geceden sonra her bir plugin’i tek tek incelemek zorunda kaldım. Bugün sizinle bu süreçte en çok işime yarayan dört plugin’i paylaşacağım: forward, cache, log ve health. Bunların her biri kendi başına bir dünya, ama birlikte doğru kullanıldığında CoreDNS’i gerçekten güçlü bir araca dönüştürüyorlar.

CoreDNS’in Mimarisi: Plugin Zinciri

CoreDNS, monolitik bir DNS sunucusu değil. Her şey plugin üzerine kurulu. Bir DNS isteği geldiğinde, Corefile’da tanımlı plugin’ler sırayla devreye giriyor. Bu zinciri anlamadan plugin’leri düzgün yapılandıramazsınız.

Corefile’ın temel yapısı şöyle:

# /etc/coredns/Corefile
.:53 {
    log
    health :8080
    cache 300
    forward . 8.8.8.8 8.8.4.4
    errors
}

Burada sıralama önemli. log plugin’i isteği önce görür, sonra health kontrolü yapılır, ardından cache‘e bakılır, cache’de yoksa forward upstream’e gider. Bu akışı kafanızda oturtun, çünkü ileride yapılandırma sorunlarının büyük çoğunluğu bu sıralamadan kaynaklanıyor.

forward Plugin’i: Upstream DNS Yönetimi

forward plugin’i, CoreDNS’in upstream DNS sunucularıyla konuşmasını sağlar. Basit görünüyor ama içinde ciddi özellikler var.

En temel kullanım:

.:53 {
    forward . 8.8.8.8 8.8.4.4 1.1.1.1
}

Bu yapılandırmada CoreDNS, gelen tüm sorguları sırayla 8.8.8.8, 8.8.4.4 ve 1.1.1.1’e iletir. Ama burada dikkat edilmesi gereken bir nokta var: varsayılan policy random‘dur. Yani her sorguda upstream sunuculardan biri rastgele seçilir. Bu, bazı durumlarda tutarsız sonuçlara yol açabilir.

Policy seçenekleri:

  • random: Her sorguda rastgele upstream seçer (varsayılan)
  • round_robin: Sırayla upstream’lere gider
  • sequential: İlk upstream çalışmıyorsa bir sonrakine geçer

Production’da genellikle sequential veya round_robin kullanmak daha öngörülebilir sonuçlar verir:

.:53 {
    forward . 8.8.8.8 8.8.4.4 {
        policy sequential
        max_fails 3
        expire 10s
        health_check 5s
    }
}

Split-Horizon DNS Yapılandırması

Şirketteki en yaygın senaryolardan biri split-horizon DNS. İç domain’ler için şirket DNS sunucusuna, dış domain’ler için public DNS’e yönlendirme:

.:53 {
    # İç domain'ler için şirket DNS'i
    forward sirket.local 192.168.1.10 192.168.1.11 {
        policy round_robin
        health_check 10s
    }
    
    # dev ortamı için ayrı DNS
    forward dev.internal 10.0.0.53 {
        expire 5s
    }
    
    # Geri kalan her şey için public DNS
    forward . 1.1.1.1 8.8.8.8 {
        tls_servername cloudflare-dns.com
        policy sequential
    }
    
    cache 300
    log
    errors
}

DNS-over-TLS ile Güvenli Forward

Özellikle public Wi-Fi ağlarında veya güvenlik politikası katı olan ortamlarda DNS-over-TLS kullanmak isteyebilirsiniz:

.:53 {
    forward . tls://1.1.1.1 tls://1.0.0.1 {
        tls_servername cloudflare-dns.com
        health_check 5s
        max_fails 2
        expire 10s
    }
    cache 300
    errors
}

tls_servername parametresi önemli. TLS bağlantısında SNI için kullanılıyor ve sertifika doğrulaması bu isimle yapılıyor.

forward Parametreleri

  • policy: Upstream seçim stratejisi (random/round_robin/sequential)
  • max_fails: Bir upstream’in başarısız kabul edilmesi için gereken hata sayısı
  • expire: Başarısız upstream’in ne kadar süre sonra tekrar deneneceği
  • health_check: Upstream sağlık kontrolü aralığı
  • tls_servername: DNS-over-TLS için sunucu adı
  • except: Belirtilen domain’leri bu forward bloğundan hariç tutar
  • force_tcp: UDP yerine TCP kullanmaya zorlar

cache Plugin’i: DNS Önbellekleme

DNS önbellekleme, hem performans hem de upstream yükü açısından kritik. Ama yanlış yapılandırıldığında ciddi sorunlara da yol açabiliyor. O geceki production problemimin yarısı da buradaydı aslında: çok agresif cache ayarları yüzünden güncellenen bir IP adresi saatlerce eski değerle cache’de kalmıştı.

Temel cache yapılandırması:

.:53 {
    cache {
        success 9984 3600    # başarılı yanıtlar: max 9984 kayıt, 3600 saniye TTL
        denial 9984 300      # NXDOMAIN yanıtları: 300 saniye
        prefetch 10 1m 10%   # prefetch ayarları
    }
    forward . 8.8.8.8
}

Parametreleri açıklayalım:

  • success [capacity] [ttl]: Pozitif yanıtlar için kapasite ve maksimum TTL
  • denial [capacity] [ttl]: Negatif yanıtlar (NXDOMAIN, SERVFAIL) için
  • prefetch [amount] [duration] [percentage]: Cache dolmadan önce yenileme

Prefetch: Sessiz Kahraman

prefetch özelliği çok az konuşulan ama çok işe yarayan bir özellik. Nasıl çalışıyor? Diyelim ki bir cache kaydının TTL’i dolmak üzere, ve bu sorgu son 1 dakika içinde en az 10 kez sorgulanmış. CoreDNS bu durumda TTL dolmadan önce sessizce upstream’den yenileme yapıyor. Kullanıcı asla stale cache durumunu görmüyor.

.:53 {
    cache {
        success 9984 1800
        denial 1000 60
        prefetch 10 60s 10%
        # Son 60 saniye içinde %10 TTL kaldığında,
        # en az 10 kez sorgulanmış kayıtları prefetch et
    }
    forward . 1.1.1.1
    errors
}

Kubernetes Ortamında Cache Stratejisi

Kubernetes’te CoreDNS cache’ini yapılandırırken dikkatli olmak gerekiyor. Pod’lar sürekli oluşturuluyor ve siliniyor, servis IP’leri değişiyor. Çok uzun TTL değerleri stale DNS kayıtlarına yol açar:

.:53 {
    errors
    health {
        lameduck 5s
    }
    ready
    kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
        ttl 30
    }
    cache {
        success 9984 30    # Kubernetes servisleri için düşük TTL
        denial 1000 5
        prefetch 5 10s 10%
    }
    forward . /etc/resolv.conf {
        max_concurrent 1000
    }
    loop
    reload
    loadbalance
}

Burada Kubernetes servisleri için cache TTL’ini 30 saniyeye düşürdüm. Bu, servis değişikliklerinin hızlı yayılmasını sağlıyor. Tabii upstream yükü artıyor, ama Kubernetes iç DNS’i zaten cluster içinde çalışıyor, bu yüzden overhead kabul edilebilir.

Cache’i Devre Dışı Bırakmak

Belirli domain’ler için cache’i devre dışı bırakmak isteyebilirsiniz. Örneğin, sık değişen dinamik kayıtlar için:

.:53 {
    cache {
        success 9984 300
        denial 1000 30
        disable success dynamic.example.com
        disable denial dynamic.example.com
    }
    forward . 8.8.8.8
}

log Plugin’i: DNS Trafiğini Anlamak

log plugin’i olmadan CoreDNS’i yönetmek kör uçmak gibi. Özellikle sorun giderme anında bu plugin hayat kurtarıyor.

Temel kullanım son derece basit:

.:53 {
    log
    forward . 8.8.8.8
}

Bu haliyle her DNS sorgusunu stdout’a yazar. Ama production’da bu çok gürültülü olabilir. Filtreleme şart:

.:53 {
    log . {
        class success
        class denial
    }
    forward . 8.8.8.8
}

Log Class’ları

  • all: Her şeyi logla (varsayılan)
  • success: Başarılı yanıtları logla
  • denial: NXDOMAIN ve NODATA yanıtlarını logla
  • error: Hataları logla (SERVFAIL vb.)
  • stdout: stdout’a yaz (varsayılan)

Log Formatını Özelleştirme

Varsayılan log formatı işlevsel ama bazen daha fazlasına ihtiyaç duyuyorsunuz. Özellikle ELK stack veya Grafana Loki’ye göndereceğiniz JSON logları için:

.:53 {
    log . {
        format combined
        class all
    }
    forward . 8.8.8.8
}

CoreDNS log formatı için birkaç seçenek:

  • {type}: Sorgu tipi (A, AAAA, MX vb.)
  • {name}: Sorgulanan domain adı
  • {class}: DNS class’ı (genellikle IN)
  • {proto}: Protokol (udp/tcp)
  • {remote}: İstemci IP adresi
  • {size}: İstek boyutu
  • {rcode}: Yanıt kodu
  • {duration}: İşlem süresi

Özel format örneği:

.:53 {
    log . {
        format "{remote} - {type} {name} {rcode} {duration}"
    }
    forward . 8.8.8.8
    cache 300
}

Üretim Ortamında Log Stratejisi

Her DNS sorgusunu loglamak production’da ciddi I/O yüküne yol açar. Kubernetes cluster’ında dakikada binlerce DNS sorgusu olabilir. Benim tercih ettiğim yaklaşım: sadece hataları ve denial’ları loglamak, başarılı sorguları örneklemek.

Bir Kubernetes ConfigMap örneği:

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        log . {
            class denial error
        }
        health {
            lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
            ttl 30
        }
        cache {
            success 9984 300
            denial 1000 30
        }
        forward . 1.1.1.1 8.8.8.8 {
            max_concurrent 1000
        }
        reload
        loadbalance
    }

Bu yapılandırmada sadece başarısız sorgular loglanıyor. Bir servis birbirine bağlanamıyorsa veya NXDOMAIN alıyorsa, log’larda hemen görünüyor.

health Plugin’i: CoreDNS’i İzleme

health plugin’i, CoreDNS’in sağlık durumunu HTTP endpoint üzerinden sunuyor. Kubernetes liveness/readiness probe’ları için vazgeçilmez, ama bağımsız deployment’larda da çok işe yarıyor.

Temel yapılandırma:

.:53 {
    health :8080
    forward . 8.8.8.8
    cache 300
    log
}

Bu haliyle http://localhost:8080/health endpoint’i CoreDNS çalışıyorsa OK döner.

health ve ready Farkı

health plugin’i ile ready plugin’ini karıştırmamak önemli. İkisi farklı amaçlar için:

  • health: CoreDNS process’inin ayakta olup olmadığını kontrol eder. Kubernetes liveness probe için kullanılır.
  • ready: Tüm plugin’lerin hazır olup olmadığını kontrol eder. Kubernetes readiness probe için kullanılır.
.:53 {
    health {
        lameduck 5s
    }
    ready :8181
    forward . 8.8.8.8
    cache 300
    errors
}

lameduck parametresi önemli: CoreDNS kapanmadan önce bu süre kadar bekler ve bu sürede sağlık endpoint’i 503 döner. Bu, Kubernetes’in pod’u servis rotasyonundan çıkarmasına zaman tanır. Özellikle rolling update sırasında DNS hataları yaşıyorsanız bu değeri artırmayı deneyin.

Kubernetes Probe Yapılandırması

apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
spec:
  template:
    spec:
      containers:
      - name: coredns
        image: coredns/coredns:1.11.1
        ports:
        - containerPort: 53
          protocol: UDP
        - containerPort: 53
          protocol: TCP
        - containerPort: 8080
          name: health
        - containerPort: 8181
          name: ready
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /ready
            port: 8181
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 3

Prometheus Metrikleri ile İzleme

health endpoint’i temel bir kontrol sağlıyor ama gerçek izleme için prometheus plugin’ini de eklemenizi öneririm. Bu plugin, CoreDNS metriklerini Prometheus formatında sunar:

.:53 {
    errors
    health {
        lameduck 5s
    }
    ready :8181
    prometheus :9153
    log . {
        class denial error
    }
    cache {
        success 9984 300
        denial 1000 30
        prefetch 10 60s 10%
    }
    forward . 1.1.1.1 8.8.8.8 {
        max_concurrent 1000
        health_check 5s
    }
    reload
}

http://localhost:9153/metrics endpoint’inden şu metriklere ulaşabilirsiniz:

  • coredns_dns_requests_total: Toplam DNS istek sayısı
  • coredns_dns_responses_total: Yanıt kodlarına göre toplam yanıt sayısı
  • coredns_dns_request_duration_seconds: İstek gecikme histogramı
  • coredns_cache_hits_total: Cache hit sayısı
  • coredns_cache_misses_total: Cache miss sayısı
  • coredns_forward_requests_total: Forward edilen istek sayısı
  • coredns_forward_request_duration_seconds: Forward gecikme süresi

Gerçek Dünya Senaryosu: Kurumsal DNS Altyapısı

Tüm bu plugin’leri bir araya getirerek kurumsal bir ortam için örnek bir Corefile oluşturalım. Senaryomuz: On-premise Kubernetes cluster’ı, şirket içi Active Directory DNS, ve internet erişimi olan bir ortam.

# Şirket içi domain'ler
sirket.local:53 {
    errors
    log . {
        class denial error
    }
    cache {
        success 1000 60
        denial 100 10
    }
    forward . 192.168.10.10 192.168.10.11 {
        policy round_robin
        health_check 10s
        max_fails 3
        expire 30s
    }
}

# Kubernetes internal DNS
cluster.local:53 {
    errors
    log . {
        class denial error
    }
    kubernetes cluster.local {
        pods insecure
        ttl 30
    }
    cache {
        success 9984 30
        denial 1000 5
    }
}

# Genel internet trafiği
.:53 {
    errors
    health {
        lameduck 5s
    }
    ready :8181
    prometheus :9153
    log . {
        class denial error
    }
    cache {
        success 9984 3600
        denial 1000 300
        prefetch 10 300s 10%
    }
    forward . tls://1.1.1.1 tls://1.0.0.1 {
        tls_servername cloudflare-dns.com
        policy sequential
        health_check 10s
        max_fails 2
        expire 15s
        max_concurrent 1000
    }
    reload 10s
}

Bu yapılandırmada birkaç önemli nokta var. Şirket içi domain’ler için ayrı bir blok tanımladım ve bu blokta cache TTL’ini düşük tutdum çünkü AD ortamlarında DNS kayıtları sık değişebilir. Kubernetes internal DNS’i için de ayrı bir blok var ve orada da TTL 30 saniye. Genel internet trafiği için DNS-over-TLS kullanıyorum ve prefetch ile birlikte 3600 saniyelik cache süresi ayarladım.

Sorun Giderme İpuçları

CoreDNS plugin’leriyle çalışırken karşılaşılan yaygın sorunlar ve çözümleri:

Cache poisoning şüphesi varsa cache’i temizleyin:

# CoreDNS pod'unu yeniden başlatarak cache'i temizleyebilirsiniz
kubectl rollout restart deployment/coredns -n kube-system

# Veya metrics endpoint'inden cache durumunu kontrol edin
curl -s http://localhost:9153/metrics | grep coredns_cache

Forward upstream sağlık durumunu kontrol edin:

# Upstream DNS'e direkt sorgu atın
dig @8.8.8.8 google.com +short

# CoreDNS üzerinden sorgu atın ve karşılaştırın
dig @localhost google.com +short

# Gecikme farkını ölçün
time dig @localhost google.com > /dev/null
time dig @8.8.8.8 google.com > /dev/null

Log seviyesini geçici olarak artırın:

# Corefile'ı geçici olarak güncelle
# reload plugin varsa otomatik yeniden yüklenecek
# Yoksa SIGHUP gönderin
kill -SIGHUP $(pidof coredns)

Sonuç

forward, cache, log ve health plugin’leri CoreDNS’in temel taşları. Bunları doğru yapılandırmadan production’a çıkmak, motorsuz araba sürmek gibi. Her birinin kendi nüansları var: forward‘da policy seçimi ve DNS-over-TLS, cache‘de TTL dengesi ve prefetch, log‘da gürültüyü azaltırken görünürlüğü korumak, health‘de lameduck süresi ve readiness ayrımı.

Yazının başında bahsettiğim o production problemini ne çözdü biliyor musunuz? Cache TTL değerlerini düzelttim, prefetch aktif ettim, log‘a denial class’ı ekledim ve health probe timeout değerlerini artırdım. Basit değişiklikler, ama bunları bilmek için o geceyi yaşamak gerekti. Umarım bu yazı size o geceyi yaşatmaz.

Her ortamın ihtiyacı farklı. Buradaki örnekleri kendi altyapınıza göre uyarlayın, Prometheus metriklerinizi izleyin ve zamanla kendi optimal değerlerinizi bulun. DNS sıkıcı bir konu gibi görünür ama her şeyin temeli odur. Temeli sağlam atarsanız üstüne ne kurarsanız kurun, rahat edersiniz.

Bir yanıt yazın

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