CoreDNS ile Multi-Cluster Kubernetes DNS Yönetimi

Birden fazla Kubernetes cluster’ı yönetiyorsanız, er ya da geç DNS çözümlemesinin nasıl bir kâbusa dönüşebileceğini yaşarsınız. Özellikle servisler arası iletişimin cluster sınırlarını aşması gerektiğinde, basit görünen bu mesele ciddi bir mimari probleme evrilir. Bu yazıda CoreDNS’i kullanarak multi-cluster ortamlarda DNS yönetimini nasıl yapılandırdığımı, hangi sorunlarla karşılaştığımı ve bu sorunları nasıl aştığımı paylaşacağım.

Multi-Cluster DNS’in Temel Sorunu

Tek bir Kubernetes cluster’ında CoreDNS gayet güzel çalışır. cluster.local domain’i altında servis discovery otomatik işler, kimse bir şeyden şikâyet etmez. Ama işin içine ikinci, üçüncü bir cluster girince tablo değişir.

Varsayılan yapılandırmada her cluster kendi cluster.local domain’ini kullanır. Cluster A’dan Cluster B’deki bir servise erişmek istediğinizde, bu isim çözülemez. Çünkü her cluster kendi DNS namespace’ini izole şekilde yönetir. Birkaç yaygın senaryo düşünelim:

  • Farklı bölgelerdeki iki cluster arasında veri replikasyonu yapan bir uygulama
  • Bir cluster’da merkezi bir authentication servisi, diğer cluster’larda ise uygulamalar
  • Disaster recovery amacıyla aktif-pasif çalışan iki cluster

Bu senaryoların hepsinde cross-cluster DNS çözümlemesi kaçınılmaz hale gelir.

CoreDNS’in Temel Mimarisi

CoreDNS, plugin tabanlı bir mimariyle çalışır. Her plugin zinciri sıralı şekilde işlenir ve bu yapı bize inanılmaz esneklik sağlar. Multi-cluster senaryolarında kullanacağımız temel plugin’ler şunlar:

  • forward: Belirli domain’leri başka DNS sunucularına yönlendirme
  • kubernetes: Kubernetes API’sinden servis ve pod kayıtlarını okuma
  • etcd: etcd’yi DNS backend olarak kullanma
  • cache: DNS yanıtlarını önbellekleme
  • health: Sağlık kontrolü endpoint’i

Corefile yapısı şuna benzer bir mantıkla çalışır:

# Temel bir CoreDNS Corefile yapısı
# /etc/coredns/Corefile

.: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
}

Bu temel yapıyı multi-cluster için genişleteceğiz.

Senaryo: İki Cluster Arası DNS Federasyonu

Elimizde şu yapı olduğunu düşünelim:

  • Cluster A (prod-eu): 10.96.0.0/12 pod CIDR, CoreDNS IP: 10.96.0.10
  • Cluster B (prod-us): 172.20.0.0/12 pod CIDR, CoreDNS IP: 172.20.0.10

Cluster A’dan service-b.default.svc.cluster-us.local şeklinde bir sorgu geldiğinde, bu sorgunun Cluster B’nin CoreDNS’ine iletilmesi gerekiyor.

İlk yapılandırma adımında Cluster A’nın CoreDNS ConfigMap’ini güncelliyoruz:

# cluster-a-coredns-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .: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
    }
    
    # Cluster B'ye ait domain'ler için yönlendirme
    cluster-us.local:53 {
        errors
        cache 30
        forward . 172.20.0.10 {
            force_tcp
            health_check 5s
        }
    }

Bu ConfigMap’i uyguladıktan sonra CoreDNS pod’larını yeniden başlatmanıza gerek yok, reload plugin’i değişikliği otomatik algılar.

# ConfigMap'i uygula
kubectl apply -f cluster-a-coredns-configmap.yaml

# CoreDNS'in reload'u algılayıp algılamadığını kontrol et
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=20

# Test için geçici bir pod başlat
kubectl run dns-test --image=busybox:1.35 --rm -it --restart=Never -- 
  nslookup my-service.default.svc.cluster-us.local

etcd Tabanlı Shared DNS Backend

Daha gelişmiş bir yaklaşım, iki cluster’ın da ortak bir etcd cluster’ını DNS backend olarak kullanmasıdır. Bu yöntem özellikle servis kayıtlarının dinamik olarak değiştiği ortamlarda çok daha sağlıklı sonuçlar verir.

Önce CoreDNS’in etcd plugin’i için gerekli yapılandırmayı hazırlayalım:

# shared-dns-backend-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns-shared
  namespace: kube-system
data:
  Corefile: |
    global.internal:53 {
        errors
        etcd {
            stubzones
            path /coredns
            endpoint https://etcd-shared.infra.example.com:2379
            tls /etc/coredns/tls/client.crt /etc/coredns/tls/client.key /etc/coredns/tls/ca.crt
        }
        cache 30
        prometheus :9153
    }
    
    cluster.local:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
        }
        cache 30
        loadbalance
    }
    
    .:53 {
        errors
        forward . /etc/resolv.conf
        cache 30
    }

etcd’ye kayıt eklemek için küçük bir betik hazırlayabiliriz. Bu betik, her cluster’daki servisleri shared etcd’ye yazan bir reconciler görevi görür:

#!/bin/bash
# register-service.sh
# Kubernetes servislerini shared etcd'ye kaydeden betik

ETCD_ENDPOINT="https://etcd-shared.infra.example.com:2379"
ETCD_CERT="/etc/etcd-client/client.crt"
ETCD_KEY="/etc/etcd-client/client.key"
ETCD_CA="/etc/etcd-client/ca.crt"
CLUSTER_ZONE="cluster-a"

register_service() {
    local service_name=$1
    local namespace=$2
    local cluster_ip=$3
    
    # CoreDNS etcd plugin'inin beklediği format
    local key="/coredns/global/internal/${CLUSTER_ZONE}/${namespace}/${service_name}"
    local value="{"host":"${cluster_ip}","ttl":30}"
    
    etcdctl --endpoints="${ETCD_ENDPOINT}" 
        --cert="${ETCD_CERT}" 
        --key="${ETCD_KEY}" 
        --cacert="${ETCD_CA}" 
        put "${key}" "${value}"
    
    echo "Registered: ${service_name}.${namespace}.${CLUSTER_ZONE}.global.internal -> ${cluster_ip}"
}

# Tüm LoadBalancer servislerini al ve kaydet
kubectl get services --all-namespaces -o json | 
    jq -r '.items[] | select(.spec.type=="LoadBalancer") | 
    "(.metadata.name) (.metadata.namespace) (.status.loadBalancer.ingress[0].ip)"' | 
while read name namespace ip; do
    if [ -n "$ip" ] && [ "$ip" != "null" ]; then
        register_service "$name" "$namespace" "$ip"
    fi
done

Stub Zone Yaklaşımı ile DNS Zinciri

Bazı durumlarda etcd’ye doğrudan erişim mümkün olmayabilir ya da mimarisel olarak tercih edilmeyebilir. Bu durumda stub zone zinciri kurabilirsiniz. Her cluster kendi DNS sunucusunu ayağa kaldırır ve diğer cluster’ların domain’leri için stub zone tanımlamaları yapar.

Bu mimarinin kritik noktası, cluster’lar arası network bağlantısının DNS sorgularına izin vermesi gerektiğidir. VPC peering, VPN tunnel ya da dedicated link üzerinden 53/UDP ve 53/TCP portlarının açık olması şart.

# Üç cluster için hub-and-spoke DNS mimarisi
# Hub cluster (merkezi) ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .: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 . 8.8.8.8 8.8.4.4
        cache 30
        loop
        reload
        loadbalance
    }
    
    # Spoke cluster 1
    spoke-1.local:53 {
        errors
        cache 30
        forward . 10.100.0.10 {
            force_tcp
            health_check 10s
            policy round_robin
        }
    }
    
    # Spoke cluster 2
    spoke-2.local:53 {
        errors
        cache 30
        forward . 10.200.0.10 {
            force_tcp
            health_check 10s
            policy round_robin
        }
    }

Yüksek Erişilebilirlik için CoreDNS HA Yapılandırması

Multi-cluster ortamında DNS, kritik bir altyapı bileşeni olduğundan single point of failure olmaması şart. Her cluster’da en az 2 CoreDNS replica çalıştırın ve anti-affinity kurallarıyla bunları farklı node’lara dağıtın:

# coredns-ha-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  selector:
    matchLabels:
      k8s-app: kube-dns
  template:
    metadata:
      labels:
        k8s-app: kube-dns
    spec:
      priorityClassName: system-cluster-critical
      tolerations:
        - key: "CriticalAddonsOnly"
          operator: "Exists"
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: k8s-app
                    operator: In
                    values:
                      - kube-dns
              topologyKey: kubernetes.io/hostname
      containers:
        - name: coredns
          image: coredns/coredns:1.11.1
          resources:
            limits:
              memory: 170Mi
              cpu: 500m
            requests:
              cpu: 100m
              memory: 70Mi
          args: [ "-conf", "/etc/coredns/Corefile" ]
          volumeMounts:
            - name: config-volume
              mountPath: /etc/coredns
              readOnly: true
          ports:
            - containerPort: 53
              name: dns
              protocol: UDP
            - containerPort: 53
              name: dns-tcp
              protocol: TCP
            - containerPort: 9153
              name: metrics
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 60
            timeoutSeconds: 5
            successThreshold: 1
            failureThreshold: 5
          readinessProbe:
            httpGet:
              path: /ready
              port: 8181
              scheme: HTTP
            initialDelaySeconds: 30
            timeoutSeconds: 2
      volumes:
        - name: config-volume
          configMap:
            name: coredns
            items:
              - key: Corefile
                path: Corefile

DNS Policy ve ndots Ayarları

Multi-cluster senaryolarında sıklıkla gözden kaçan bir konu var: uygulamaların DNS arama davranışı. Kubernetes’in varsayılan ndots:5 ayarı, service-b.default.svc.cluster-us.local gibi tam bir FQDN’i bile önce arama domain’leri listesiyle denemeye çalışır. Bu durum gereksiz DNS sorgularına ve gecikmelere yol açar.

# Cross-cluster iletişim yapan pod için optimize DNS yapılandırması
apiVersion: v1
kind: Pod
metadata:
  name: cross-cluster-app
  namespace: default
spec:
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 10.96.0.10
    searches:
      - default.svc.cluster.local
      - svc.cluster.local
      - cluster.local
    options:
      - name: ndots
        value: "3"
      - name: timeout
        value: "2"
      - name: attempts
        value: "3"
  containers:
    - name: app
      image: myapp:latest

ndots değerini 3’e indirerek servisadi.namespace.svc.cluster.local formatındaki sorgular doğrudan tam FQDN olarak değerlendirilir ve arama listesiyle tekrar denenmez. Pratikte bu küçük değişiklik DNS query sayısını dramatik şekilde düşürür.

Troubleshooting: Gerçek Dünyadan Sorunlar

Birkaç somut sorun ve çözümü paylaşayım.

Sorun 1: DNS sorgularının karşı cluster’a ulaşmaması

# Önce CoreDNS pod'larının cross-cluster DNS'e ulaşıp ulaşamadığını test et
kubectl exec -n kube-system 
  $(kubectl get pods -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[0].metadata.name}') 
  -- nslookup my-service.default.svc.cluster-us.local 172.20.0.10

# Eğer bağlantı hatası alıyorsan network policy'leri kontrol et
kubectl get networkpolicy -n kube-system

# CoreDNS'in karşı taraf DNS'e TCP/UDP 53 portunda erişimini kontrol et
kubectl exec -n kube-system 
  $(kubectl get pods -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[0].metadata.name}') 
  -- nc -zv 172.20.0.10 53

Sorun 2: Yüksek DNS latency

Eğer cross-cluster sorgular yavaş geliyorsa, önce cache ayarlarını gözden geçirin. Sonra forward plugin’in health_check intervalini optimize edin:

# CoreDNS metriklerini Prometheus ile izle
# coredns_dns_request_duration_seconds metriği kritik

# Anlık sorgu süresini test etmek için
for i in {1..10}; do
  time kubectl exec -n default test-pod -- 
    nslookup remote-service.default.svc.cluster-us.local 2>/dev/null
done

# DNS query log'larını aç (geçici olarak, production'da dikkatli kullan)
kubectl patch configmap coredns -n kube-system --patch '
data:
  Corefile: |
    .:53 {
      log
      errors
      kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
      }
      forward . /etc/resolv.conf
      cache 30
    }
'

Prometheus ile CoreDNS İzleme

Multi-cluster DNS yönetiminde observability hayati önem taşır. CoreDNS zaten Prometheus metrikleri sunuyor, bunları kullanmak için bir ServiceMonitor tanımlayın:

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

İzlemeniz gereken kritik metrikler:

  • coredns_dns_requests_total: Toplam sorgu sayısı, cluster başına filtreleyin
  • coredns_dns_responses_total: Yanıt kodlarına göre dağılım, SERVFAIL artışına dikkat
  • coredns_forward_request_duration_seconds: Upstream forwarding süresi
  • coredns_cache_hits_total ve coredns_cache_misses_total: Cache etkinliği
  • coredns_forward_healthcheck_failures_total: Upstream DNS sağlık kontrolü başarısızlıkları

Dikkat Edilmesi Gereken Noktalar

Buraya kadar anlattıklarım temiz bir ortamda gayet güzel çalışır. Ama gerçek hayatta karşılaşacağınız bazı tuzaklar var:

TTL yönetimi: Cluster’lar arası DNS kayıtlarında TTL değerlerini makul tutun. Çok düşük TTL (örneğin 5 saniye) DNS sunucularınızı gereksiz yere yorar, çok yüksek TTL ise servis IP değişikliklerinin geç yansımasına neden olur. 30-60 saniye çoğu senaryo için iyi bir denge noktasıdır.

Network policy çakışmaları: CoreDNS pod’larının hem gelen sorguları kabul etmesi hem de karşı cluster’ın DNS sunucusuna çıkabilmesi gerekir. Kısıtlayıcı network policy’leriniz varsa mutlaka kube-system namespace’i için özel kurallar yazın.

DNS döngüleri: Stub zone yapılandırmalarında yanlış yönlendirme tanımlamaları DNS döngülerine yol açabilir. loop plugin’i bu durumda koruyucu bir önlem olarak mutlaka aktif tutulmalıdır.

TLS sertifika yönetimi: etcd tabanlı shared backend kullanıyorsanız, istemci sertifikalarının her iki cluster’da da güncel olması gerekir. cert-manager ile bu sertifikaların otomatik yenilenmesini sağlamak çok büyük bir yük alır omuzlarınızdan.

Sonuç

Multi-cluster CoreDNS yönetimi kulağa karmaşık gelse de doğru adımlarla sistematik bir şekilde yapılandırıldığında son derece sağlam ve öngörülebilir bir yapı ortaya çıkıyor. Hangi yaklaşımı seçeceğiniz mimari kısıtlamalarınıza bağlı: eğer cluster’lar arası network bağlantınız doğrudan ve güvenilirse forward/stub zone yaklaşımı yeterli olur. Daha dinamik ve merkezi yönetim istiyorsanız shared etcd backend tercih edilebilir.

Her iki yaklaşımda da şu pratik kurallara sadık kalın: HA için en az 3 replica, ndots değerini mümkün olan en düşük seviyede tutun, Prometheus metriklerini aktif izleyin ve cross-cluster DNS sorgularını düzenli olarak test eden basit synthetic monitoring betikleri yazın. DNS sessiz sedasız çalışırken kimse sizi aramaz, ama bozulduğunda tüm cluster sizin üzerinize yıkılır.

Bir yanıt yazın

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