Zombie Süreçler: Linux’ta Tespit ve Temizleme Yöntemleri

Bir gece yarısı alarm çaldığında ve sunucunuza bağlandığınızda ps aux çıktısında onlarca etiketi görmek… Bu sahneyi yaşayan her sysadmin bilir o tuhaf hissi. Ne tam anlamıyla ölü, ne de yaşıyor. İşte bu yüzden “zombie” diyoruz onlara. Linux’ta zombie süreçler, yeni başlayan sistem yöneticilerinin kafasını karıştıran ama aslında bir kez anlaşıldığında oldukça mantıklı bir kavram. Bu yazıda zombie süreçlerin ne olduğunu, neden oluştuklarını, nasıl tespit edeceğinizi ve nasıl temizleyeceğinizi gerçek dünya senaryolarıyla birlikte ele alacağız.

Zombie Süreç Nedir?

Linux’ta her sürecin bir yaşam döngüsü vardır. Süreç çalışır, işini bitirir ve sonlanır. Ancak bir süreç sonlandığında, kernel hemen tüm kaynaklarını serbest bırakmaz. Önce sürecin çıkış durumunu (exit status) bir yerde saklar çünkü ebeveyn sürecin bu bilgiye ihtiyacı olabilir.

Ebeveyn süreç, çocuk sürecin çıkış durumunu wait() veya waitpid() sistem çağrısıyla alana kadar, o çocuk süreç zombie (zombi) durumunda kalır. Yani süreç teknik olarak bitmiştir, CPU kullanmaz, bellek tüketmez; ama process table’da bir giriş olarak durmaya devam eder.

Bunu şöyle düşünebilirsiniz: Bir eleman işten ayrılmış ama İK departmanı çıkış evraklarını henüz imzalamamış. Adam gitmişi gitmiş ama sistemde hâlâ “aktif” görünüyor.

Zombie süreçlerin temel özellikleri:

  • CPU kullanmazlar
  • Bellek tüketmezler (neredeyse hiç)
  • Process table’da yer kaplarlar
  • PID’lerini tutarlar
  • Durumları ps çıktısında Z veya olarak görünür

Neden Önemli?

“Madem kaynak tüketmiyor, ne önemi var?” diye düşünebilirsiniz. Haklısınız, bir iki tane zombie süreç gerçekten sorun değil. Ama şu senaryoyu düşünün: Hatalı kodlanmış bir uygulama sürekli çocuk süreçler yaratıyor ve bunların hiçbirini toplamıyor. Zamanla process table dolmaya başlar.

Linux’ta sistem genelinde maksimum süreç sayısı /proc/sys/kernel/pid_max ile sınırlıdır. Bu değer dolduğunda yeni süreç oluşturamazsınız. fork() çağrıları başarısız olmaya başlar. Web sunucunuz yeni bağlantı kabul edemez hale gelir. Veritabanı yeni sorgu thread’i açamaz. Sistem çalışıyor görünse de aslında kilitlenmiş gibi davranır.

Gerçek bir senaryo: Bir müşterimizin PHP-FPM sunucusunda her gün belirli saatlerde servis yanıtsız kalıyordu. Araştırdığımızda göründü ki hatalı bir cron job, her dakika bir shell script çalıştırıyor; script PHP süreçleri yaratıyor ama ebeveyn script bu süreçleri beklemiyor. Günler içinde binlerce zombie birikmişti.

Zombie Süreçleri Tespit Etmek

ps Komutu ile Tespit

En klasik yöntem ps komutudur. Zombie süreçler çıktıda Z durum koduyla veya etiketiyle görünür.

ps aux | grep Z

Bu komut her Z içeren satırı getirir ama “grep” kelimesinin kendisi de Z içerdiğinden biraz gürültülü olabilir. Daha temiz bir çıktı için:

ps aux | awk '$8 == "Z"'

Burada $8 sütunu process state’i temsil eder. Sadece durumu Z olan satırları filtreler.

Daha detaylı bilgi için:

ps -el | grep Z

Bu komutun çıktısında şöyle satırlar görürsünüz:

# Örnek çıktı:
# Z  5 27 18342 18300  0  80   0 -     0 exit   ?        00:00:00 [php] <defunct>

Burada 18342 zombie sürecin PID’i, 18300 ise ebeveyn sürecin PID’idir (PPID).

top ve htop ile Gerçek Zamanlı İzleme

top komutunun özet satırında zombie sayısını görebilirsiniz:

top

Çıktının üst kısmında şuna benzer bir satır görürsünüz:

Tasks: 245 total,   1 running, 244 sleeping,   0 stopped,   3 zombie

O 3 zombie rakamı sizi uyarır. top çalışırken o tuşuna basıp S=Z filtresi ekleyerek sadece zombie süreçleri listeleyebilirsiniz.

htop daha görsel bir deneyim sunar. F6 ile sütunlara göre sıralayabilir, STATE sütununa göre filtreleyebilirsiniz:

htop

htop içinde / tuşuyla arama yapıp defunct yazarsanız zombie süreçleri hızla bulursunuz.

/proc Dosya Sistemi ile Manuel İnceleme

Her sürecin /proc/[PID]/status dosyasında durum bilgisi bulunur:

cat /proc/18342/status | grep State

Çıktı şöyle görünür:

State:  Z (zombie)

Tüm zombie PID’leri bulmak için:

for pid in /proc/[0-9]*/status; do
    state=$(grep "^State:" "$pid" | awk '{print $2}')
    if [ "$state" = "Z" ]; then
        echo "Zombie PID: $(echo $pid | cut -d'/' -f3)"
        cat "$pid" | grep -E "Name:|Pid:|PPid:|State:"
        echo "---"
    fi
done

Bu script size her zombie sürecin adını, PID’ini ve PPID’ini gösterir.

Zombie Sayısını Anlık İzlemek

Sisteminizdeki zombie sayısını tek komutla öğrenmek için:

ps aux | awk '$8=="Z" {count++} END {print "Zombie count:", count}'

Ya da çok daha kısa:

ps -eo stat | grep -c Z

Bunu bir monitoring scripti olarak kullanabilir, belirli eşiğin üzerine çıktığında alert alabilirsiniz.

Ebeveyn Süreci Bulmak

Zombie süreci silmek için doğrudan kill komutu işe yaramaz çünkü süreç zaten ölü. Asıl yapmanız gereken ebeveyn süreci bulmak ve ona SIGCHLD sinyali göndermek ya da onu yeniden başlatmak.

Zombie sürecin ebeveynini bulmak:

# Zombie PID'in PPID'ini öğren
ps -o ppid= -p 18342

Ya da daha kapsamlı:

ps -el | awk '$8=="Z" {print "Zombie PID: "$4, "Parent PID: "$5}'

Ebeveyn süreci bulduktan sonra onun ne olduğunu öğrenmek için:

ps -p 18300 -o pid,ppid,cmd,stat

Zombie Süreçleri Temizlemek

Yöntem 1: Ebeveyne SIGCHLD Göndermek

En nazik yöntem budur. SIGCHLD sinyali, ebeveyn sürece “çocuklarını topla” mesajı gönderir. Ebeveyn bu sinyale doğru yanıt veriyorsa zombie süreçler temizlenir.

kill -SIGCHLD 18300

18300, ebeveyn sürecin PID’i. Sinyal gönderdikten sonra zombie’nin kaybolup kaybolmadığını kontrol edin:

ps aux | awk '$8=="Z"'

Yöntem 2: Ebeveyn Süreci Yeniden Başlatmak

Ebeveyn süreç SIGCHLD sinyaline yanıt vermiyorsa veya bu sinyali handle etmiyorsa, bir üst seçenek servis yöneticisinden süreci yeniden başlatmaktır:

# systemd ile
systemctl restart uygulama-servisi

# Klasik yöntemle
kill -HUP 18300

SIGHUP genellikle bir süreci konfigürasyonunu yeniden yüklemesi için kullanılır ve pek çok uygulama buna yanıt olarak child süreçlerini düzgünce temizler.

Yöntem 3: Ebeveyn Süreci Öldürmek

Eğer ebeveyn süreç tamamen hatalıysa ve yeniden başlatma işe yaramıyorsa, onu öldürmek en doğru çözümdür. Ebeveyn ölünce tüm zombie çocuklar init (PID 1) veya systemd tarafından evlat edinilir ve temizlenir.

kill -9 18300

Bu noktada dikkatli olun. Ebeveyn süreç kritik bir servisin ana süreci olabilir. Önce ne olduğunu iyice anlayın.

Yöntem 4: Tüm Zombie’lerin Ebeveynlerini Toplu Temizlemek

Birden fazla zombie varsa şu script işe yarar:

#!/bin/bash

echo "Zombie süreçler aranıyor..."

zombie_pids=$(ps aux | awk '$8=="Z" {print $2}')

if [ -z "$zombie_pids" ]; then
    echo "Zombie süreç bulunamadı."
    exit 0
fi

echo "Bulunan zombie PID'ler: $zombie_pids"

for zpid in $zombie_pids; do
    ppid=$(ps -o ppid= -p $zpid 2>/dev/null | tr -d ' ')
    if [ -n "$ppid" ] && [ "$ppid" != "1" ]; then
        echo "Zombie PID: $zpid -> Ebeveyn PID: $ppid'e SIGCHLD gönderiliyor"
        kill -SIGCHLD $ppid
    fi
done

sleep 2

remaining=$(ps aux | awk '$8=="Z"' | wc -l)
echo "Kalan zombie sayısı: $remaining"

Bu scripti çalıştırdığınızda zombie ebeveynlerine SIGCHLD gönderir ve 2 saniye bekledikten sonra durumu raporlar.

Gerçek Dünya Senaryoları

Senaryo 1: Hatalı Python Script

Bir data pipeline uygulaması düşünün. Python ile yazılmış, multiprocessing modülü kullanıyor ama join() çağrısı eksik:

# HATALI KOD - zombie üretir
import multiprocessing
import time

def worker():
    time.sleep(1)

while True:
    p = multiprocessing.Process(target=worker)
    p.start()
    # p.join() eksik! Zombie üretir
    time.sleep(0.5)

Bu kodu birkaç saat çalıştırdığınızda sisteminizi zombie’ye boğar. Doğru kullanım p.join() veya p.daemon = True ayarlamaktır.

Bunu tespit etmek için:

# En çok zombie çocuğa sahip ebeveyni bul
ps -el | awk '$8=="Z" {print $5}' | sort | uniq -c | sort -rn | head -5

Senaryo 2: Apache/PHP Ortamında

Web sunucularında özellikle CGI script’leri veya shell exec çağrıları zombie üretebilir. Şüphelendiğinizde:

# Apache worker'larının zombie durumunu kontrol et
ps aux | grep apache | head -20

# PHP-FPM zombie kontrolü
ps aux | grep php-fpm | awk '$8=="Z"'

# Sistemdeki tüm defunct süreçleri ebeveynleriyle göster
ps axo stat,ppid,pid,comm | awk '$1~/Z/ {print "PPID:"$2, "PID:"$3, "CMD:"$4}'

Senaryo 3: Docker Container İçinde

Docker container’larında zombie sorunu sıkça görülür çünkü container’larda genellikle PID 1’i alan uygulama, init görevlerini üstlenmez yani zombie’leri toplamaz.

# Container içinde zombie kontrolü
docker exec -it container_adi ps aux | awk '$8=="Z"'

# Tüm container'larda zombie ara
for container in $(docker ps -q); do
    count=$(docker exec $container ps aux 2>/dev/null | awk '$8=="Z"' | wc -l)
    if [ $count -gt 0 ]; then
        name=$(docker inspect --format='{{.Name}}' $container)
        echo "Container: $name -> Zombie: $count"
    fi
done

Docker’da çözüm için --init flagini kullanabilirsiniz:

docker run --init your-image

Ya da tini gibi bir init süreç yöneticisi kullanabilirsiniz. Bu, container içinde düzgün bir PID 1 sağlar ve zombie’leri otomatik toplar.

Monitoring ve Alert Kurulumu

Zombie süreçleri proaktif izlemek için basit bir bash monitoring scripti:

#!/bin/bash

THRESHOLD=10
LOG_FILE="/var/log/zombie_monitor.log"
ALERT_EMAIL="[email protected]"

zombie_count=$(ps -eo stat | grep -c "^Z")
timestamp=$(date '+%Y-%m-%d %H:%M:%S')

echo "$timestamp - Zombie count: $zombie_count" >> $LOG_FILE

if [ $zombie_count -gt $THRESHOLD ]; then
    echo "$timestamp - UYARI: $zombie_count zombie süreç tespit edildi!" >> $LOG_FILE
    
    # Detaylı bilgi kaydet
    echo "=== Zombie Detayları ===" >> $LOG_FILE
    ps axo stat,ppid,pid,comm | awk '$1~/Z/' >> $LOG_FILE
    
    # Mail gönder (mailutils kurulu olmalı)
    echo "Sunucuda $zombie_count zombie süreç tespit edildi. Log: $LOG_FILE" | 
        mail -s "UYARI: Zombie Süreç Alarmı" $ALERT_EMAIL
fi

Bu scripti cron’a ekleyin:

# Her 5 dakikada bir kontrol
*/5 * * * * /usr/local/bin/zombie_monitor.sh

Kernel ve Sistem Parametreleri

Zombie sorunuyla sık karşışıyorsanız bazı kernel parametrelerini incelemeniz faydalı olabilir:

# Maksimum process sayısını kontrol et
cat /proc/sys/kernel/pid_max

# Anlık süreç sayısını öğren
ps aux | wc -l

# Thread sayısı sınırını öğren
cat /proc/sys/kernel/threads-max

# Gerekirse pid_max değerini artır (kalıcı için sysctl.conf'a yaz)
echo "kernel.pid_max = 4194304" >> /etc/sysctl.conf
sysctl -p

Ancak pid_max artırmak asıl sorunu çözmez. Sadece biraz zaman kazandırır. Asıl çözüm zombie üreten uygulamayı düzeltmektir.

Zombie Oluşmasını Önlemek

Sistem yöneticisi olarak yapabilecekleriniz sınırlıdır çünkü zombie sorunu çoğunlukla uygulama kodundan kaynaklanır. Ama şunları yapabilirsiniz:

  • Uygulama geliştiricilerini bilgilendirin: wait(), waitpid() çağrılarının önemi hakkında.
  • Docker kullanıyorsanız --init flag ekleyin: Her container’da otomatik.
  • systemd servislerinde KillMode=control-group kullanın: Servis sonlandığında tüm çocuk süreçler de temizlenir.
  • Periyodik monitoring yapın: Yukarıdaki script gibi araçlarla zombie sayısını izleyin.
  • nohup ve disown kullanımına dikkat edin: Ebeveynden koparılan süreçler bazen beklenmedik davranışlar sergileyebilir.

Geliştiricilere yönelik temel öneri şudur: Her fork() çağrısından sonra mutlaka wait() çağrısı yapılmalı ya da SIGCHLD sinyal handler kurulmalıdır. Daemon process’lerde ise double-fork tekniği kullanılarak zombie riski minimize edilebilir.

Sonuç

Zombie süreçler, çoğu zaman gereksiz yere panik yaratır ama temelde anlaşılması ve yönetilmesi zor değildir. Birkaç tane zombie sisteminize zarar vermez; ama yüzlerce, binlerce zombie birikimine izin verirseniz işler sarpa sarabilir.

Özetle yapmanız gerekenler şunlardır: ps aux | awk '$8=="Z"' ile zombie’leri tespit edin, ebeveyn PID’i bulun, önce SIGCHLD deneyin, işe yaramazsa ebeveyn süreci yeniden başlatın veya öldürün. Uzun vadede ise monitoring kurun ve uygulamanızdaki sorunu kökten çözün.

En güzel yanı şu: Zombie süreçlerle savaşmak aslında sistemin sizi bir şey hakkında uyardığı anlamına gelir. Bir uygulama kötü davranıyor, bir servis hatalı çalışıyor. Bu sinyali doğru okursanız, daha büyük bir felaket olmadan önce müdahale edebilirsiniz. İyi sysadmin’lik de zaten bu değil mi?

Yorum yapın