Nginx ile Yük Dengeleme Sorunlarını Giderme

Prodüksiyonda bir Nginx yük dengeleyici çalıştırıyorsunuz ve işler tam da en kötü zamanda ters gitmeye başlıyor. Bazı istekler timeout alıyor, bazı backend sunucular yanıt vermiyor, ya da tüm trafik tek bir sunucuya yığılıyor. Bu yazıda gerçek dünya senaryoları üzerinden Nginx yük dengeleme sorunlarını nasıl tespit edip çözeceğinizi adım adım ele alacağız.

Temel Kavramlar ve Yaygın Sorun Tipleri

Nginx yük dengelemesinde karşılaşılan sorunları genel olarak birkaç kategoriye ayırabiliriz:

  • Backend erişilebilirlik sorunları: Upstream sunucular yanıt vermiyor veya zaman zaman düşüyor
  • Dağıtım dengesizliği: Trafik tek bir sunucuya yığılıyor
  • Session tutarlılığı sorunları: Kullanıcılar her istekte farklı sunucuya yönlendiriliyor
  • Timeout ve bağlantı sorunları: İstekler yarıda kesiliyor
  • Health check başarısızlıkları: Sağlıklı sunucular hatalı şekilde devre dışı bırakılıyor

Sorunları gidermeden önce mevcut yapılandırmanızı iyi anlamanız gerekiyor. Başlangıç noktamız her zaman log analizi olacak.

Log Yapılandırmasını Doğru Kurmak

Varsayılan Nginx log formatı yük dengeleme sorunlarını debug etmek için yetersiz kalır. Upstream bilgilerini de loga ekleyen özel bir format tanımlamanız gerekiyor.

# /etc/nginx/nginx.conf içine ekleyin
http {
    log_format upstream_debug '$remote_addr - $remote_user [$time_local] '
                              '"$request" $status $body_bytes_sent '
                              '"$http_referer" "$http_user_agent" '
                              'upstream: $upstream_addr '
                              'upstream_status: $upstream_status '
                              'upstream_response_time: $upstream_response_time '
                              'request_time: $request_time '
                              'upstream_connect_time: $upstream_connect_time '
                              'upstream_header_time: $upstream_header_time';

    access_log /var/log/nginx/access.log upstream_debug;
}

Bu log formatı sayesinde her isteğin hangi backend sunucuya gittiğini, ne kadar sürede yanıt aldığınızı ve bağlantı sürelerini görebilirsiniz. Nginx’i yeniden yükleyin:

nginx -t && systemctl reload nginx

Şimdi logları gerçek zamanlı takip etmeye başlayabilirsiniz:

tail -f /var/log/nginx/access.log | grep -E "upstream_status: [^2]"

Bu komut size 2xx dışındaki yanıtları gösteren satırları filtreler. Hatalı upstream yanıtlarını anında görmeye başlarsınız.

Upstream Dağıtım Analizini Gerçekleştirmek

İlk şüphelendiğiniz durum genellikle trafiğin eşit dağılmadığıdır. Bunu doğrulamak için log analizi yapın:

# Son 1000 istek için upstream dağılımını göster
tail -1000 /var/log/nginx/access.log | 
grep -oP 'upstream: K[0-9.:]+' | 
sort | uniq -c | sort -rn

Çıktı şöyle bir şey olacak:

    847 192.168.1.101:8080
    143 192.168.1.102:8080
     10 192.168.1.103:8080

Bu çıktı açıkça bir sorun olduğunu gösteriyor. Üç sunucu olmasına rağmen trafik büyük ölçüde ilk sunucuya gidiyor. Bu durumun birkaç olası nedeni var.

İlk neden: Upstream yapılandırmanızdaki weight değerleri yanlış ayarlanmış olabilir:

cat /etc/nginx/conf.d/upstream.conf

Şunun gibi bir şey görüyorsanız sorun burada:

upstream backend {
    server 192.168.1.101:8080 weight=10;
    server 192.168.1.102:8080 weight=1;
    server 192.168.1.103:8080 weight=1;
}

İkinci neden: least_conn veya ip_hash direktiflerinin yanlış kullanımı. ip_hash kullanıyorsanız ve kullanıcılarınız belirli IP aralıklarından geliyorsa, trafik doğal olarak belirli sunuculara yığılır.

Senaryo 1: Backend Sunucu Düşüyor ve Yavaş Recovery

Prodüksiyonda en sık karşılaşılan senaryo şu: Bir backend sunucu zaman zaman düşüyor ama Nginx bunu fark etmeden oraya istek göndermeye devam ediyor. Bu durumda kullanıcılar 502 hatası alıyor.

Önce mevcut upstream sağlık durumunu kontrol edin:

# Nginx Plus kullanıyorsanız
curl http://localhost/nginx_status

# Açık kaynak Nginx için stub_status
curl http://localhost/nginx_status

Stub status aktif değilse önce etkinleştirin:

# /etc/nginx/conf.d/status.conf
server {
    listen 127.0.0.1:8090;
    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
}

Şimdi upstream yapılandırmanıza passive health check parametreleri ekleyin:

upstream backend {
    least_conn;
    
    server 192.168.1.101:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.102:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.103:8080 max_fails=3 fail_timeout=30s;
    
    keepalive 32;
}

Bu yapılandırmayla bir sunucu 30 saniye içinde 3 kez başarısız olursa, Nginx onu 30 saniyeliğine devre dışı bırakır. keepalive 32 ise connection pooling yaparak performansı artırır.

Backend sunucularınızın gerçekten erişilebilir olup olmadığını elle test edin:

# Her backend'e direkt bağlantı testi
for server in 192.168.1.101 192.168.1.102 192.168.1.103; do
    echo -n "$server: "
    curl -s -o /dev/null -w "%{http_code} - %{time_total}sn" 
         --connect-timeout 5 
         http://$server:8080/health
done

Senaryo 2: Timeout Sorunlarını Derinlemesine İncelemek

Kullanıcılar “sayfa yüklenmiyor” şikayeti yapıyor ama sunucular ayakta. Bu klasik bir timeout problemidir. Nginx’in timeout değerleri yanlış yapılandırılmış olabilir.

Önce logdan timeout olan istekleri çıkarın:

# request_time değeri 30 saniyeyi aşan istekler
awk '/upstream_response_time/ {
    match($0, /upstream_response_time: ([0-9.]+)/, arr)
    if (arr[1]+0 > 30) print $0
}' /var/log/nginx/access.log | tail -50

Sonra proxy timeout değerlerini gözden geçirin:

# /etc/nginx/conf.d/proxy.conf
server {
    listen 80;
    server_name example.com;
    
    location / {
        proxy_pass http://backend;
        
        # Bağlantı kurma timeout
        proxy_connect_timeout 10s;
        
        # Backend'den veri okuma timeout
        proxy_read_timeout 60s;
        
        # Backend'e veri gönderme timeout
        proxy_send_timeout 60s;
        
        # Upstream'in yanıt başlığını göndermesi için timeout
        proxy_next_upstream_timeout 30s;
        
        # Sorunlu upstream'de kaç kez retry yapılacak
        proxy_next_upstream_tries 3;
        
        # Hangi durumlarda sonraki upstream'e geçilsin
        proxy_next_upstream error timeout http_500 http_502 http_503;
    }
}

proxy_next_upstream direktifi kritik öneme sahip. Bu direktif sayesinde bir backend hata verdiğinde Nginx otomatik olarak sıradaki sunucuyu dener. Ancak dikkatli olun: POST isteklerinde bu davranış veri tutarlılığı sorunlarına yol açabilir.

Senaryo 3: Session Yapışkanlığı (Sticky Session) Sorunları

Kullanıcılar login olduktan sonra defalarca oturum açmak zorunda kalıyorsa bu büyük olasılıkla session sticky sorunundan kaynaklanıyor. Session verileri bir sunucuda tutuluyorsa kullanıcının her seferinde o sunucuya yönlendirilmesi gerekiyor.

Açık kaynak Nginx’te ip_hash kullanabilirsiniz:

upstream backend {
    ip_hash;
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
    server 192.168.1.103:8080;
}

Ama ip_hash ile ilgili kritik bir sorun var: Kullanıcı bir proxy veya NAT arkasındaysa, tüm kullanıcılar aynı IP adresini paylaşır ve hepsi aynı backend’e yönlendirilir. Daha iyi bir yaklaşım cookie tabanlı yönlendirme yapmaktır.

Nginx Plus olmadan bunu ngx_http_upstream_hash_module ile yapabilirsiniz:

upstream backend {
    hash $cookie_SESSIONID consistent;
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
    server 192.168.1.103:8080;
}

consistent parametresi, bir sunucu devre dışı kaldığında sadece o sunucuya giden trafiği diğerlerine dağıtır. Tüm hash tablosunu yeniden hesaplamaz, bu da mevcut oturumların gereksiz yere kırılmasını önler.

Session sorununu doğrulamak için şu testi yapın:

# Aynı session cookie ile arka arkaya istek gönderin
COOKIE="SESSIONID=test123abc"
for i in {1..10}; do
    echo -n "İstek $i -> "
    curl -s -b "$COOKIE" -D - http://example.com/ | 
         grep -E "X-Served-By|Server|upstream"
done

Eğer her seferinde farklı sunucu görüyorsanız sticky session çalışmıyor demektir.

Log Analizi ile Performans Darboğazlarını Bulmak

Sadece hataları değil, yavaş yanıtları da takip etmek önemlidir. Şu script ile upstream yanıt sürelerinin dağılımını analiz edebilirsiniz:

#!/bin/bash
# upstream_perf_analysis.sh
# Kullanım: bash upstream_perf_analysis.sh /var/log/nginx/access.log

LOG_FILE="${1:-/var/log/nginx/access.log}"

echo "=== Upstream Yanıt Süresi Analizi ==="
echo ""

echo "Sunucu bazlı ortalama yanıt süreleri:"
grep -oP 'upstream: KS+ .* upstream_response_time: K[d.]+' 
    "$LOG_FILE" 2>/dev/null || 

awk '{
    match($0, /upstream: ([0-9.:]+)/, addr)
    match($0, /upstream_response_time: ([0-9.]+)/, time)
    if (addr[1] != "" && time[1] != "") {
        count[addr[1]]++
        total[addr[1]] += time[1]
        if (time[1] > max[addr[1]]) max[addr[1]] = time[1]
    }
} END {
    for (server in count) {
        printf "%-25s Ort: %.3fs  Max: %.3fs  İstek: %dn",
               server, total[server]/count[server], max[server], count[server]
    }
}' "$LOG_FILE" | sort

Bu script size hangi sunucunun ne kadar yanıt verdiğini net olarak gösterir. Bir sunucunun ortalaması diğerlerinden belirgin şekilde yüksekse, o sunucuda kaynak sorunu (CPU, RAM, disk I/O) olabilir.

Upstream Bağlantı Havuzu Sorunları

Çok sayıda kısa süreli istek alan sistemlerde TIME_WAIT soketleri ciddi bir sorun oluşturabilir. Mevcut durumu kontrol edin:

# TIME_WAIT soket sayısını kontrol et
ss -s | grep -i time_wait

# Hangi bağlantılar TIME_WAIT durumunda
ss -tan state time-wait | grep :8080 | wc -l

# Upstream sunucu başına bağlantı durumları
ss -tan | grep -E "192.168.1.(101|102|103):8080" | 
awk '{print $1}' | sort | uniq -c | sort -rn

Çok fazla TIME_WAIT varsa, keepalive bağlantılarını aktif etmek bu sorunu büyük ölçüde çözer. Nginx’te upstream keepalive şöyle yapılandırılır:

upstream backend {
    least_conn;
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
    server 192.168.1.103:8080;
    
    # Keepalive connection pool boyutu
    keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        
        # Keepalive için Connection header'ı temizle
        proxy_set_header Connection "";
        
        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_http_version 1.1 ve proxy_set_header Connection "" direktifleri keepalive’ın düzgün çalışması için zorunludur. HTTP/1.0 keepalive desteklemez.

Gerçek Zamanlı Monitoring ile Sorunları Anlık Yakalamak

Sorun oluştuğu anda müdahale edebilmek için canlı monitoring önemli. Şu bash one-liner’lar günlük işinize yarayacak:

# Her 2 saniyede upstream hata oranını göster
watch -n 2 'tail -1000 /var/log/nginx/access.log | 
awk '"'"'{
    match($0, /upstream: ([0-9.:]+)/, addr)
    match($0, /upstream_status: ([0-9]+)/, status)
    if (addr[1] != "") {
        total[addr[1]]++
        if (status[1]+0 >= 500) errors[addr[1]]++
    }
} END {
    printf "%-25s %-10s %-10s %sn", "Sunucu", "Toplam", "Hata", "Hata%"
    for (s in total) {
        pct = (errors[s]/total[s])*100
        printf "%-25s %-10d %-10d %.1f%%n", s, total[s], errors[s], pct
    }
}'"'"

# 502 hatalarını gerçek zamanlı say
tail -f /var/log/nginx/access.log | awk '
/upstream_status: 502/ {
    count++
    print strftime("%H:%M:%S"), "502 Hatası - Toplam:", count, $0
    fflush()
}'

Yapılandırma Değişikliklerini Güvenli Şekilde Test Etmek

Prodüksiyona yeni upstream yapılandırması uygulmadan önce mutlaka test edin:

# Syntax kontrolü
nginx -t

# Mevcut yapılandırmayı dump et
nginx -T | grep -A 20 "upstream backend"

# Graceful reload (mevcut bağlantıları kesmeden)
nginx -s reload

# Reload sonrası log'da hata var mı kontrol et
journalctl -u nginx --since "1 minute ago" | grep -i error

# Yük dengelemenin çalıştığını doğrula
for i in {1..20}; do
    curl -s -o /dev/null -H "X-Test: debug" http://localhost/ &
done
wait
sleep 1
tail -25 /var/log/nginx/access.log | grep -oP 'upstream: KS+' | sort | uniq -c

Upstream Sunucuları Geçici Olarak Devre Dışı Bırakmak

Bakım yapmanız gereken bir backend sunucusu varsa, onu trafikten çıkarmak için bir kaç yol var. Nginx açık kaynak versiyonunda down direktifini kullanabilirsiniz:

# upstream.conf dosyasını düzenle
upstream backend {
    least_conn;
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
    server 192.168.1.103:8080 down;  # Bu sunucu devre dışı
}

Sonra graceful reload yapın:

nginx -t && nginx -s reload

Bakım bitince down direktifini kaldırın ve yeniden reload edin. Bu yöntemin dezavantajı, dosya düzenlemesi gerektirmesidir. Daha dinamik bir çözüm için upstream_conf modülü veya Nginx Plus API kullanılabilir.

Eğer acil bir durum varsa ve sunucuyu anında devre dışı bırakmak istiyorsanız, iptables ile bağlantıları kesebilirsiniz:

# Nginx'den o sunucuya gelen bağlantıları reddet (Nginx sunucusunda çalıştırın)
iptables -I OUTPUT -d 192.168.1.103 -p tcp --dport 8080 -j REJECT

# Geri almak için
iptables -D OUTPUT -d 192.168.1.103 -p tcp --dport 8080 -j REJECT

Bu yöntem Nginx’in o sunucuyu başarısız olarak işaretlemesine neden olur ve max_fails ile fail_timeout parametrelerinize göre o sunucuya istek göndermeyi bırakır.

Sık Yapılan Yapılandırma Hatalarını Kontrol Listesi

Yük dengeleme sorunlarını giderirken şu noktaları sistematik olarak kontrol edin:

  • resolver direktifi eksik: DNS tabanlı upstream kullanıyorsanız resolver direktifi tanımlanmamışsa isimler çözümlenemez
  • proxy_pass sonunda slash farkı: proxy_pass http://backend ile proxy_pass http://backend/ farklı davranır
  • Worker sayısı ve worker_connections uyumsuzluğu: worker_processes * worker_connections değeri maksimum eşzamanlı bağlantı sayınızı belirler
  • Buffer boyutları: Çok büyük yanıtlar için proxy_buffer_size ve proxy_buffers değerleri yetersiz olabilir
  • Upstream sunucu portları: Backend uygulamanız farklı bir portta çalışıyor olabilir
# Tüm upstream bağlantılarını ve portları kontrol et
ss -tlnp | grep -E "8080|8081|8082|3000"

# Nginx worker başına açık bağlantı sayısını kontrol et
for pid in $(pgrep -f "nginx: worker"); do
    echo -n "Worker $pid: "
    ls /proc/$pid/fd | wc -l
done

Sonuç

Nginx yük dengeleme sorunları çoğunlukla üç ana kaynaktan gelir: yetersiz log yapılandırması, yanlış timeout değerleri ve backend sağlık kontrollerinin yokluğu. Bu yazıda ele aldığımız yaklaşımları özetlemek gerekirse:

İlk adım her zaman detaylı log formatı kurmak olmalı. Upstream adresi, yanıt süresi ve bağlantı sürelerini loglamadan sorunun kaynağını bulmak çok zorlaşır. İkinci adım sistematik analiz: Hangi sunucuya ne kadar trafik gidiyor, hata oranları neler, yanıt süreleri ne durumda.

max_fails, fail_timeout ve proxy_next_upstream gibi direktifleri doğru yapılandırmak, backend sorunlarının kullanıcıya yansımasını minimize eder. Keepalive bağlantıları ise hem performansı artırır hem de TIME_WAIT sorunlarını azaltır.

En önemlisi, bu sorunları prodüksiyonda yaşamadan önce yük testleri ve staging ortamında simüle etmek, hazırlıklı olmanızı sağlar. Her değişikliği nginx -t ile test edin, nginx -s reload ile graceful şekilde uygulayın ve reload sonrası logları mutlaka gözlemleyin.

Bir yanıt yazın

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