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 bir0 9 1-5: Hafta içi her gün saat 09:00’da0 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.shdeğil,scriptolmalı) - Ç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!