TEKNİK YAZI / Backend Sistemleri

Cron Job Nedir?

Cron job oluşturmayı; zaman ifadesi, mutlak yollar, environment, çakışan çalışma, loglama ve kaçırılan görev sınırlarıyla öğrenin.

Cron job, Unix benzeri sistemlerde bir komutu belirli 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; retry, tekil çalışma, timeout ve başarı garantisi gibi davranışları uygulamanın ayrıca tasarlaması gerekir.

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 komuttan önce kullanıcı alanı bulunabilir. 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

Alanların tümünün aynı anda sağlanması gerektiği varsayımı ilk üç alan için doğrudur, fakat son iki alan için geçerli değildir. crontab(5) kılavuzuna göre ayın günü ve haftanın günü alanlarını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

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 oturumu interaktif shell ile aynı environment’a sahip değildir. 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 güvenli secret mekanizması 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 log 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 + işaretinden sonra keser ve satırın kalanını standart girdiye yönlendirir. Her % karakteri ters bölü ile 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 kaybolmaz; 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.

Uygulamada çoğu sunucuda yerel bir posta aktarım aracı kurulu olmaz. Bu durumda çıktı hiçbir adrese ulaşmaz ve görevin ürettiği hata sessizce kaybolur. “Cron çalışmıyor” olarak 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 /run/lock/example-worker.lock timeout 600 /usr/local/bin/example-worker >> /var/log/example-worker.log 2>&1

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

Kilit dosyasının yolu, görevin hangi kullanıcının crontab’ında olduğuna göre seçilmelidir. /run dizini root’a aittir ve normal bir kullanıcı oraya yazamaz; root olmayan bir crontab girdisinde /run/örnek.lock gibi bir yol flock’un başlamasını engeller. Debian ve Ubuntu’da herkesin yazabildiği /run/lock dizini veya servis hesabının kendi dizini uygun seçeneklerdir. Bu tür bir hata yalnızca çı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 komutu timeout ile sınırlamak gerekir. Görev ayrıca mümkün olduğunda idempotent olmalı ve yarım kalan işlemi güvenle ayırt edebilmelidir.

Kaçırılan görevler ve retry

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 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 unit’ini tetikler ve systemctl enable --now nightly-report.timer ile devreye alınır. Persistent=true, kaçırılan çalışmayı makine açıldığında telafi eder. RandomizedDelaySec=, çok sayıda görevin aynı saniyeye yığılmasını engeller; cron’da doğrudan karşılığı yoktur. Kurulu timer’lar systemctl list-timers ile listelenir.

Yaz saati geçişleri

Cron, sistem saat dilimine göre çalışır ve saat değişimlerinde davranışı sezgisel değildir. crontab(5) kılavuzuna göre saatler ileri alındığında atlanan aralığa denk gelen zamanlar hiç eşleşmez ve o güne ait görev çalışmaz; saatler geri alındığında tekrarlanan aralıktaki zamanlar iki kez eşleşir ve görev iki kez çalışır. Yukarıdaki 30 2 * * * örneği pek çok ülkede tam bu aralığa denk gelir.

Türkiye gibi sabit saat dilimi kullanan ülkelerde bu sorun oluşmaz. Geçiş uygulanan bir ortamda çalışan yedekleme veya raporlama işlerinde ise görevi geçiş saatinin dışına almak, CRON_TZ= ile saat dilimini açıkça sabitlemek ya da çalışmayı idempotent kurup iki kez çalışmaya dayanıklı hâle getirmek gerekir. Bu davranışın ayrıntısı cron uygulamasına göre değişir; cronie kullanan dağıtımlar atlanan işleri geçişten sonra bir kez çalıştırır.

Cron ayrıca başarısız komutu kendiliğinden tekrar denemez. Retry 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 alarm veya dead-letter akışına 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  # bazı diğer dağıtımlar
crontab -l

Dağıtıma göre cron kayıtları journal veya ayrı bir sistem logunda bulunabilir:

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 environment ile elle çalıştırın. “Cron çalışmıyor” görünen sorunların önemli bölümü göreli yol, izin veya eksik environment değişkeninden kaynaklanır.

Cron ne zaman yeterli değildir?

Şu gereksinimlerde daha zengin bir scheduler veya kuyruk sistemi düşünün:

Sonuç

Cron, tek makinede basit ve öngörülebilir takvimli görevler için güçlü bir temel araçtır. Üretim kalitesi ise zaman ifadesinden çok görevin idempotency, kilit, timeout, environment, log ve hata görünürlüğü kararlarından gelir. Önce komutun güvenli tekil çalışmasını kurun, ardından takvime bağlayın.

Kaynaklar