OOM Killer Nedir: Linux’ta Bellek Yetersizliği Yönetimi

Sunucunuz gece yarısı aniden yanıt vermez hale geldi, SSH ile bağlanmaya çalışıyorsunuz ama bağlantı kopuyor, birkaç dakika sonra her şey normale döndü ama kritik bir servis artık ayakta değil. Logları incelediğinizde şu satırları görüyorsunuz: Out of memory: Kill process 1823 (java) score 892 oom_score_adj 0. İşte bu, Linux çekirdeğinin son çare mekanizması olan OOM Killer’ın devreye girdiğinin habercisi. Peki bu mekanizma tam olarak ne yapar, neden bu kararı verir ve en önemlisi, siz bir sysadmin olarak bu durumu nasıl yönetirsiniz?

OOM Killer Nedir ve Neden Var?

Linux çekirdeği, fiziksel RAM ve swap alanının toplamını aşan bellek talepleri geldiğinde ciddi bir sorunla karşı karşıya kalır. Teorik olarak sistem kilitlenebilir, tüm süreçler donabilir ve kurtarma için fiziksel müdahale gerekebilir. OOM Killer (Out of Memory Killer), çekirdeğin bu felaketi önlemek için tasarladığı otomatik bir kurtarma mekanizmasıdır.

Çekirdek, kullanılabilir bellek kritik seviyelerin altına düştüğünde bir ya da birden fazla süreci sonlandırarak sistemi ayakta tutmaya çalışır. Bu mekanizma kusursuz değildir, bazen yanlış süreci öldürür, bazen geç devreye girer, ama çoğu zaman tam bir sistem çöküşünü engeller.

Overcommit kavramını anlamak bu noktada kritik. Linux, varsayılan olarak süreçlerin talep ettiği belleği gerçekte mevcut olandan daha fazla tahsis etmeye izin verir. Yani bir süreç 4 GB bellek talep ederse, çekirdek bunu fiziksel RAM olmadan bile onaylayabilir, çünkü sürecin o belleği gerçekten kullanmayabileceğini varsayar. Bu “overcommit” stratejisi sayesinde sistemler çok daha verimli çalışır, ta ki tüm süreçler aynı anda o belleği kullanmaya kalkana kadar.

OOM Killer Hangi Süreci Öldürür?

Çekirdek, süreci rastgele seçmez. Her süreç için bir oom_score hesaplanır ve en yüksek skora sahip süreç kurban seçilir. Bu skoru etkileyen faktörler şunlardır:

  • Kullandığı bellek miktarı: Daha fazla bellek kullanan süreçler daha yüksek skor alır
  • Çalışma süresi: Yeni başlatılan süreçler daha savunmasızdır
  • Süreç önceliği: nice değeri skoru etkiler
  • Çocuk süreçlerin bellek kullanımı: Alt süreçlerin kullandığı bellek de hesaba katılır
  • Root süreci mi?: Root olarak çalışan süreçlere hafif bir indirim uygulanır

Herhangi bir sürecin anlık OOM skorunu görmek oldukça basit:

# Belirli bir sürecin OOM skorunu göster
cat /proc/$(pgrep nginx)/oom_score

# Tüm süreçlerin OOM skorlarını listele ve sırala
for pid in /proc/[0-9]*/; do
    pid_num=$(basename $pid)
    score=$(cat /proc/$pid_num/oom_score 2>/dev/null)
    comm=$(cat /proc/$pid_num/comm 2>/dev/null)
    echo "$score $pid_num $comm"
done | sort -rn | head -20

Bu komutun çıktısı size anlık olarak hangi sürecin OOM Killer’ın hedefi olabileceğini gösterir. Production ortamında bunu düzenli olarak izlemek, sürprizlerden korunmanın en pratik yollarından biridir.

OOM Olaylarını Kernel Loglarında Tespit Etmek

Bir OOM olayı yaşandıktan sonra yapılacak ilk iş, ne olduğunu anlamaktır. Kernel logları bu konuda son derece ayrıntılı bilgi içerir:

# dmesg ile OOM olaylarını filtrele
dmesg | grep -i "out of memory|oom_kill|killed process"

# Sistem loglarında OOM olaylarına bak
grep -i "oom|out of memory" /var/log/syslog | tail -50

# journald kullanıyorsanız
journalctl -k | grep -i "oom|out of memory" | tail -30

# Zaman damgasıyla birlikte görmek için
dmesg -T | grep -A5 "Out of memory"

Tipik bir OOM log çıktısı şöyle görünür:

[Mon Jan 15 03:24:17 2024] Out of memory: Kill process 4821 (java) score 892 oom_score_adj 0
[Mon Jan 15 03:24:17 2024] Killed process 4821 (java) total-vm:8420148kB, anon-rss:7821032kB, file-rss:4096kB

Bu satırlarda birkaç kritik bilgi var: hangi sürecin öldürüldüğü, OOM skoru, sanal bellek kullanımı ve gerçek RSS (Resident Set Size) kullanımı. Bu bilgileri doğru okumak, sorununun kaynağını anlamanızı sağlar.

oom_score_adj ile Süreci Koruma veya Kurban Etme

OOM Killer’ın kararını etkilemenin en doğrudan yolu oom_score_adj değeridir. Bu değer -1000 ile 1000 arasında değişir:

  • -1000: Süreci OOM Killer’dan tamamen koru
  • -500: Skoru önemli ölçüde düşür, öldürülme ihtimalini azalt
  • 0: Varsayılan değer, müdahale yok
  • 500: Skoru yükselt, önce bu süreç öldürülsün
  • 1000: Bu süreci her zaman ilk öldür
# Çalışan bir sürecin oom_score_adj değerini değiştir (MySQL örneği)
echo -500 > /proc/$(pgrep mysql)/oom_score_adj

# Nginx worker process'lerini koru
for pid in $(pgrep nginx); do
    echo -300 > /proc/$pid/oom_score_adj
    echo "Nginx PID $pid için oom_score_adj ayarlandı"
done

# Bir uygulamayı başlatırken oom_score_adj belirle
# Test/geliştirme ortamındaki süreçleri kurban adayı yap
sudo -u appuser bash -c 'echo 500 > /proc/$$/oom_score_adj; exec /opt/testapp/start.sh'

Ancak burada dikkat edilmesi gereken önemli bir nokta var: Bu değerler süreç yeniden başlatıldığında sıfırlanır. Production ortamında kalıcı hale getirmeniz gerekir.

Systemd ile Kalıcı OOM Ayarları

Modern Linux sistemlerinde servisler genellikle systemd ile yönetilir. Systemd, OOM ayarlarını servis dosyasında tanımlamanıza olanak tanır:

# /etc/systemd/system/myapp.service dosyasını düzenle
cat > /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Critical Application
After=network.target

[Service]
Type=simple
User=appuser
ExecStart=/opt/myapp/bin/start.sh
Restart=always

# OOM ayarları
OOMScoreAdjust=-500

# Bellek limiti (opsiyonel ama önerilir)
MemoryMax=2G
MemoryHigh=1.5G

[Install]
WantedBy=multi-user.target
EOF

# Değişiklikleri uygula
systemctl daemon-reload
systemctl restart myapp

# Ayarın aktif olup olmadığını kontrol et
systemctl show myapp | grep OOM
cat /proc/$(pgrep -f myapp)/oom_score_adj

OOMPolicy direktifi de son derece kullanışlıdır:

  • OOMPolicy=continue: OOM olayında servis öldürülse bile systemd servisi yeniden başlatmaz, sadece devam eder
  • OOMPolicy=stop: Servis öldürülürse durdur
  • OOMPolicy=kill: Servis grubundaki tüm süreçleri öldür

cgroups ile Bellek Yönetimi

Gerçek anlamda proaktif bir yaklaşım için cgroups kullanmak gerekir. cgroups, bellek limitlerini süreç grupları düzeyinde belirlemenizi sağlar ve OOM Killer’ın devreye girmesini önlemenin en etkili yollarından biridir:

# cgroup v2 ile bellek limiti belirleme (modern sistemler)
# Önce mevcut memory cgroup ayarlarını kontrol et
cat /sys/fs/cgroup/system.slice/mysql.service/memory.max
cat /sys/fs/cgroup/system.slice/mysql.service/memory.current

# Belirli bir servis için bellek limiti ayarla (systemctl override)
mkdir -p /etc/systemd/system/mysql.service.d/
cat > /etc/systemd/system/mysql.service.d/memory.conf << 'EOF'
[Service]
MemoryMax=4G
MemoryHigh=3.5G
MemorySwapMax=512M
EOF

systemctl daemon-reload
systemctl restart mysql

# Anlık bellek kullanımını izle
watch -n 1 "cat /sys/fs/cgroup/system.slice/mysql.service/memory.current | awk '{print $1/1024/1024" MB"}'"

Gerçek Dünya Senaryosu: Java Uygulaması OOM Sorunu

En sık karşılaşılan senaryolardan biri Java uygulamalarının belleği zamanla tükenmesidir. Bir e-ticaret platformunda production sorununu nasıl çözdüğümüze bakalım.

Belirti: Her 3-4 günde bir Tomcat servisi OOM Killer tarafından öldürülüyor, müşteriler erişim sorunu yaşıyor.

# Önce mevcut durumu analiz et
free -h
ps aux --sort=-%mem | head -15

# Java heap kullanımını izle (JVM çalışırken)
jstat -gcutil $(pgrep -f tomcat) 5000 10

# GC loglarını kontrol et
tail -100 /opt/tomcat/logs/gc.log | grep -i "allocation failure|full gc"

# Bellek sızıntısını tespit etmek için periyodik snapshot al
cat > /usr/local/bin/memory_snapshot.sh << 'EOF'
#!/bin/bash
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/memory_snapshots.log"

echo "=== $TIMESTAMP ===" >> $LOG_FILE
free -m >> $LOG_FILE
ps aux --sort=-%mem | head -10 >> $LOG_FILE

# Java heap dump (eğer bellek %80 üzerindeyse)
JAVA_PID=$(pgrep -f tomcat)
if [ -n "$JAVA_PID" ]; then
    RSS_MB=$(cat /proc/$JAVA_PID/status | grep VmRSS | awk '{print $2/1024}' | cut -d. -f1)
    echo "Tomcat RSS: ${RSS_MB}MB" >> $LOG_FILE
fi
EOF

chmod +x /usr/local/bin/memory_snapshot.sh

# Cron ile her 15 dakikada bir çalıştır
echo "*/15 * * * * root /usr/local/bin/memory_snapshot.sh" > /etc/cron.d/memory_monitor

Sonunda heap dump analizi yaparak bir connection pool’un kapatılmadan biriktiği tespit edildi. JVM parametrelerine -Xmx4g -Xms2g eklenerek bellek kullanımı sınırlandırıldı ve oom_score_adj -200 ile servis daha az kurban adayı haline getirildi.

Kernel Overcommit Ayarlarını Yapılandırmak

Bellek yönetimini sistem genelinde etkilemenin bir diğer yolu kernel overcommit politikasını değiştirmektir:

# Mevcut overcommit ayarını göster
cat /proc/sys/vm/overcommit_memory
# 0: Heuristic overcommit (varsayılan)
# 1: Her zaman overcommit'e izin ver
# 2: Overcommit'i sınırla

cat /proc/sys/vm/overcommit_ratio
# overcommit_memory=2 ile kullanılır, RAM'in yüzde kaçına kadar izin verildiğini gösterir

# Overcommit oranını görüntüle
cat /proc/sys/vm/overcommit_kbytes

# Production veritabanı sunucusu için overcommit'i kısıtla
echo 2 > /proc/sys/vm/overcommit_memory
echo 80 > /proc/sys/vm/overcommit_ratio

# Kalıcı hale getirmek için sysctl.conf
cat >> /etc/sysctl.d/99-memory.conf << 'EOF'
# OOM ve overcommit ayarları
vm.overcommit_memory = 2
vm.overcommit_ratio = 80
vm.panic_on_oom = 0
vm.oom_kill_allocating_task = 0
EOF

sysctl -p /etc/sysctl.d/99-memory.conf

vm.panic_on_oom = 0 ayarı, OOM durumunda sistemi panikletmek yerine OOM Killer’ı devreye sokar. Bazı özel durumlarda (çok kritik sistemler) 1 yaparak sistemin otomatik yeniden başlaması sağlanabilir, ancak bu genellikle önerilmez.

Bellek Baskısını Proaktif İzlemek

OOM Killer devreye girmeden önce sisteminizi izlemek ve uyarı almak çok daha akıllıca bir yaklaşımdır:

#!/bin/bash
# /usr/local/bin/oom_guard.sh
# Bellek kullanımını izle ve kritik eşikte uyar veya müdahale et

THRESHOLD=85  # Yüzde eşiği
ALERT_EMAIL="[email protected]"
LOG_FILE="/var/log/oom_guard.log"

get_mem_usage() {
    free | grep Mem | awk '{printf "%.0f", $3/$2 * 100}'
}

get_swap_usage() {
    free | grep Swap | awk '{if($2>0) printf "%.0f", $3/$2 * 100; else print "0"}'
}

MEM_USAGE=$(get_mem_usage)
SWAP_USAGE=$(get_swap_usage)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$TIMESTAMP] RAM: %$MEM_USAGE, SWAP: %$SWAP_USAGE" >> $LOG_FILE

if [ "$MEM_USAGE" -gt "$THRESHOLD" ]; then
    echo "[$TIMESTAMP] UYARI: Bellek kullanımı %$MEM_USAGE eşiği aştı!" >> $LOG_FILE
    
    # En çok bellek kullanan 5 süreci logla
    echo "Top bellek tüketicileri:" >> $LOG_FILE
    ps aux --sort=-%mem | head -6 >> $LOG_FILE
    
    # E-posta gönder
    echo "Sunucu $(hostname) bellek kullanımı kritik seviyede: %$MEM_USAGE" | 
        mail -s "OOM UYARISI: $(hostname)" $ALERT_EMAIL
    
    # Page cache temizle (hafif müdahale)
    sync
    echo 1 > /proc/sys/vm/drop_caches
fi

# Swap kullanımı çok yüksekse daha agresif temizlik
if [ "$SWAP_USAGE" -gt "90" ]; then
    echo "[$TIMESTAMP] KRİTİK: Swap kullanımı %$SWAP_USAGE!" >> $LOG_FILE
    echo 3 > /proc/sys/vm/drop_caches
fi

Bu scripti cron ile her 5 dakikada çalıştırmak, OOM olaylarından önce sizi uyarır:

chmod +x /usr/local/bin/oom_guard.sh
echo "*/5 * * * * root /usr/local/bin/oom_guard.sh" > /etc/cron.d/oom_guard

swappiness Ayarı ve Bellek Yönetimi Dengesi

Kernel’in swap kullanma eğilimini kontrol eden vm.swappiness parametresi de OOM davranışını dolaylı olarak etkiler:

# Mevcut swappiness değerini kontrol et (varsayılan: 60)
cat /proc/sys/vm/swappiness

# Veritabanı sunucusu için düşük swappiness önerilir
echo 10 > /proc/sys/vm/swappiness

# Genel amaçlı sunucu için
echo 30 > /proc/sys/vm/swappiness

# Kalıcı hale getir
echo "vm.swappiness = 10" >> /etc/sysctl.d/99-memory.conf
sysctl -p /etc/sysctl.d/99-memory.conf

# Mevcut swap kullanımını detaylı incele
for pid in /proc/[0-9]*/; do
    pid_num=$(basename $pid)
    swap=$(grep -i VmSwap /proc/$pid_num/status 2>/dev/null | awk '{print $2}')
    comm=$(cat /proc/$pid_num/comm 2>/dev/null)
    if [ -n "$swap" ] && [ "$swap" -gt 0 ]; then
        echo "$swap KB - PID: $pid_num ($comm)"
    fi
done | sort -rn | head -15

swappiness = 0 değeri RAM dolmadan swap kullanmayı tamamen engeller, bu da bellek baskısı altında OOM Killer’ın daha hızlı devreye girmesi anlamına gelebilir. Değeri çok düşük tutmak her zaman iyi değildir, iş yüküne göre dengelemek gerekir.

OOM Olayı Sonrası Yapılması Gerekenler

Bir OOM olayı yaşandıktan sonra sistematik bir post-mortem yaklaşımı benimseyin:

# Olayın zaman damgasını ve etkilenen süreçleri belirle
dmesg -T | grep -B2 -A10 "Out of memory" | tail -50

# O anki bellek durumunu yeniden oluşturmaya çalış
# (oom loglarında genellikle tam bellek haritası bulunur)
dmesg | grep "oom_kill_process|Total swap|Free swap|total_vm|oom-kill"

# Servis ne zaman başarısız oldu?
journalctl -u myservice --since "2024-01-15 03:00" --until "2024-01-15 04:00"

# Sistem genelinde bellek trendini incele (eğer monitoring varsa)
# Prometheus/Grafana yoksa SAR kullan
sar -r 1 10  # Anlık
sar -r -f /var/log/sysstat/sa$(date +%d)  # Bugünün geçmişi

Bir OOM olayının ardından şu sorulara cevap aramalısınız:

  • Hangi süreç ne kadar bellek kullanıyordu ve bu normal mi?
  • Bellek kullanımı ani mi arttı yoksa zamanla mı birikiyor?
  • Öldürülen süreç doğru süreç miydi, yoksa önemli bir servis mi kurban gitti?
  • Benzer durumun tekrarını önlemek için hangi limitler konulabilir?

Konteyner ve Kubernetes Ortamlarında OOM

Konteyner ortamlarında OOM biraz farklı çalışır. Docker ve Kubernetes kendi limit mekanizmalarına sahiptir:

# Docker konteynerine bellek limiti koy
docker run -m 512m --memory-swap 1g myapp:latest

# Çalışan konteynerin bellek limitini kontrol et
docker stats --no-stream mycontainer
docker inspect mycontainer | grep -i memory

# OOM nedeniyle öldürülen konteyneri tespit et
docker inspect mycontainer | grep OOMKilled
# "OOMKilled": true çıkıyorsa konteyner OOM tarafından öldürülmüş demektir

Kubernetes tarafında ise:

# Pod'un OOM nedeniyle restart edip etmediğini kontrol et
kubectl describe pod mypod | grep -A5 "Last State|OOM|Reason"

# Tüm namespace'lerdeki OOM'u kontrol et
kubectl get pods --all-namespaces | grep -i "oomkilled|error|crashloop"

Sonuç

OOM Killer, Linux’un son savunma hattıdır ve devreye girdiğinde zaten bir şeylerin yanlış gittiğinin göstergesidir. Bir sysadmin olarak asıl hedefiniz, OOM Killer’ın hiç iş yapmak zorunda kalmaması gereken bir ortam yaratmaktır.

Bunun için yapmanız gerekenler oldukça nettir: uygulamalarınıza bellek limitleri koyun, kritik servisleri oom_score_adj ile koruyun, swappiness ve overcommit politikalarını iş yüküne göre ayarlayın ve bellek kullanımını proaktif olarak izleyin. cgroups ve systemd entegrasyonu ile bu yönetimi çok daha sistematik ve kalıcı hale getirebilirsiniz.

OOM olayları yaşandığında ise kernel loglarını derinlemesine okuyun, hangi sürecin ne kadar bellek kullandığını anlayın ve bir daha aynı sorunla karşılaşmamak için kalıcı çözümler üretin. Bellek yönetimi, reaktif değil proaktif bir yaklaşım gerektirir. Gece yarısı alarm almak yerine, o alarmların hiç gelmeyeceği bir sistem tasarlamak, işte bu gerçek anlamda sistem yöneticiliğidir.

Similar Posts

Bir yanıt yazın

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