Systemd Timer ile Cron’a Modern Alternatif

Yıllardır cron kullanıyorsanız, muhtemelen şu soruları en az bir kez kendinize sordunuz: “Bu job çalıştı mı?”, “Neden çıktı göremiyorum?”, “Bağımlılıkları nasıl yöneteceğim?” Cron, onlarca yıldır görev zamanlama işinin temel taşı oldu, ama modern sistemlerin ihtiyaçlarına cevap vermekte zorlandığı yerler var. İşte tam bu noktada systemd timer’lar devreye giriyor. Systemd zaten büyük ihtimalle sunucunuzda çalışıyor, logları var, bağımlılık yönetimi var ve servis entegrasyonu var. Hadi bu araçla tanışalım.

Neden Systemd Timer?

Cron’u tamamen kötülemek niyetinde değilim. Basit, evrensel, her Linux dağıtımında çalışır. Ama production ortamında karşılaştığım bazı acı gerçekler var:

  • Log yönetimi yok: Cron job’ı çalıştı ama ne oldu? Varsayılan olarak sadece mail göndermeye çalışır. Mail altyapısı yoksa çıktı kaybolur.
  • Bağımlılık yönetimi yok: Network gelene kadar bekle, bir servis ayağa kalkana kadar çalışma gibi koşullar tanımlamak için hacks gerekir.
  • Başarısızlık takibi zor: Hangi job başarısız oldu, ne zaman? Cron bunu yönetmez.
  • Resource kontrolü yok: Bir backup scripti sistemi kasıp kavurursa cron’dan bunu sınırlandırmasını bekleyemezsiniz.
  • Servis yönetimiyle entegrasyon yok: systemctl status ile cron job durumuna bakamaz, systemctl stop ile çalışan bir job’ı durduramaz (kolayca).

Systemd timer’lar bu sorunların büyük kısmını çözer. Artık journal’da loglar var, systemctl status ile durum görebilirsiniz, cgroups sayesinde resource limitleri koyabilirsiniz.

Temel Kavramlar: Timer ve Service İkili

Systemd timer’ın çalışma mantığını anlamak için iki dosyadan oluşan bir yapı olduğunu bilmek gerekiyor:

  • .timer dosyası: Ne zaman çalışacağını tanımlar. Cron’daki zaman ifadesidir.
  • .service dosyası: Ne çalışacağını tanımlar. Asıl işi yapan kısım.

Bu ayrım başlangıçta biraz zahmetli görünür, ama uzun vadede çok mantıklı. Aynı servisi farklı timer’larla farklı zamanlarda çalıştırabilirsiniz. Ya da timer olmadan servisi elle tetikleyebilirsiniz.

Dosyalar genellikle iki yerde bulunur:

  • /etc/systemd/system/: Sistem genelindeki (root) timer ve servisler buraya gider.
  • ~/.config/systemd/user/: Kullanıcı bazlı timer’lar için. --user flag’i ile yönetilir.

İlk Timer’ınızı Yazmak

Klasik bir örnekle başlayalım: Her gece yedekleme yapan bir script.

Önce servisi yazıyoruz:

# /etc/systemd/system/gece-yedek.service
[Unit]
Description=Gece Yarisi Veritabani Yedeği
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/veritabani-yedek.sh
StandardOutput=journal
StandardError=journal

Şimdi timer dosyası:

# /etc/systemd/system/gece-yedek.timer
[Unit]
Description=Her Gece 02:30'da Veritabani Yedeği Al

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
AccuracySec=1min

[Install]
WantedBy=timers.target

Dikkat edin: After=postgresql.service ve Requires=postgresql.service ile backup scripti sadece PostgreSQL çalışıyorken devreye giriyor. Bunu cron’la yapmak için script içine kontrol koymanız gerekirdi.

Şimdi aktif edelim:

sudo systemctl daemon-reload
sudo systemctl enable --now gece-yedek.timer

Durumuna bakmak için:

systemctl status gece-yedek.timer
systemctl list-timers --all

list-timers komutu size tüm timer’ları, son çalışma zamanını ve bir sonraki tetiklenme zamanını gösterir. Cron’da bunu görmek için ne yapardınız? crontab -l sadece tanımı gösterir, son çalışma zamanını değil.

OnCalendar Sözdizimi

Cron’daki * ifadesine alışıksanız, OnCalendar başlangıçta biraz farklı görünür ama aslında çok daha okunabilir.

Genel format: GünAdı Yıl-Ay-Gün Saat:Dakika:Saniye

Bazı pratik örnekler:

# Her dakika
OnCalendar=minutely

# Her saat başı
OnCalendar=hourly

# Her gün gece yarısı
OnCalendar=daily

# Her Pazartesi sabah 08:00
OnCalendar=Mon *-*-* 08:00:00

# Her ayın 1'i ve 15'i saat 10:00
OnCalendar=*-*-1,15 10:00:00

# Hafta içi her gün 09:00 ile 18:00 arası her saat
OnCalendar=Mon..Fri *-*-* 09..18:00:00

# Her 5 dakikada bir
OnCalendar=*:0/5

Yazdığınız zaman ifadesinin doğru olup olmadığını test etmek için harika bir araç var:

systemd-analyze calendar "Mon..Fri *-*-* 08:30:00"

Bu komut bir sonraki kaç tetiklenme zamanını listeler. Cron expression test etmek için web sitesi açmak zorunda kalmaktan çok daha pratik.

Monotonic Timer’lar: Zaman Bazlı Değil, Süre Bazlı

Systemd timer’ların cron’dan önemli bir farkı daha var: monotonic timer’lar. Bunlar belirli bir saatten değil, sistemin durumuna göre çalışır.

# /etc/systemd/system/baslangic-kontrolu.timer
[Unit]
Description=Sistem Başladıktan 5 Dakika Sonra Kontrol

[Timer]
OnBootSec=5min
OnUnitActiveSec=1h

[Install]
WantedBy=timers.target

Bu timer sistemi açtıktan 5 dakika sonra ilk kez çalışır, sonra her 1 saatte bir tekrar eder.

Kullanabileceğiniz monotonic ifadeler:

  • OnBootSec: Sistem boot’tan sonra belirtilen süre geçince.
  • OnStartupSec: Systemd başladıktan sonra (container ortamlarında boot’tan farklı olabilir).
  • OnActiveSec: Bu timer aktif olduktan sonra.
  • OnUnitActiveSec: İlgili servis en son aktif olduktan sonra.
  • OnUnitInactiveSec: İlgili servis en son devre dışı kaldıktan sonra.

Persistent Timer: Kaçırılan Job’ları Telafi Etmek

Cron’da ciddi bir sorun vardır: Sistem kapalıyken zamanı gelen job’lar çalışmaz ve bir daha denenmez. Sabah 03:00’ta çalışması gereken backup, sunucu o saatte kapalıysa atlanır.

Systemd timer’da Persistent=true seçeneği bunu çözer. Sistem açıldığında, en son tetiklenme zamanından bu yana kaçırılan job varsa hemen çalıştırır.

[Timer]
OnCalendar=daily
Persistent=true

Bu özellikle laptop’larda ya da her zaman açık olmayan sistemlerde çok değerli. Ancak dikkatli kullanın: Uzun süredir kapalı bir sistem açıldığında birkaç backup job’ı aynı anda tetiklenebilir.

Gerçek Dünya Senaryosu: Log Rotasyonu ve Temizlik

Şimdi daha gerçekçi bir senaryo ele alalım. Uygulama loglarını sıkıştıran ve 30 günden eski olanları silen bir sistem kuralım.

# /usr/local/bin/log-temizle.sh
#!/bin/bash
set -euo pipefail

LOG_DIR="/var/log/myapp"
RETENTION_DAYS=30

# 7 günden eski logları sıkıştır
find "$LOG_DIR" -name "*.log" -mtime +7 ! -name "*.gz" -exec gzip {} ;

# 30 günden eski sıkıştırılmış logları sil
find "$LOG_DIR" -name "*.gz" -mtime +"$RETENTION_DAYS" -delete

echo "Log temizliği tamamlandı: $(date)"
echo "Kalan dosya sayısı: $(find $LOG_DIR -type f | wc -l)"
# /etc/systemd/system/log-temizle.service
[Unit]
Description=Uygulama Log Temizleme ve Rotasyon
After=local-fs.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/log-temizle.sh
User=logadmin
Group=logadmin

# Resource limitleri
CPUQuota=20%
MemoryMax=256M
IOWeight=50

# Güvenlik
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/log/myapp
# /etc/systemd/system/log-temizle.timer
[Unit]
Description=Haftalik Log Temizleme

[Timer]
OnCalendar=Sun 03:00:00
Persistent=true
RandomizedDelaySec=30min

[Install]
WantedBy=timers.target

Burada RandomizedDelaySec=30min kullandım. Bu özellik çok işe yarıyor: Eğer 100 sunucunuzda aynı timer varsa ve hepsi tam 03:00’ta çalışırsa shared storage ya da database’de yük patlaması yaşanabilir. Bu seçenek her sunucuya rastgele bir gecikme ekler, yükü dağıtır.

Ayrıca CPUQuota=20% ve MemoryMax=256M ile bu script sistemi kasmasın diye kaynak sınırı koyduk. Cron’da bunu yapmak için nice, ionice ve manuel memory kontrolü gerekir.

Log’ları Okumak: Journalctl ile Hata Ayıklama

Systemd timer’ların en büyük avantajlarından biri journal entegrasyonu. Bir job başarısız olduğunda veya beklenmedik çıktı ürettiğinde:

# Son çalışmanın loglarını gör
journalctl -u log-temizle.service

# Canlı takip
journalctl -u log-temizle.service -f

# Son 24 saatin logları
journalctl -u log-temizle.service --since "24 hours ago"

# Sadece hata ve üzeri
journalctl -u log-temizle.service -p err

# Timer ve servis birlikte
journalctl -u log-temizle.service -u log-temizle.timer

Cron’da bunu yapmak için ya script içinde kendi log sistemini kurmanız, ya syslog’a yönlendirmeniz ya da mail altyapısı kurmanız gerekirdi. Systemd’de her şey otomatik olarak journal’a yazılıyor.

Kullanıcı Seviyesinde Timer’lar

Root yetkisi gerektirmeyen işler için kullanıcı timer’larını tercih edin. Bir developer’ın kendi workspace’ini senkronlayan timer:

mkdir -p ~/.config/systemd/user/

# ~/.config/systemd/user/workspace-sync.service
cat > ~/.config/systemd/user/workspace-sync.service << 'EOF'
[Unit]
Description=Workspace Git Sync
After=network-online.target

[Service]
Type=oneshot
ExecStart=/home/ahmet/bin/git-sync-all.sh
StandardOutput=journal
StandardError=journal
EOF

# ~/.config/systemd/user/workspace-sync.timer
cat > ~/.config/systemd/user/workspace-sync.timer << 'EOF'
[Unit]
Description=Her 2 Saatte Workspace Sync

[Timer]
OnCalendar=*:0/120
Persistent=true

[Install]
WantedBy=default.target
EOF
# Kullanıcı timer'ını aktif et
systemctl --user daemon-reload
systemctl --user enable --now workspace-sync.timer
systemctl --user list-timers

# Kullanıcı oturumu olmasa da çalışması için (sunucularda gerekebilir)
sudo loginctl enable-linger ahmet

loginctl enable-linger komutu önemli. Bunu yapmazsanız kullanıcı logout olduğunda timer’lar durur. Sunucularda servis hesapları için bunu mutlaka aktifleştirin.

Gelişmiş Özellik: OnFailure ile Hata Bildirimi

Bir job başarısız olduğunda ne yapacaksınız? Systemd’de bunu servis seviyesinde yönetebilirsiniz.

# /etc/systemd/system/kritik-job.service
[Unit]
Description=Kritik İş Süreci
OnFailure=job-hata-bildir@%n.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/kritik-islem.sh
# /etc/systemd/system/[email protected]
[Unit]
Description=Job Hata Bildirimi - %i

[Service]
Type=oneshot
ExecStart=/usr/local/bin/hata-bildir.sh %i
# /usr/local/bin/hata-bildir.sh
#!/bin/bash
JOB_NAME="$1"
HOSTNAME=$(hostname)
ZAMAN=$(date '+%Y-%m-%d %H:%M:%S')

# Son 50 satır logu al
LOG=$(journalctl -u "$JOB_NAME" -n 50 --no-pager)

# Slack webhook veya mail gönder
curl -s -X POST "$SLACK_WEBHOOK_URL" 
  -H 'Content-type: application/json' 
  -d "{
    "text": "HATA: $JOB_NAME başarısız oldu!",
    "attachments": [{
      "color": "danger",
      "text": "Sunucu: $HOSTNAMEnZaman: $ZAMANnLog:n```$LOG```"
    }]
  }"

Bu yapıyla herhangi bir kritik job başarısız olduğunda anında Slack bildirimi alırsınız. Cron’da bunu implement etmek için her script’e manuel hata handling eklemeniz gerekirdi.

Cron’dan Timer’a Geçiş Referansı

Varolan cron job’larınızı timer’a çevirirken zaman ifadelerini nasıl karşılık getireceğinizi bilmek önemli.

Yaygın cron ifadelerinin systemd karşılıkları:

  • (her dakika): OnCalendar=minutely ya da OnCalendar=:*:00
  • 0 (her saat başı): OnCalendar=hourly
  • 0 0 * (her gece yarısı): OnCalendar=daily
  • 0 2 (her gece 02:00): OnCalendar=-- 02:00:00
  • 0 9 1-5 (hafta içi 09:00): OnCalendar=Mon..Fri --* 09:00:00
  • 0 0 1 (her ayın 1’i): OnCalendar=--01 00:00:00
  • /15 (her 15 dakika): OnCalendar=:0/15
  • 0 0 0 (her Pazar): OnCalendar=weekly ya da OnCalendar=Sun 00:00:00

Geçişi kolaylaştırmak için basit bir helper script yazabilirsiniz:

#!/bin/bash
# mevcut-cron-listele.sh
# Tüm kullanıcıların crontab'larını listeler ve timer şablonu önerir

echo "=== Sistem Crontab (/etc/crontab) ==="
cat /etc/crontab 2>/dev/null

echo -e "n=== Cron.d Dizini ==="
ls -la /etc/cron.d/ 2>/dev/null

echo -e "n=== Kullanıcı Crontab'ları ==="
for user in $(cut -d: -f1 /etc/passwd); do
    crontab_content=$(crontab -u "$user" -l 2>/dev/null)
    if [ -n "$crontab_content" ]; then
        echo "--- $user ---"
        echo "$crontab_content"
    fi
done

echo -e "n=== Mevcut Systemd Timer'lar ==="
systemctl list-timers --all

Timer Yönetimi: Günlük Operasyonlar

Timer’larla çalışırken en sık kullandığım komutlar:

# Tüm timer'ları listele (en yakın çalışma zamanına göre sıralı)
systemctl list-timers

# Tüm timer'lar (aktif olmayanlar dahil)
systemctl list-timers --all

# Timer'ı devre dışı bırakmadan durdur
systemctl stop gece-yedek.timer

# Timer'ı şimdi manuel tetikle (test için harika)
systemctl start gece-yedek.service

# Timer'ı tamamen devre dışı bırak
systemctl disable --now gece-yedek.timer

# Son çalışma bilgisi
systemctl show gece-yedek.service --property=ActiveEnterTimestamp

# Timer'ın ne zaman tetikleneceğini gör
systemctl show gece-yedek.timer --property=NextElapseUSecRealtime

Özellikle systemctl start gece-yedek.service komutu çok değerli. Bir timer’ı gecesini beklemeye gerek yok, servisi direkt tetikleyip test edebilirsiniz. Cron’da bunu yapmak için ya saati değiştirmeniz ya da komutu elle çalıştırmanız gerekirdi.

Hangi Durumlarda Cron Hala Mantıklı?

Dürüst olmak gerekirse, systemd timer her zaman doğru tercih değil.

Cron’u tercih etmeniz gereken durumlar:

  • Taşınabilirlik kritikse: Alpine Linux, minimal container image’ları ya da BSD sistemlerinde çalışan scriptler için cron daha evrensel.
  • Çok basit, tek satırlık işler: Eğer script sadece bir dosyayı kopyalıyorsa ve log/dependency/resource limiti gerektirmiyorsa, cron daha az overhead’la aynı işi yapar.
  • Systemd olmayan sistemler: Docker container içinde sadece crond çalıştırmak bazen daha temiz bir yaklaşım.
  • Takım cron’a alışkınsa: Operasyonel sürtüşme de bir faktör. Herkesin bildiği cron yerine yeni bir şey öğretmek bazen değmez.

Ama modern bir Linux sunucusunda, özellikle systemd kullanan bir dağıtımda, timer’ların getirdiği avantajlar genellikle öğrenme maliyetini karşılar.

Sonuç

Systemd timer’lar cron’un yerini almak için değil, onu daha iyi yapmak için tasarlanmış araçlar. Eğer zaten systemd kullanan bir ortamda çalışıyorsanız, bu araçları kullanmamak için bir neden kalmıyor.

En büyük pratik faydalar günlük operasyonlarda kendini gösteriyor: Bir job neden başarısız oldu sorusuna journalctl ile saniyeler içinde cevap bulmak, systemctl list-timers ile tüm zamanlanmış işlerin durumunu tek ekranda görmek, production’da bir backup script’i disk I/O’yu mahvettiğinde IOWeight ile bunu anında sınırlamak.

Başlamak için büyük bir migration planı yapmak zorunda değilsiniz. Yeni yazacağınız timer’ları systemd ile yazın, eskilerini de kullandıkça birer birer geçirin. Özellikle kritik ve izlenmesi gereken job’lardan başlayın; onlarda journal entegrasyonunun değerini hemen göreceksiniz.

Cron hala çalışıyor, hala geçerli. Ama 2024’te yeni bir zamanlanmış görev yazıyorsanız, systemd timer’ı ilk seçenek olarak değerlendirmenizi öneririm.

Yorum yapın