Kubernetes’te CoreDNS Kurulumu ve Yapılandırması

Kubernetes cluster’ı ilk kurduğumda DNS konusu beni en çok zorlayan şeylerden biriydi. Pod’lar birbirini bulamıyor, servisler resolve edilemiyor, her şey bir muamma gibi görünüyordu. Sonra CoreDNS’i gerçekten anlamaya başlayınca hem sorunları çözmek hem de yapılandırmayı özelleştirmek çok daha kolay hale geldi. Bu yazıda Kubernetes ortamında CoreDNS’i sıfırdan kurmaktan başlayarak production’da karşılaşacağınız gerçek senaryolara kadar kapsamlı şekilde ele alacağım.

CoreDNS Nedir ve Neden Kubernetes’in Standart DNS Çözümü?

CoreDNS, Go ile yazılmış, plugin tabanlı bir DNS sunucusudur. Kubernetes 1.13 sürümünden itibaren kube-dns’in yerini alarak cluster’ların varsayılan DNS çözümü haline geldi. Plugin mimarisi sayesinde son derece esnek bir yapıya sahip; istediğiniz özelliği açıp kapatabiliyorsunuz.

Kubernetes’teki rolünü kısaca özetlemek gerekirse: her Pod, DNS sorguları için cluster’ın DNS servis IP’sine yönlendirilir. Bu IP, CoreDNS servisine aittir ve CoreDNS bu sorguları karşılayarak Pod’ların birbirini isim üzerinden bulmasını sağlar. my-service.my-namespace.svc.cluster.local gibi bir FQDN çözümlemek istediğinizde işi yapan CoreDNS’tir.

Kurulum Öncesi Gereksinimler

Mevcut bir cluster’da CoreDNS’in kurulu olup olmadığını kontrol etmekle başlayın:

kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl get deployment coredns -n kube-system
kubectl get configmap coredns -n kube-system -o yaml

Eğer cluster’ınızda eski kube-dns çalışıyorsa ve CoreDNS’e geçiş yapacaksanız, önce mevcut yapılandırmayı yedekleyin:

kubectl get configmap kube-dns -n kube-system -o yaml > kube-dns-backup.yaml
kubectl get deployment kube-dns -n kube-system -o yaml > kube-dns-deployment-backup.yaml

Kubeadm ile kurulmuş bir cluster’da CoreDNS zaten gelir ama bazen versiyon eski kalıyor. Mevcut versiyonu görmek için:

kubectl describe deployment coredns -n kube-system | grep Image

Temel CoreDNS Kurulumu

Kubeadm kullanıyorsanız CoreDNS otomatik olarak kurulur. Ancak bare-metal ya da özel bir ortamda manuel kurulum yapmanız gerekebilir. Aşağıdaki manifest dosyaları ile CoreDNS’i cluster’ınıza ekleyebilirsiniz.

Önce ServiceAccount ve ClusterRole tanımları:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: coredns
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:coredns
rules:
  - apiGroups:
    - ""
    resources:
    - endpoints
    - services
    - pods
    - namespaces
    verbs:
    - list
    - watch
  - apiGroups:
    - discovery.k8s.io
    resources:
    - endpointslices
    verbs:
    - list
    - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: system:coredns
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:coredns
subjects:
- kind: ServiceAccount
  name: coredns
  namespace: kube-system
EOF

Corefile Yapılandırması

CoreDNS’in kalbi Corefile’dır. Tüm davranışı bu dosya belirler. Kubernetes’te bu dosya bir ConfigMap olarak saklanır:

cat <<EOF | kubectl apply -f -
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
    }
EOF

Bu Corefile’daki her satırın ne anlama geldiğini bilmek önemli. Gerçek ortamlarda hata ayıklarken bu detayları bilmek hayat kurtarıyor:

  • errors: Hataları stdout’a yazar, log izleme için kritik
  • health: /health endpoint’i açar, liveness probe için kullanılır
  • lameduck 5s: Graceful shutdown sırasında 5 saniye bekler, pod yeniden başlarken DNS kesintisini önler
  • ready: /ready endpoint’i açar, readiness probe için
  • kubernetes: Kubernetes servis ve pod isimlerini çözer
  • pods insecure: Pod IP’lerini reverse DNS için kayıt etmeden de çözebilir
  • fallthrough: Eşleşme olmayan sorguları bir sonraki plugin’e iletir
  • ttl 30: DNS kayıtlarının geçerlilik süresi
  • prometheus: Metrik endpoint’i açar
  • forward: Cluster dışı sorgular için upstream DNS
  • max_concurrent 1000: Maksimum eşzamanlı sorgu sayısı
  • cache 30: 30 saniyelik cache
  • loop: DNS döngülerini tespit eder
  • reload: Corefile değişikliklerini otomatik uygular
  • loadbalance: DNS yanıtlarında round-robin uygular

CoreDNS Deployment ve Servis Tanımı

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/name: "CoreDNS"
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  selector:
    matchLabels:
      k8s-app: kube-dns
  template:
    metadata:
      labels:
        k8s-app: kube-dns
    spec:
      priorityClassName: system-cluster-critical
      serviceAccountName: coredns
      tolerations:
        - key: "CriticalAddonsOnly"
          operator: "Exists"
      nodeSelector:
        kubernetes.io/os: linux
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                  - key: k8s-app
                    operator: In
                    values: ["kube-dns"]
              topologyKey: kubernetes.io/hostname
      containers:
      - name: coredns
        image: registry.k8s.io/coredns/coredns:v1.11.1
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            memory: 170Mi
          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: 5
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - all
          readOnlyRootFilesystem: true
      volumes:
        - name: config-volume
          configMap:
            name: coredns
            items:
            - key: Corefile
              path: Corefile
---
apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "CoreDNS"
  annotations:
    prometheus.io/port: "9153"
    prometheus.io/scrape: "true"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 10.96.0.10
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP
  - name: metrics
    port: 9153
    protocol: TCP
EOF

Burada dikkat etmenizi istediğim birkaç nokta var. clusterIP: 10.96.0.10 değeri cluster’ınızın servis CIDR’ına göre değişmeli. Kubeadm kurulumlarında bu genellikle 10.96.0.10 olur. podAntiAffinity kuralı ise iki CoreDNS pod’unun aynı node’a düşmesini önlüyor; tek node’un gitmesiyle DNS’in çökmemesi için kritik.

Gerçek Dünya Senaryosu: Özel DNS Zone Ekleme

Diyelim ki şirket içi bir DNS sunucunuz var ve bazı domain’leri ona yönlendirmek istiyorsunuz. Örneğin internal.sirket.com adreslerini 192.168.1.53 adresindeki iç DNS’e sormak istiyorsunuz:

kubectl edit configmap coredns -n kube-system

Corefile’a şunu ekleyin:

internal.sirket.com:53 {
    errors
    cache 30
    forward . 192.168.1.53 192.168.1.54
}

Tam Corefile şöyle görünecek:

kubectl patch configmap coredns -n kube-system --type merge -p '
{
  "data": {
    "Corefile": ".:53 {n    errorsn    health {n       lameduck 5sn    }n    readyn    kubernetes cluster.local in-addr.arpa ip6.arpa {n       pods insecuren       fallthrough in-addr.arpa ip6.arpan       ttl 30n    }n    prometheus :9153n    forward . /etc/resolv.conf {n       max_concurrent 1000n    }n    cache 30n    loopn    reloadn    loadbalancen}ninternal.sirket.com:53 {n    errorsn    cache 30n    forward . 192.168.1.53 192.168.1.54n}n"
  }
}'

reload plugin’i aktifse CoreDNS değişikliği otomatik algılar, pod restart’a gerek yoktur. Yine de emin olmak için kontrol edin:

kubectl rollout restart deployment/coredns -n kube-system
kubectl rollout status deployment/coredns -n kube-system

Gerçek Dünya Senaryosu: Stub Zone ile Split-Horizon DNS

Hybrid cloud ortamlarında çok karşılaşılan bir durum: on-premise sistemler ile cloud’daki Kubernetes’in aynı domain isimlerini farklı IP’lere çözmesi gerekiyor. Bu durumda stub zone yapılandırması işe yarıyor:

cat <<EOF | kubectl apply -f -
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 {
           max_concurrent 1000
           policy sequential
        }
        cache 30
        loop
        reload
        loadbalance
    }
    prod.sirket.com:53 {
        errors
        cache 30
        forward . 10.0.0.53 {
           force_tcp
        }
    }
    staging.sirket.com:53 {
        errors
        cache 30
        forward . 10.0.1.53
    }
EOF

force_tcp opsiyonu özellikle büyük DNS yanıtları için önemli. UDP ile 512 byte’ın üzerindeki yanıtlarda truncation yaşanabiliyor ve bu bazen garip sorunlara yol açıyor.

Hata Ayıklama: DNS Sorunlarını Tespit Etmek

Production’da “pod’lar birbirini bulamıyor” şikayeti aldığınızda nereye bakacağınızı bilmek çok değerli. İlk önce CoreDNS pod’larının sağlığını kontrol edin:

kubectl get pods -n kube-system -l k8s-app=kube-dns -o wide
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50

Sonra test pod’u oluşturup DNS sorgusu yapın:

kubectl run dns-test --image=busybox:1.35 --rm -it --restart=Never -- sh

# Pod içinde:
nslookup kubernetes.default
nslookup kubernetes.default.svc.cluster.local
nslookup google.com
cat /etc/resolv.conf

Daha detaylı debug için dnsutils imajını kullanın:

kubectl run dnsutils --image=registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 
  --rm -it --restart=Never -- bash

# Pod içinde:
dig @10.96.0.10 kubernetes.default.svc.cluster.local
dig @10.96.0.10 google.com
nslookup -type=SRV _https._tcp.kubernetes.default.svc.cluster.local

CoreDNS loglarında hata görüyorsanız log seviyesini artırabilirsiniz. Corefile’a log plugin’ini ekleyin:

kubectl edit configmap coredns -n kube-system
# errors satırının altına ekleyin:
# log

Bu, tüm sorguları loglar. Yüksek trafikli ortamda log miktarı çok artacağından sadece debug sırasında açın.

CoreDNS Metriklerini İzlemek

prometheus plugin’i aktifse CoreDNS metrikleri 9153 portunda yayınlanır. Prometheus + Grafana kuruluysa hemen dashboard ekleyebilirsiniz. Metrik endpoint’ini hızlıca test etmek için:

kubectl port-forward -n kube-system service/kube-dns 9153:9153 &
curl http://localhost:9153/metrics | grep coredns_dns_requests_total

İzlenmesi gereken temel metrikler:

  • coredns_dns_requests_total: Toplam sorgu sayısı, ani artışlar loop’a işaret edebilir
  • coredns_dns_responses_total: Yanıt tiplerine göre dağılım, SERVFAIL artışı yapılandırma sorununa işaret eder
  • coredns_forward_requests_total: Upstream’e iletilen sorgular
  • coredns_cache_hits_total: Cache hit oranı, düşükse TTL değerini gözden geçirin
  • coredns_panics_total: Bu sıfır olmalı, artıyorsa ciddi bir sorun var

NodeLocal DNSCache ile Performansı Artırmak

Büyük cluster’larda CoreDNS pod’larına gelen yük çok artabiliyor. NodeLocal DNSCache, her node’da bir DNS cache daemon’ı çalıştırarak hem latency’yi düşürür hem de CoreDNS üzerindeki yükü azaltır.

# NodeLocal DNSCache manifest'ini indirin
curl https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml -O

# Değişkenleri ayarlayın
KUBEDNS_SERVICE_IP=$(kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}')
LOCAL_DNS_IP="169.254.20.10"

sed -i "s/__PILLAR__DNS__SERVER__/${KUBEDNS_SERVICE_IP}/g" nodelocaldns.yaml
sed -i "s/__PILLAR__LOCAL__DNS__/${LOCAL_DNS_IP}/g" nodelocaldns.yaml
sed -i "s/__PILLAR__DNS__DOMAIN__/cluster.local/g" nodelocaldns.yaml

kubectl apply -f nodelocaldns.yaml

NodeLocal DNSCache devreye girdiğinde pod’ların /etc/resolv.conf dosyasında nameserver olarak 169.254.20.10 görünmeli:

kubectl run test-pod --image=busybox --rm -it --restart=Never -- cat /etc/resolv.conf

Sık Karşılaşılan Sorunlar ve Çözümleri

5 saniye DNS timeout sorunu: Bu klasik bir Kubernetes DNS problemidir. Linux’ta conntrack tablosu, UDP sorgularında race condition’a yol açabilir. Çözüm olarak CoreDNS’in forward plugin’inde prefer_udp yerine TCP kullanmak ya da pod spec’ine dnsConfig eklemek işe yarar:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: dns-config-test
spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"
      - name: edns0
      - name: single-request-reopen
  containers:
  - name: test
    image: busybox
    command: ["sleep", "3600"]
EOF

ndots:2 değeri özellikle önemli. Varsayılan ndots:5 ile her DNS sorgusunda önce 5 farklı arama yapılıyor (servis.namespace.svc.cluster.local, servis.namespace.svc, vs.). Bu gereksiz yük yaratıyor. ndots:2‘ye düşürmek latency’yi belirgin şekilde azaltıyor.

CoreDNS loop tespiti: loop plugin’i bir döngü tespit ettiğinde pod crash’e giriyor. Logda Loop ... detected for zone "." görüyorsanız upstream DNS olarak cluster’ın kendi DNS IP’sini gösteriyorsunuzdur. /etc/resolv.conf‘u kontrol edin:

cat /etc/resolv.conf
# Eğer burada cluster DNS IP'si varsa loop oluşur
# forward . /etc/resolv.conf yerine direkt IP kullanın

Düzeltme olarak Corefile’da upstream’i açık şekilde belirtin:

forward . 8.8.8.8 1.1.1.1

Sonuç

CoreDNS, Kubernetes ekosisteminin en kritik bileşenlerinden biri ama çoğu zaman “varsayılan kurulum yeterli” diye üzerine düşülmüyor. Oysa performans sorunlarının, intermittent bağlantı hatalarının ve servis keşif problemlerinin büyük kısmının arkasında DNS yapılandırma eksiklikleri var.

Anlatılanları özetlemek gerekirse: kurulum tek başına yetmiyor, Corefile’ı gerçekten anlamak gerekiyor. ndots değerini ortamınıza göre ayarlayın, NodeLocal DNSCache’i ciddi iş yüklerinde mutlaka değerlendirin, Prometheus metrikleriyle DNS sağlığını aktif olarak izleyin. Özellikle stub zone ve forward yapılandırmalarını doğru kurmak, hybrid ortamlarda saatlerce harcanan hata ayıklama süresini önlüyor.

DNS konusunda “çalışıyor, dokunma” refleksiyle yaklaşmak, ileride en kötü zamanda sizi yakalayan sorunlara zemin hazırlıyor. Şimdi beş dakika ayırıp cluster’ınızdaki CoreDNS yapılandırmasını gözden geçirmenizi öneririm.

Bir yanıt yazın

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