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

Bir sunucuda her şey yolunda giderken terminale bakıyorsunuz ve garip bir şey dikkatinizi çekiyor: ps aux çıktısında Z durumuyla işaretlenmiş süreçler. Kill komutu işe yaramıyor, süreç gidip gelmiyor, sistemde asılı kalıyor. İşte bu, zombie süreç sorunuyla karşılaştığınızın en net göstergesi. Paniklemeden önce şunu bilin: zombie süreçler aslında göründükleri kadar tehlikeli değil, ama nasıl oluştukları ve nasıl temizleneceği konusunda sağlam bir anlayışa ihtiyacınız var.

Zombie Süreç Nedir?

Linux’ta bir süreç hayatını tamamladığında, kernel onun çıkış kodunu ve bazı istatistik bilgilerini process table’da tutar. Bu bilgileri parent process (üst süreç) okuyacak ve ardından kernel o sürecin kaydını temizleyecek. Ama parent süreç bu okuma işlemini yapmadan önce de child süreç ölmüşse, o child süreç “zombie” haline gelir.

Teknik olarak konuşursak: süreç çalışmasını bitirmiş (exit() çağrılmış), kaynakları serbest bırakılmış, ama process table’daki kaydı hala duruyor. wait() veya waitpid() çağrısını parent süreç yapana kadar bu kayıt orada kalmaya devam ediyor.

Bu yüzden zombie sürece defunct process de denir. ps çıktısında [defunct] ibaresiyle de görebilirsiniz.

Neden Tehlikeli Değil (Ama Görmezden de Gelinmemeli)

Zombie süreçler CPU veya bellek tüketmez. Çünkü süreç zaten ölmüş, sadece process table’da bir satır olarak var. Ancak her süreç bir PID kullanır ve sistemin kullanabileceği maksimum PID sayısı sınırlıdır. /proc/sys/kernel/pid_max dosyasına bakarsanız bu limiti görebilirsiniz, genellikle 32768 veya daha yüksek bir değer.

Yüzlerce hatta binlerce zombie süreç birikirse, yeni süreç başlatılamaz hale gelebilirsiniz. Bu da üretim ortamında ciddi bir sorun demek. Bir web sunucusunda yeni worker fork edilemiyor, cron job’lar başlatılamıyor, ssh bağlantısı kurulamıyor gibi durumlarla karşılaşabilirsiniz.

Zombie Süreçleri Tespit Etme

ps ile Temel Tespit

En hızlı kontrol yöntemi ps komutu:

ps aux | grep 'Z'

Ama bu yöntem yanlış pozitiflere yol açabilir, çünkü herhangi bir sütunda Z harfi geçen satırları da yakalar. Daha temiz bir yol:

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

Buradaki 8. sütun STAT sütunudur ve Z değeri zombie süreçleri gösterir. Daha da net bir çıktı için:

ps -eo pid,ppid,stat,comm | grep -w Z

Bu komut size PID, PPID (parent PID), durum ve komut adını verir. PPID burada kritik önem taşıyor, çünkü sorunun kaynağı parent süreç.

/proc ile Detaylı İnceleme

Bir zombie süreç bulduğunuzda, /proc filesystem üzerinden detay alabilirsiniz:

# Zombie sürecin detaylarını gör
cat /proc/<PID>/status

# Parent PID'i bul
cat /proc/<PID>/status | grep PPid

# Zombie sürecin ne zaman başladığını anlamak için
ls -la /proc/<PID>/

Gerçek dünya senaryosu: Bir müşterinin production PostgreSQL sunucusunda bağlantı sorunları yaşandığında, sorunun pg_dump çağrısı yapan bir backup script’inden kaynaklanan onlarca zombie süreç olduğunu /proc incelemesiyle tespit etmiştim. Script wait() çağrısını düzgün yapmıyordu.

top ve htop ile İzleme

top komutunu açıp Shift+Z ile zombie filter uygulayabilirsiniz. Ama htop çok daha kullanışlı:

htop

htop‘ta F4 ile filter açın ve Z yazın. Tüm zombie süreçler listede görünür. Üstelik süreç ağacını görmek için F5 kullanabilirsiniz, bu parent-child ilişkisini anlamayı çok kolaylaştırır.

Sistem genelinde zombie sayısını hızlıca öğrenmek için:

# Anlık zombie sayısı
cat /proc/loadavg

# Çıktı: 0.15 0.10 0.05 1/342 12453
# Buradaki 1/342 ifadesinde 342 toplam süreç sayısı

# Sadece zombie sayısı
ps -eo stat | grep -c Z

Monitoring Script ile Sürekli İzleme

Zombie oluşumunu izlemek için basit bir bash script yazabilirsiniz:

#!/bin/bash
# zombie_monitor.sh
# Zombie süreçleri izle ve log'la

THRESHOLD=10
LOGFILE="/var/log/zombie_monitor.log"

while true; do
    ZOMBIE_COUNT=$(ps -eo stat | grep -c Z)
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    
    if [ "$ZOMBIE_COUNT" -gt "$THRESHOLD" ]; then
        echo "[$TIMESTAMP] UYARI: $ZOMBIE_COUNT zombie süreç tespit edildi" | tee -a "$LOGFILE"
        
        # Detayları logla
        echo "[$TIMESTAMP] Zombie listesi:" >> "$LOGFILE"
        ps -eo pid,ppid,stat,comm | grep -w Z >> "$LOGFILE"
        
        # İsteğe bağlı: mail gönder
        # echo "Zombie uyarisi: $ZOMBIE_COUNT süreç" | mail -s "Zombie Alert" [email protected]
    fi
    
    sleep 60
done

Bu script’i systemd service olarak çalıştırabilir ya da cron’a ekleyebilirsiniz.

Zombie Süreçleri Temizleme

Yöntem 1: Parent Süreci Restart Etmek

Zombie süreçten kurtulmanın en temiz yolu parent süreci yeniden başlatmaktır. Parent süreç restart edildiğinde, tüm child zombie’leri de temizlenir.

Önce zombie’nin parent PID’ini bulun:

# Zombie PID'leri ve parent'larını listele
ps -eo pid,ppid,stat,comm | awk '$3 == "Z" {print "Zombie PID:"$1, "Parent PID:"$2, "Komut:"$4}'

Parent PID’i bulduktan sonra o süreci tanımlayın:

# Parent sürecin ne olduğunu öğren
ps -p <PARENT_PID> -o pid,comm,cmd

Eğer parent süreç bir uygulama sunucusuysa (örneğin nginx worker, Java uygulaması), o uygulamayı graceful şekilde restart edin:

# Örneğin parent bir nginx worker ise
sudo systemctl restart nginx

# Bir Java uygulamasıysa
sudo systemctl restart myapp.service

Yöntem 2: SIGCHLD Sinyali Göndermek

Parent süreci restart etmek mümkün değilse veya istemiyorsanız, parent’a SIGCHLD sinyali gönderebilirsiniz. Bu sinyal, “bir child sürecin durumu değişti, git kontrol et” anlamına gelir ve parent’ı wait() çağrısı yapmaya teşvik eder:

# Parent PID'e SIGCHLD gönder
kill -SIGCHLD <PARENT_PID>

# Veya
kill -17 <PARENT_PID>

Bu yöntem her zaman işe yaramaz, çünkü parent sürecin SIGCHLD handler’ını düzgün implemente etmiş olması gerekir. İyi yazılmış uygulamalar bunu zaten yapar.

Yöntem 3: Parent Süreci Kill Etmek

Parent süreci restart etme seçeneğiniz yoksa ve SIGCHLD işe yaramadıysa, parent süreci tamamen kill edebilirsiniz. Parent ölünce, zombie child’lar init (PID 1) veya systemd tarafından “evlat edinilir” ve onlar wait() çağrısını yaparak zombie’leri temizler:

# Önce SIGTERM dene
kill -SIGTERM <PARENT_PID>

# İşe yaramazsa SIGKILL
kill -SIGKILL <PARENT_PID>

Uyarı: Production ortamında parent süreci kill etmeden önce bunun servis kesintisine yol açıp açmayacağını değerlendirin. Genellikle bir uygulama sunucusunun worker sürecini kill ediyorsunuz demek bu.

Yöntem 4: Toplu Temizleme Script’i

Eğer çok sayıda zombie biriktiyse ve hepsi aynı parent’tan geliyorsa:

#!/bin/bash
# cleanup_zombies.sh

echo "Mevcut zombie süreçler:"
ps -eo pid,ppid,stat,comm | grep -w Z

echo ""
echo "Zombie parent'ları tespit ediliyor..."

# Her zombie için parent'ı bul ve SIGCHLD gönder
ps -eo pid,ppid,stat | awk '$3 == "Z" {print $2}' | sort -u | while read PPID; do
    if [ "$PPID" != "0" ] && [ "$PPID" != "1" ]; then
        echo "Parent PID $PPID'e SIGCHLD gönderiliyor..."
        kill -SIGCHLD "$PPID" 2>/dev/null
        
        # Biraz bekle
        sleep 2
        
        # Hala zombie varsa SIGTERM dene
        REMAINING=$(ps -eo ppid,stat | awk -v ppid="$PPID" '$1 == ppid && $2 == "Z"' | wc -l)
        if [ "$REMAINING" -gt 0 ]; then
            echo "Hala $REMAINING zombie var, SIGTERM gönderiliyor..."
            kill -SIGTERM "$PPID" 2>/dev/null
        fi
    fi
done

echo ""
echo "İşlem sonrası zombie sayısı: $(ps -eo stat | grep -c Z)"

Gerçek Dünya Senaryoları ve Çözümleri

Senaryo 1: Web Uygulaması Worker’ları

Bir Node.js uygulaması cluster modunda çalışıyor ve worker süreçleri düzgün temizlenmiyor. Bu çok yaygın bir durum.

# Node.js cluster worker zombie'lerini tespit et
ps aux | grep node | grep -v grep
ps -eo pid,ppid,stat,comm | grep -w Z

# Master process'e sinyal gönder
# Önce master PID'i bul
MASTER_PID=$(pgrep -f "node app.js" | head -1)
kill -SIGCHLD $MASTER_PID

Kodda düzeltme yapmak için developer’a şunu önerin: Her worker’ın exit event’ini dinleyip worker.process.kill() çağrısı yapmasını sağlayın.

Senaryo 2: Cron Job Kaynaklı Zombie’ler

Sık karşılaşılan başka bir durum: Bir cron job her dakika çalışıyor ama önceki çalışma bitmiyor ve zombie’ler birikiyor.

# Hangi cron job'ların zombie oluşturduğunu tespit et
ps -eo pid,ppid,stat,comm,cmd | grep -w Z

# Cron daemon'u kontrol et
ps -eo pid,ppid,stat,comm | grep cron

# Cron log'larını incele
tail -100 /var/log/syslog | grep CRON
tail -100 /var/log/cron.log

Çözüm olarak cron job’a flock ekleyerek eş zamanlı çalışmayı engelleyebilirsiniz:

# /etc/cron.d/myjob
* * * * * root /usr/bin/flock -n /var/lock/myjob.lock /usr/local/bin/myjob.sh

Senaryo 3: Docker Container Zombie’leri

Docker ortamında PID 1 olarak çalışan uygulama, child süreçleri düzgün reap etmiyorsa ciddi zombie problemi oluşur.

# Container içinde zombie kontrol
docker exec <container_id> ps aux | grep Z

# Container'ın PID 1'ini kontrol et
docker inspect <container_id> | grep -i "pid"

Bu sorunu çözmek için Dockerfile’a tini init sistemi ekleyin:

# Container'ı tini ile başlat
docker run --init myimage

# Ya da Dockerfile'a ekle
# FROM ubuntu:20.04
# RUN apt-get install -y tini
# ENTRYPOINT ["/usr/bin/tini", "--"]
# CMD ["myapp"]

Alternatif olarak Docker Compose’da:

# docker-compose.yml
services:
  myapp:
    image: myimage
    init: true

Kernel ve Sistem Parametreleri

Zombie sorununu sistematik olarak azaltmak için kernel parametrelerine de bakabilirsiniz:

# Mevcut PID limitini görüntüle
cat /proc/sys/kernel/pid_max

# Geçici olarak artır (root gerekli)
echo 65536 > /proc/sys/kernel/pid_max

# Kalıcı yapmak için /etc/sysctl.conf'a ekle
echo "kernel.pid_max = 65536" >> /etc/sysctl.conf
sysctl -p

Ayrıca sistemin genel süreç limitlerine bakın:

# Kullanıcı bazında süreç limiti
ulimit -u

# Sistem genelinde max süreç sayısı
cat /proc/sys/kernel/threads-max

# Root olarak limit artırma
echo "* soft nproc 65535" >> /etc/security/limits.conf
echo "* hard nproc 65535" >> /etc/security/limits.conf

Proaktif Önlemler

Zombie sorununu temizlemek kadar, oluşmasını önlemek de önemli. Birkaç pratik önlem:

Uygulama Geliştirme Tarafında

  • Parent süreçlerin her zaman wait() veya waitpid() çağırması gerektiğini developer ekibinizle paylaşın.
  • Uzun süre çalışan daemon’larda SIGCHLD handler’ı mutlaka implement edilmeli.
  • Fork-exec pattern kullanılıyorsa, her fork işleminin bir wait ile eşleştiğinden emin olun.

Sistem Tarafında

# Systemd servislerinde KillMode ayarını kontrol et
# /etc/systemd/system/myservice.service içinde:
# [Service]
# KillMode=control-group

# Servisi reload et
systemctl daemon-reload

İzleme ve Alarm Kurma

Prometheus ve node_exporter kullanıyorsanız, zombie süreç metriği zaten mevcut:

# node_exporter zombie metriği
# node_processes_state{state="Z"}

# Basit bir Prometheus alert rule:
# - alert: HighZombieProcesses
#   expr: node_processes_state{state="Z"} > 20
#   for: 5m
#   labels:
#     severity: warning

Zabbix kullanıyorsanız da benzer bir user parameter ekleyebilirsiniz:

# /etc/zabbix/zabbix_agentd.conf.d/zombie.conf
UserParameter=zombie.count,ps -eo stat | grep -c Z

Sonuç

Zombie süreçler, Linux sistem yönetiminde sık karşılaşılan ama genellikle yanlış anlaşılan bir konudur. Birkaç zombie görmek normal; bunlar genellikle geçici ve sistematik bir sorun olmadan kendiliğinden temizlenir. Asıl alarm verilmesi gereken durum, zombie sayısının sürekli artması veya PID tükenmesi riskinin ortaya çıkmasıdır.

Özetlemek gerekirse süreç şu şekilde olmalı: ps veya htop ile zombie tespiti, ps -eo ile parent PID’in belirlenmesi, önce SIGCHLD ile yumuşak temizleme, gerekirse parent restart, son çare olarak parent kill. Uzun vadede ise uygulama kodunda wait() çağrılarının doğru kullanımı ve monitoring/alarm altyapısı kurulması en sağlıklı yaklaşım.

Docker ve container ortamlarında --init flag’ini veya tini kullanmayı alışkanlık haline getirin. Bu tek adım, container kaynaklı zombie problemlerinin büyük çoğunluğunu ortadan kaldırır. Sonuçta iyi bir sysadmin, sorunları çözmekten çok oluşmadan önlemekle övünür.

Benzer Konular

Bir yanıt yazın

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