Disk doldu uyarısı aldığınızda genellikle iş işten geçmiş olur. Servisler çökmüş, loglar şişmiş, geçici dosyalar her yere yayılmış durumda. Oysa birkaç saatlik çalışmayla bu sorunların büyük kısmını tamamen otomatik hale getirebilirsiniz. Cron ile log rotasyonu ve disk temizliği konusu, sysadmin’lerin “biliyorum ama hiç düzgünce yapmadım” dediği klasik konulardan biri. Bu yazıda sıfırdan, production ortamda kullanabileceğiniz bir sistem kuruyoruz.
Neden Cron ile Yapmalıyız?
Logrotate zaten var, systemd timer da var, neden cron? Şu yüzden: Esneklik. Kendi yazdığınız script’leri, özel temizlik mantıklarını, farklı sunucular için farklı politikaları cron ile çok daha kolay yönetebilirsiniz. Logrotate harika bir araç ama “şu dizindeki 30 günden eski dosyaları sil, ama bugün erişilenlere dokunma” gibi özel durumları handle etmek için yine script yazmanız gerekiyor. O noktada cron devreye giriyor.
Bunun yanında, ne zaman çalıştığını, ne yaptığını ve ne döndürdüğünü tam olarak kontrol etmek istiyorsanız kendi script’iniz her zaman kazanır.
Cron Temellerini Hatırlayalım
Cron syntax’ını herkese tekrar anlatmak istemiyorum ama özellikle log rotasyonu bağlamında sık kullanılan birkaç zamanlamayı netleştirelim.
# Crontab formatı: dakika saat gun ay haftanin-gunu komut
# Her gece 02:30'da çalıştır
30 2 * * * /opt/scripts/log-cleanup.sh
# Her Pazar 03:00'da haftalık temizlik
0 3 * * 0 /opt/scripts/weekly-cleanup.sh
# Her ayın 1'i 04:00'da aylık arşivleme
0 4 1 * * /opt/scripts/monthly-archive.sh
# Her 6 saatte bir disk kontrolü
0 */6 * * * /opt/scripts/disk-check.sh
Crontab düzenlemek için crontab -e kullanıyoruz. System-wide job’lar için ise /etc/cron.d/ dizinine dosya bırakabilir veya /etc/crontab dosyasını düzenleyebilirsiniz. Ben production’da her zaman /etc/cron.d/ tercih ediyorum, çünkü her servis için ayrı dosya tutmak yönetimi kolaylaştırıyor.
Disk Durumunu İzleme Script’i
Her şeyden önce, neyin ne kadar yer kapladığını bilmemiz lazım. Basit ama işe yarayan bir monitoring script’iyle başlayalım.
#!/bin/bash
# /opt/scripts/disk-monitor.sh
THRESHOLD=80
LOG_FILE="/var/log/disk-monitor.log"
EMAIL="[email protected]"
HOSTNAME=$(hostname)
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] Disk kontrol başladı - $HOSTNAME" >> "$LOG_FILE"
df -h | grep -vE '^Filesystem|tmpfs|cdrom|udev' | while read line; do
USAGE=$(echo "$line" | awk '{print $5}' | sed 's/%//')
PARTITION=$(echo "$line" | awk '{print $6}')
DEVICE=$(echo "$line" | awk '{print $1}')
if [ "$USAGE" -ge "$THRESHOLD" ]; then
MESSAGE="UYARI: $HOSTNAME üzerinde $PARTITION ($DEVICE) %$USAGE dolu!"
echo "[$DATE] $MESSAGE" >> "$LOG_FILE"
# Email bildirimi
echo "$MESSAGE" | mail -s "Disk Uyarısı: $HOSTNAME" "$EMAIL"
fi
done
echo "[$DATE] Disk kontrol tamamlandı" >> "$LOG_FILE"
Bu script’i her 6 saatte bir çalıştırıyorum. %80 eşiğini geçen partition için mail atıyor. Basit, güvenilir, işe yarıyor.
Log Rotasyon Script’i
Logrotate’in halledemediği veya halletmesini istemediğiniz durumlar için kendi rotasyon script’inizi yazın. Özellikle uygulama logları için bu çok daha esnek bir yaklaşım.
#!/bin/bash
# /opt/scripts/log-rotate.sh
LOG_DIR="/var/log/myapp"
ARCHIVE_DIR="/var/log/myapp/archive"
MAX_LOG_AGE=7 # 7 günden eski logları arşivle
MAX_ARCHIVE_AGE=30 # 30 günden eski arşivleri sil
MAX_LOG_SIZE=100 # 100 MB üzeri logları hemen döndür
DATE=$(date '+%Y%m%d_%H%M%S')
SCRIPT_LOG="/var/log/log-rotate.log"
# Arşiv dizini yoksa oluştur
mkdir -p "$ARCHIVE_DIR"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$SCRIPT_LOG"
}
log_message "Log rotasyon başladı"
# Büyük logları hemen döndür (boyut kontrolü)
find "$LOG_DIR" -maxdepth 1 -name "*.log" -type f | while read logfile; do
SIZE_MB=$(du -m "$logfile" | awk '{print $1}')
BASENAME=$(basename "$logfile" .log)
if [ "$SIZE_MB" -ge "$MAX_LOG_SIZE" ]; then
ARCHIVE_NAME="${ARCHIVE_DIR}/${BASENAME}_${DATE}.log.gz"
gzip -c "$logfile" > "$ARCHIVE_NAME"
: > "$logfile" # Dosyayı truncate et, silme!
log_message "Boyut nedeniyle döndürüldü: $logfile (${SIZE_MB}MB) -> $ARCHIVE_NAME"
fi
done
# Eski logları arşivle
find "$LOG_DIR" -maxdepth 1 -name "*.log" -type f -mtime +"$MAX_LOG_AGE" | while read logfile; do
BASENAME=$(basename "$logfile" .log)
ARCHIVE_NAME="${ARCHIVE_DIR}/${BASENAME}_${DATE}.log.gz"
gzip -c "$logfile" > "$ARCHIVE_NAME"
rm -f "$logfile"
log_message "Arşivlendi: $logfile -> $ARCHIVE_NAME"
done
# Eski arşivleri temizle
DELETED_COUNT=$(find "$ARCHIVE_DIR" -name "*.gz" -mtime +"$MAX_ARCHIVE_AGE" | wc -l)
find "$ARCHIVE_DIR" -name "*.gz" -mtime +"$MAX_ARCHIVE_AGE" -delete
log_message "$DELETED_COUNT adet eski arşiv silindi"
log_message "Log rotasyon tamamlandı"
Önemli bir detay: Log dosyasını silmek yerine truncate ediyorum (> "$logfile"). Çünkü bazı uygulamalar açık dosya descriptor’ı üzerinden yazmaya devam eder, silerseniz log kaybı yaşarsınız. Truncate bu sorunu çözer.
Geçici Dosya Temizliği
/tmp ve /var/tmp dizinleri sessiz sedasız şişen yerlerdir. Özellikle yoğun web sunucularında PHP session dosyaları, upload artıkları ve çeşitli geçici dosyalar burada birikerek ciddi yer kaplar.
#!/bin/bash
# /opt/scripts/tmp-cleanup.sh
SCRIPT_LOG="/var/log/tmp-cleanup.log"
FREED_SPACE=0
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$SCRIPT_LOG"
}
get_dir_size() {
du -sm "$1" 2>/dev/null | awk '{print $1}'
}
log_message "Geçici dosya temizliği başladı"
# /tmp temizliği - 24 saatten eski dosyalar
TMP_BEFORE=$(get_dir_size /tmp)
find /tmp -type f -atime +1 -delete 2>/dev/null
find /tmp -type d -empty -not -name "tmp" -delete 2>/dev/null
TMP_AFTER=$(get_dir_size /tmp)
FREED=$((TMP_BEFORE - TMP_AFTER))
log_message "/tmp temizlendi: ${FREED}MB kazanıldı"
# /var/tmp temizliği - 7 günden eski dosyalar
VARTMP_BEFORE=$(get_dir_size /var/tmp)
find /var/tmp -type f -atime +7 -delete 2>/dev/null
VARTMP_AFTER=$(get_dir_size /var/tmp)
FREED=$((VARTMP_BEFORE - VARTMP_AFTER))
log_message "/var/tmp temizlendi: ${FREED}MB kazanıldı"
# PHP session temizliği (varsa)
if [ -d "/var/lib/php/sessions" ]; then
PHP_BEFORE=$(get_dir_size /var/lib/php/sessions)
find /var/lib/php/sessions -type f -mtime +1 -delete 2>/dev/null
PHP_AFTER=$(get_dir_size /var/lib/php/sessions)
FREED=$((PHP_BEFORE - PHP_AFTER))
log_message "PHP sessions temizlendi: ${FREED}MB kazanıldı"
fi
# Core dump temizliği
if [ -d "/var/crash" ]; then
find /var/crash -type f -mtime +7 -delete 2>/dev/null
log_message "Core dump dosyaları temizlendi"
fi
log_message "Geçici dosya temizliği tamamlandı"
Paket Yöneticisi Önbellek Temizliği
APT önbelleği ve eski kernel’lar diskin farkında olmadan nasıl dolduğunun iyi bir örneğidir. Bunu da otomasyona alalım.
#!/bin/bash
# /opt/scripts/package-cache-cleanup.sh
SCRIPT_LOG="/var/log/package-cleanup.log"
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$SCRIPT_LOG"
}
# Root kontrolü
if [ "$(id -u)" -ne 0 ]; then
echo "Bu script root olarak çalıştırılmalıdır!"
exit 1
fi
log_message "Paket önbellek temizliği başladı"
# APT önbellek boyutunu kaydet
APT_BEFORE=$(du -sm /var/cache/apt/archives/ 2>/dev/null | awk '{print $1}')
# APT temizlik
apt-get autoremove -y --purge >> "$SCRIPT_LOG" 2>&1
apt-get autoclean >> "$SCRIPT_LOG" 2>&1
apt-get clean >> "$SCRIPT_LOG" 2>&1
APT_AFTER=$(du -sm /var/cache/apt/archives/ 2>/dev/null | awk '{print $1}')
FREED=$((APT_BEFORE - APT_AFTER))
log_message "APT önbelleği temizlendi: ${FREED}MB kazanıldı"
# Eski kernel temizliği (mevcut kernel'ı koru)
CURRENT_KERNEL=$(uname -r)
log_message "Mevcut kernel: $CURRENT_KERNEL"
# Kullanılmayan kernel'ları listele ve kaldır
OLD_KERNELS=$(dpkg --list | grep -E 'linux-(image|headers|modules)-[0-9]' |
grep -v "$CURRENT_KERNEL" |
grep -v "linux-image-generic" |
awk '{print $2}')
if [ -n "$OLD_KERNELS" ]; then
log_message "Eski kernel'lar kaldırılıyor: $OLD_KERNELS"
echo "$OLD_KERNELS" | xargs apt-get purge -y >> "$SCRIPT_LOG" 2>&1
else
log_message "Kaldırılacak eski kernel bulunamadı"
fi
log_message "Paket temizliği tamamlandı"
Bu script’i aylık çalıştırmanızı öneririm. Haftalık çalıştırırsanız sorun olmaz ama çok sık kernel temizliği yapmak pek anlam ifade etmiyor.
Hepsini Birleştiren Master Crontab
Şimdi bu script’lerin tamamını organize bir şekilde cron’a ekleyelim.
# /etc/cron.d/disk-maintenance
# Disk bakım ve log rotasyon job'ları
# Ortam değişkenleri
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
[email protected]
# Disk monitoring - Her 6 saatte bir
0 */6 * * * root /opt/scripts/disk-monitor.sh
# Log rotasyon - Her gece 02:00'da
0 2 * * * root /opt/scripts/log-rotate.sh
# Geçici dosya temizliği - Her gece 03:00'da
0 3 * * * root /opt/scripts/tmp-cleanup.sh
# Paket önbellek temizliği - Her ayın 1'i 04:00'da
0 4 1 * * root /opt/scripts/package-cache-cleanup.sh
# Haftalık disk raporu - Her Pazartesi 08:00'da
0 8 * * 1 root /opt/scripts/weekly-disk-report.sh
/etc/cron.d/ altındaki dosyalarda kullanıcı belirtmek zorundasınız, unutmayın.
Haftalık Disk Raporu
İşler otomatik gidiyorken bile ne olduğunu bilmek istersiniz. Haftalık bir özet maili bunun için biçilmiş kaftan.
#!/bin/bash
# /opt/scripts/weekly-disk-report.sh
EMAIL="[email protected]"
HOSTNAME=$(hostname)
REPORT_FILE="/tmp/disk-report-$(date +%Y%m%d).txt"
{
echo "========================================"
echo "HAFTALIK DiSK RAPORU: $HOSTNAME"
echo "Tarih: $(date '+%d.%m.%Y %H:%M')"
echo "========================================"
echo ""
echo "--- DISK KULLANIMI ---"
df -h | grep -vE 'tmpfs|udev'
echo ""
echo "--- EN BÜYÜK DİZİNLER (Top 10) ---"
du -sh /* 2>/dev/null | sort -rh | head -10
echo ""
echo "--- /var ALTINDA EN BÜYÜKLER ---"
du -sh /var/* 2>/dev/null | sort -rh | head -10
echo ""
echo "--- SON 7 GÜNDE OLUŞTURULAN BÜYÜK DOSYALAR (>50MB) ---"
find / -xdev -type f -size +50M -mtime -7 2>/dev/null |
xargs du -sh 2>/dev/null | sort -rh | head -20
echo ""
echo "--- LOG ROTASYON ÖZETİ (son 7 gün) ---"
if [ -f "/var/log/log-rotate.log" ]; then
grep "$(date -d '7 days ago' '+%Y-%m-%d')" /var/log/log-rotate.log | tail -20
fi
echo ""
echo "--- iNODE KULLANIMI ---"
df -i | grep -vE 'tmpfs|udev'
echo ""
echo "========================================"
echo "Rapor sonu"
echo "========================================"
} > "$REPORT_FILE"
mail -s "Haftalık Disk Raporu: $HOSTNAME - $(date '+%d.%m.%Y')" "$EMAIL" < "$REPORT_FILE"
rm -f "$REPORT_FILE"
İnode kullanımını da rapora ekledim çünkü bu çok atlanan bir konu. Disk GB olarak boş görünür ama inode dolmuşsa yine yazamazsınız. Özellikle mail sunucuları ve küçük dosya üreten uygulamalarda sık karşılaşılan bir durum.
Script’leri Doğru Konuma Koymak ve İzinler
Script’lerinizi doğru şekilde ayarlamak kadar izinlerini doğru vermek de kritik.
# Script dizini oluştur
mkdir -p /opt/scripts
# Script'leri kopyala
cp disk-monitor.sh /opt/scripts/
cp log-rotate.sh /opt/scripts/
cp tmp-cleanup.sh /opt/scripts/
cp package-cache-cleanup.sh /opt/scripts/
cp weekly-disk-report.sh /opt/scripts/
# Sadece root çalıştırabilsin, başkası okuyamasın
chmod 700 /opt/scripts/*.sh
chown root:root /opt/scripts/*.sh
# Cron job dosyasının izni
chmod 644 /etc/cron.d/disk-maintenance
chown root:root /etc/cron.d/disk-maintenance
Script’lerinizi 777 yapmayın. 700 yeterli, hatta yeterinden fazla. Sadece root çalıştırmalı.
Gerçek Dünya Senaryosu: E-Ticaret Sunucusu
Bir e-ticaret sitesinde karşılaştığım tipik bir durumu paylaşayım. 500GB disk, her gece dolmaya başlıyor. Sebebine baktık:
- Nginx access logları günde 8GB büyüyor
- Kullanıcı upload’ları temizlenmiyor
- PHP error logları şişmiş
- Eski sipariş PDF’leri /var/www altında duruyor
Bu senaryo için yazılan script:
#!/bin/bash
# /opt/scripts/ecommerce-cleanup.sh
# E-ticaret sunucusu özel temizlik script'i
NGINX_LOG_DIR="/var/log/nginx"
UPLOAD_DIR="/var/www/html/uploads/temp"
PDF_DIR="/var/www/html/orders/pdf"
APP_LOG_DIR="/var/www/html/storage/logs"
ARCHIVE_BASE="/data/log-archives"
DATE=$(date '+%Y%m%d')
SCRIPT_LOG="/var/log/ecommerce-cleanup.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$SCRIPT_LOG"; }
log "=== E-ticaret temizlik başladı ==="
# Nginx loglarını döndür (servis restart olmadan)
for logfile in "$NGINX_LOG_DIR"/*.log; do
SIZE_MB=$(du -m "$logfile" 2>/dev/null | awk '{print $1}')
if [ "${SIZE_MB:-0}" -gt 500 ]; then
DESTDIR="$ARCHIVE_BASE/nginx/$DATE"
mkdir -p "$DESTDIR"
cp "$logfile" "$DESTDIR/$(basename $logfile)"
gzip "$DESTDIR/$(basename $logfile)"
: > "$logfile"
nginx -s reopen 2>/dev/null
log "Nginx log döndürüldü: $logfile (${SIZE_MB}MB)"
fi
done
# Geçici upload'ları temizle (1 saatten eski)
UPLOAD_BEFORE=$(du -sm "$UPLOAD_DIR" 2>/dev/null | awk '{print $1}')
find "$UPLOAD_DIR" -type f -mmin +60 -delete 2>/dev/null
UPLOAD_AFTER=$(du -sm "$UPLOAD_DIR" 2>/dev/null | awk '{print $1}')
log "Upload temp temizlendi: $((UPLOAD_BEFORE - UPLOAD_AFTER))MB kazanıldı"
# 90 günden eski sipariş PDF'lerini arşivle
PDF_ARCHIVE="$ARCHIVE_BASE/pdfs/$DATE"
mkdir -p "$PDF_ARCHIVE"
find "$PDF_DIR" -name "*.pdf" -mtime +90 -exec mv {} "$PDF_ARCHIVE/" ;
PDF_COUNT=$(ls "$PDF_ARCHIVE" 2>/dev/null | wc -l)
log "Arşivlenen PDF sayısı: $PDF_COUNT"
# Eski arşiv PDF'lerini sıkıştır
find "$ARCHIVE_BASE/pdfs" -name "*.pdf" -mtime +1 -exec gzip {} ;
# Uygulama logları
find "$APP_LOG_DIR" -name "*.log" -mtime +7 | while read f; do
gzip "$f" && mv "${f}.gz" "$ARCHIVE_BASE/app-logs/"
log "App log arşivlendi: $f"
done
log "=== E-ticaret temizlik tamamlandı ==="
log "Toplam disk durumu: $(df -h / | tail -1 | awk '{print $5}') kullanımda"
Bu script sayesinde o sunucudaki haftalık disk artışı 8GB’dan 400MB’a indi. Dramatik bir fark.
Script’lerde Hata Yönetimi
Production’da çalışan script’lerde hata yönetimi olmadan olmaz. Birkaç pratik öneri:
set -euo pipefail kullanın: Script herhangi bir komutta hata alınca durur, tanımsız değişken kullanımını yakalar, pipe hataları görünür hale gelir.
Lock dosyası kullanın: Uzun süren script’ler overlapping çalışmasın diye:
#!/bin/bash
LOCK_FILE="/var/run/disk-cleanup.lock"
# Lock kontrolü
if [ -f "$LOCK_FILE" ]; then
PID=$(cat "$LOCK_FILE")
if kill -0 "$PID" 2>/dev/null; then
echo "Script zaten çalışıyor (PID: $PID)"
exit 1
fi
fi
# Lock oluştur
echo $$ > "$LOCK_FILE"
# Script bitince lock'u kaldır
trap "rm -f $LOCK_FILE" EXIT INT TERM
set -euo pipefail
# Ana işlemler buraya gelir
echo "Script çalışıyor..."
Dry-run modu ekleyin: Test aşamasında gerçekten silmeden ne yapacağını görmek için:
DRY_RUN=${1:-false}
delete_file() {
if [ "$DRY_RUN" = "--dry-run" ]; then
echo "[DRY-RUN] Silinecekti: $1"
else
rm -f "$1"
log "Silindi: $1"
fi
}
Cron Job’larını Test Etmek
Cron job yazdınız, test etmek istiyorsunuz. Script’i direkt çalıştırmak yeterli değil, cron ortamı farklı. PATH değişkeni, HOME dizini, kullanıcı context’i hepsi farklı olabilir.
# Cron ortamını simüle ederek test et
env -i HOME=/root PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
/bin/bash /opt/scripts/log-rotate.sh
# Cron loglarını izle
tail -f /var/log/syslog | grep CRON
# Veya
journalctl -f -u cron
Bir script’i cron’a eklemeden önce mutlaka yukarıdaki şekilde cron ortamında test edin. PATH hatası en sık karşılaşılan sorun, bu yüzden script’lerinizde her zaman tam path kullanın (/bin/gzip gibi, sadece gzip değil).
Sonuç
Disk yönetimi ve log rotasyonu, sysadmin işinin “yaparım bir gün” dediğiniz o köşesinde uzun süre beklemeye devam eder, ta ki 3 gece sabahı disk doldu alarmıyla uyandığınıza kadar. Oysa burada anlattığım yapıyı bir kez kurduğunuzda, artık bu konuyu düşünmenize bile gerek kalmıyor.
Özetleyecek olursam: Disk monitor script’iyle erken uyarı sistemi kuruyorsunuz, log rotasyon ile logları kontrol altında tutuyorsunuz, geçici dosya temizliği ile sessiz sedasız büyüyen artıklardan kurtuluyorsunuz, haftalık rapor ile ne olduğunu takip ediyorsunuz. Bunların hepsi bir arada düzgün çalıştığında disk sorunları büyük ölçüde tarihe karışıyor.
Son olarak şunu söyleyeyim: Bu script’leri kopyalayıp yapıştırmadan önce kendi ortamınıza göre düzenleyin. Hangi dizinlerin kritik olduğunu, hangi dosyaların saklanması gerektiğini siz biliyorsunuz. Script’ler sadece iskelet, asıl değer sizin sunucunuzu tanımanızdan geliyor.