Cron ile Otomatik Çalışan Bash Scriptleri

Sistem yöneticiliğinde zamanın ne kadar kıymetli olduğunu hepimiz biliyoruz. Aynı komutu her gün elle çalıştırmak, log dosyalarını manuel temizlemek ya da yedekleme işlemlerini unutmamak için hatırlatıcı kurmak… Bunların hepsi gereksiz efor. İşte tam bu noktada cron ve bash scriptlerinin kombinasyonu hayat kurtarıyor. Bu yazıda cron’u sıfırdan anlayacak, gerçek dünya senaryolarında kullanılabilecek scriptler yazacak ve olası tuzaklardan nasıl kaçınacağımızı öğreneceğiz.

Cron Nedir ve Nasıl Çalışır?

Cron, Unix/Linux sistemlerinde belirli zamanlarda görev çalıştırmak için kullanılan bir zamanlayıcı daemon’udur. Sistem açıldığında arka planda çalışmaya başlar ve tanımladığınız zamanlamalara göre komutları veya scriptleri tetikler.

Cron görevleri crontab dosyalarında saklanır. Her kullanıcının kendi crontab’ı olabilir, bir de sistem genelinde geçerli olan /etc/crontab ve /etc/cron.d/ dizini vardır.

Crontab’ı düzenlemek için:

crontab -e    # Mevcut kullanıcının crontab'ını düzenle
crontab -l    # Mevcut crontab'ı listele
crontab -r    # Crontab'ı sil (dikkatli ol!)
crontab -u kullanici -e  # Başka bir kullanıcının crontab'ını düzenle

Cron Zaman Formatı

Cron ifadesi 5 alandan oluşur:

* * * * * komut
│ │ │ │ │
│ │ │ │ └── Haftanın günü (0-7, 0 ve 7 = Pazar)
│ │ │ └──── Ay (1-12)
│ │ └────── Ayın günü (1-31)
│ └──────── Saat (0-23)
└────────── Dakika (0-59)

Birkaç pratik örnek:

  • 0 2 * : Her gün saat 02:00’de
  • /15 * : Her 15 dakikada bir
  • 0 9 1-5 : Hafta içi her gün saat 09:00’da
  • 0 0 1 : Her ayın ilk günü gece yarısı
  • 30 6 0 : Her Pazar sabah 06:30’da

Özel ifadeler de kullanabilirsin:

  • @reboot: Sistem her başladığında
  • @daily: Her gün gece yarısı
  • @weekly: Her Pazar gece yarısı
  • @monthly: Her ayın ilk günü
  • @hourly: Her saatin başında

İlk Gerçek Senaryomuz: Log Temizleme Scripti

Sistemlerde log dosyaları zamanla disk alanını doldurur. Bunu otomatize etmek için basit ama etkili bir script yazalım.

#!/bin/bash
# /usr/local/bin/log_temizle.sh
# Eski log dosyalarını temizler

LOG_DIR="/var/log/uygulama"
ARSIV_DIR="/var/log/arsiv"
KACGUN=30
TARIH=$(date +%Y%m%d_%H%M%S)
SCRIPT_LOG="/var/log/log_temizle.log"

# Log fonksiyonu
log_yaz() {
    echo "[${TARIH}] $1" >> "$SCRIPT_LOG"
}

log_yaz "Temizleme başladı"

# Arsiv dizini yoksa oluştur
mkdir -p "$ARSIV_DIR"

# 30 günden eski log dosyalarını arşivle
find "$LOG_DIR" -name "*.log" -mtime +${KACGUN} -exec gzip {} ; 2>/dev/null
find "$LOG_DIR" -name "*.log.gz" -mtime +${KACGUN} -exec mv {} "$ARSIV_DIR/" ;

# 90 günden eski arşivleri sil
SILINEN=$(find "$ARSIV_DIR" -name "*.gz" -mtime +90 -delete -print | wc -l)

log_yaz "Silinen arşiv sayısı: $SILINEN"
log_yaz "Temizleme tamamlandı"

Bu scripti crontab’a eklemek için:

# Her gün gece 03:00'te çalıştır
0 3 * * * /usr/local/bin/log_temizle.sh

Scripti çalıştırılabilir yapmayı unutma:

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

Ortam Değişkenleri Tuzağı

Cron ile bash script yazarken en sık karşılaşılan sorun ortam değişkenleridir. Cron, terminalinden çok farklı bir ortamda çalışır. PATH, HOME gibi değişkenler ya tanımlı değildir ya da farklı değerlere sahiptir.

Bu yüzden scriptlerin başına her zaman şunu ekle:

#!/bin/bash
# Ortam değişkenlerini açıkça tanımla
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export HOME=/root

# Ya da crontab'ın başına ekle:
# PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Crontab’ın üst kısmına da ekleyebilirsin:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[email protected]

MAILTO değişkeni önemli. Cron bir görev çalıştırdığında ve çıktı üretildiğinde bu adrese mail atar. Eğer mail istemiyorsan çıktıyı /dev/null‘a yönlendir:

0 3 * * * /usr/local/bin/log_temizle.sh > /dev/null 2>&1

Ama dikkat: Bu şekilde hataları da göremezsin. Daha iyi bir yaklaşım, sadece başarılı çıktıyı bastırmak ve hataları loglamak:

0 3 * * * /usr/local/bin/log_temizle.sh >> /var/log/cron_output.log 2>&1

Yedekleme Scripti: Gerçek Dünya Senaryosu

İşte her sysadmin’in ihtiyaç duyduğu şey: Güvenilir bir yedekleme scripti. Bu örnek MySQL veritabanlarını yedekleyip uzak sunucuya gönderecek.

#!/bin/bash
# /usr/local/bin/db_yedekle.sh
# MySQL veritabanlarını yedekler ve uzak sunucuya gönderir

# Konfigürasyon
DB_USER="yedek_kullanici"
DB_PASS="guclu_sifre"
YEDEK_DIR="/var/backup/mysql"
UZAK_SUNUCU="backup.sirket.com"
UZAK_DIZIN="/backups/mysql"
SAKLAMA_SURESI=7  # gün
LOG_DOSYA="/var/log/db_yedekle.log"
TARIH=$(date +%Y%m%d_%H%M%S)

# Fonksiyonlar
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_DOSYA"
}

hata_kontrol() {
    if [ $? -ne 0 ]; then
        log "HATA: $1"
        # Slack bildirimi gönder (opsiyonel)
        curl -s -X POST "$SLACK_WEBHOOK" 
            -H 'Content-type: application/json' 
            --data "{"text":"DB Yedekleme Hatası: $1"}" 2>/dev/null
        exit 1
    fi
}

log "Yedekleme başlıyor..."

# Yedekleme dizini oluştur
mkdir -p "$YEDEK_DIR"

# Tüm veritabanlarını listele
VERITABANLARI=$(mysql -u"$DB_USER" -p"$DB_PASS" -e "SHOW DATABASES;" 2>/dev/null | 
    grep -Ev "(Database|information_schema|performance_schema|sys)")

for DB in $VERITABANLARI; do
    DOSYA_ADI="${YEDEK_DIR}/${DB}_${TARIH}.sql.gz"
    log "Yedekleniyor: $DB"
    
    mysqldump -u"$DB_USER" -p"$DB_PASS" 
        --single-transaction 
        --quick 
        --lock-tables=false 
        "$DB" | gzip > "$DOSYA_ADI"
    
    hata_kontrol "$DB yedeklemesi başarısız"
    log "$DB yedeklendi: $DOSYA_ADI"
done

# Uzak sunucuya gönder
log "Dosyalar uzak sunucuya aktarılıyor..."
rsync -avz --delete 
    -e "ssh -i /root/.ssh/yedek_key -o StrictHostKeyChecking=no" 
    "$YEDEK_DIR/" 
    "${UZAK_SUNUCU}:${UZAK_DIZIN}/"

hata_kontrol "rsync başarısız"

# Eski yedekleri temizle
find "$YEDEK_DIR" -name "*.sql.gz" -mtime +"$SAKLAMA_SURESI" -delete
log "Eski yedekler temizlendi"

log "Yedekleme başarıyla tamamlandı"

Bu scripti her gece 01:00’de çalıştırmak için:

0 1 * * * /usr/local/bin/db_yedekle.sh

Script Kilitleme: Çakışmaları Önle

Uzun süren bir script cron tarafından tekrar tetiklenirse ne olur? İki instance aynı anda çalışır ve kaos başlar. Bunu önlemek için flock ya da PID dosyası kullanabilirsin.

#!/bin/bash
# Kilit dosyası ile tek instance garantisi

KILIT_DOSYASI="/tmp/yedekleme.lock"
SCRIPT_ADI=$(basename "$0")

# flock ile kilit mekanizması
exec 200>"$KILIT_DOSYASI"

if ! flock -n 200; then
    echo "[$SCRIPT_ADI] Zaten çalışıyor, çıkılıyor..." >&2
    exit 1
fi

# PID'i kilit dosyasına yaz
echo $$ >&200

echo "Script çalışıyor, PID: $$"

# Uzun süren işlemler burada...
sleep 60

echo "İşlem tamamlandı"

# flock otomatik olarak script bitince kilidi bırakır

Alternatif olarak daha basit bir PID kontrolü:

#!/bin/bash
PID_DOSYA="/var/run/uzun_islem.pid"

if [ -f "$PID_DOSYA" ]; then
    PID=$(cat "$PID_DOSYA")
    if kill -0 "$PID" 2>/dev/null; then
        echo "Script zaten çalışıyor (PID: $PID)"
        exit 1
    else
        echo "Eski PID dosyası temizleniyor"
        rm -f "$PID_DOSYA"
    fi
fi

echo $$ > "$PID_DOSYA"
trap "rm -f $PID_DOSYA" EXIT

# İşlemler...
echo "Script çalışıyor"

Servis İzleme ve Otomatik Yeniden Başlatma

Bir servis çöktüğünde anında haberdar olmak ve otomatik olarak yeniden başlatmak için:

#!/bin/bash
# /usr/local/bin/servis_izle.sh
# Kritik servisleri izler, çökürlerse yeniden başlatır

SERVISLER=("nginx" "mysql" "redis" "php8.1-fpm")
MAIL_ALICI="[email protected]"
LOG="/var/log/servis_izle.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG"
}

bildirim_gonder() {
    local SERVIS=$1
    local DURUM=$2
    echo "$DURUM: $SERVIS servisi | $(hostname) | $(date)" | 
        mail -s "[$DURUM] $SERVIS servisi - $(hostname)" "$MAIL_ALICI"
}

for SERVIS in "${SERVISLER[@]}"; do
    if ! systemctl is-active --quiet "$SERVIS"; then
        log "UYARI: $SERVIS çalışmıyor, yeniden başlatılıyor..."
        
        systemctl start "$SERVIS"
        sleep 3
        
        if systemctl is-active --quiet "$SERVIS"; then
            log "BAŞARILI: $SERVIS yeniden başlatıldı"
            bildirim_gonder "$SERVIS" "ÇÖZÜLDÜ"
        else
            log "KRİTİK: $SERVIS başlatılamadı!"
            bildirim_gonder "$SERVIS" "KRİTİK HATA"
        fi
    else
        log "OK: $SERVIS çalışıyor"
    fi
done

Bunu her 5 dakikada bir çalıştır:

*/5 * * * * /usr/local/bin/servis_izle.sh

Disk Kullanımı Uyarı Scripti

Disk dolmadan önce uyarı almak her sysadmin’in ihtiyacı:

#!/bin/bash
# /usr/local/bin/disk_uyari.sh
# Disk kullanımı belirli eşiği geçince uyarı gönderir

ESIK=85  # Yüzde
MAIL_ALICI="[email protected]"
HOSTNAME=$(hostname)

while IFS= read -r SATIR; do
    KULLANIM=$(echo "$SATIR" | awk '{print $5}' | sed 's/%//')
    MOUNT=$(echo "$SATIR" | awk '{print $6}')
    DOSYA_SISTEMI=$(echo "$SATIR" | awk '{print $1}')
    
    if [ "$KULLANIM" -gt "$ESIK" ]; then
        MESAJ="UYARI: $HOSTNAME üzerinde $MOUNT bölümü %$KULLANIM dolu!n"
        MESAJ+="Dosya sistemi: $DOSYA_SISTEMIn"
        MESAJ+="$(df -h | grep "$MOUNT")"
        
        echo -e "$MESAJ" | mail -s "[DISK UYARI] $HOSTNAME - $MOUNT %$KULLANIM" "$MAIL_ALICI"
        echo -e "$MESAJ"
    fi
done < <(df -h | grep -vE '^(Filesystem|tmpfs|udev|devtmpfs)' | awk '{print $1, $2, $3, $4, $5, $6}')

Her saat başı çalıştırmak için:

0 * * * * /usr/local/bin/disk_uyari.sh

Cron Çıktılarını Doğru Yönetmek

Cron ile çalışan scriptlerin çıktılarını düzgün yönetmek önemli. İşte birkaç yaklaşım:

Tüm çıktıyı loga yaz:

0 2 * * * /usr/local/bin/script.sh >> /var/log/script.log 2>&1

Sadece hataları logla:

0 2 * * * /usr/local/bin/script.sh > /dev/null 2>> /var/log/script_hata.log

Tarihli log rotasyonu:

0 2 * * * /usr/local/bin/script.sh >> /var/log/script_$(date +%Y%m%d).log 2>&1

Dikkat: Crontab içinde % karakteri özel anlam taşır, kaçış karakteri % kullanman gerekir.

Logrotate ile entegrasyon için /etc/logrotate.d/cron-scriptler dosyası oluştur:

/var/log/script.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0640 root adm
}

Sistem Crontab ve Cron.d Kullanımı

Kişisel crontab yerine sistem genelinde geçerli olmasını istediğin işler için /etc/cron.d/ dizinini kullan. Bu dosyaların formatı biraz farklı, kullanıcı adı da belirtmen gerekiyor:

# /etc/cron.d/yedekleme-gorevleri
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Dakika  Saat  Gun  Ay  HaftaGunu  Kullanici  Komut
0         1     *    *   *          root        /usr/local/bin/db_yedekle.sh
*/5       *     *    *   *          root        /usr/local/bin/servis_izle.sh
0         0     1    *   *          www-data    /usr/local/bin/aylik_rapor.sh

Ayrıca hazır dizinleri de kullanabilirsin:

  • /etc/cron.hourly/: Her saat çalışan scriptler
  • /etc/cron.daily/: Her gün çalışan scriptler
  • /etc/cron.weekly/: Her hafta çalışan scriptler
  • /etc/cron.monthly/: Her ay çalışan scriptler

Bu dizinlere script koyarken dikkat edilmesi gerekenler:

  • Dosya adında nokta olmamalı (script.sh değil, script olmalı)
  • Çalıştırılabilir olmalı (chmod +x)
  • Shebang satırı olmalı

Cron İşlerini Test Etme ve Debug Etme

Cron ile en çok yaşanan sorun “terminalden çalışıyor ama cron’dan çalışmıyor” durumu. Bunu debug etmek için:

Cron ortamını simüle et:

# Cron'un kullandığı ortamı göster
env -i /bin/bash --login -c 'env'

# Cron ortamında script test et
env -i HOME=/root SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash /usr/local/bin/script.sh

Syslog’dan cron kayıtlarını takip et:

# Debian/Ubuntu
tail -f /var/log/syslog | grep CRON

# RHEL/CentOS
tail -f /var/log/cron

# Systemd tabanlı sistemler
journalctl -u cron -f
# ya da
journalctl -u crond -f

Cron daemon durumu:

systemctl status cron    # Debian/Ubuntu
systemctl status crond   # RHEL/CentOS

Güvenlik Konuları

Cron ile çalışan scriptlerde güvenliği ihmal etme:

Script izinleri:

# Sadece root çalıştırabilsin
chmod 700 /usr/local/bin/hassas_script.sh
chown root:root /usr/local/bin/hassas_script.sh

Hassas bilgileri sakla:

#!/bin/bash
# Şifreleri script içinde yazma, ayrı dosyadan oku
source /etc/script_config/db.conf
# ya da
DB_PASS=$(cat /etc/secrets/db_password)

Config dosyasının izinleri:

chmod 600 /etc/script_config/db.conf
chown root:root /etc/script_config/db.conf

Geçici dosyaları güvenli oluştur:

# mktemp kullan
TEMP_DOSYA=$(mktemp /tmp/islem_XXXXXX.tmp)
trap "rm -f $TEMP_DOSYA" EXIT

# İşlemler...
echo "veri" > "$TEMP_DOSYA"

Crontab erişimini sınırla:

# /etc/cron.allow dosyası varsa sadece bu kullanıcılar crontab kullanabilir
echo "root" > /etc/cron.allow
echo "deploy" >> /etc/cron.allow

# /etc/cron.deny ile belirli kullanıcıları engelle
echo "webapp" > /etc/cron.deny

Anacron: Kaçırılan Görevler İçin

Sunucu her zaman açık olmayabilir. Dizüstü bilgisayarlar ya da sık yeniden başlatılan sistemlerde cron zamanında çalışamaz. Anacron bu sorunu çözer, kaçırılan görevleri sistem açılınca çalıştırır.

/etc/anacrontab dosyası:

# Periyot  Gecikme(dk)  İş-Adı              Komut
1          5            cron.daily           run-parts /etc/cron.daily
7          25           cron.weekly          run-parts /etc/cron.weekly
@monthly   45           cron.monthly         run-parts /etc/cron.monthly
  • Periyot: Kaç günde bir çalışacak
  • Gecikme: Sistem açıldıktan kaç dakika sonra başlayacak
  • İş-Adı: Timestamp için benzersiz isim
  • Komut: Çalıştırılacak komut

Sonuç

Cron ve bash scriptlerinin kombinasyonu, sysadmin işinin en temel yapı taşlarından biri. Basit bir log temizlemeden karmaşık çok adımlı yedekleme süreçlerine kadar her şeyi otomatize edebilirsin.

Ama burada kritik birkaç noktayı asla atlama: Scriptlerin çıktılarını ve hatalarını mutlaka logla, ortam değişkenlerini açıkça tanımla, kilit mekanizmalarıyla çakışmaları önle ve güvenlik konularına dikkat et. Bir script yazıp crontab’a eklemek kolay, ama onu güvenilir, hata toleranslı ve sürdürülebilir yapmak işin asıl zor kısmı.

Son olarak şunu söyleyeyim: Yazdığın scripti mutlaka önce elle çalıştır, çıktılarını kontrol et, sonra cron’a ekle. “Cron’dan çalışmıyor ama terminalden çalışıyor” sorununu debug etmek, doğrudan cron’a ekleyip beklemekten çok daha az zaman alır. Sistemlerinde keyifli otomasyonlar!

Yorum yapın