إدارة أنظمة لينكس

المهام المجدولة: cron والمؤقتات

22 دقيقة الدرس 8 من 28

المهام المجدولة: cron والمؤقتات

كل نظام إنتاجي يُشغّل أعمالاً بجدول زمني: تدوير السجلات، وإعادة ضغط قواعد البيانات، وتجديد الشهادات، وإنشاء لقطات، وتدفئة الذاكرة المؤقتة، وتقارير الحالة. تتوفر آليتان على لينكس الحديث: cron الكلاسيكي ومؤقتات systemd. يجب على مهندس DevOps المتمرس أن يتقن الاثنين، ويعرف متى يختار كل منهما، ويفهم أوجه الفشل التي تعرقل الفرق في الإنتاج.

cron: المُجدِّد الكلاسيكي

يُشحن cron مع أنظمة Unix منذ عام 1975. لا يزال الأداة المناسبة للمهام البسيطة المملوكة للمستخدمين وللأنظمة التي لا يتوفر فيها systemd. يقرأ الخادم ملفات crontab ويُشغّل الأوامر عند تطابق الأوقات.

كل سطر في crontab يتبع مواصفة زمنية ثابتة من خمسة حقول متبوعة بالأمر:

# ┌──────────── الدقيقة (0-59) # │ ┌─────────── الساعة (0-23) # │ │ ┌──────────── يوم الشهر (1-31) # │ │ │ ┌─────────── الشهر (1-12) # │ │ │ │ ┌──────────── يوم الأسبوع (0-7؛ 0 و7 = الأحد) # │ │ │ │ │ # * * * * * الأمر # تشغيل يومي في 02:30 UTC 30 2 * * * /usr/local/bin/db-backup.sh # تشغيل كل 15 دقيقة */15 * * * * /usr/local/bin/health-check.sh # تشغيل في 09:00 أيام الأسبوع فقط 0 9 * * 1-5 /usr/local/bin/send-report.sh # تشغيل في أول كل شهر عند منتصف الليل 0 0 1 * * /usr/local/bin/monthly-cleanup.sh # سلاسل خاصة (مدعومة في معظم تطبيقات cron الحديثة) @reboot /usr/local/bin/warm-cache.sh @daily /usr/local/bin/rotate-keys.sh # يعادل 0 0 * * * @hourly /usr/local/bin/sync-metrics.sh # يعادل 0 * * * *

إدارة ملفات Crontab

استخدم crontab -e لتعديل crontab المستخدم الحالي، وcrontab -l لسرده، وcrontab -r لحذفه. تذهب المهام الخاصة بالنظام والمملوكة لـ root إلى /etc/crontab أو كملفات منفصلة تحت /etc/cron.d/ — وتحتوي هذه على حقل إضافي لتحديد المستخدم الذي تُنفَّذ الأوامر باسمه:

# /etc/cron.d/myapp — لاحظ حقل USER بعد مواصفة الوقت 30 2 * * * appuser /opt/myapp/bin/backup.sh >> /var/log/myapp/backup.log 2>&1 # عرض خادم cron والمهام المحملة (التوزيعات المُدارة بـ systemd) systemctl status cron # Debian/Ubuntu systemctl status crond # RHEL/CentOS/Fedora # التحقق من معالجة cron للملف (ابحث عن أخطاء الصلاحيات والتركيب) grep CRON /var/log/syslog # Debian/Ubuntu grep CRON /var/log/cron # RHEL/CentOS
مشكلة إنتاجية — الإخفاقات الصامتة: يلتقط cron المخرجات الاعتيادية وأخطاء stdout ويرسلها بريداً للمستخدم المحلي افتراضياً. على الخوادم التي لا تحتوي على وكيل نقل بريد (MTA) — وهو حال معظم الأجهزة الافتراضية السحابية — تُهمَل هذه المخرجات بصمت. دائماً أعد توجيه المخرجات صراحةً: >> /var/log/myapp/job.log 2>&1 وأرسل تلك السجلات إلى مُجمِّع السجلات المركزي.

مؤقتات systemd: البديل الحديث

مؤقتات systemd هي وحدات .timer تُنشّط وحدة .service مُقترنة بها. إنها أقوى من cron لأحمال العمل الإنتاجية لأنها تتكامل مع journald للتسجيل، وتدعم ترتيب التبعيات، ويمكنها اللحاق بالتشغيلات الفائتة عبر الاستمرارية، وتُصدر أحداثاً منظمة يمكن لأنظمة المراقبة جمعها.

المؤقت يقترن دائماً بوحدة .service بنفس الاسم الأساسي. سير العمل: اكتب الخدمة، اكتب المؤقت، مكّن المؤقت.

# /etc/systemd/system/db-backup.service [Unit] Description=Database Backup Job After=network.target postgresql.service [Service] Type=oneshot User=appuser ExecStart=/usr/local/bin/db-backup.sh StandardOutput=journal StandardError=journal # /etc/systemd/system/db-backup.timer [Unit] Description=Run Database Backup Daily at 02:30 UTC [Timer] OnCalendar=*-*-* 02:30:00 AccuracySec=1min Persistent=true [Install] WantedBy=timers.target # تمكين وتشغيل المؤقت (ليس الخدمة مباشرة) systemctl daemon-reload systemctl enable --now db-backup.timer # فحص المؤقت systemctl list-timers --all systemctl status db-backup.timer journalctl -u db-backup.service --since today
فكرة أساسية — Persistent=true: إذا كان النظام مُغلَقاً عندما كان يُفترض أن يُطلق المؤقت، فإن Persistent=true يجعل systemd يُشغّل المهمة فوراً عند الإقلاع التالي إذا فُوّت آخر تشغيل. هذا ضروري للمهام الليلية على الخوادم التي قد تُعاد تشغيلها للصيانة. لا توفر cron آلية مماثلة.

صياغة OnCalendar

يستخدم systemd لغة تعبيرات تقويم خاصة به، وهي أكثر تعبيراً من صيغة cron ذات الخمسة حقول. استخدم systemd-analyze calendar للتحقق من أي تعبير قبل نشره:

# التحقق ومعاينة أوقات الإطلاق التالية systemd-analyze calendar "Mon..Fri *-*-* 09:00:00" systemd-analyze calendar "weekly" # تعبيرات OnCalendar الشائعة OnCalendar=daily # منتصف الليل كل يوم (00:00:00) OnCalendar=weekly # منتصف ليل الاثنين OnCalendar=hourly # بداية كل ساعة OnCalendar=*:0/15 # كل 15 دقيقة OnCalendar=Mon..Fri 09:00:00 # أيام الأسبوع في 09:00 OnCalendar=*-*-1 00:00:00 # أول كل شهر OnCalendar=Sat *-*-* 03:00:00 # السبت في 03:00 OnCalendar=2026-07-04 12:00:00 # تاريخ ووقت محدد (تشغيل واحد) # مؤقتات أحادية الوتيرة (نسبية، ليست قائمة على التقويم) OnBootSec=5min # 5 دقائق بعد الإقلاع OnUnitActiveSec=1h # ساعة بعد آخر تنشيط (متكرر) RandomizedDelaySec=300 # تأخير عشوائي حتى 5 دقائق — حرج في الأساطيل

مخطط: هندسة cron مقارنة بمؤقت systemd

cron vs systemd timer architecture comparison cron cron daemon crontab / cron.d Shell / Command stdout → بريد (غالباً صامت) بلا ترتيب تبعيات صياغة 5 حقول بلا لحاق بالفائت مخرجات مفقودة systemd Timer .timer unit .service unit Process (isolated) journald (سجلات منظمة) After=، Requires= OnCalendar + جيتر عزل cgroups لحاق بالتشغيل الفائت
يُوجِّه cron المخرجات إلى البريد المحلي (يُهمَل غالباً)؛ توجّه مؤقتات systemd إلى journald مع تسجيل منظم كامل واللحاق بالتشغيلات الفائتة.

أفضل ممارسات الجدولة في الإنتاج

تجنب قطيع الرعد

في أسطول من 50 خادماً، تُطلَق كل مهمة cron مضبوطة على 0 3 * * * في آنٍ واحد، مما يُنشئ ذروة حركة مرور على قواعد البيانات وواجهات البرمجة المشتركة. بالنسبة لـ cron، أضف سكوناً عشوائياً في بداية السكريبت: sleep $((RANDOM % 300)). بالنسبة لمؤقتات systemd، استخدم RandomizedDelaySec=300 في قسم [Timer] — يُطبّق systemd التأجيل لكل مضيف دون التعديل على السكريبت.

استخدم القفل لمنع التداخل

إذا استغرق تشغيل مهمة وقتاً أطول من فترتها، تبدأ نسخة ثانية بينما الأولى لا تزال تعمل. استخدم flock لمنع ذلك:

#!/usr/bin/env bash # /usr/local/bin/db-backup.sh set -euo pipefail LOCKFILE=/var/lock/db-backup.lock exec 200>"$LOCKFILE" flock -n 200 || { echo "Already running — skipping"; exit 0; } # --- العمل الفعلي أدناه --- pg_dumpall -U postgres | gzip > /backups/db-$(date +%F).sql.gz find /backups -name "*.sql.gz" -mtime +7 -delete

بالنسبة لخدمات systemd، اضبط Type=oneshot — لن يبدأ systemd نسخة ثانية بينما الأولى نشطة. ادمجها مع فحوصات ExecStartPre= عند الحاجة.

ممارسة احترافية — استخدم chronic من moreutils مع cron: ثبّت moreutils وأضف chronic قبل أوامر cron. يُثبّط المخرجات ما لم يخرج الأمر بحالة غير صفرية، فيصل بريد cron فقط عند حدوث فشل فعلي — عكس السلوك الافتراضي الذي يُنتج ضجيجاً في كل تشغيل. مثال: chronic /usr/local/bin/db-backup.sh

متى تستخدم cron مقابل مؤقتات systemd

استخدم cron عندما: تحتاج مهام خاصة بالمستخدم (crontab -e)، أو النظام المستهدف لا يحتوي على systemd (الحاويات، Alpine Linux، بعض الأنظمة المدمجة)، أو تصون بيئة قديمة حيث الاتساق أهم من الميزات.

استخدم مؤقتات systemd عندما: يجب أن تعتمد المهمة على خدمة شبكة أو قاعدة بيانات (After=)، أو تحتاج لحاقاً مضموناً بعد إعادة التشغيل (Persistent=true)، أو تريد حدوداً لموارد cgroup على عملية المهمة، أو تريد تدفق السجلات بصورة طبيعية إلى journald. على نطاق الشركات الكبرى، مؤقتات systemd هي المعيار للعمل المجدول على مستوى المضيف لأن قصة الملاحظة والموثوقية أفضل جوهرياً.

فكرة أساسية — الحاويات وKubernetes: داخل حاويات Docker، تجنّب cron ومؤقتات systemd معاً؛ لا يتناسب أيٌّ منهما مع نموذج الحاوية ذات العملية الواحدة. للعمل المجدول في الحاويات، استخدم موارد Kubernetes CronJob أو مُجدِّل موزع (Temporal، Celery Beat، AWS EventBridge). على الخوادم الفعلية أو الأجهزة الافتراضية، مؤقتات systemd هي الأداة الصحيحة.

تشخيص المهام المجدولة

عندما تفشل مهمة بصمت، اتّبع هذا التسلسل التشخيصي:

  1. تحقق من أن المؤقت أُطلق: systemctl list-timers --all | grep job-name
  2. تحقق من رمز الخروج للخدمة: systemctl status job-name.service
  3. اقرأ السجل الكامل: journalctl -u job-name.service -n 100 --no-pager
  4. شغّل الخدمة يدوياً لإعادة إنتاج المشكلة: systemctl start job-name.service
  5. بالنسبة لـ cron: تحقق من /var/log/syslog أو /var/log/cron للسطور الخاصة بالخادم، ثم تحقق مما إذا كان السكريبت يستخدم متغيرات بيئة لا يُصدرها cron (PATH، HOME — اضبط هذه دائماً صراحةً في أعلى سكريبتات cron).