Modern yazılım sistemleri artık çok katmanlı ve dağıtık yapılardan oluşuyor. Microservice mimarileri, container tabanlı dağıtımlar ve dinamik servis keşifleri hayatımızı kolaylaştırdığı kadar, yeni kırılganlık alanlarını da beraberinde getiriyor.
Bu makalede, dışarıdan bakıldığında sağlıklı çalışan bir sistemin, sadece DNS çözümlemelerinin cache’lenmemesi nedeniyle nasıl zincirleme bir şekilde çöktüğünü inceleyeceğiz.
Senaryonun Başlangıcı: Sessiz Bir Tehlike
- Uygulama konteynerleri sorunsuz çalışıyor
- CPU, bellek, disk kullanımı normal seviyelerde
- Monitoring araçlarında bir anormallik gözlemlenmiyor
- Load balancer aktif, pod’lar ayakta, response time normal
Ancak birden, kullanıcılar bazı API’lerin çalışmadığını bildirmeye başlıyor. Arka arkaya gelen loglarda “timeout”, “connection refused” gibi hata mesajları görülmeye başlanıyor. İlginç olan şu ki:
- Her şey hala “çalışıyor gibi”
- Hiçbir servis doğrudan crash etmemiş
- Ağ bağlantısı kesilmemiş
Fakat sistem işlevini yerine getiremiyor. Hatalar görünmüyor ama etkiliyor.
Derinlemesine İnceleme: DNS’in Görünmeyen Etkisi
İncelemeler sonucunda olayın DNS tarafında başladığı ortaya çıkıyor. Peki ne olmuştu?
Teknik Süreç:
- Container içinde çalışan uygulama, her outbound HTTP çağrısı öncesi DNS çözümlemesi yapıyor.
- Uygulama herhangi bir DNS çözümleme önbelleği kullanmıyor (ne işletim sistemi düzeyinde ne de uygulama içinde).
- Her sorgu
8.8.8.8
gibi harici bir DNS sunucusuna gönderiliyor. - DNS sunucusunda birkaç saniyelik latency artışı (örneğin 300-500ms) yaşanıyor.
- Yüzlerce pod aynı anda bu sunucuya DNS sorgusu gönderiyor → DNS sunucusu yanıt veremez hale geliyor.
- Uygulama, çözümleyemediği hostname’ler nedeniyle bağlantı kuramıyor → timeout zinciri başlıyor.
- Downstream servisler erişilemez hale geliyor, bazıları bağlantı kuyruğunda boğuluyor.
- Load balancer’lar backend’i “fail” olarak işaretliyor → fallback tetikleniyor ama cache yok.
Teknik Kırılım: DNS Nasıl Çalışır ve Neden Cache Önemlidir?
DNS (Domain Name System), bir alan adını (örneğin api.myservice.com
) IP adresine çeviren bir sistemdir. Bu çözümleme süreci 4 seviyede gerçekleşir:
- Yerel Resolver (cache): İlk olarak sistem belleğindeki önbelleğe bakılır.
- /etc/hosts dosyası: Manuel tanımlı adres varsa kullanılır.
- Recursive DNS Resolver: Genellikle
resolv.conf
dosyasındaki adresler kullanılır. - Root, TLD ve Authoritative DNS: Gerekirse zincirin sonunda gerçek DNS sağlayıcısına ulaşılır.
DNS Cache’in Rolü
DNS çözümlemesi hızlı gibi görünse de, her sorgunun recursive şekilde yukarıdaki aşamalardan geçmesi milisaniyelerle ifade edilen gecikmelere neden olabilir. Çok sayıda sorguda bu, sistemde ciddi gecikmelere yol açar.
Ayrıca:
- DNS sunucuları rate-limit uygular.
- Ağ gecikmesi, paket kaybı gibi etmenlerle birleşince çözümleme süreleri uzar.
- Aynı sorguların tekrar tekrar yapılması büyük bir israf ve risk doğurur.
Container’larda Neden Bu Kadar Kırılgan?
Container ortamlarında (özellikle Docker/Kubernetes), DNS çözümlemesi izole bir ağ ortamında çalışır. Çoğu zaman aşağıdaki sorunlar görülür:
- Minimal container image’lar (
alpine
,distroless
gibi) sistem cache mekanizmalarını içermez. glibc
yerinemusl
kullanılan sistemlerdenscd
gibi DNS cache daemon’ları çalışmaz.- Uygulama dilleri (örn: Go) kendi DNS resolver’ını kullanır ve işletim sistemine bağımlı değildir.
- Kubernetes ortamında CoreDNS sorgularını hızla peş peşe alan binlerce pod nedeniyle throttling yaşanabilir.
Görünmeyen Krizin Belirtileri
Bu tip problemler, klasik monitoring sistemleriyle tespit edilmez. Aşağıdaki belirtiler gözlemlenebilir:
Belirti | Açıklama |
---|---|
Yavaşlayan response time | Özellikle dış servislere yapılan çağrılarda |
Aralıklı timeout hataları | Bazı servislerde zaman zaman yanıt alınamaması |
Connection refused | DNS çözümleme başarısızsa IP bile bulunamaz |
“Pod sağlıklı” ama çalışmıyor | Health check’ler IP’yi çözdüğü sürece pod “healthy” gözükür |
Servis discovery hataları | SRV veya A kayıtlarının çözülememesi |
Nasıl Önlenebilir?
1. Lokal DNS Cache Daemon Kullanın
Container içinde DNS cache proxy kurarak çözümlemeleri lokal olarak saklayabilirsiniz:
apt install dnsmasq
/etc/resolv.conf
dosyasını şu şekilde yapılandırın:
nameserver 127.0.0.1
2. CoreDNS Cache Plugin (Kubernetes)
Kubernetes cluster’ınızda CoreDNS
konfigürasyonuna şu bloğu ekleyin:
cache 30
Bu sayede aynı DNS sorgusu için 30 saniyelik bir cache süresi uygulanır.
3. Uygulama DNS Ayarlarını Gözden Geçirin
Özellikle Go, Java, Python gibi dillerde DNS cache süreleri ayarlanabilir:
Java:
security.provider.1=sun.security.provider.Sun
networkaddress.cache.ttl=60
Go:
Go’nun net.Resolver
çözümleyicisi genellikle işletim sistemi çözümleyicisini atlayabilir.
Python:
requests
veya aiohttp
gibi kütüphaneler DNS çözümlemeyi her istek için yeniden yapabilir.
4. Observability Araçları ile DNS Trafiğini İzleyin
tcpdump
ile DNS sorgularını gözlemleyiniztcpdump -i eth0 port 53
strace
ile DNS çağrılarını yakalayınız:strace -e trace=network ./app
dig
,drill
,resolvectl
ile manuel çözümleme süresi ölçümleri yapın.
5. Harici DNS’e Bağımlılığı Azaltın
- Internal DNS sunucuları (örneğin
bind
,unbound
) kullanın. - Kubernetes servisleri için FQDN kullanmak yerine
ClusterIP
veya DNS SRV kayıtları tercih edin. - Eğer mümkünse servis discovery’i DNS dışı bir protokolle gerçekleştirin (örn: Consul, Istio, gRPC resolver).
DNS sorunları genellikle loglara yansımaz. Uygulama hatası gibi görünmez. Ancak sonuçları zincirleme şekilde tüm sistemde hissedilir. Özellikle cache yapılmayan DNS sorguları yüzünden:
- Ağ yükü artar
- DNS servisleri çökebilir
- Servis discovery başarısız olur
- Sistem kullanılamaz hale gelir
Bu yüzden DNS konfigürasyonu bir “altyapı detayı” değil, doğrudan uygulama güvenilirliği stratejisinin parçası olmalıdır.