TEKNİK YAZI / Backend Sistemleri

Cron Job Nedir? Linux'ta Zamanlanmış Görevler

Cron job tanımlama rehberi: zaman ifadesi, ortam değişkenleri, % karakteri, flock ile kilit, zaman aşımı, yaz saati geçişleri ve kaçırılan görevler.

Cron job, Unix benzeri sistemlerde bir komutu belirli bir takvime göre başlatan zamanlanmış görevdir. Yedekleme, rapor üretme veya geçici veriyi temizleme gibi işler için kullanılır. Cron yalnızca komutu planlanan anda başlatır; yeniden deneme (retry), tekil çalışma, zaman aşımı (timeout) ve başarı garantisi gibi davranışları görevin kendisinin veya onu saran araçların sağlaması gerekir.

Linux’ta yaygın iki uygulama vardır: Debian ve Ubuntu’daki cron paketi (Vixie cron tabanlı) ve Fedora, RHEL gibi dağıtımlardaki cronie. Aşağıda iki uygulamanın ayrıştığı yerler ayrıca belirtilmiştir.

Cron ifadesi nasıl okunur?

Kullanıcı crontab girdisi beş zaman alanı ve komuttan oluşur:

┌──────── dakika (0-59)
│ ┌────── saat (0-23)
│ │ ┌──── ayın günü (1-31)
│ │ │ ┌── ay (1-12)
│ │ │ │ ┌ haftanın günü (0-7, pazar 0 veya 7)
│ │ │ │ │
* * * * * komut

Her gece 02.30’da çalışan örnek:

30 2 * * * /usr/local/bin/nightly-report

Sistem crontab biçimlerinde (/etc/crontab, /etc/cron.d/) komuttan önce kullanıcı alanı bulunur. crontab -e ile düzenlenen kullanıcı crontab’ında bu alan yoktur; iki biçimi birbirine karıştırmayın.

Ayın günü ve haftanın günü birlikte yazıldığında

Dakika, saat ve ay alanları VE ile bağlanır: görevin çalışması için üçünün de eşleşmesi gerekir. Ayın günü ile haftanın günü için bu kural geçerli değildir. crontab(5) kılavuzuna göre bu iki alanın ikisi de kısıtlanmışsa, yani ikisi de * değilse, görev iki koşuldan herhangi biri sağlandığında çalışır. Kılavuzun örneğinde 30 4 1,15 * 5 girdisi ayın 1’i ile 15’inde 04.30’da ve ayrıca her cuma çalışır.

Bu kural sık yanlış anlaşılır. Aşağıdaki girdi “ayın 13’ü cumaya denk geldiğinde” anlamına gelmez:

0 0 13 * 5 /usr/local/bin/rapor

Girdi her ayın 13’ünde, ayrıca her cuma çalışır. “Ayın 13’ü ve aynı zamanda cuma” koşulunu cron ifadesiyle kurmak mümkün değildir; iki alandan biri * bırakılıp kontrol komutun içinde yapılmalıdır:

0 0 13 * * [ "$(date +\%u)" = 5 ] && /usr/local/bin/rapor

Uygulamalar bir alanın kısıtlanıp kısıtlanmadığına alanın ilk karakterine bakarak karar verebilir. Debian cron’un crontab(5) kılavuzu, */2 gibi * ile başlayan bir değerin kısıtlanmamış sayıldığını ve bu yüzden 0 0 */2 * sun girdisinin yalnız tek tarihli pazar günlerinde çalıştığını belirtir.

Kısayollar

Sık kullanılan takvimler için özel adlar tanımlıdır: @yearly, @monthly, @weekly, @daily, @hourly ve sistem açılışında bir kez çalışan @reboot. Bunlar beş alanın yerine yazılır:

@daily /usr/local/bin/nightly-report

Güvenli bir görev tanımlamak

Cron, görevi etkileşimli kabuktaki ortam değişkenleriyle çalıştırmaz. PATH, çalışma dizini ve uygulamaya özgü değişkenleri varsaymak yerine açıkça tanımlayın:

SHELL=/bin/sh
PATH=/usr/local/bin:/usr/bin:/bin

*/5 * * * * cd /srv/example && /usr/bin/php bin/worker.php >> /var/log/example-worker.log 2>&1

Bu örnekte:

Sırları doğrudan crontab içine yazmak yerine yalnızca ilgili servis hesabının okuyabildiği bir yapılandırma dosyası veya bir sır yönetim aracı kullanın.

Yüzde işareti komutu böler

Crontab satırında % sıradan bir karakter değildir. crontab(5) kılavuzuna göre komut kısmı satır sonuna veya ilk % karakterine kadar çalıştırılır; kaçırılmamış % yeni satıra dönüştürülür ve ilk % işaretinden sonraki veri komuta standart girdi olarak aktarılır.

Bu nedenle tarih damgalı bir günlük adı üretmeye çalışan aşağıdaki girdi çalışmaz:

0 3 * * * /usr/local/bin/yedek >> /var/log/yedek-$(date +%Y%m%d).log 2>&1

Cron komutu ilk % karakterinde keser: kabuğa giden komut $(date + ile biter ve satırın kalanı standart girdiye aktarılır. Her % karakteri ters bölüyle kaçırılmalıdır:

0 3 * * * /usr/local/bin/yedek >> /var/log/yedek-$(date +\%Y\%m\%d).log 2>&1

Daha dayanıklı yöntem, tarih üretimini ve yönlendirmeyi bir betiğin içine taşıyıp crontab satırında yalnızca betiği çağırmaktır.

Yönlendirme yapılmazsa çıktı nereye gider?

Görevin standart çıktısı ve standart hatası yönlendirilmediğinde cron bunları e-posta olarak göndermeye çalışır. MAILTO tanımlı ve boş değilse belirtilen adrese, MAILTO="" ise hiçbir yere, tanımlı değilse crontab sahibine gönderilir.

Birçok sunucuda yerel bir posta aktarım aracı (MTA) kurulu değildir. Bu durumda çıktı hiçbir adrese ulaşmaz ve görevin ürettiği hata sessizce kaybolur. “Cron çalışmıyor” diye teşhis edilen durumların bir bölümü aslında çalışan fakat çıktısı hiçbir yere yazılmayan görevlerdir. Bu yüzden her girdide >> dosya 2>&1 yönlendirmesini açıkça yazmak gerekir.

Çakışan çalışmaları önlemek

Bir görev beş dakikada bir başlayıp on dakika sürerse birden fazla kopya aynı anda çalışabilir. İş kuralı paralelliğe uygun değilse kilit kullanın:

*/5 * * * * /usr/bin/flock -n /var/lib/example/worker.lock /usr/bin/timeout -k 30 600 /usr/local/bin/example-worker >> /var/log/example-worker.log 2>&1

flock -n, kilit başka bir süreçteyse yeni çalışmayı beklemeden sonlandırır.

Kilit dosyasını yalnız görevin sahibinin yazabildiği bir dizinde tutun: servis hesabının kendi dizini (örnekteki /var/lib/example/) veya root görevleri için doğrudan /run altı (ör. /run/example-worker.lock). /run root’a aittir; root olmayan bir crontab girdisinde oraya kilit yazmaya çalışmak flock’un başlamasını engeller. Debian ve Ubuntu’da /run/lock herkesin yazabildiği bir dizin olarak bağlanır ve pratik görünür, fakat başka bir kullanıcı aynı adla kilit dosyasını önceden oluşturup kilidi tutarak görevin hiç çalışmamasına yol açabilir. Kilit hataları da yalnız çıktı yönlendirildiğinde görünür olduğundan kilitli girdilerde de >> dosya 2>&1 yazmak gerekir.

Kilit tek başına yeterli değildir. Takılan bir çalışma kilidi süresiz tutar ve sonraki tüm çalışmalar flock -n tarafından sessizce atlanır; kilit bu durumda yeni bir arıza biçimi üretir. Bunu önlemek için komut timeout ile sınırlanır. timeout -k 30 600, komuta 600 saniye sonra SIGTERM gönderir ve komut 30 saniye içinde kapanmazsa SIGKILL uygular; bu, kill ve PID yazısında anlatılan önce SIGTERM, gerekirse SIGKILL sırasının aynısıdır. -k verilmezse SIGTERM’i yok sayan bir iş kilidi yine süresiz tutabilir. Görev ayrıca mümkün olduğunda idempotent olmalı, yani iki kez çalışması tek çalışmayla aynı sonucu vermeli ve yarım kalan işlemi güvenle ayırt edebilmelidir.

Kaçırılan görevler, saat değişimleri ve yeniden deneme

Kaçırılan çalışmalar ve systemd timer

Makine kapalıyken zamanı geçen klasik cron girdisi, sistem açıldığında otomatik olarak telafi edilmez. Günlük veya daha seyrek işlerde anacron, systemd timer ya da uygulama düzeyinde bir zamanlayıcı (scheduler) kaçırılan çalışmaları yönetmek için daha uygun olabilir.

systemd timer’ında bu davranış Persistent=true ile açıkça istenir:

# /etc/systemd/system/nightly-report.timer
[Unit]
Description=Gecelik rapor

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

Timer, aynı ada sahip nightly-report.service birimini tetikler ve systemctl enable --now nightly-report.timer ile devreye alınır; servis biriminin nasıl yazıldığı systemd servis yazısında anlatılıyor. Persistent=true, kaçırılan çalışmayı makine açıldığında telafi eder. RandomizedDelaySec=, aynı anda tetiklenecek çok sayıda görevi zamana yaymak için başlangıca 0 ile verilen süre arasında rastgele bir gecikme ekler. Debian cron’da bunun karşılığı yoktur; cronie’de RANDOM_DELAY değişkeni benzer işi dakika cinsinden yapar, fakat rastgele katsayı cron servisi başlarken bir kez belirlenir ve servis çalıştığı sürece sabit kalır. Kurulu timer’lar systemctl list-timers ile listelenir.

Yaz saati geçişleri

Cron, sistemin saat dilimine göre çalışır. cronie’nin crontab(5) kılavuzu, yaz saati geçişinde atlanan saatlerin hiç eşleşmeyeceğini ve tekrar eden saatlerin iki kez eşleşeceğini söyler. Ancak hem cronie’nin hem Debian cron’un cron(8) kılavuzu, 3 saatten küçük saat değişimleri için özel bir davranış tanımlar:

Başka cron uygulamaları farklı davranabilir; kullandığınız uygulamanın cron(8) kılavuzunu kontrol edin. Türkiye sabit saat dilimi (UTC+3) kullandığı için bu geçişler Türkiye saatiyle çalışan sunucularda oluşmaz. Geçiş uygulanan bir saat diliminde çalışan yedekleme veya raporlama işlerinde görevi geçiş saatlerinin dışına almak, sunucuyu UTC’de çalıştırmak ya da görevi iki kez çalışmaya ve gecikmeye dayanıklı kurmak gerekir. Görev tablosuna ayrı bir saat dilimi vermek CRON_TZ= değişkeniyle mümkündür, fakat bu cronie’ye özgüdür. Debian cron kullanıcı başına saat dilimini desteklemez; crontab’a yazılan TZ değişkeni yalnız çalıştırılan komutları etkiler, görevin ne zaman başlayacağını değiştirmez.

Yeniden deneme

Cron başarısız komutu kendiliğinden yeniden denemez. Yeniden deneme gerekiyorsa:

  1. yalnızca geçici hataları sınıflandırın,
  2. deneme sayısını ve bekleme aralığını sınırlayın,
  3. aynı işin tekrar çalışmasına dayanıklı bir işlem kimliği kullanın,
  4. kalıcı hatayı görünür bir alarma veya ayrı bir hata kuyruğuna (dead-letter) taşıyın.

Gözlemleme ve teşhis

Önce cron servisinin çalıştığını ve görevin doğru kullanıcıya kurulduğunu doğrulayın:

systemctl status cron   # Debian/Ubuntu
systemctl status crond  # Fedora, RHEL ve türevleri
crontab -l

Dağıtıma göre cron kayıtları journal’da veya ayrı bir sistem günlüğünde bulunur:

journalctl -u cron --since today
journalctl -u crond --since today

Journal kayıtları cron’un görevi hangi kullanıcı için ve ne zaman başlattığını gösterir; görevin kendi çıktısını içermez. Komutun ne yazdığını görmek için girdideki yönlendirme hedefine bakılmalıdır.

Komutu cron’a eklemeden önce aynı kullanıcı, çalışma dizini ve ortam değişkenleriyle elle çalıştırın. “Cron çalışmıyor” görünen sorunların önemli bir bölümü göreli yol, izin veya eksik ortam değişkeninden kaynaklanır.

Cron ne zaman yeterli değildir?

Şu gereksinimlerde daha zengin bir iş zamanlayıcı veya kuyruk sistemi düşünün:

Sürekli çalışması gereken bir iş ise zamanlanmış görev değil, servis yöneticisi altında çalışan bir daemon olarak tasarlanmalıdır.

Sonuç

Cron, tek makinede basit ve öngörülebilir takvimli görevler için yeterli bir araçtır. Bir görevin üretimde güvenilir çalışması ise zaman ifadesinden çok idempotent olma, kilit, zaman aşımı, ortam değişkenleri, günlükleme ve hata görünürlüğü kararlarına bağlıdır. Önce komutun tek başına güvenle çalıştığını doğrulayın, ardından takvime bağlayın; kaçırılan çalışmaların telafisi veya rastgele gecikme gerekiyorsa systemd timer’ı tercih edin.

Birincil kaynaklar