Nginx ile Çoklu Backend Yönetimi: Upstream Yapılandırması
Birden fazla backend sunucusu yönetmek, modern web mimarilerinin kaçınılmaz bir parçası haline geldi. Tek bir sunucuya tüm trafiği yüklemek yerine, iş yükünü dağıtmak hem performansı artırıyor hem de yüksek erişilebilirlik sağlıyor. Nginx’in upstream modülü, bu işi son derece zarif bir şekilde çözüyor. Basit bir load balancer olarak başlayan yapılandırmalar, zamanla aktif sağlık kontrolleri, ağırlıklı dağıtım ve özel yük dengeleme algoritmalarına dönüşebiliyor. Bu yazıda, gerçek dünya senaryoları üzerinden Nginx upstream yapılandırmasının tüm detaylarını ele alacağız.
Upstream Nedir ve Neden Önemlidir?
Nginx’te upstream bloğu, bir veya daha fazla backend sunucusunu mantıksal bir grup altında tanımlamanı sağlar. Reverse proxy olarak yapılandırdığında Nginx, gelen istekleri bu grup içindeki sunuculara dağıtır. Bu yaklaşımın sağladığı avantajlar oldukça somuttur.
- Yüksek Erişilebilirlik: Bir backend düşerse, Nginx trafiği otomatik olarak diğer sunuculara yönlendirir.
- Yatay Ölçeklendirme: Yeni bir sunucu eklemek için sadece upstream bloğunu güncellemen yeterlidir.
- Şeffaf Geçişler: Bakım sırasında bir sunucuyu devre dışı bırakabilirsin, kullanıcılar fark etmez.
- Merkezi Yönetim: Tüm backend mantığı tek bir Nginx yapılandırmasında toplanır.
Tipik bir kullanım senaryosu şöyle görünür: Üç adet Node.js API sunucun var, hepsi 3000 portunda çalışıyor. İstemciler doğrudan bu sunuculara değil, 80/443 portunda dinleyen Nginx’e bağlanıyor. Nginx aldığı her isteği upstream grubundaki sunuculardan birine iletti.
Temel Upstream Yapılandırması
En basit haliyle bir upstream bloğu şöyle tanımlanır:
# /etc/nginx/nginx.conf veya /etc/nginx/conf.d/upstream.conf
upstream api_backends {
server 192.168.1.10:3000;
server 192.168.1.11:3000;
server 192.168.1.12:3000;
}
server {
listen 80;
server_name api.sirketim.com;
location /api/ {
proxy_pass http://api_backends;
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;
}
}
Bu yapılandırmayla Nginx, gelen istekleri üç sunucu arasında round-robin algoritmasıyla dağıtır. Yani sırayla 1, 2, 3, 1, 2, 3 şeklinde devam eder. Hiçbir ek parametre girmeden bu basit dağıtım aktif hale gelir.
Yapılandırmayı test etmek ve yeniden yüklemek için:
# Yapılandırma sözdizimini kontrol et
nginx -t
# Nginx'i sıfırsız yeniden yükle (graceful reload)
systemctl reload nginx
# Veya doğrudan sinyal gönder
nginx -s reload
Yük Dengeleme Algoritmaları
Nginx, farklı senaryolara uygun birkaç yük dengeleme yöntemi sunar.
Round Robin (Varsayılan)
Hiçbir direktif belirtmezsen round-robin devreye girer. Her sunucu sırayla birer istek alır. Sunucular benzer kapasitedeyse ve istekler homojen bir yapıdaysa idealdir.
Weighted Round Robin
Sunucuların kapasiteleri farklıysa ağırlık (weight) parametresi işe yarar:
upstream api_backends {
server 192.168.1.10:3000 weight=3;
server 192.168.1.11:3000 weight=2;
server 192.168.1.12:3000 weight=1;
}
Bu yapılandırmada, her 6 istekten 3’ü ilk sunucuya, 2’si ikinciye, 1’i üçüncüye gider. Güçlü sunucuya daha fazla yük vermek istediğinde bu parametre hayat kurtarır.
IP Hash
Aynı istemcinin her seferinde aynı backend’e gitmesini istiyorsan, session tabanlı uygulamalarda sıkça karşılaşılan bir ihtiyaç bu:
upstream app_backends {
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
ip_hash direktifi, istemcinin IP adresini hash’leyerek her seferinde aynı sunucuya yönlendirir. Session bilgisini merkezi bir Redis’e taşımamışsan bu yöntem işe yarar. Ancak bir sunucu çökerse, o sunucuya atanmış kullanıcıların session’ları kaybolur, bunu aklında tut.
Least Connections
Aktif bağlantısı en az olan sunucuya istek gönderir. İsteklerin işlenme süresi değişken olduğunda, uzun süren işlemlerin bir sunucuyu tıkamaması için idealdir:
upstream api_backends {
least_conn;
server 192.168.1.10:3000;
server 192.168.1.11:3000;
server 192.168.1.12:3000;
}
Sunucu Parametreleri: Detaylı Yapılandırma
Her server direktifine birden fazla parametre ekleyebilirsin. Bu parametreler, backend davranışını ince ayar yapmanı sağlar.
upstream robust_backends {
server 192.168.1.10:3000 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.11:3000 weight=2 max_fails=3 fail_timeout=30s;
server 192.168.1.12:3000 backup;
server 192.168.1.13:3000 down;
}
Parametrelerin anlamları:
- weight=N: Sunucuya verilecek ağırlık, varsayılan 1’dir.
- max_fails=N: Sunucunun devre dışı sayılmadan önce kaç kez başarısız olabileceği, varsayılan 1’dir.
- fail_timeout=Ns: Başarısız denemeler arasındaki süre ve sunucunun ne kadar süre devre dışı kalacağı.
- backup: Diğer tüm sunucular çöktüğünde devreye giren yedek sunucu.
- down: Sunucuyu kalıcı olarak devre dışı bırakır, yapılandırmadan silmeden geçici olarak durdurur.
Bağlantı Havuzu ve Keepalive
Her istek için yeni bir TCP bağlantısı açmak maliyetlidir. Nginx’in keepalive direktifiyle backend bağlantılarını yeniden kullanabilirsin:
upstream api_backends {
server 192.168.1.10:3000;
server 192.168.1.11:3000;
server 192.168.1.12:3000;
keepalive 32;
keepalive_requests 100;
keepalive_timeout 60s;
}
server {
listen 80;
server_name api.sirketim.com;
location /api/ {
proxy_pass http://api_backends;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
keepalive 32 ifadesi, her worker process için maksimum 32 adet idle bağlantı tutulacağını belirtir. proxy_http_version 1.1 ve proxy_set_header Connection "" direktifleri, HTTP/1.1 keepalive için zorunludur. Bu ikisini unutursan keepalive çalışmaz.
Gerçek Dünya Senaryosu: Mikroservis Mimarisi
Diyelim ki e-ticaret platformun var. Kullanıcı servisi, ürün servisi ve sipariş servisi ayrı ayrı çalışıyor. Her servisin kendi backend sunucuları mevcut:
# /etc/nginx/conf.d/ecommerce.conf
upstream user_service {
least_conn;
server 10.0.1.10:4001 weight=2 max_fails=3 fail_timeout=15s;
server 10.0.1.11:4001 weight=2 max_fails=3 fail_timeout=15s;
server 10.0.1.12:4001 backup;
keepalive 16;
}
upstream product_service {
server 10.0.2.10:4002 max_fails=2 fail_timeout=10s;
server 10.0.2.11:4002 max_fails=2 fail_timeout=10s;
server 10.0.2.12:4002 max_fails=2 fail_timeout=10s;
keepalive 24;
}
upstream order_service {
ip_hash;
server 10.0.3.10:4003 max_fails=2 fail_timeout=20s;
server 10.0.3.11:4003 max_fails=2 fail_timeout=20s;
keepalive 16;
}
server {
listen 443 ssl http2;
server_name api.eticaret.com;
ssl_certificate /etc/ssl/certs/eticaret.crt;
ssl_certificate_key /etc/ssl/private/eticaret.key;
# Kullanici servisi
location /api/v1/users/ {
proxy_pass http://user_service;
proxy_http_version 1.1;
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_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 15s;
}
# Urun servisi
location /api/v1/products/ {
proxy_pass http://product_service;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 3s;
proxy_read_timeout 15s;
proxy_cache product_cache;
proxy_cache_valid 200 5m;
proxy_cache_key "$scheme$request_method$host$request_uri";
}
# Siparis servisi
location /api/v1/orders/ {
proxy_pass http://order_service;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
}
Bu yapılandırmada dikkat edilecek noktalara bakalım. Ürün servisi için least_conn yerine round-robin tercih ettim çünkü ürün istekleri genellikle kısa sürer ve homojendir. Sipariş servisi için ip_hash kullandım çünkü sipariş akışı çok adımlı ve durum bilgisi tutması gerekiyor. Kullanıcı servisi için least_conn mantıklı çünkü login, profil güncelleme gibi işlemler değişken sürelerde tamamlanır.
Sağlık Kontrolleri ile Aktif İzleme
Nginx açık kaynak sürümünde pasif sağlık kontrolleri varsayılan olarak gelir. max_fails ve fail_timeout parametreleri bu kontrolü yönetir. Bir sunucu belirtilen sayıda başarısız olursa, fail_timeout süresi boyunca devre dışı kalır.
upstream monitored_backends {
server 192.168.1.10:3000 max_fails=3 fail_timeout=30s;
server 192.168.1.11:3000 max_fails=3 fail_timeout=30s;
server 192.168.1.12:3000 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
location /api/ {
proxy_pass http://monitored_backends;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
}
proxy_next_upstream direktifi, hangi hata durumlarında Nginx’in bir sonraki backend’i denemesi gerektiğini belirler. Bu parametreleri açıklayalım:
- error: Backend ile bağlantı hatası oluştuğunda.
- timeout: Bağlantı zaman aşımında.
- invalid_header: Geçersiz header döndüğünde.
- http_500, http_502, http_503, http_504: Belirtilen HTTP hata kodlarında.
- proxy_next_upstream_tries 3: Maksimum 3 farklı backend dene.
- proxy_next_upstream_timeout 10s: Toplam deneme süresi 10 saniyeyi geçmesin.
Upstream Durumunu İzleme
Nginx stub_status modülüyle temel metrikler alınabilir, ancak upstream seviyesinde detaylı izleme için farklı bir yaklaşım gerekir. İşte basit bir durum kontrol endpoint’i:
server {
listen 8080;
server_name localhost;
# Stub status - temel Nginx metrikleri
location /nginx_status {
stub_status on;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
# Upstream saglik kontrol endpoint'i
location /health {
access_log off;
return 200 "healthyn";
add_header Content-Type text/plain;
}
}
Backend’lerin sağlık durumunu harici araçlarla izlemek için şu bash scripti kullanılabilir:
#!/bin/bash
# /usr/local/bin/check_upstream_health.sh
BACKENDS=(
"192.168.1.10:3000"
"192.168.1.11:3000"
"192.168.1.12:3000"
)
HEALTH_ENDPOINT="/health"
TIMEOUT=5
ALERT_EMAIL="[email protected]"
for backend in "${BACKENDS[@]}"; do
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}"
--connect-timeout $TIMEOUT
"http://${backend}${HEALTH_ENDPOINT}")
if [ "$HTTP_CODE" != "200" ]; then
echo "KRITIK: Backend $backend sagliksiz! HTTP Code: $HTTP_CODE" |
mail -s "Upstream Alert: $backend" $ALERT_EMAIL
logger -t nginx-upstream "Backend $backend DOWN - HTTP: $HTTP_CODE"
else
logger -t nginx-upstream "Backend $backend OK - HTTP: $HTTP_CODE"
fi
done
Bu scripti crontab’a ekleyerek düzenli aralıklarla çalıştırabilirsin:
# Her 2 dakikada bir kontrol et
*/2 * * * * /usr/local/bin/check_upstream_health.sh
Dinamik Upstream Yapılandırması
Sunucu havuzuna sunucu ekleyip çıkarmak için tipik akış şöyle olur:
# Yeni sunucu ekle
# 1. Yapılandırma dosyasini duzenle
vim /etc/nginx/conf.d/upstream.conf
# 2. Sontaksi kontrol et
nginx -t
# 3. Graceful reload yap (aktif baglantilar kesilmez)
nginx -s reload
# Bir sunucuyu bakima al (down parametresiyle)
# upstream blogunu guncelle: server 192.168.1.10:3000 down;
nginx -s reload
# Bakim sonrasi geri al: server 192.168.1.10:3000;
nginx -s reload
# Nginx worker process sayisini kontrol et
ps aux | grep nginx
# Aktif baglanti sayisini gor
ss -tnp | grep nginx | wc -l
Graceful reload’un güzelliği şu: Nginx mevcut bağlantıları kesmez, sadece yeni yapılandırmayı yeni worker’lara yükler. Aktif istekler tamamlanana kadar eski worker’lar çalışmaya devam eder.
Upstream ile SSL Termination ve Backend Encryption
Genellikle SSL Nginx’te sonlanır ve backend’e HTTP üzerinden gider. Ama bazı durumlarda backend ile de şifreli iletişim gerekir:
upstream secure_backends {
server 192.168.1.10:443;
server 192.168.1.11:443;
}
server {
listen 443 ssl;
server_name api.sirketim.com;
ssl_certificate /etc/ssl/certs/sirketim.crt;
ssl_certificate_key /etc/ssl/private/sirketim.key;
location / {
proxy_pass https://secure_backends;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/ssl/certs/internal-ca.crt;
proxy_ssl_certificate /etc/ssl/certs/client.crt;
proxy_ssl_certificate_key /etc/ssl/private/client.key;
proxy_ssl_protocols TLSv1.2 TLSv1.3;
proxy_ssl_session_reuse on;
}
}
proxy_ssl_session_reuse on direktifi, backend ile SSL handshake’i tekrar kullanır. Bu, özellikle yüksek trafikli ortamlarda ciddi performans kazanımı sağlar.
Performans Tuning: Buffer ve Timeout Optimizasyonu
Upstream yapılandırmasında buffer ayarları sık gözardı edilir, oysa kritik öneme sahiptir:
upstream api_backends {
server 192.168.1.10:3000;
server 192.168.1.11:3000;
keepalive 64;
}
server {
listen 80;
location /api/ {
proxy_pass http://api_backends;
proxy_http_version 1.1;
proxy_set_header Connection "";
# Buffer ayarlari
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
# Timeout ayarlari
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# Header boyutu
proxy_max_temp_file_size 0;
large_client_header_buffers 4 32k;
}
}
Buffer parametrelerinin etkisi:
- proxy_buffer_size: Backend’den gelen ilk response chunk için kullanılır, genellikle header’ları içerir.
- proxy_buffers 8 32k: 8 adet 32KB buffer, toplam 256KB. Büyük response’lar için artırılabilir.
- proxy_busy_buffers_size: Aynı anda istemciye gönderim için kullanılabilecek maksimum buffer boyutu.
Eğer backend büyük JSON payload’ları döndürüyorsa, proxy_buffers değerini artırman gerekebilir. Tersine, küçük API response’ları için bu değerleri düşürerek bellek tasarrufu yapılabilir.
Hata Sayfaları ve Fallback Mekanizması
Tüm backend’ler çöktüğünde kullanıcıya anlamlı bir hata göstermek önemlidir:
upstream api_backends {
server 192.168.1.10:3000 max_fails=3 fail_timeout=30s;
server 192.168.1.11:3000 max_fails=3 fail_timeout=30s;
server 192.168.1.12:3000 backup;
}
server {
listen 80;
server_name api.sirketim.com;
# Ozel hata sayfasi dizini
root /var/www/error_pages;
location /api/ {
proxy_pass http://api_backends;
proxy_intercept_errors on;
error_page 502 503 504 /maintenance.html;
}
location = /maintenance.html {
internal;
add_header Content-Type text/html;
add_header Cache-Control "no-cache, no-store";
}
}
proxy_intercept_errors on direktifi, backend’den gelen hata kodlarını Nginx’in yakalamasını sağlar. Böylece kendi hata sayfalarını gösterebilirsin.
Log Formatı ile Upstream İzleme
Upstream seviyesinde detaylı loglama yapmak, sorun gidermeyi kolaylaştırır:
# /etc/nginx/nginx.conf
log_format upstream_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream_addr=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_response_time=$upstream_response_time '
'upstream_connect_time=$upstream_connect_time '
'request_time=$request_time';
server {
access_log /var/log/nginx/upstream_access.log upstream_log;
}
Bu log formatıyla hangi backend’in ne kadar sürede yanıt verdiğini, kaç kez retry yapıldığını ve hangi upstream IP’sine istek gittiğini görebilirsin. Log’ları analiz etmek için:
# En yavaş yanit veren upstream'leri bul
awk '{print $14, $12}' /var/log/nginx/upstream_access.log |
sort -k1 -nr | head -20
# 502 hatasi veren upstream'leri listele
grep 'upstream_status=502' /var/log/nginx/upstream_access.log |
awk '{print $13}' | sort | uniq -c | sort -nr
# Ortalama upstream response time hesapla
awk -F'upstream_response_time=' '{print $2}'
/var/log/nginx/upstream_access.log |
awk '{sum+=$1; count++} END {print "Ortalama:", sum/count, "saniye"}'
Sonuç
Nginx upstream yapılandırması, ilk bakışta birkaç satır konfigürasyondan ibaret gibi görünse de aslında çok katmanlı bir dünya barındırıyor. Doğru algoritma seçimi, keepalive optimizasyonu, sağlık kontrolleri ve log izleme mekanizmaları bir araya geldiğinde ortaya gerçekten güvenilir bir yük dengeleme katmanı çıkıyor.
Pratik tavsiyem şu: Yeni bir upstream yapılandırması kurarken önce round-robin ile başla. Trafiği izle, log’ları analiz et ve ancak somut bir ihtiyaç gördüğünde least_conn veya ip_hash’e geç. Keepalive değerlerini de sunucudaki bağlantı limitlerini ve worker process sayısını göz önünde bulundurarak belirle. Her worker process için keepalive sayısı kadar idle bağlantı tutulduğunu unutma, yanlış hesaplamak backend tarafında connection limit sorunlarına yol açabilir.
Monitoring konusuna özellikle dikkat et. Upstream’in davranışını görmeden yaptığın her optimizasyon tahmine dayalı kalır. $upstream_response_time değişkenini log’larına ekle, Grafana’ya aktar ve her backend’in ne zaman yavaşladığını gerçek zamanlı takip et. Sorunları kullanıcılar şikayet etmeden önce görmek, iyi bir sysadmin ile reaktif bir sysadmin arasındaki farkı belirler.
