CoreDNS Yüksek Erişilebilirlik ve Ölçeklendirme

Kubernetes ortamında DNS çözümlemesi bozulduğunda ne olduğunu bir kere yaşayanlar bilir: her şey aniden durur, servisler birbirini bulamaz, hata mesajları anlamsız gelmeye başlar ve ekip panik moduna girer. CoreDNS bu senaryonun tam ortasında oturuyor ve çoğu zaman gereken ilgiyi almıyor. Yüksek erişilebilirlik ve ölçeklendirme konusunda ise durum daha da vahim: birçok kurulum varsayılan ayarlarla bırakılıyor, tekil hata noktaları görmezden geliniyor. Bu yazıda CoreDNS’i gerçekten üretime hazır hale getirmek için ne yapılması gerektiğini, neyin çalışıp neyin çalışmadığını doğrudan paylaşacağım.

CoreDNS Mimari Temelleri ve HA Gereklilikleri

CoreDNS, plugin tabanlı mimarisiyle son derece esnek bir DNS sunucusu. Kubernetes’te kube-dns’in yerini almasının temel sebebi de bu esneklik. Ama esneklik beraberinde karmaşıklık getiriyor ve yanlış yapılandırılmış bir CoreDNS kümesi, yüksek erişilebilirlik yanılsaması yaratabilir.

Temel HA gereklilikleri şunlar:

  • Birden fazla replica: Tek pod çalışan CoreDNS production’da kabul edilemez
  • Anti-affinity kuralları: Tüm pod’ların aynı node’da çalışmaması
  • Resource limitleri: Bellek sızıntısı veya CPU spike’larının kontrol altında tutulması
  • Health check mekanizmaları: Sağlıksız instance’ların trafikten çıkarılması
  • Graceful shutdown: Mevcut sorguların tamamlanması için yeterli süre

Şimdi bunları tek tek nasıl uygulayacağımıza bakalım.

Replica Sayısı ve Pod Anti-Affinity

Kubernetes’te CoreDNS deployment’ı varsayılan olarak 2 replica ile gelir. Küçük kümeler için bu yeterli görünebilir, ama node sayısı arttıkça ve sorgu yükü büyüdükçe bu sayı yetersiz kalır.

Genel kural olarak her 1000 node için minimum 2 CoreDNS pod’u düşünülebilir, ama bunu gerçek metriklerle doğrulamak şart. Şu deployment yapılandırmasıyla başlayalım:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
spec:
  replicas: 4
  selector:
    matchLabels:
      k8s-app: kube-dns
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    metadata:
      labels:
        k8s-app: kube-dns
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: k8s-app
                operator: In
                values:
                - kube-dns
            topologyKey: kubernetes.io/hostname
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: k8s-app
                  operator: In
                  values:
                  - kube-dns
              topologyKey: topology.kubernetes.io/zone
      priorityClassName: system-cluster-critical
      tolerations:
      - key: "CriticalAddonsOnly"
        operator: "Exists"
      - key: "node-role.kubernetes.io/control-plane"
        effect: NoSchedule

Burada requiredDuringSchedulingIgnoredDuringExecution ile pod’ların farklı node’lara dağıtılmasını zorunlu kılıyoruz. preferredDuringScheduling kısmıyla ise farklı availability zone’lara yayılmasını tercih ediyoruz. “Required” ile “preferred” arasındaki fark kritik: required kural sağlanamazsa pod schedule edilmez, preferred ise en iyi çabayı ifade eder.

Resource Limitleri ve HPA Yapılandırması

Resource limitlerini doğru ayarlamak hem HA hem de güvenlik açısından önemli. Çok düşük limit koyarsanız OOMKill yaşarsınız, çok yüksek bırakırsanız node kaynakları israf edilir.

resources:
  requests:
    cpu: 100m
    memory: 70Mi
  limits:
    cpu: 200m
    memory: 170Mi

Bu değerler küçük-orta ölçekli kümeler için başlangıç noktası. Prometheus metrikleriyle gerçek kullanımı izleyip buna göre ayarlamalısınız. Ben genellikle ilk iki hafta limitlieri geniş tutup sonra metrikler ışığında daraltıyorum.

Yük arttıkça otomatik ölçeklendirme için HPA kullanabilirsiniz:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: coredns
  namespace: kube-system
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: coredns
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Pods
        value: 2
        periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Pods
        value: 1
        periodSeconds: 120

stabilizationWindowSeconds değerlerine dikkat edin. Scale-up için 30 saniye yeterli olabilir, ama scale-down için 300 saniye koyuyoruz. DNS sorgu trafiği ani iniş çıkışlar gösterebilir ve erken scale-down sonra hızlı scale-up ihtiyacı doğurabilir, bu da geçici latency artışına yol açar.

Corefile Optimizasyonu

Yüksek trafik altında CoreDNS davranışını belirleyen şey büyük ölçüde Corefile yapılandırması. Varsayılan yapılandırma production için yeterince optimize edilmemiş.

.: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
        prefer_udp
        health_check 5s
        policy round_robin
    }
    cache {
        success 9984 30 0
        denial 9984 5 0
        prefetch 10 1m 10%
        serve_stale 30s
    }
    loop
    reload
    loadbalance
}

Önemli parametreler:

  • lameduck 5s: Shutdown sırasında 5 saniye boyunca yeni bağlantı kabul etmeyi durdurur ama mevcut sorgular tamamlanır
  • max_concurrent 1000: Upstream’e eş zamanlı maksimum sorgu sayısı, varsayılan değer çok düşük
  • prefetch 10 1m 10%: Son 1 dakikada 10 kez sorgulanmış ve TTL’inin %10’u kalan kayıtları önceden yeniler
  • serve_stale 30s: Upstream erişilemezse 30 saniye boyunca eski cache yanıtı dönmeye devam eder

serve_stale özelliği kritik. Upstream DNS sunucunuz geçici olarak erişilemez olduğunda, süresi dolmuş cache yanıtıyla bile hizmet devam edebilir. Bu özellik olmadan tüm DNS çözümlemesi durur.

NodeLocal DNSCache ile Latency Azaltma

Büyük kümelerde en etkili ölçeklendirme yöntemlerinden biri NodeLocal DNSCache. Her node üzerinde bir DNS cache agent çalıştırarak CoreDNS’e giden merkezi trafik yükünü dramatik biçimde azaltıyor.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-local-dns
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: node-local-dns
  template:
    metadata:
      labels:
        k8s-app: node-local-dns
    spec:
      hostNetwork: true
      dnsPolicy: Default
      tolerations:
      - operator: Exists
      containers:
      - name: node-cache
        image: registry.k8s.io/dns/k8s-dns-node-cache:1.22.28
        resources:
          requests:
            cpu: 25m
            memory: 5Mi
          limits:
            cpu: 100m
            memory: 30Mi
        args:
        - -localip
        - "169.254.20.10,10.96.0.10"
        - -conf
        - /etc/Corefile
        - -upstreamsvc
        - kube-dns-upstream
        - -health-port
        - "8080"
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        volumeMounts:
        - mountPath: /run/xtables.lock
          name: xtables-lock
          readOnly: false
        - mountPath: /etc/coredns
          name: config-volume
      volumes:
      - name: xtables-lock
        hostPath:
          path: /run/xtables.lock
          type: FileOrCreate
      - name: config-volume
        configMap:
          name: node-local-dns

NodeLocal DNSCache, link-local IP adresi (169.254.20.10) üzerinden çalışır. Bu IP’ye gelen sorgular önce yerel cache’e bakılır, cache’de yoksa CoreDNS’e iletilir. Büyük kümelerde CoreDNS üzerindeki yük %70-80 oranında azalabilir.

PodDisruptionBudget ile Bakım Koruması

Node bakımı veya cluster upgrade sırasında CoreDNS pod’larının tamamının aynı anda durmaması için PodDisruptionBudget şart:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: coredns-pdb
  namespace: kube-system
spec:
  minAvailable: 2
  selector:
    matchLabels:
      k8s-app: kube-dns

minAvailable: 2 diyerek her zaman en az 2 CoreDNS pod’unun çalışır durumda olmasını garanti ediyoruz. kubectl drain komutu çalıştırıldığında bu kural dikkate alınır ve minAvailable şartı sağlanmadan pod evict edilmez.

Dikkat: maxUnavailable: 1 yazmak ile minAvailable: 2 yazmak farklı şeyler ifade eder. Toplam replica sayısı değişebileceğinden minAvailable ile mutlak sayı vermek daha güvenli.

Prometheus ile İzleme ve Alerting

CoreDNS’in sağlığını izlemeden HA kurulumun hiçbir anlamı yok. CoreDNS varsayılan olarak :9153 portunda Prometheus metrikleri sunuyor.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: coredns
  namespace: kube-system
  labels:
    release: prometheus
spec:
  endpoints:
  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
    interval: 15s
    port: metrics
    scheme: https
    tlsConfig:
      insecureSkipVerify: true
  jobLabel: jobLabel
  namespaceSelector:
    matchNames:
    - kube-system
  selector:
    matchLabels:
      k8s-app: kube-dns

Alert kuralları için şunlar kritik:

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: coredns-alerts
  namespace: kube-system
spec:
  groups:
  - name: coredns
    rules:
    - alert: CoreDNSDown
      expr: absent(up{job="coredns"} == 1)
      for: 2m
      labels:
        severity: critical
      annotations:
        summary: "CoreDNS tamamen erişilemez"
        description: "CoreDNS 2 dakikadır hiçbir instance cevap vermiyor"

    - alert: CoreDNSHighErrorRate
      expr: |
        (rate(coredns_dns_responses_total{rcode="SERVFAIL"}[5m]) /
        rate(coredns_dns_responses_total[5m])) > 0.05
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "CoreDNS SERVFAIL oranı yüksek"
        description: "Son 5 dakikada SERVFAIL oranı %5 üzerinde"

    - alert: CoreDNSLatencyHigh
      expr: |
        histogram_quantile(0.99,
          rate(coredns_dns_request_duration_seconds_bucket[5m])
        ) > 0.5
      for: 10m
      labels:
        severity: warning
      annotations:
        summary: "CoreDNS p99 latency yüksek"
        description: "p99 DNS sorgu süresi 500ms üzerinde"

Özellikle CoreDNSHighErrorRate alert’i pratikte çok değerli. Tek bir upstream DNS sunucusunun problem yaşaması SERVFAIL oranını ani şekilde artırabilir ve bu alert sayesinde müdahale edebilirsiniz.

Gerçek Dünya Senaryosu: Split-Brain DNS ve External DNS Entegrasyonu

Kurumsal ortamlarda sık karşılaşılan bir senaryo: iç servisler için Kubernetes DNS kullanılırken dış domain’ler için şirket içi DNS sunucuları devrede. Bu yapıyı CoreDNS ile yönetmek hem HA’yı hem de doğru yönlendirmeyi gerektiriyor.

.: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
    cache {
        success 9984 30 0
        denial 9984 5 0
        prefetch 10 1m 10%
        serve_stale 30s
    }
    forward . 8.8.8.8 8.8.4.4 {
        max_concurrent 1000
        health_check 5s
        policy sequential
    }
    loop
    reload
    loadbalance
}

# Şirket içi domain için özel yönlendirme
sirket.internal:53 {
    errors
    cache 30
    forward . 10.0.1.53 10.0.1.54 {
        max_concurrent 500
        health_check 10s
        policy round_robin
    }
}

# Prod namespace için özel TTL ve logging
prod.cluster.local:53 {
    errors
    log
    kubernetes cluster.local {
        namespaces prod
        ttl 5
    }
    cache 10
}

Bu yapıda sirket.internal sorgular şirket içi DNS’e yönlendirilirken genel internet sorguları Google DNS’e gidiyor. İkisi için ayrı cache ve health_check parametreleri var. Şirket içi DNS sunucusu erişilemez olduğunda bile serve_stale sayesinde kısa süreli kesinti atlatılabiliyor.

Upstream DNS Havuzu ve Failover

CoreDNS’in forward plugin’i birden fazla upstream destekliyor ama yük dağılımı ve failover davranışı policy seçimine bağlı:

  • sequential: Listedeki ilk sunucu çalıştığı sürece hep ona yönlendirir, hata olursa sıradakine geçer
  • round_robin: Her sorgu sırayla farklı sunucuya gönderilir
  • random: Rastgele seçim yapılır

Çoğu production senaryosunda sequential kullanıyorum. Birincil DNS sunucusu sağlıklı olduğu sürece tüm trafik oraya gitsin, devre dışı kalınca ikinciye geçsin. round_robin teorik olarak daha iyi dağılım sağlıyor ama sorunlu bir upstream’in %50 trafik alması durumunda gecikme önemli ölçüde artıyor.

Health check aralığı da kritik. health_check 5s ile 5 saniyede bir kontrol yapılıyor. Çok sık kontrol upstream’e gereksiz yük bindirir, çok seyrek kontrol ise arızalı sunucunun uzun süre trafikte kalmasına yol açar.

Cluster Dışı CoreDNS Deployment’ı

Kubernetes dışı ortamlarda, örneğin bare metal sunucularda veya VM tabanlı altyapılarda da CoreDNS HA kurulumu yapabilirsiniz. Burada Keepalived ile VIP yaklaşımı kullanıyorum:

# Her CoreDNS sunucusuna kurulum
sudo apt-get install -y keepalived

# /etc/keepalived/keepalived.conf (MASTER sunucu)
cat << 'EOF' > /etc/keepalived/keepalived.conf
vrrp_script chk_coredns {
    script "/bin/systemctl is-active coredns"
    interval 2
    weight -20
    fall 3
    rise 2
}

vrrp_instance DNS_VIP {
    state MASTER
    interface ens3
    virtual_router_id 51
    priority 110
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass gizli_parola_buraya
    }
    virtual_ipaddress {
        10.0.0.100/24
    }
    track_script {
        chk_coredns
    }
}
EOF

systemctl enable --now keepalived
# CoreDNS systemd service
cat << 'EOF' > /etc/systemd/system/coredns.service
[Unit]
Description=CoreDNS DNS server
Documentation=https://coredns.io
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=coredns
ExecStart=/usr/local/bin/coredns -conf /etc/coredns/Corefile
Restart=on-failure
RestartSec=5s
LimitNOFILE=65536
LimitNPROC=512

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now coredns

Bu kurulumda 10.0.0.100 VIP’i active sunucuya yönlenir. CoreDNS servis hata verirse Keepalived health check’i yakalar ve VIP backup sunucuya taşınır. Failover süresi genellikle 2-4 saniye, bu süre içinde UDP sorguları zaten retry yapar.

Performans Testleri ve Kapasite Planlaması

HA kurulumunuzu test etmeden canlıya almak büyük risk. Basit bir stres testi için:

# dnsperf ile yük testi
# Önce test dosyası oluştur
cat << 'EOF' > /tmp/dns-queries.txt
kubernetes.default.svc.cluster.local A
google.com A
github.com A
kube-dns.kube-system.svc.cluster.local A
EOF

# 60 saniye boyunca 1000 QPS ile test
dnsperf -s 10.96.0.10 
        -d /tmp/dns-queries.txt 
        -t 60 
        -Q 1000 
        -c 10

# Sonuç: Queries sent, completed, lost ve latency istatistikleri
# CoreDNS metriklerini anlık izleme
kubectl exec -n kube-system 
  $(kubectl get pods -n kube-system -l k8s-app=kube-dns -o name | head -1) 
  -- sh -c 'while true; do
    curl -s localhost:9153/metrics | grep -E "coredns_dns_requests_total|coredns_cache_hits_total" | grep -v "^#"
    sleep 5
  done'

Cache hit oranı %80 altındaysa ya çok fazla unique sorgu var ya da TTL değerleri çok düşük. Her iki durumda da CoreDNS üzerindeki yük artıyor.

Sonuç

CoreDNS HA ve ölçeklendirme konusunu özetlersek şu adımlar şart:

  • Birden fazla replica ve güçlü anti-affinity kuralları olmadan HA kurulum olmaz, yanılsamadan ibaret
  • NodeLocal DNSCache büyük kümelerde oyunun kurallarını tamamen değiştiriyor, mutlaka değerlendirin
  • Corefile’daki serve_stale, prefetch ve max_concurrent parametreleri küçük değişikliklerle büyük etki yaratıyor
  • PodDisruptionBudget olmadan en iyi hazırlıklarınız cluster upgrade sırasında çöpe gidebilir
  • Alerting ve izleme kurulmadan bir HA kurulumu kör uçuştan farksız

DNS, altyapının kan dolaşımı. Sağlam çalışırken kimse fark etmez, bozulunca herkes sen neredeydin diye sorar. Bu yüzden CoreDNS’e hak ettiği ilgiyi göstermek ve HA yapılandırmasını ciddiye almak gerekiyor. Yukarıdaki yapılandırmaları birebir kopyalamak yerine kendi ortamınızın özelliklerine göre uyarlamanızı ve her değişikliği test ortamında doğrulamanızı şiddetle tavsiye ederim.

Bir yanıt yazın

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