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.
