Yüksek CPU Kullanan Nginx Worker Süreçleri Nasıl Tespit Edilir ve Çözülür

Sabahın 3’ünde telefonun çaldığını ve monitoring sisteminin “CPU usage %95” alarmı verdiğini düşün. Sunucuya bağlandığında top komutunda nginx worker süreçlerinin CPU’yu lime lime yediğini görüyorsun. Bu senaryo, üretim ortamlarında çalışan hemen her sysadmin’in bir şekilde karşılaştığı klasik bir kabustur. Bu yazıda nginx worker süreçlerinin neden yüksek CPU tükettiğini, bunu nasıl teşhis edeceğini ve nasıl çözeceğini adım adım ele alacağız.

Önce Durumu Anlamak: İlk Müdahale

Panik yapmadan önce elimizdeki verileri toplayalım. Yüksek CPU durumunda ilk yapman gereken şey anlık durumu fotoğraflamak.

# Nginx worker süreçlerini CPU kullanımına göre sırala
ps aux --sort=-%cpu | grep nginx | head -20

# Daha detaylı bilgi için
top -b -n 1 | grep nginx

# Worker PID'lerini listele
pgrep -a nginx

Bu komutların çıktısına baktığında genellikle şunu görürsün: nginx: master process neredeyse sıfır CPU kullanırken, nginx: worker process satırları her biri %20-40 arasında CPU tüketiyordur. Master süreç asla yüksek CPU kullanmaz, yüksek CPU her zaman worker’larda görülür. Bu önemli bir ayrım.

# Gerçek zamanlı izleme için
watch -n 1 'ps aux --sort=-%cpu | grep nginx'

# Sistem genelinde ne olduğunu görmek için
vmstat 1 5
iostat -x 1 5

vmstat çıktısında us (user space) değeri yüksekse sorun nginx’in kendi işlemlerinde, sy (system) değeri yüksekse kernel düzeyinde bir şeyler oluyor demektir. Bu ayrım teşhis sürecini yönlendirecek.

Yüksek CPU’nun Yaygın Nedenleri

1. Keepalive Bağlantı Sorunları

En sık karşılaşılan senaryolardan biri yanlış yapılandırılmış keepalive ayarlarıdır. Binlerce idle bağlantı worker’ları meşgul edebilir.

# Aktif bağlantı sayısını ve durumlarını kontrol et
ss -s
netstat -an | grep :80 | awk '{print $6}' | sort | uniq -c | sort -rn

# Nginx status modülü aktifse
curl http://localhost/nginx_status

nginx_status çıktısında Active connections değeri anormali yüksekse ve waiting sayısı çok fazlaysa, keepalive timeout değerlerin çok uzun ayarlanmış olabilir.

# /etc/nginx/nginx.conf içinde kontrol edilmesi gereken değerler
http {
    keepalive_timeout 65;        # Varsayılan 75s, 30-65 arası genelde yeterli
    keepalive_requests 1000;     # Tek bağlantı üzerinden max istek sayısı
    
    # Worker bağlantı limiti
    events {
        worker_connections 1024;
        use epoll;               # Linux için epoll mutlaka kullanılmalı
    }
}

2. Regex Tabanlı Location Blokları

Bu konuda pek çok kişi farkında değil ama karmaşık regex’ler worker’ları ciddi şekilde yorabilir.

# Nginx konfigürasyonundaki regex location bloklarını listele
grep -n "location ~" /etc/nginx/sites-enabled/* /etc/nginx/conf.d/*

Eğer konfigürasyonunda bunun gibi satırlar varsa:

# KÖTÜ - Her istek için pahalı regex çalışıyor
location ~* .(php|asp|aspx|jsp)$ {
    # işlemler...
}

location ~ ^/api/v[0-9]+/users/[a-zA-Z0-9_-]+/orders/[0-9]+$ {
    proxy_pass http://backend;
}

# DAHA İYİ - Önce exact match, sonra prefix, en son regex
location = /favicon.ico { return 204; }
location ^~ /static/ { root /var/www; }
location ~* .(css|js|png)$ { expires 30d; }

Her gelen istek için nginx önce exact match, sonra prefix match, sonra regex match dener. Yüzlerce regex bloğun varsa ve trafik yoğunsa bu ciddi CPU maliyeti yaratır.

3. SSL/TLS Handshake Yükü

HTTPS trafiği olan sunucularda SSL handshake işlemleri CPU’nun önemli bir kısmını tüketebilir.

# SSL bağlantı istatistiklerini kontrol et
openssl s_client -connect localhost:443 -tls1_2 2>&1 | grep "Cipher|Protocol"

# Nginx SSL session cache durumu
curl -s http://localhost/nginx_status
# Session reuse oranı düşükse handshake maliyeti yüksektir

SSL session cache doğru yapılandırılmamışsa her bağlantı yeni bir handshake yapar:

http {
    # SSL session cache - 1m yaklaşık 4000 session tutar
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # Modern cipher suite kullan, eski ve yavaş olanları dışla
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    
    # OCSP stapling CPU yükünü azaltır
    ssl_stapling on;
    ssl_stapling_verify on;
}

4. Upstream Backend Yavaşlaması

Worker’ların yüksek CPU kullanmasının bir başka nedeni de upstream backend’in yavaş yanıt vermesidir. Bu durumda worker’lar bağlantıları açık tutup beklerken sistem kaynakları tükenir.

# Upstream bağlantı sürelerini log'dan analiz et
awk '{print $NF}' /var/log/nginx/access.log | sort -n | tail -20

# Daha detaylı log formatı kullanıyorsan
awk '{print $upstream_response_time}' /var/log/nginx/access.log 2>/dev/null

# Gerçek zamanlı upstream sürelerini izle
tail -f /var/log/nginx/access.log | awk '{print $upstream_response_time, $request}'

Log formatını daha bilgi verici hale getirmek için:

http {
    log_format detailed '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent" '
                        'rt=$request_time uct=$upstream_connect_time '
                        'uht=$upstream_header_time urt=$upstream_response_time';
    
    access_log /var/log/nginx/access.log detailed;
}

Derin Teşhis: strace ve perf ile Worker Analizi

Yüzeysel kontroller yetersiz kalıyorsa daha derin araçlara ihtiyaç duyarsın.

# Yüksek CPU kullanan worker PID'ini bul
HIGH_CPU_PID=$(ps aux --sort=-%cpu | grep 'nginx: worker' | head -1 | awk '{print $2}')
echo "Analiz edilecek PID: $HIGH_CPU_PID"

# strace ile sistem çağrılarını izle (dikkatli kullan, production'da yük yaratır)
strace -p $HIGH_CPU_PID -c -e trace=network,file 2>&1 &
sleep 10
kill %1

# Alternatif olarak perf kullan (daha az overhead)
perf top -p $HIGH_CPU_PID

strace çıktısında hangi sistem çağrısının en fazla zaman aldığını görebilirsin. Eğer epoll_wait çok kısa aralıklarla dönüyorsa busy-wait durumu var demektir. read veya write çağrıları yoğunsa veri işleme darboğazı vardır.

# Nginx worker'ın açık dosya/soket sayısını kontrol et
ls -la /proc/$HIGH_CPU_PID/fd | wc -l
cat /proc/$HIGH_CPU_PID/net/tcp | wc -l

# Worker'ın memory map'ine bak
cat /proc/$HIGH_CPU_PID/status | grep -E "VmRSS|VmSize|Threads"

Log Analizi ile Sorunlu İstekleri Bulmak

Çoğu zaman CPU spike’ının arkasında belirli bir URL pattern’i veya istemci adresi yatar.

# Son 1000 satırdaki en yavaş istekler
tail -1000 /var/log/nginx/access.log | awk '{print $7, $NF}' | sort -k2 -rn | head -20

# En fazla istek yapan IP'ler
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# Belirli bir zaman aralığındaki hata oranı
awk '$9 >= 500' /var/log/nginx/access.log | wc -l

# Gerçek zamanlı istek sayısı (saniyede kaç istek geliyor)
tail -f /var/log/nginx/access.log | awk '{print strftime("%H:%M:%S"), NR}' | uniq -f1 -c

Bazen sorun bot trafiği veya DDoS benzeri yüktür. Bunu hızlıca tespit etmek için:

# User-agent analizi
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# Saniyede 100'den fazla istek yapan IP'leri bul
awk '{print $1, $4}' /var/log/nginx/access.log | 
  awk '{gsub(/[/,"",$2); print $1, $2}' | 
  sort | uniq -c | sort -rn | 
  awk '$1 > 100 {print $0}' | head -10

Worker Sayısı ve CPU Affinity Optimizasyonu

Yanlış worker yapılandırması da CPU sorunlarının kaynağı olabilir.

# /etc/nginx/nginx.conf
worker_processes auto;          # CPU çekirdek sayısı kadar worker
worker_cpu_affinity auto;       # Her worker'ı bir CPU çekirdeğine bağla

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;             # Bir seferde birden fazla bağlantı kabul et
}

worker_rlimit_nofile 65535;     # Worker başına max açık dosya sayısı
# Mevcut worker sayısını kontrol et
ps aux | grep 'nginx: worker' | grep -v grep | wc -l

# CPU çekirdek sayısı
nproc

# İdeal worker sayısı genellikle çekirdek sayısına eşit olmalı
# Çok fazla worker context switching maliyeti yaratır

worker_processes auto ayarı çoğu durumda doğrudur, ancak hyper-threading aktifse fiziksel çekirdek sayısını kullanmak bazen daha verimli olur.

Gzip Sıkıştırma ve Buffer Ayarları

Yanlış yapılandırılmış gzip ayarları CPU’yu gereksiz yere meşgul edebilir.

http {
    gzip on;
    gzip_comp_level 2;           # 1-9 arası, yüksek = daha fazla CPU
    gzip_min_length 1000;        # Küçük dosyaları sıkıştırma, zaman kaybı
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/javascript text/xml;
    
    # Buffer boyutları - doğru ayar gereksiz disk I/O önler
    client_body_buffer_size 128k;
    client_max_body_size 10m;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 16k;
    
    # Proxy buffer ayarları
    proxy_buffer_size 4k;
    proxy_buffers 8 16k;
    proxy_busy_buffers_size 32k;
}

gzip_comp_level için 2 veya 3 genellikle en iyi CPU/sıkıştırma dengesini sağlar. Level 9’a çıkmak CPU kullanımını %40-50 artırırken sıkıştırma oranını yalnızca birkaç yüzde iyileştirir.

Rate Limiting ile Anlık Yükü Kesmek

CPU spike yaşadığın anlarda rate limiting kısa vadede nefes aldırır.

http {
    # IP başına istek limiti tanımla
    limit_req_zone $binary_remote_addr zone=one:10m rate=30r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    
    # Bağlantı limiti
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    
    server {
        location / {
            limit_req zone=one burst=50 nodelay;
            limit_conn addr 20;
        }
        
        location /api/ {
            limit_req zone=api burst=20 nodelay;
        }
    }
}
# Rate limiting devreye girince log'a yazılır, kontrol et
grep "limiting requests" /var/log/nginx/error.log | tail -20

# Konfigürasyon değişikliği sonrası test et
nginx -t && nginx -s reload

Upstream Bağlantı Havuzu Optimizasyonu

Eğer nginx bir reverse proxy olarak çalışıyorsa, upstream bağlantı yönetimi kritik önem taşır.

http {
    upstream backend {
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
        
        # Keepalive bağlantı havuzu - her worker için
        keepalive 32;
        keepalive_requests 1000;
        keepalive_timeout 60s;
    }
    
    server {
        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;          # keepalive için HTTP/1.1 şart
            proxy_set_header Connection "";   # keepalive için boş bırak
            
            proxy_connect_timeout 5s;
            proxy_send_timeout 30s;
            proxy_read_timeout 30s;
        }
    }
}

proxy_http_version 1.1 ve Connection “” ayarı olmadan upstream keepalive çalışmaz ve her istek için yeni TCP bağlantısı açılır. Bu hem CPU hem de latency açısından büyük maliyet demektir.

Acil Durum Playbook’u

Üretimde CPU spike yaşadığında hızlı müdahale için bir sıra takip etmek gerekir.

#!/bin/bash
# nginx_cpu_debug.sh - Hızlı tanı scripti

echo "=== NGINX CPU DEBUG RAPORU ==="
echo "Tarih: $(date)"
echo ""

echo "--- Worker Süreçleri ---"
ps aux --sort=-%cpu | grep nginx

echo ""
echo "--- Bağlantı Durumu ---"
ss -s

echo ""
echo "--- Nginx Status ---"
curl -s http://localhost/nginx_status 2>/dev/null || echo "Status endpoint bulunamadı"

echo ""
echo "--- Son 100 İstek Süreleri ---"
tail -100 /var/log/nginx/access.log | awk '{print $NF}' | 
  awk '{sum+=$1; count++} END {printf "Ort: %.3f s, Toplam: %d istekn", sum/count, count}'

echo ""
echo "--- Top 10 URL ---"
tail -1000 /var/log/nginx/access.log | awk '{print $7}' | 
  cut -d? -f1 | sort | uniq -c | sort -rn | head -10

echo ""
echo "--- Son Nginx Hataları ---"
tail -50 /var/log/nginx/error.log | grep -E "error|crit|alert|emerg"

Bu scripti çalıştırmak anlık durumun bir fotoğrafını verir ve daha hızlı karar almanı sağlar.

chmod +x nginx_cpu_debug.sh
./nginx_cpu_debug.sh > /tmp/nginx_debug_$(date +%Y%m%d_%H%M%S).txt

Uzun Vadeli İzleme ve Önleme

Sorunları tekrar yaşamamak için proaktif izleme kurmalısın.

# Nginx metric'lerini her dakika kaydet
cat > /etc/cron.d/nginx-monitor << 'EOF'
* * * * * root curl -s http://localhost/nginx_status >> /var/log/nginx/status_history.log
EOF

# Basit CPU alarm scripti
cat > /usr/local/bin/nginx_cpu_alert.sh << 'EOF'
#!/bin/bash
CPU_THRESHOLD=80
NGINX_CPU=$(ps aux | grep 'nginx: worker' | grep -v grep | 
  awk '{sum+=$3} END {print int(sum)}')

if [ "$NGINX_CPU" -gt "$CPU_THRESHOLD" ]; then
    echo "UYARI: Nginx CPU kullanımı ${NGINX_CPU}% - $(date)" | 
      mail -s "Nginx CPU Alert" [email protected]
fi
EOF
chmod +x /usr/local/bin/nginx_cpu_alert.sh

# Cron'a ekle
echo "*/5 * * * * root /usr/local/bin/nginx_cpu_alert.sh" > /etc/cron.d/nginx-cpu-alert

Prometheus ve Grafana kullanıyorsan nginx-prometheus-exporter kurarak çok daha detaylı metrik takibi yapabilirsin. Ancak basit bir kurulum için yukarıdaki cron tabanlı yöntem bile hayat kurtarır.

Sonuç

Nginx worker’larının yüksek CPU kullanımı, genellikle tek bir sebebe değil birden fazla faktörün bir araya gelmesine bağlıdır. Önce ps, ss ve nginx_status ile hızlı triage yap, ardından log analizine geç ve sorunlu pattern’leri bul. SSL session cache, keepalive ayarları, gereksiz karmaşık regex’ler ve yanlış buffer boyutları genellikle ilk bakılacak yerlerdir.

Gerçek üretim ortamında işler her zaman kitaptaki gibi gitmez. Bazen sorun bir upstream servisin yavaşlamasından kaynaklanır, bazen bir bot saldırısı başlamıştır, bazen de deploy edilen yeni bir konfigürasyon beklenmedik bir yük yaratmaktadır. Bu yüzden her müdahaleden önce en son yapılan değişiklikleri kontrol etmek iyi bir alışkanlıktır.

Bu yazıda paylaşılan debug scriptini ve ayar önerilerini direkt olarak kopyala-yapıştır yapma. Kendi ortamının gereksinimlerini anlayarak adapte et. worker_connections değeri 500 kullanıcılı bir uygulama için 1024 yeterliyken, yüksek trafikli bir API için 8192 bile az gelebilir. Profil çıkar, ölç, sonra optimize et sıralaması her zaman geçerlidir.

Bir yanıt yazın

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