Cron’u yıllarca kullandım, gayet iyi çalışıyor diyenleri anlıyorum. Ama systemd timer’larına geçtiğimde gerçekten “neden daha önce yapmadım” dedim. Loglama, bağımlılık yönetimi, hata yakalama ve esnek zamanlama açısından systemd timer’lar cron’u birçok senaryoda geride bırakıyor. Bu yazıda systemd’nin iki farklı timer türünü, OnCalendar ve Monotonic Timer‘ları, gerçek dünya senaryolarıyla derinlemesine inceleyeceğiz.
Systemd Timer Nedir ve Neden Kullanmalıyım?
Systemd timer’lar, .timer uzantılı unit dosyaları aracılığıyla .service unit’lerini belirli zamanlarda veya belirli koşullar sağlandığında tetikleyen mekanizmalardır. Cron’dan temel farkları şunlardır:
- Bağımlılık yönetimi: Servis başlamadan önce network.target veya başka bir servisin hazır olmasını bekleyebilirsiniz
- Merkezi loglama:
journalctlile timer ve servis loglarını tek yerden takip edersiniz - Missed run recovery: Sistem kapalıyken kaçırılan görevleri
Persistent=trueile sonraki açılışta çalıştırabilirsiniz - Daha iyi hata yönetimi: Servis başarısız olduğunda systemd bunu yakalar, yeniden deneme politikaları tanımlayabilirsiniz
- Resource control: CPU, bellek ve I/O limitlerini doğrudan servis üzerinden ayarlarsınız
Timer Unit Dosyasının Anatomisi
Bir timer sistemi iki dosyadan oluşur: .timer dosyası ve buna karşılık gelen .service dosyası. İsimlendirme kuralı basit: yedek-al.timer çalıştığında varsayılan olarak yedek-al.service‘i arar.
Temel bir timer yapısı şöyle görünür:
# /etc/systemd/system/yedek-al.timer
[Unit]
Description=Günlük Yedek Alma Timer'ı
Requires=local-fs.target
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/yedek-al.service
[Unit]
Description=Yedek Alma Servisi
After=network.target
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/yedek-al.sh
StandardOutput=journal
StandardError=journal
Burada dikkat edilmesi gereken Type=oneshot: Timer tarafından tetiklenen servisler genellikle bu tipte olur çünkü görev tamamlanınca process çıkış yapar.
OnCalendar: Takvim Tabanlı Zamanlama
OnCalendar, cron’a en yakın hissettiren timer türüdür. İnsan tarafından okunabilir zaman ifadelerini kullanır ve oldukça güçlü bir sözdizimi sunar.
OnCalendar Sözdizimi
Temel format şudur: HaftaGünü Yıl-Ay-Gün Saat:Dakika:Saniye
Systemd’nin hazır kısaltmaları:
- hourly: Her saat başı (
-- :00:00) - daily: Her gün gece yarısı (
--* 00:00:00) - weekly: Her Pazartesi gece yarısı (
Mon --* 00:00:00) - monthly: Her ayın ilk günü (
--01 00:00:00) - yearly / annually: Her yılın ilk günü (
--01 00:00:00– yıl başı) - quarterly: Her çeyrekte bir
Özel ifadeler ise çok daha esnek:
# Her gün saat 03:30'da
OnCalendar=*-*-* 03:30:00
# Her Pazartesi ve Çarşamba saat 14:00'te
OnCalendar=Mon,Wed *-*-* 14:00:00
# Her ayın 1. ve 15. günü saat 08:00'de
OnCalendar=*-*-1,15 08:00:00
# Her 4 saatte bir
OnCalendar=*-*-* 00/4:00:00
# Her Cuma saat 17:30 - haftalık rapor için ideal
OnCalendar=Fri *-*-* 17:30:00
# Ocak, Nisan, Temmuz, Ekim aylarının ilk günü - çeyreklik görev
OnCalendar=*-1,4,7,10-01 00:00:00
OnCalendar’ı Test Etmek
Bir zamanlama ifadesinin ne zaman tetikleneceğini canlıya almadan test edebilirsiniz. Bu özelliği çok seviyorum:
# Sonraki 5 tetiklenme zamanını göster
systemd-analyze calendar "Mon,Wed *-*-* 14:00:00"
# Çıktı örneği:
# Original form: Mon,Wed *-*-* 14:00:00
# Normalized form: Mon,Wed *-*-* 14:00:00
# Next elapse: Wed 2024-01-17 14:00:00 TRT
# (in UTC): Wed 2024-01-17 11:00:00 UTC
# From now: 1h 23min left
# Daha fazla iterasyon için
systemd-analyze calendar --iterations=5 "Mon *-*-* 09:00:00"
Bu komut production’a almadan önce hayat kurtarır. Cron expression’larında yaptığınız hatayı ancak o zaman gelip geçmesiyle anlardınız; burada anında görürsünüz.
Monotonic Timer: Süre Tabanlı Zamanlama
Monotonic timer’lar, takvime değil belirli olaylardan itibaren geçen süreye göre çalışır. “Sistem açıldıktan 10 dakika sonra” veya “son çalışmadan 6 saat sonra” gibi senaryolar için biçilmiş kaftandır.
Monotonic timer direktifleri:
- OnActiveSec: Timer unit’i aktif olduktan X süre sonra
- OnBootSec: Sistem boot’undan X süre sonra
- OnStartupSec: Systemd manager başladıktan X süre sonra (kullanıcı session’ları için)
- OnUnitActiveSec: İlgili servis en son aktif olduktan X süre sonra
- OnUnitInactiveSec: İlgili servis en son deaktif olduktan X süre sonra
Süre birimleri oldukça esnek:
10s= 10 saniye5min= 5 dakika2h= 2 saat1d= 1 gün1h 30min= 1,5 saat (birden fazla birim birleştirilebilir)
Pratik Monotonic Timer Örneği
# /etc/systemd/system/cache-temizle.timer
[Unit]
Description=Sistem Açıldıktan 15 Dakika Sonra Cache Temizle
[Timer]
# Boot'tan 15 dakika sonra ilk çalışma
OnBootSec=15min
# Sonraki çalışmalar için: bir önceki servis deaktif olduktan 6 saat sonra
OnUnitInactiveSec=6h
[Install]
WantedBy=timers.target
# /etc/systemd/system/cache-temizle.service
[Unit]
Description=Uygulama Cache Temizleme
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cache-temizle.sh
Nice=10
Bu yapı şunu yapar: Sistem açıldıktan 15 dakika sonra ilk çalışmayı yapar, ardından her tamamlanmadan 6 saat sonra tekrar çalışır. Makine kapalıyken sayaç duraklar, açılınca kaldığı yerden devam eder. Cron ile bunu sağlamak çok daha karmaşık olurdu.
Gerçek Dünya Senaryosu 1: PostgreSQL Yedekleme
Üretim ortamındaki bir PostgreSQL veritabanı için kapsamlı bir timer yapısı kuralım:
# /etc/systemd/system/pg-yedek.timer
[Unit]
Description=PostgreSQL Günlük Yedek Timer
Requires=postgresql.service
[Timer]
# Her gün gece 02:00'de
OnCalendar=*-*-* 02:00:00
# Kaçırılan yedekleri boot sonrası çalıştır
Persistent=true
# Tüm sistemlerin aynı anda çalışmasını önlemek için 5 dakikaya kadar rastgele gecikme
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
# /etc/systemd/system/pg-yedek.service
[Unit]
Description=PostgreSQL Yedek Alma
After=postgresql.service
Requires=postgresql.service
[Service]
Type=oneshot
User=postgres
Group=postgres
# Ortam değişkenleri
Environment=BACKUP_DIR=/var/backups/postgresql
Environment=RETENTION_DAYS=30
ExecStart=/usr/local/bin/pg-yedek.sh
# Başarısız olursa email gönder
OnFailure=bildirim-gonder@%n.service
# Kaynak limitleri - yedek sırasında sistemi boğmasın
CPUWeight=50
IOWeight=50
Nice=15
# Güvenlik
PrivateTmp=true
NoNewPrivileges=true
StandardOutput=journal
StandardError=journal
SyslogIdentifier=pg-yedek
#!/bin/bash
# /usr/local/bin/pg-yedek.sh
set -euo pipefail
BACKUP_DIR="${BACKUP_DIR:-/var/backups/postgresql}"
RETENTION_DAYS="${RETENTION_DAYS:-30}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
HOSTNAME=$(hostname -s)
mkdir -p "$BACKUP_DIR"
echo "Yedek başlıyor: $TIMESTAMP"
# Tüm veritabanlarını yedekle
pg_dumpall --clean --if-exists |
gzip -9 > "$BACKUP_DIR/pgdumpall_${HOSTNAME}_${TIMESTAMP}.sql.gz"
# Eski yedekleri temizle
find "$BACKUP_DIR" -name "pgdumpall_*.sql.gz"
-mtime +${RETENTION_DAYS} -delete
echo "Yedek tamamlandı. Boyut: $(du -sh $BACKUP_DIR | cut -f1)"
Gerçek Dünya Senaryosu 2: Let’s Encrypt Sertifika Yenileme
Certbot’u cron ile değil systemd timer ile yönetelim. Çoğu dağıtım bunu otomatik kuruyor ama nasıl çalıştığını anlamak önemli:
# /etc/systemd/system/certbot-yenile.timer
[Unit]
Description=Let's Encrypt Sertifika Yenileme
[Timer]
# Günde iki kez çalıştır (Let's Encrypt önerisi)
OnCalendar=*-*-* 00,12:00:00
# Sunucuların aynı anda LE sunucularına yüklenmemesi için
RandomizedDelaySec=43200
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/certbot-yenile.service
[Unit]
Description=Certbot Sertifika Yenileme
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
StandardOutput=journal
StandardError=journal
Burada RandomizedDelaySec=43200 dikkat çekiyor. 12 saate kadar rastgele gecikme ekliyor. Bu sayede milyonlarca sunucu aynı anda Let’s Encrypt API’ye bağlanmıyor. Topluluk dostu bir yaklaşım.
Gerçek Dünya Senaryosu 3: Log Rotasyon ve Temizlik
# /etc/systemd/system/log-temizlik.timer
[Unit]
Description=Uygulama Log Temizlik Timer
[Timer]
# Her Pazar gece 01:00'de
OnCalendar=Sun *-*-* 01:00:00
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/log-temizlik.service
[Unit]
Description=Eski Uygulama Loglarını Temizle
[Service]
Type=oneshot
ExecStart=/usr/local/bin/log-temizlik.sh
StandardOutput=journal
StandardError=journal
SyslogIdentifier=log-temizlik
# Sadece bu servis için gerekli dizinlere erişim
ReadWritePaths=/var/log/uygulama
ReadOnlyPaths=/etc/log-temizlik.conf
Timer Yönetimi: Günlük Operasyonlar
Timer’ları etkinleştirmek, durdurmak ve izlemek için ihtiyacınız olan komutlar:
# Timer'ı etkinleştir ve hemen başlat
systemctl enable --now pg-yedek.timer
# Tüm aktif timer'ları listele (bir sonraki çalışma zamanıyla)
systemctl list-timers
# Sadece belirli bir timer'ın durumu
systemctl status pg-yedek.timer
# Timer'ı geçici olarak durdur (reboot'ta yeniden başlar)
systemctl stop pg-yedek.timer
# Timer'ı kalıcı olarak devre dışı bırak
systemctl disable pg-yedek.timer
# Timer'ı manuel olarak tetikle (zamanlama beklemeden)
systemctl start pg-yedek.service
# Timer loglarını görüntüle
journalctl -u pg-yedek.timer
journalctl -u pg-yedek.service
# Son 24 saatin logları
journalctl -u pg-yedek.service --since "24 hours ago"
# Canlı log takibi
journalctl -u pg-yedek.service -f
systemctl list-timers çıktısı özellikle kullanışlı. Size şu sütunları gösterir:
- NEXT: Bir sonraki tetiklenme zamanı
- LEFT: Ne kadar süre kaldığı
- LAST: Son tetiklenme zamanı
- PASSED: Son tetiklenmeden ne kadar geçtiği
- UNIT: Timer unit adı
- ACTIVATES: Tetiklendiğinde başlatılacak servis
Persistent=true ile Kaçırılan Görevler
Bu direktif çok kritik. Persistent=true ayarlandığında systemd, servisin son çalışma zamanını /var/lib/systemd/timers/ altında saklar. Sistem kapalıyken kaçırılan bir OnCalendar görevi, sistem açıldıktan sonra hemen çalıştırılır.
# Persistence dosyalarını görmek için
ls -la /var/lib/systemd/timers/
# Bir timer'ın son çalışma bilgisini sıfırla
systemctl clean pg-yedek.service --what=state
Dikkat: Persistent=true her zaman istenen davranış değildir. Örneğin sabah 03:00’teki bir rapor görevi hafta sonu kapalı olan bir sunucuda Pazartesi açılışında tetiklenmesini istemeyebilirsiniz. Bu durumda Persistent=false bırakın.
OnCalendar ve Monotonic’i Birlikte Kullanmak
Tek bir timer’da hem OnCalendar hem de Monotonic direktifleri kullanabilirsiniz. Systemd her ikisini de değerlendirir ve hangisi önce tetiklenirse onu çalıştırır:
# /etc/systemd/system/sistem-kontrol.timer
[Unit]
Description=Sistem Sağlık Kontrol Timer
[Timer]
# Her gün saat 06:00'da çalış
OnCalendar=*-*-* 06:00:00
# Aynı zamanda boot'tan 5 dakika sonra da çalış
OnBootSec=5min
# Ve son çalışmadan 12 saat sonra da tetiklensin
OnUnitInactiveSec=12h
Persistent=true
[Install]
WantedBy=timers.target
Bu konfigürasyonla sistem her boot’ta 5 dakika sonra bir kontrol yapar, ardından düzenli takvime döner. Sunucu beklenmedik şekilde yeniden başladıktan sonra hızlıca durum raporu almanız gerektiğinde çok işe yarar.
Hata Durumunda Bildirim Servisi
Timer ile çalışan bir servis başarısız olduğunda email veya bildirim gönderen yardımcı bir servis kuralım:
# /etc/systemd/system/[email protected]
[Unit]
Description=%i Servisi Başarısız Oldu - Bildirim
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/servis-bildirimi.sh %i
#!/bin/bash
# /usr/local/bin/servis-bildirimi.sh
SERVIS_ADI="$1"
SUNUCU=$(hostname -f)
ZAMAN=$(date '+%Y-%m-%d %H:%M:%S')
LOG=$(journalctl -u "$SERVIS_ADI" -n 50 --no-pager 2>/dev/null || echo "Log alınamadı")
echo "Konu: HATA: $SERVIS_ADI başarısız oldu - $SUNUCU
Tarih: $ZAMAN
Servis: $SERVIS_ADI
Sunucu: $SUNUCU
Son 50 satır log:
$LOG
" | mail -s "HATA: $SERVIS_ADI başarısız - $SUNUCU" [email protected]
Artık herhangi bir servis dosyasına OnFailure=bildirim-gonder@%n.service ekleyerek otomatik hata bildirimi sağlayabilirsiniz. %n specifier o anki unit’in adını verir.
Kullanıcı Seviyesi Timer’lar
Systemd timer’lar sadece root için değil, normal kullanıcılar da kendi home dizinlerinde timer tanımlayabilir:
# Kullanıcı timer dizini
mkdir -p ~/.config/systemd/user/
# Kullanıcı timer'ını etkinleştir
systemctl --user enable --now kullanici-gorevi.timer
# Kullanıcı timer'larını listele
systemctl --user list-timers
# Kullanıcı oturumu kapalıyken de çalışması için (linger aktif etmek gerekir)
sudo loginctl enable-linger kullanici_adi
loginctl enable-linger olmadan kullanıcı oturumu açık değilse timer’lar çalışmaz. CI/CD pipeline’larında veya kişisel cron görevlerinde bu özelliği sık kullanıyorum.
Cron’dan Systemd Timer’a Geçiş
Mevcut cron görevlerinizi taşırken şu tabloyu referans alabilirsiniz:
0 2(her gün 02:00):OnCalendar=-- 02:00:00/15(her 15 dakika):OnCalendar=-- *:0/15:000 9 1(her Pazartesi 09:00):OnCalendar=Mon --* 09:00:000 0 1(her ayın ilki):OnCalendar=--01 00:00:00@reboot(boot sonrası):OnBootSec=0veyaOnBootSec=30s
@reboot için monotonic timer kullanmak aslında daha iyidir çünkü OnBootSec=30s gibi kısa bir gecikme ekleyerek sistemin tam olarak ayağa kalkmasını bekleyebilirsiniz. Cron’daki @reboot çok erken çalışabilir ve bağımlı olduğunuz servisler henüz hazır olmayabilir.
Sonuç
Systemd timer’lar başta karmaşık görünebilir; cron ile iki satırla yapılanı burada iki dosyaya yazmak gerekiyor. Ama bu yatırımın karşılığını ilk ciddi prodüksiyon sorununda alıyorsunuz. journalctl ile anında nereden geldiğini görebildiğiniz bir log, Persistent=true ile bir daha kaçırılmayan kritik yedek ve OnFailure ile anında haberdar olduğunuz bir hata: bunlar cron’un asla veremediği şeyler.
Tavsiyem şu: Yeni kuracağınız görevlerde direkt systemd timer kullanın, mevcut cron görevlerinizi ise yavaş yavaş taşıyın. Özellikle kritik yedekleme, sertifika yenileme ve sistem bakım görevlerini önceliklendirin. systemd-analyze calendar komutunu alışkanlık haline getirin, tahmin ettiğinizden çok zaman kazandıracak.
Cron kötü değil, ama systemd timer’lar bir sistem yöneticisinin gerçekten ihtiyaç duyduğu görünürlüğü ve kontrolü sağlıyor. Birini biliyorsanız diğerini öğrenmek için harcayacağınız birkaç saat kesinlikle değer.