İçeriğe Atla
Mustafa Erbay
Teknoloji · 12 dk okuma · görüntülenme Read in English
100%

Nginx'in Sinsi DNS Tuzağı: Docker Container'ına Ulaşamama

Kendi VPS'imde Nginx'in Docker container'larına ulaşamama sorununu nasıl çözdüm? `resolver` direktifi ve dinamik network çözümleme ihtiyacının derinlemesine…

Nginx logosu, DNS sunucusu simgesi ve Docker konteynerleri arasındaki karmaşık bağlantıyı gösteren bir görsel.

Geçtiğimiz perşembe sabahıydı, hesapciyiz.com’un API endpoint’lerinden biri aniden 502 Bad Gateway hatası döndürmeye başladı. İlk aklıma gelen, elbette backend uygulamasının çöktüğü veya bir database bağlantı sorunu olduğuydu. Ancak docker logs komutunu çalıştırdığımda, ilgili container’ın sağlıklı bir şekilde çalıştığını gördüm.

Nginx error loglarına baktığımda ise durum daha da ilginçleşiyordu: [error] 31#31: *12345 host not found in upstream "my-api-service". Bazen de upstream prematurely closed connection gibi mesajlar görüyordum. Kendi VPS’imde 13’ten fazla Docker container yönettiğim için, bu tür network ve erişim sorunları ara sıra baş gösterse de, bu seferki biraz daha sinsiydi. Çünkü servis aslında hayattaydı.

Semptomlar ve İlk Gözlemler: Neler Yanlış Gidiyordu?

hesapciyiz.com’un API’si neden 502 veriyordu? Öncelikle belirtiler şunlardı:

  • 502 Bad Gateway: Kullanıcılar API’ye istek attığında bu hatayı alıyordu.
  • Nginx Hata Logları: host not found in upstream "my-api-service" veya could not resolve host gibi DNS çözümleme hataları görüyordum. Bazen de upstream prematurely closed connection ile karşılaşıyordum ki bu daha genel bir hata gibi görünse de, kökeninde aynı DNS sorunu yatabiliyordu.
  • Kesintili Çalışma: En sinir bozucu yanı, sorunun sürekli olmamasıydı. Bazen düzeliyor, sonra tekrar başlıyordu. Bu da sorunun cache, TTL veya dinamik IP atamalarıyla ilgili olabileceğine işaret ediyordu.
  • Container Sağlıklıydı: docker ps komutuyla kontrol ettiğimde, API container’ı Up durumdaydı ve docker logs herhangi bir uygulama hatası göstermiyordu.
  • Host’tan Doğrudan Erişim: Sunucuya SSH ile bağlanıp curl http://my-api-service:8000/health (container içindeki portu ve adını kullanarak) denediğimde, API’nin başarıyla yanıt verdiğini gördüm. Bu, uygulamanın gerçekten çalıştığını ve Docker network’ü içinde erişilebilir olduğunu doğruluyordu.

İlk aklıma gelen, Docker network’ünde bir sorun olup olmadığıydı. docker network inspect bridge yaparak network ayarlarını kontrol ettim, ama her şey olağan görünüyordu. Hatta container’ın IP adresini alıp doğrudan Nginx config’inde denedim, o şekilde çalıştı. Ama Docker container’ları dinamik IP aldığı için bu kalıcı bir çözüm değildi. Nginx’in host adına göre çözümleme yapması gerekiyordu.

Host’tan ping atabiliyordum, container içinden dışarı çıkabiliyordum, ama Nginx… Nginx sanki başka bir dünyadaydı ve my-api-service adını bir türlü bulamıyordu. Bu durum bana, Nginx’in DNS çözümlemesini nasıl yaptığını ve Docker’ın dinamik network yapısıyla bunun nasıl çakıştığını tekrar hatırlattı.

Docker Network’ünün Dinamik Yapısı ve Nginx’in Statik DNS Anlayışı

Bu sorunun temelinde Nginx’in DNS çözümleme alışkanlıkları ile Docker’ın dinamik network yönetimi arasındaki bir uyumsuzluk yatıyor. Öncelikle Docker tarafına bakalım:

Docker, container’ları kendi oluşturduğu bridge network’lerine bağlar. Bu network’ler içinde, her container’a dinamik olarak bir IP adresi atanır. Ayrıca, Docker’ın gömülü bir DNS sunucusu (genellikle 127.0.0.11 adresinde çalışır) sayesinde, aynı network’teki container’lar birbirlerine servis isimleriyle (my-api-service gibi) erişebilirler. Bu, developer’lar için hayatı kolaylaştıran harika bir özellik. Ancak, bir container yeniden başlatıldığında, aynı servisin farklı bir IP adresi alması oldukça olasıdır.

Peki, Nginx bu dinamizmle nasıl başa çıkıyor? İşte sorun burada başlıyor. Varsayılan olarak, Nginx bir proxy_pass direktifinde veya bir upstream bloğunda bir hostname gördüğünde, bu hostname’i yalnızca Nginx başlatıldığında veya yapılandırma dosyası (nginx.conf) yeniden yüklendiğinde (reload edildiğinde) DNS sunucusuna sorar ve elde ettiği IP adresini önbelleğe alır.

Benim VPS’imde 13’ten fazla container çalıştığı için, bu dinamizm ve restart’lar oldukça sık yaşanıyor. Bir container’ın memory limit’ini aşıp OOM-killed olması (geçen ay sleep 360 yazıp OOM-killed olduğum gibi), yeni bir deployment yapılması veya basit bir sistem güncellemesi bile container’ların yeniden başlamasına neden olabiliyor. Her yeniden başlatma, potansiyel bir IP değişikliği ve dolayısıyla Nginx için bir sorun demek.

Bu durum, sanki Nginx, kapısına gelen postacının adresini bir kere sormuş, sonra o postacının taşındığını hiç öğrenmeden eski adrese mektup göndermeye çalışıyor gibiydi. Benim hesapciyiz.com API’si de bu “taşınan postacı” durumuna düşmüştü ve Nginx onu bulamıyordu.

Kök Neden: Nginx ve DNS Çözümlemesinin Derinlikleri

Nginx’in bu “tek seferlik DNS çözümlemesi” davranışı, aslında yüksek performans için tasarlanmış bir optimizasyondur. Her istekte DNS sorgusu yapmak yerine, çözümlenmiş IP’yi önbelleğe alarak latency’yi azaltır. Ancak dinamik ortamlar, özellikle Docker veya Kubernetes gibi orkestrasyon araçları kullanıldığında, bu optimizasyon bir zafiyete dönüşebilir.

Bir upstream bloğu veya proxy_pass direktifi içinde bir hostname kullandığınızda, Nginx varsayılan olarak bu host adını başlatma sırasında sistemin /etc/resolv.conf dosyasında tanımlı DNS sunucuları üzerinden çözer. Eğer bu host adı /etc/hosts içinde tanımlı değilse veya DNS sunucusu tarafından bulunamazsa, Nginx başlangıçta hata verir veya hata loglarına düşer. Ama bizim durumumuzda, başlangıçta her şey yolunda gidiyordu; sorun sonradan, container IP’si değiştiğinde ortaya çıkıyordu.

Bu benim kendi VPS’imde islistesi.com’un backend servisi için de başıma gelmişti. Bir deployment sonrası container yeniden başladı, yeni IP aldı, Nginx eski IP’ye istek atıp durdu. Durumu fark ettiğimde, Nginx’i yeniden başlatmak geçici bir çözüm oluyordu ama bu sorunun kökünü çözmüyordu. Her deployment sonrası Nginx’i elle restart etmek sürdürülebilir bir operasyon değildi.

Sorunun özü, Nginx’in DNS önbelleğini yenileme mekanizmasının eksikliğinden kaynaklanıyordu. Statik IP’ler veya çok nadiren değişen hostnameler için bu davranış kabul edilebilirken, Docker gibi dinamik IP atayan ve servislerin sıkça yeniden başlatılabildiği ortamlarda, bu durum ciddi kesintilere yol açabiliyor.

Çözüme Doğru Adımlar: Nginx Resolver Direktifi

Bu tür dinamik DNS çözümleme sorunlarını aşmak için Nginx’in resolver direktifi devreye giriyor. resolver direktifi, Nginx’e belirli host adlarını çözümlemek için hangi DNS sunucusunu kullanması gerektiğini ve bu çözümlemelerin ne kadar süreyle önbellekte tutulacağını (TTL - Time To Live) belirtmenizi sağlar.

Bu direktif, Nginx’e “Ey Nginx, bu host adını her seferinde şuraya sor ve cevabı bu kadar süre hatırla” demenin bir yolu. Böylece Nginx, her isteği veya düzenli aralıklarla DNS sunucusuna sorarak en güncel IP adresini alabilir.

resolver direktifini kullanırken dikkat etmemiz gereken iki önemli nokta var:

  1. DNS Sunucusu Adresi: Hangi DNS sunucusunu kullanacağımızı belirtmeliyiz. Docker ortamında, container’lar arası isim çözümlemesi için genellikle Docker’ın kendi internal DNS sunucusu olan 127.0.0.11 adresini kullanırız. Bu, en hızlı ve güvenilir çözüm yoludur. Alternatif olarak, sunucunuzun /etc/resolv.conf dosyasında tanımlı DNS sunucularını (örneğin 8.8.8.8 veya 1.1.1.1) da kullanabilirsiniz, ancak bu durumda Docker’ın internal isim çözümlemesi yerine genel internet DNS’i üzerinden çözümleme yapılır.
  2. valid Parametresi: Bu parametre, Nginx’in DNS çözümlemesini ne kadar süreyle önbelleğe alacağını (TTL) belirler. Dinamik ortamlarda, IP adresleri sık değişebileceği için bu süreyi kısa tutmak önemlidir. Ben genelde valid=5s veya valid=10s gibi kısa süreler kullanırım. Bu, Nginx’in her 5 veya 10 saniyede bir DNS kaydını yenileyeceği anlamına gelir.

Bu iki parametreyi doğru ayarlayarak, Nginx’i Docker container’larının dinamik IP değişikliklerine karşı daha esnek ve dirençli hale getirebiliriz.

Nginx Yapılandırması: resolver Uygulaması

Şimdi gelelim Nginx yapılandırmasına. resolver direktifini genellikle http bloğu içine global olarak veya belirli server veya location blokları içine yerel olarak tanımlayabiliriz. Benim gibi tek bir VPS’te birden fazla servis barındırıyorsanız, genellikle http bloğu içinde global olarak tanımlamak daha pratiktir.

İşte hesapciyiz.com için uyguladığım Nginx yapılandırmasının basitleştirilmiş bir örneği:

http {
    # Docker'ın internal DNS sunucusu ve 5 saniye TTL
    resolver 127.0.0.11 valid=5s; 

    # Alternatif olarak host'un DNS'i: 
    # resolver 8.8.8.8 8.8.4.4 valid=30s; # Google DNS

    server {
        listen 80;
        server_name hesapciyiz.com www.hesapciyiz.com;

        location /api/ {
            # Dinamik çözümleme için hostname'i bir değişkene atamak şart.
            # Nginx, proxy_pass içinde bir değişken gördüğünde, resolver'ı kullanır.
            set $upstream_docker_service "my-api-service:8000"; 

            proxy_pass http://$upstream_docker_service;
            
            # Standart proxy_set_header direktifleri
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            
            # Bağlantı ve okuma zaman aşımı ayarları
            proxy_connect_timeout 5s;
            proxy_send_timeout 5s;
            proxy_read_timeout 15s;
        }

        # Diğer location blokları (örneğin statik dosyalar veya diğer uygulamalar)
        # ...
    }
}

Bu yapılandırmada birkaç kritik nokta var:

  1. resolver 127.0.0.11 valid=5s;: Bu direktif, Nginx’e Docker network’ü içindeki servis isimlerini çözümlemek için Docker’ın kendi DNS sunucusunu kullanmasını ve bu çözümlemeyi yalnızca 5 saniye boyunca önbellekte tutmasını söylüyor. Bu, my-api-service container’ı yeniden başlatılıp IP’si değişse bile, Nginx’in en geç 5 saniye içinde yeni IP adresini öğrenmesini sağlar.
  2. set $upstream_docker_service "my-api-service:8000";: Bu adım çok önemli. Nginx, proxy_pass direktifi içinde doğrudan bir hostname (proxy_pass http://my-api-service:8000;) görürse, bu hostname’i başlangıçta bir kez çözer. Ancak, bir değişken ($upstream_docker_service) kullanıldığında, Nginx bu değişkenin değerini her istekte veya düzenli aralıklarla dinamik olarak çözer. Bu, resolver direktifinin aktifleşmesini sağlar. Bu, Nginx’in tasarımsal bir “quirk”üdür ve dinamik DNS çözümlemesi için bilinmesi gereken bir detaydır.
  3. proxy_connect_timeout, proxy_send_timeout, proxy_read_timeout: Bu zaman aşımı ayarlarını da eklemek, özellikle Docker container’ları yeni başlatıldığında veya yoğun yük altında olduğunda bağlantı sorunlarını azaltmaya yardımcı olabilir. İlk bağlantı bazen yavaş kalabiliyor, o yüzden bu timeout’ları biraz yüksek tutmak iyi oluyor. Benim VPS’te kaynak yoğunluğundan (CPU, RAM) dolayı container’lar bazen yavaş yanıt verebiliyor, bu yüzden bu timeout’lar kurtarıcı olabiliyor.

Bu değişiklikleri uyguladıktan sonra, Nginx’i yeniden yüklemek veya başlatmak gerekiyor.

systemd ve Nginx Restart Stratejileri

Nginx yapılandırma dosyasında bir değişiklik yaptığınızda, bu değişikliklerin aktif olması için Nginx servisini yeniden yüklemeniz veya yeniden başlatmanız gerekir. Burada iki temel komut devreye girer:

  • sudo systemctl reload nginx: Bu komut, Nginx’i sıfır downtime ile yeniden yapılandırır. Mevcut worker süreçleri yeni yapılandırmayı yüklerken, eski worker’lar hala aktif bağlantıları işleyebilir. Yeni bağlantılar yeni worker’lara yönlendirilir. Çoğu durumda, özellikle küçük yapılandırma değişiklikleri için bu tercih edilir. Ancak, resolver direktifi gibi DNS önbelleklemesini etkileyen değişikliklerde, bazen eski worker’ların DNS önbelleği hemen temizlenmeyebilir.
  • **`
Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Haftalık özet — AI değil, bizzat ben seçiyorum

Haftada bir mail: o haftanın en önemli yazısı, perde arkası notları, ve "bu hafta gerçekten kullandığım araç" bölümü. Az gürültü, çok sinyal.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar