Debian’da Zamanlı Görevler: cron ve systemd Timer Kullanımı

Sunucularda işlerin otomatik çalışmasını sağlamak, sistem yöneticiliğinin tam ortasında duran bir beceri. Geceleri uyurken yedeklerin alınması, log dosyalarının temizlenmesi, sertifika yenilemelerinin yapılması… Bunların hepsi zamanlı görevler sayesinde olur. Debian üzerinde bunu yapmanın iki ana yolu var: eski dost cron ve modern alternatif systemd timer. İkisi de güçlü, ama farklı senaryolarda birbirinden ayrışıyor. Bu yazıda her ikisini de gerçek dünya örnekleriyle ele alacağız.

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

cron, Unix dünyasının en köklü araçlarından biri. 1970’lerden gelen bir miras ama hâlâ gündelik kullanımda. Debian’da cron paketi varsayılan olarak kurulu gelir, cron daemon’u arka planda sürekli çalışır ve tanımlı görevleri zamanı geldiğinde tetikler.

Cron görevlerini iki farklı yerde tanımlayabilirsiniz:

  • Kullanıcı crontab’ı: crontab -e komutuyla her kullanıcı kendi görevlerini yönetir
  • Sistem crontab’ı: /etc/cron.d/ dizini ve /etc/crontab dosyası, root dahil sistem genelinde çalışır
  • Hazır dizinler: /etc/cron.daily/, /etc/cron.weekly/, /etc/cron.hourly/, /etc/cron.monthly/ klasörleri

crontab Sözdizimi

Bir cron satırı şu yapıya sahiptir:

* * * * * 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)

Kullanıcı crontab’ını düzenlemek için:

crontab -e

Mevcut crontab’ı listelemek için:

crontab -l

Başka bir kullanıcının crontab’ını root olarak listelemek için:

crontab -u www-data -l

Gerçek Dünya Örneği: Veritabanı Yedeği

Diyelim ki her gece saat 02:30’da PostgreSQL veritabanınızı yedeklemek istiyorsunuz:

# /etc/cron.d/postgres-backup dosyası
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
[email protected]

30 2 * * * postgres /usr/bin/pg_dump -Fc mydb > /var/backups/postgres/mydb_$(date +%Y%m%d).dump

Burada dikkat edilmesi gereken birkaç nokta var. /etc/cron.d/ altındaki dosyalarda hangi kullanıcıyla çalışacağını belirtmeniz gerekir, yukarıda postgres kullanıcısını belirttik. Ayrıca $(date +%Y%m%d) ifadesindeki yüzde işaretlerinin % şeklinde escape edilmesi zorunlu, aksi hâlde cron bunu yanlış yorumlar.

Gerçek Dünya Örneği: Log Temizleme Scripti

Uygulama logları zamanla disk doldurabiliyor. Haftalık temizlik için:

#!/bin/bash
# /usr/local/bin/temizle-loglar.sh

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

find "$LOG_DIR" -name "*.log" -mtime +$RETENTION_DAYS -delete
find "$LOG_DIR" -name "*.log.gz" -mtime +$RETENTION_DAYS -delete

echo "$(date): Log temizleme tamamlandı" >> /var/log/temizlik.log

Bu scripti çalıştırılabilir yapın:

chmod +x /usr/local/bin/temizle-loglar.sh

Ardından /etc/cron.d/log-temizlik dosyasına ekleyin:

# Her Pazar gece yarısı çalışır
0 0 * * 0 root /usr/local/bin/temizle-loglar.sh

cron’un Güçlü Özellikleri ve Sınırları

cron’un artıları açısından değerlendirirsek: basit sözdizimi, her Linux dağıtımında mevcut, kullanıcı bazlı yönetim ve düşük öğrenme eğrisi öne çıkar.

Ancak sınırları da gerçek. Görev çıktıları varsayılan olarak mail ile gelir, merkezi bir log mekanizması yoktur. Sistem açık değilse kaçırılan görevler çalışmaz. Görevler arasında bağımlılık tanımlanamaz. Saniye hassasiyetinde görev tanımlanamaz. İşte tam da bu noktada systemd timer devreye giriyor.

systemd Timer: Modern Yaklaşım

systemd timer’lar, systemd ekosisteminin bir parçası. Her timer bir .timer birimi ve ona karşılık gelen bir .service biriminden oluşur. Bu yapı daha güçlü bir esneklik sunuyor.

Timer Birimlerinin Anatomisi

Systemd timer’lar /etc/systemd/system/ dizininde oluşturulur. İki dosyaya ihtiyacınız var:

yedekal.service – Asıl işi yapan servis:

[Unit]
Description=Veritabanı Yedekleme Servisi
After=network.target postgresql.service

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

yedekal.timer – Zamanlama tanımı:

[Unit]
Description=Veritabanı Yedekleme Zamanlayıcısı
Requires=yedekal.service

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

[Install]
WantedBy=timers.target

Timer’ı etkinleştirmek ve başlatmak için:

systemctl daemon-reload
systemctl enable yedekal.timer
systemctl start yedekal.timer

OnCalendar Sözdizimi

systemd timer’ların en güçlü yanlarından biri, okunabilir zaman ifadeleri. Birkaç örnek:

  • daily: Her gün gece yarısı
  • weekly: Her Pazartesi gece yarısı
  • monthly: Ayın ilk günü
  • -* 02:30:00: Her gün 02:30’da
  • Mon,Wed,Fri -* 08:00:00: Pazartesi, Çarşamba, Cuma sabah 8’de
  • -1 00:00:00: Her ayın birinci günü gece yarısı
  • :00/15:00: Her 15 dakikada bir

Bir zaman ifadesini doğrulamak için harika bir araç:

systemd-analyze calendar "*-*-* 02:30:00"

Bu komut bir sonraki tetiklenme zamanını da gösterir.

Persistent=true Neden Önemli?

Bu parametre, cron’un yapamadığı bir şeyi yapar. Sunucu bir sebepten kapalıyken görev tetiklenme zamanını geçtiyse, sunucu tekrar açıldığında görevi hemen çalıştırır. Özellikle yedekleme ve raporlama görevleri için kritik bir özellik.

Gerçek Dünya Örneği: Let’s Encrypt Sertifika Yenileme

Certbot zaten kendi timer’ını kuruyor ama nasıl çalıştığını görmek öğretici:

systemctl status certbot.timer

Benzer bir şey kendiniz de yapabilirsiniz. SSL sertifika kontrolü için özel bir script yazalım:

#!/bin/bash
# /usr/local/bin/ssl-kontrol.sh

DOMAIN="sirketim.com"
WARN_DAYS=14
CERT_FILE="/etc/letsencrypt/live/$DOMAIN/cert.pem"

if [ ! -f "$CERT_FILE" ]; then
    echo "Sertifika dosyası bulunamadı: $CERT_FILE"
    exit 1
fi

EXPIRY=$(openssl x509 -enddate -noout -in "$CERT_FILE" | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

if [ "$DAYS_LEFT" -le "$WARN_DAYS" ]; then
    echo "UYARI: $DOMAIN sertifikasının süresi $DAYS_LEFT gün sonra doluyor!" | 
        mail -s "SSL Sertifika Uyarısı" [email protected]
fi

echo "$(date): $DOMAIN - $DAYS_LEFT gün kaldı"

Buna karşılık gelen systemd dosyaları:

# /etc/systemd/system/ssl-kontrol.service
[Unit]
Description=SSL Sertifika Kontrol Servisi

[Service]
Type=oneshot
ExecStart=/usr/local/bin/ssl-kontrol.sh
# /etc/systemd/system/ssl-kontrol.timer
[Unit]
Description=Günlük SSL Kontrol

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Çalışan Timer’ları İzlemek

systemd timer’ların en büyük avantajlarından biri merkezi loglama. Herhangi bir görevin çıktısına şu şekilde bakabilirsiniz:

# Tüm aktif timer'ları listele
systemctl list-timers --all

# Belirli bir servisin loglarına bak
journalctl -u yedekal.service

# Son 1 saatteki loglar
journalctl -u yedekal.service --since "1 hour ago"

# Gerçek zamanlı log takibi
journalctl -u yedekal.service -f

systemctl list-timers çıktısı son çalışma zamanını, bir sonraki çalışma zamanını ve kalan süreyi gösterir. Bu cron’da hiç olmayan bir şeffaflık.

cron mu, systemd Timer mı? Hangisini Seçmeli?

İkisi arasında seçim yaparken şu kriterlere bakabilirsiniz:

cron tercih edin eğer:

  • Basit, tek seferlik görevler tanımlıyorsanız
  • Kullanıcı bazlı görev yönetimi gerekiyorsa
  • Ekibinizde systemd’ye yabancı kişiler varsa
  • Mevcut eski sistem scriptleriniz cron’a entegre çalışıyorsa

systemd timer tercih edin eğer:

  • Detaylı log ve hata takibi istiyorsanız
  • Persistent=true ile kaçırılan görevlerin çalışması önemliyse
  • Görevin belirli bir servis başladıktan sonra çalışmasını istiyorsanız (After= direktifi)
  • Kaynak limitleri (CPU, bellek) uygulamak istiyorsanız
  • Görevin başarısız olduğunda yeniden denenmesini istiyorsanız

Hybrid Yaklaşım: Birden Fazla Saniye Hassasiyeti

cron’un yapamadığı ama zaman zaman ihtiyaç duyulan bir senaryo: dakikadan kısa aralıklarda çalıştırma. Diyelim ki bir health-check scripti her 30 saniyede bir çalışsın:

# /etc/systemd/system/healthcheck.timer
[Unit]
Description=30 Saniyede Bir Health Check

[Timer]
OnBootSec=30s
OnUnitActiveSec=30s

[Install]
WantedBy=timers.target
# /etc/systemd/system/healthcheck.service
[Unit]
Description=Uygulama Health Check

[Service]
Type=oneshot
ExecStart=/usr/local/bin/health-check.sh

Bunu cron ile yapmak için genellikle sleep komutlarıyla çirkin hack’lere başvurmak gerekir. systemd bu durumu temiz bir şekilde halleder.

Pratik İpuçları ve Dikkat Edilecekler

cron Ortam Değişkenleri Sorunu

cron görevleri çalışırken kullanıcının normal shell ortamını devralmaz. Bu yüzden script’lerin tam yol kullanması veya crontab dosyasının başında PATH tanımlanması gerekir:

# /etc/cron.d/uygulama-gorev
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/root
MAILTO=""

0 3 * * * root /usr/local/bin/bakim-scripti.sh >> /var/log/bakim.log 2>&1

MAILTO="" ile e-posta gönderimini kapatabilirsiniz. 2>&1 ise stderr’i stdout’a yönlendirerek tüm çıktıyı log dosyasına yazar.

systemd’de Kaynak Sınırlama

Yedekleme gibi ağır işler sunucuyu zorlayabilir. systemd servise kaynak limiti koymanızı sağlar:

[Service]
Type=oneshot
User=postgres
ExecStart=/usr/local/bin/veritabani-yedek.sh
CPUQuota=50%
MemoryMax=512M
IOWeight=10
Nice=19

Bu ayarlarla yedekleme scripti en düşük öncelikte çalışır, CPU’nun en fazla yarısını ve 512 MB belleği kullanabilir. Prodüksiyon sistemlerde bu kritik.

Başarısız Görevler İçin Bildirim

systemd’de bir görev başarısız olduğunda email göndermenin temiz bir yolu:

[Unit]
Description=Önemli Yedekleme
OnFailure=bildirim-gonder@%n.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/kritik-yedek.sh
# /etc/systemd/system/[email protected]
[Unit]
Description=Başarısızlık Bildirimi

[Service]
Type=oneshot
ExecStart=/usr/local/bin/bildirim-gonder.sh %i

Bu template servis yapısı sayesinde herhangi bir servisin başarısız olduğunda otomatik bildirim alırsınız.

Görev Çakışmasını Önleme

Uzun süren bir görev hâlâ çalışırken bir sonraki tetiklenme zamanı gelirse ne olur? systemd’de bu durumu kontrol altına alabilirsiniz:

[Service]
Type=oneshot
ExecStart=/usr/local/bin/uzun-surecli-script.sh

# Aynı anda birden fazla örnek çalışmasın

Bunun için servis dosyasına şunu ekleyin:

[Service]
Type=simple
ExecStart=/usr/local/bin/uzun-surecli-script.sh

[Timer]
OnCalendar=hourly
Persistent=true

Ve servis tarafında Type=oneshot kullanmak genellikle yeterli; systemd önceki çalışma bitene kadar yenisini başlatmaz.

Mevcut Sistemi Analiz Etmek

Devralınan bir sunucuda hangi görevlerin ne zaman çalıştığını anlamak bazen gerçek bir bulmacaya döner. Şu komutları kullanarak hızlı bir envanter çıkarabilirsiniz:

# Tüm kullanıcıların crontab'larını tara
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

# /etc/cron.d/ altındaki görevler
ls -la /etc/cron.d/
cat /etc/cron.d/*

# Aktif systemd timer'lar
systemctl list-timers --all --no-pager

# Son 24 saatte çalışan timer servisleri
journalctl --since "24 hours ago" | grep -E ".service|.timer"

Sonuç

cron ve systemd timer’lar birbirinin rakibi değil, farklı ihtiyaçlara hitap eden araçlar. cron hâlâ sadeliği ve her yerde bulunmasıyla değerini koruyor. Kullanıcı bazlı basit görevler için gayet iş görür. Ancak modern Debian sunucu yönetiminde kritik görevler için systemd timer’a geçmek mantıklı. Merkezi loglama, Persistent=true ile kaçırılan görevlerin telafi edilmesi, kaynak sınırlama ve diğer servislerle entegrasyon açısından systemd çok daha güçlü bir altyapı sunuyor.

Önerim şu: yeni kurduğunuz görevler için systemd timer kullanmaya başlayın. Mevcut cron görevlerini hepsini bir anda migrate etmeye çalışmayın, bu gereksiz bir risk. Ancak sorun yaşadığınız veya daha iyi izleme istediğiniz görevleri birer birer systemd’ye taşıyın. Zamanla farkı somut olarak göreceksiniz; özellikle gece 3’te bir yedek neden çalışmadı sorusuna journalctl ile saniyeler içinde yanıt bulduğunuzda.

Bir yanıt yazın

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