Çok Fazla Açık Dosya Hatası: ulimit Yapılandırması ile Çözüm

Gece 2’de alarm çalıyor, production sunucunuz yanıt vermiyor ve log dosyalarında “Too many open files” hataları akıyor. Bu senaryoyu yaşadıysanız, ulimit yapılandırmasının ne kadar kritik olduğunu acı bir şekilde öğrenmişsinizdir. Yaşamadıysanız, bu yazıyı okuyarak o acıyı yaşamadan öğrenebilirsiniz.

Sorunun Kökü: Linux’ta Dosya Tanımlayıcıları

Linux’ta her şey bir dosyadır. Gerçek dosyalar, soketler, pipe’lar, cihaz dosyaları… Bir process bir ağ bağlantısı açtığında, bir soket dosyası okuyup yazdığında ya da bir log dosyasına bir şeyler yazdığında, işletim sistemi buna bir file descriptor (dosya tanımlayıcısı) atar. Bu tanımlayıcılar integer değerlerdir ve her process için sınırlı sayıda bulunur.

İşte burada ulimit devreye giriyor. ulimit, bir shell ve onun başlattığı process’ler için kaynak limitlerini belirleyen mekanizmadır. “Too many open files” hatası aldığınızda, bu limitin dolduğu anlamına gelir. Sorun basit görünse de çözümü birkaç katman içeriyor ve her katmanı doğru yapılandırmazsanız sorun devam eder.

Durumu Tespit Etmek

Paniklemeden önce durumu anlamak gerekir. Hangi process sorun çıkarıyor, ne kadar dosya açık, sistem genelindeki durum nedir?

Mevcut Limitleri Kontrol Etmek

# Mevcut shell için soft ve hard limitleri göster
ulimit -a

# Sadece açık dosya limitini göster (soft limit)
ulimit -n

# Hard limiti göster
ulimit -Hn

# Soft limiti göster
ulimit -Sn

Bu komutları çalıştırdığınızda muhtemelen şöyle bir çıktı görürsünüz:

open files                      (-n) 1024

1024! Modern bir web uygulaması, veritabanı bağlantıları, log dosyaları ve ağ soketleriyle bu sayıyı dakikalar içinde doldurabilir.

Sistem Geneli Dosya Tanımlayıcı Durumu

# Sistem genelinde kaç dosya tanımlayıcısı kullanılıyor
cat /proc/sys/fs/file-nr

# Çıktı: kullanılan / serbest bırakılan ama kullanılmayan / maksimum
# Örnek: 32768   0       1048576

# Sistem geneli maksimum limiti göster
cat /proc/sys/fs/file-max

# Belirli bir process'in açık dosyalarını say
ls /proc/<PID>/fd | wc -l

# Belirli bir process'in açık dosyalarını listele
ls -la /proc/<PID>/fd

# lsof ile sistem geneli açık dosyaları say
lsof | wc -l

# En çok dosya açan process'leri bul
lsof | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

Özellikle lsof komutu sizin en iyi arkadaşınız olacak. Nginx, Java uygulamanız veya MySQL gibi belirli bir servisin sorun çıkardığını düşünüyorsanız:

# Belirli bir kullanıcının açık dosyalarını göster
lsof -u nginx

# Belirli bir process ID'nin açık dosyalarını göster
lsof -p 1234

# Belirli bir servisin kaç dosya açık tuttuğunu göster
lsof -p $(pgrep nginx) | wc -l

# Ağ bağlantılarını da dahil et
lsof -p $(pgrep java) -i

Soft Limit ve Hard Limit Farkı

Bu kavramı anlamak kritik. Yanlış anlaşıldığında yapılandırma değişiklikleri işe yaramıyor gibi görünür.

Soft Limit: Process’in o an için geçerli olan limiti. Process bu limiti aşamaz ama root olmayan kullanıcılar bile bu değeri hard limit’e kadar artırabilir.

Hard Limit: Soft limit’in ulaşabileceği maksimum değer. Bunu yalnızca root değiştirebilir.

Pratik sonucu şu: Eğer bir uygulama “Too many open files” hatası alıyorsa ve soft limit 1024 ise, uygulamayı başlatan kullanıcı hard limit’e kadar bu değeri artırabilir. Ama hard limit de 1024 ise, root olmadan hiçbir şey değişmez.

# Soft limiti artır (sadece hard limite kadar)
ulimit -Sn 65536

# Hard limiti artır (root gerektirir)
sudo ulimit -Hn 65536

# Her ikisini birden ayarla
ulimit -n 65536

Dikkat: ulimit komutunun etkisi sadece mevcut shell ve ondan başlatılan process’ler için geçerlidir. Sistem genelinde kalıcı bir değişiklik için farklı yerleri yapılandırmanız gerekir.

Kalıcı Yapılandırma: /etc/security/limits.conf

Shell’de yaptığınız ulimit değişiklikleri o oturumla birlikte kaybolur. Kalıcı hale getirmek için /etc/security/limits.conf dosyasını düzenlemeniz gerekir.

sudo nano /etc/security/limits.conf

Dosyanın formatı şöyle:

# <domain>  <type>  <item>  <value>
*           soft    nofile  65536
*           hard    nofile  65536
nginx       soft    nofile  100000
nginx       hard    nofile  100000
deploy      soft    nofile  50000
deploy      hard    nofile  50000
root        soft    nofile  65536
root        hard    nofile  65536

Domain alanında kullanabilecekleriniz:

  • *: Tüm kullanıcılar için geçerli
  • kullanıcı_adı: Belirli bir kullanıcı için
  • @grup_adı: Belirli bir grup için
  • root: Root kullanıcısı için (ayrıca belirtilmesi önerilir, * bazen root’u kapsamaz)

Type alanı:

  • soft: Soft limit
  • hard: Hard limit
  • : Her ikisi birden

Item alanında sık kullanılanlar:

  • nofile: Açık dosya sayısı limiti
  • nproc: Maksimum process sayısı
  • memlock: Kilitlenebilir bellek miktarı (KB)
  • stack: Stack boyutu (KB)

Bu değişikliklerin geçerli olması için /etc/pam.d/common-session ve /etc/pam.d/common-session-noninteractive dosyalarında PAM limits modülünün yüklü olduğundan emin olun:

grep pam_limits /etc/pam.d/common-session
# Şunu görmelisiniz: session required pam_limits.so

# Eğer yoksa ekleyin
echo "session required pam_limits.so" | sudo tee -a /etc/pam.d/common-session

Systemd Servisleri için Yapılandırma

Modern Linux sistemlerde çoğu servis systemd tarafından yönetilir. systemd kendi kaynak limit mekanizmasına sahiptir ve /etc/security/limits.conf bu servisler için geçerli olmayabilir. Bu, insanların en çok kafasının karıştığı noktadır.

Bir systemd servisi için limit ayarlamak istiyorsanız, servis dosyasını düzenlemeniz gerekir:

# Nginx için örnek
sudo systemctl edit nginx

Bu komut override dosyası oluşturur. İçine şunu yazın:

[Service]
LimitNOFILE=100000

Veya doğrudan servis dosyasını düzenleyebilirsiniz:

# Servis dosyasının nerede olduğunu bul
systemctl show nginx | grep FragmentPath

# Dosyayı düzenle
sudo nano /lib/systemd/system/nginx.service

Service bölümüne şu satırları ekleyin:

[Service]
LimitNOFILE=65536
LimitNPROC=65536

Değişiklikten sonra:

sudo systemctl daemon-reload
sudo systemctl restart nginx

# Değişikliğin geçerli olduğunu doğrula
cat /proc/$(pgrep nginx | head -1)/limits | grep "open files"

systemd’de Kullanabileceğiniz Limit Direktifleri

  • LimitNOFILE: Açık dosya sayısı
  • LimitNPROC: Process sayısı
  • LimitMEMLOCK: Kilitlenebilir bellek
  • LimitCORE: Core dump boyutu
  • LimitAS: Address space boyutu

Çekirdek Seviyesi Yapılandırma

Sistem geneli maksimum değerleri de artırmanız gerekebilir. Process başına limitler ne kadar yüksek olursa olsun, sistem geneli limit dolduysa yeni dosya tanımlayıcısı açılamaz.

# Mevcut sistem geneli limiti kontrol et
cat /proc/sys/fs/file-max

# Geçici olarak artır
echo 2097152 | sudo tee /proc/sys/fs/file-max

# Kalıcı hale getirmek için sysctl.conf
sudo nano /etc/sysctl.conf

/etc/sysctl.conf dosyasına şunları ekleyin:

# Maksimum açık dosya sayısı
fs.file-max = 2097152

# İnotify izleyici limitleri (gerekirse)
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512

Değişiklikleri uygulayın:

sudo sysctl -p

# Doğrula
sysctl fs.file-max

Gerçek Dünya Senaryosu: Nginx + Node.js Uygulaması

Bir müşterimin sisteminde yaşanan gerçek senaryoyu paylaşayım. Trafik artışıyla birlikte Nginx 502 hatası vermeye başlamıştı ve Node.js uygulama loglarında “EMFILE: too many open files” hataları dolup taşıyordu.

Önce durumu tespit ettik:

# Nginx'in kaç dosya açtığını kontrol et
ls /proc/$(pgrep nginx | head -1)/fd | wc -l

# Node.js process'inin limitini kontrol et
cat /proc/$(pgrep node)/limits

# Aktif bağlantı sayısını kontrol et
ss -s
netstat -an | grep ESTABLISHED | wc -l

Node.js process’inin limiti 1024’tü ve 1020 dosya açıktı. Nginx ise 4 worker process çalıştırıyordu ve her biri yaklaşık 800 dosya tutuyordu.

Adım adım çözüm uyguladık:

# 1. Önce /etc/security/limits.conf güncelle
cat >> /etc/security/limits.conf << 'EOF'
www-data    soft    nofile  65536
www-data    hard    nofile  65536
deploy      soft    nofile  65536
deploy      hard    nofile  65536
EOF

# 2. Nginx systemd override oluştur
mkdir -p /etc/systemd/system/nginx.service.d/
cat > /etc/systemd/system/nginx.service.d/limits.conf << 'EOF'
[Service]
LimitNOFILE=65536
EOF

# 3. Node.js uygulamamız PM2 ile çalışıyordu
# PM2 ecosystem dosyasında
cat pm2.config.js

PM2 ile çalışan Node.js uygulamaları için ecosystem dosyasına şunu ekleyin:

module.exports = {
  apps: [{
    name: 'myapp',
    script: 'app.js',
    instances: 'max',
    node_args: '--max-old-space-size=2048',
    env: {
      NODE_ENV: 'production',
      UV_THREADPOOL_SIZE: 128
    },
    // PM2 process sınırları
    max_restarts: 10,
    restart_delay: 4000
  }]
}

PM2’nin kendi limitlerini ayarlamak için:

# PM2'yi root ile başlatıp limit ver
sudo su -c "ulimit -n 65536 && pm2 start ecosystem.config.js" deploy

# Veya PM2 startup scriptini güncelle
pm2 startup
# Ardından /etc/systemd/system/pm2-deploy.service dosyasına LimitNOFILE ekle

Java Uygulamaları için Özel Durumlar

Java uygulamaları dosya tanımlayıcısı tüketimi konusunda özellikle dikkat gerektirir. JVM’in kendisi, her thread için ek tanımlayıcılar kullanır ve connection pool’lar, JMX bağlantıları gibi şeyler de eklenir.

# Java process'inin açık dosyalarını detaylı incele
lsof -p $(pgrep java) | grep -v "^java" | awk '{print $5}' | sort | uniq -c | sort -rn

# Dosya tipleri dağılımını gör
lsof -p $(pgrep java) | awk '{print $5}' | sort | uniq -c | sort -rn

# Java process'in limitini kontrol et
cat /proc/$(pgrep java)/limits

Tomcat veya Spring Boot gibi uygulamalar için systemd service dosyasında şunları yapılandırın:

[Unit]
Description=My Java Application
After=network.target

[Service]
Type=simple
User=appuser
ExecStart=/usr/bin/java -jar /opt/myapp/app.jar
LimitNOFILE=65536
LimitNPROC=4096

[Install]
WantedBy=multi-user.target

Postgresql ve MySQL için Yapılandırma

Veritabanları da bu sorundan nasibini alır. Her bağlantı, her tablo ve her index dosyası için tanımlayıcı açılır.

# PostgreSQL için kontrol
cat /proc/$(pgrep postgres | head -1)/limits

# MySQL/MariaDB için
cat /proc/$(pgrep mysqld)/limits

MySQL’in /etc/mysql/mysql.conf.d/mysqld.cnf dosyasına eklenecekler:

[mysqld]
open_files_limit = 65536
table_open_cache = 4000
table_definition_cache = 2000

PostgreSQL için /etc/postgresql/14/main/postgresql.conf:

max_files_per_process = 1000

Ve PostgreSQL’in systemd override’ı:

sudo systemctl edit postgresql

# İçine yaz:
[Service]
LimitNOFILE=65536

Yapılandırmaları Doğrulama ve Test

Değişiklik yaptıktan sonra doğrulamayı unutmayın. Birçok kez insanlar değişiklik yaptıklarını sanıp doğrulamadıkları için sorun devam eder.

# Değişiklikten sonra yeni bir shell aç ve kontrol et
su - nginx
ulimit -n

# Process başlatıldıktan sonra kontrol et
cat /proc/$(pgrep nginx | head -1)/limits | grep "Max open files"

# Systemd servisi için
systemctl show nginx | grep -i limit

# Canlı test: limit dolmadan önce kaç dosya açılabiliyor
# (Bu scripti test ortamında çalıştırın!)
python3 -c "
import resource
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
print(f'Soft limit: {soft}')
print(f'Hard limit: {hard}')
"

Ayrıca sürekli izleme için basit bir monitoring scripti yazabilirsiniz:

#!/bin/bash
# /usr/local/bin/check_fd_usage.sh

THRESHOLD=80  # Yüzde
SERVICES=("nginx" "node" "java" "postgres")

for service in "${SERVICES[@]}"; do
    PID=$(pgrep $service | head -1)
    if [ -z "$PID" ]; then
        continue
    fi
    
    MAX=$(cat /proc/$PID/limits | grep "Max open files" | awk '{print $4}')
    CURRENT=$(ls /proc/$PID/fd 2>/dev/null | wc -l)
    
    if [ "$MAX" != "unlimited" ]; then
        PERCENT=$((CURRENT * 100 / MAX))
        if [ $PERCENT -gt $THRESHOLD ]; then
            echo "UYARI: $service (PID: $PID) dosya tanımlayıcı kullanımı %$PERCENT ($CURRENT/$MAX)"
        fi
    fi
done
chmod +x /usr/local/bin/check_fd_usage.sh

# Cron'a ekle
echo "*/5 * * * * root /usr/local/bin/check_fd_usage.sh >> /var/log/fd_monitor.log 2>&1" | sudo tee -a /etc/crontab

Sık Yapılan Hatalar

Yıllar içinde gördüğüm en yaygın hataları listelemeliyim:

Sadece shell’de ulimit değiştirmek: ulimit -n 65536 yazıp servisi yeniden başlatmak işe yaramaz çünkü servis zaten farklı bir shell üzerinden başlatılmaktadır.

limits.conf’u güncelleyip oturumu kapatmamak: PAM tabanlı değişiklikler yeni oturum açıldığında geçerli olur. Mevcut shell’de hemen test etmeye çalışmak yanıltıcı sonuçlar verir.

systemd servislerinde limits.conf’a güvenmek: systemd kendi limit mekanizmasını kullanır. Her ikisini de yapılandırmanız güvenli bir yaklaşımdır.

Root için limit koymamak: * wildcard bazen root’u kapsamaz. Root için ayrıca limit tanımlayın.

Değişikliği doğrulamamak: Her zaman /proc//limits dosyasını kontrol ederek gerçek process’in limitinin değiştiğini doğrulayın.

Sonuç

“Too many open files” hatası sinir bozucu görünse de aslında çözümü net olan bir sorundur. Önemli olan hangi katmanda sorun yaşandığını doğru tespit etmek ve o katmana uygun çözümü uygulamaktır.

Sıralamayı şöyle özetleyebilirim: Önce lsof ve /proc//limits ile sorunu tespit edin. Servis systemd ile mi başlıyor? O zaman systemctl edit ile LimitNOFILE ayarlayın. Kullanıcı oturumu mu? /etc/security/limits.conf yeterlidir. Sistem geneli sınıra mı takıldınız? sysctl ile fs.file-max değerini artırın.

Production sistemlerde her kritik servis için dosya tanımlayıcı kullanımını izlemeye almanızı şiddetle tavsiye ederim. Gece 2’de alarm çalmadan önce bu değerleri öğrenmek her zaman daha iyidir.

Benzer Konular

Bir yanıt yazın

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