بلاگ/جایگزینی Cron با Systemd Timer در لینوکس: زمان‌بندی دقیق و قابل‌اعتماد وظایف روی سرور مجازی و اختصاصی
به‌روز شده

جایگزینی Cron با Systemd Timer در لینوکس: زمان‌بندی دقیق و قابل‌اعتماد وظایف روی سرور مجازی و اختصاصی

1405/07/210 بازدید
جایگزینی Cron با Systemd Timer در لینوکس: زمان‌بندی دقیق و قابل‌اعتماد وظایف روی سرور مجازی و اختصاصی

جایگزینی Cron با Systemd Timer در لینوکس: زمان‌بندی دقیق و قابل‌اعتماد وظایف روی سرور مجازی و اختصاصی

زمان‌بندی وظایف در لینوکس با systemd timer به جای cron

اگر مدیر یک سرور مجازی یا سرور اختصاصی هستید، حتماً با crontab برای زمان‌بندی بکاپ، پاک‌سازی لاگ یا اجرای اسکریپت‌های نگهداری کار کرده‌اید — در کنار کارهای ثابت دیگری مثل امن‌سازی دسترسی SSH به سرور لینوکس که معمولاً همزمان با همین وظایف زمان‌بندی‌شده انجام می‌شود. cron سال‌ها ابزار پیش‌فرض این کار بوده، اما چند محدودیت دارد که در عمل دردسرساز می‌شوند: اگر سرور در لحظهٔ اجرای یک job خاموش یا در حال ریبوت باشد، آن job به‌سادگی حذف می‌شود و هیچ‌وقت دوباره اجرا نمی‌شود. از طرف دیگر، وقتی یک اسکریپت کرون شکست می‌خورد، معمولاً هیچ لاگ قابل‌استفاده‌ای برای فهمیدن دلیل آن وجود ندارد، مگر این‌که خودتان redirect و لاگ‌گیری دستی اضافه کرده باشید.

systemd timer همان کاری را انجام می‌دهد که cron انجام می‌داد، اما با ابزارهایی که دقیقاً همین دو مشکل را حل می‌کنند. در این مقاله با نحوهٔ ساخت فایل‌های .service و .timer، استفاده از Persistent=true برای جلوگیری از ازدست‌رفتن وظایف، و بررسی لاگ‌ها با journalctl آشنا می‌شوید.

محدودیت‌های واقعی Cron روی سرور

قبل از رفتن سراغ systemd، بهتر است دقیق‌تر ببینیم cron کجا کم می‌آورد:

  • بدون اجرای جبرانی: اگر سرور بین ساعت ۲ تا ۴ بامداد خاموش بوده و job بکاپ شما دقیقاً ساعت ۳ زمان‌بندی شده بود، آن بکاپ هرگز اجرا نمی‌شود. cron فقط در لحظهٔ دقیق زمان‌بندی‌شده چک می‌کند، نه بعد از آن.
  • لاگ‌گیری ضعیف: خروجی استاندارد یک job کرون معمولاً به ایمیل محلی سرور (که اغلب تنظیم نشده) فرستاده می‌شود یا کلاً گم می‌شود. فهمیدن این‌که یک job دیشب شکست خورده یا نه، بدون redirect دستی تقریباً غیرممکن است.
  • وابستگی ضعیف: cron نمی‌داند که یک سرویس باید اول بالا بیاید، یا یک mount باید اول آماده باشد. هر job مستقل از بقیهٔ سیستم اجرا می‌شود.
  • مدیریت متفرق: هر خط crontab یک رشتهٔ زمان‌بندی جدا از منطق اجراست؛ دیباگ کردن یک crontab طولانی با چند ده خط به مرور سخت می‌شود.

Systemd Timer چیست و چطور کار می‌کند

systemd timer یک unit از نوع .timer است که به یک unit از نوع .service وصل می‌شود. فایل .service مشخص می‌کند چه چیزی اجرا شود، و فایل .timer مشخص می‌کند چه زمانی. این جداسازی به این معنی است که همان service را می‌توانید هم با یک timer و هم به‌صورت دستی با systemctl start اجرا کنید، بدون این‌که منطق اجرا را دوباره بنویسید.

هر دو فایل مستقیماً زیر مدیریت systemd قرار می‌گیرند، یعنی همان ابزارهایی که برای مانیتور کردن سرویس‌های دیگر سرور (nginx، postgresql، docker) استفاده می‌کنید، برای وظایف زمان‌بندی‌شده هم کار می‌کنند: systemctl status، systemctl list-timers، و journalctl -u. اگر یک اپلیکیشن خودتان را هم با systemd service مدیریت می‌کنید — مثلاً مطابق دیپلوی اپلیکیشن Go روی سرور اختصاصی و VPS با systemd و Nginx — همین دستورها بدون تغییر روی آن سرویس هم کار می‌کنند.

مقایسهٔ Cron و Systemd Timer

جدول زیر تفاوت‌های کلیدی را کنار هم نشان می‌دهد:

ویژگیCronSystemd Timer
اجرای job ازدست‌رفته بعد از ریبوتخیربله، با Persistent=true
لاگ متمرکز و قابل‌جست‌وجوخیر (نیاز به redirect دستی)بله، از طریق journalctl
وابستگی به سرویس‌ها و mount هاپشتیبانی نمی‌شودبله (After=, Requires=)
محدود کردن منابع (CPU، حافظه)پشتیبانی نمی‌شودبله، از طریق cgroup در فایل service
دقت زمان‌بندی تصادفی (جلوگیری از thundering herd)خیربله، با RandomizedDelaySec=
سینتکس زمان‌بندیکوتاه‌تر (* * * * *)کمی طولانی‌تر اما خواناتر (OnCalendar=)
در دسترس بودن روی توزیع‌های قدیمی‌ترهمه‌جافقط روی سیستم‌هایی با systemd

برای اکثر سرورهای مجازی و اختصاصی امروزی که Ubuntu، Debian، CentOS/Rocky یا AlmaLinux با systemd اجرا می‌کنند، ردیف آخر جدول عملاً محدودیتی ایجاد نمی‌کند.

اگر همین‌جا بخواهید مصرف CPU و RAM یک سرویس را هم محدود کنید، محدود کردن مصرف CPU و RAM سرویس‌ها با systemd و cgroups در سرور لینوکس نحوهٔ تنظیم آن را در همین فایل service نشان می‌دهد.

ساخت یک وظیفهٔ بکاپ با فایل .service و .timer

فرض کنید می‌خواهید هر شب یک اسکریپت بکاپ از دیتابیس را اجرا کنید. ابتدا فایل سرویس را می‌سازیم:

# /etc/systemd/system/db-backup.service
[Unit]
Description=Backup دیتابیس به صورت شبانه

[Service]
Type=oneshot
ExecStart=/usr/local/bin/db-backup.sh

نکته: Type=oneshot به systemd می‌گوید که این سرویس یک‌بار اجرا می‌شود و تمام می‌شود، برخلاف سرویس‌های دائمی مثل nginx.

حالا فایل timer را می‌سازیم:

# /etc/systemd/system/db-backup.timer
[Unit]
Description=اجرای روزانهٔ db-backup.service

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

[Install]
WantedBy=timers.target

سه خط بخش [Timer] نکته‌های مهمی دارند:

  • OnCalendar=*-*-* 03:00:00 یعنی هر روز ساعت ۳ بامداد.
  • Persistent=true یعنی اگر سرور در ساعت ۳ خاموش بود، به‌محض بالا آمدن سیستم، این job یک‌بار اجرا می‌شود تا جایش خالی نماند.
  • RandomizedDelaySec=300 یک تأخیر تصادفی تا ۵ دقیقه اضافه می‌کند؛ وقتی چند سرور مجازی روی یک هاست فیزیکی مشترک هستند، این گزینه از اجرای همزمان چند job سنگین در یک لحظهٔ دقیق جلوگیری می‌کند.

برای فعال‌سازی:

sudo systemctl daemon-reload
sudo systemctl enable --now db-backup.timer

توجه کنید که enable --now روی فایل .timer اجرا می‌شود، نه روی .service — این timer است که زمان‌بندی را مدیریت می‌کند و در زمان مقرر، service مربوط به خودش را صدا می‌زند.

اجرای جبرانی وظایف ازدست‌رفته با Persistent=true

این همان قابلیتی است که بیشترین تفاوت را با cron ایجاد می‌کند. بدون Persistent=true، اگر سرور بین زمان‌بندی‌شده و لحظهٔ فعلی خاموش بوده باشد، آن اجرا برای همیشه از دست می‌رود — دقیقاً مثل cron. با افزودن این یک خط، systemd زمان آخرین اجرای موفق را در دیسک ذخیره می‌کند و اگر متوجه شود یک اجرا به‌دلیل خاموشی سرور رد شده، به‌محض بوت بعدی آن را اجرا می‌کند.

برای وظایفی مثل بکاپ شبانه یا پاک‌سازی فایل‌های موقت، این یعنی یک ریبوت یا یک دوره maintenance کوتاه دیگر باعث نمی‌شود یک شب بکاپ نداشته باشید.

لاگ‌گیری با journalctl به‌جای شکست‌های بی‌صدا

برای دیدن خروجی کامل یک وظیفه، کافی است نام سرویس را به journalctl بدهید:

journalctl -u db-backup.service --since "1 day ago"

این دستور خروجی استاندارد و استاندارد خطای اسکریپت، به‌همراه کد خروج (exit code) و زمان دقیق شروع و پایان را نشان می‌دهد؛ بدون این‌که لازم باشد خودتان در اسکریپت >> /var/log/db-backup.log 2>&1 اضافه کنید. اگر فقط می‌خواهید وضعیت کلی و زمان اجرای بعدی را ببینید:

systemctl list-timers db-backup.timer

این دستور آخرین زمان اجرا، زمان اجرای بعدی، و این‌که آیا اجرای قبلی موفق بوده یا نه را در یک خط نشان می‌دهد — دقیقاً اطلاعاتی که در یک crontab ساده اصلاً وجود ندارد.

خروجی دستور journalctl برای یک سرویس systemd شامل کد خروج و زمان دقیق شروع و پایان اجرا

اگر چند سرور دارید و می‌خواهید لاگ‌های journalctl همهٔ آن‌ها را در یک جا جمع کنید، راه‌اندازی لاگ متمرکز با Grafana Loki و Alloy گزینهٔ بعدی طبیعی است.

چه زمانی هنوز سراغ Cron بروید

systemd timer برای اکثر وظایف نگهداری سرور گزینهٔ بهتری است، اما دو مورد وجود دارد که cron هنوز ساده‌تر است:

  • اسکریپت‌های خیلی کوچک و یک‌باره که نیازی به لاگ‌گیری دقیق یا اجرای جبرانی ندارند؛ نوشتن یک خط crontab سریع‌تر از ساخت دو فایل unit است.
  • محیط‌های بدون systemd (مثلاً برخی کانتینرهای حداقلی یا توزیع‌های قدیمی‌تر) که اصلاً systemd ندارند.

برای وظایف production روی یک سرور مجازی یا اختصاصی — بکاپ‌گیری، rotation لاگ، پاک‌سازی فایل‌های موقت، health-check دوره‌ای — ارزش افزودهٔ Persistent=true و لاگ متمرکز معمولاً بیشتر از هزینهٔ نوشتن دو فایل اضافه است.

جمع‌بندی

cron ابزار ساده و قدیمی‌ای است که هنوز هم کار می‌کند، اما روی سرورهای امروزی دو ضعف اصلی آن — نبود اجرای جبرانی و نبود لاگ متمرکز — می‌توانند باعث شوند یک بکاپ شبانه بی‌صدا از دست برود. systemd timer با دو فایل ساده (.service و .timer)، گزینهٔ Persistent=true، و لاگ‌گیری یکپارچه با journalctl، همان زمان‌بندی را با قابلیت رهگیری و اطمینان بیشتر فراهم می‌کند.

اگر این تغییر را روی چند سرور به‌صورت هم‌زمان انجام می‌دهید، اتوماسیون راه‌اندازی و پیکربندی چند سرور مجازی و اختصاصی با Ansible می‌تواند ساخت و فعال‌سازی فایل‌های .service و .timer را روی همهٔ سرورها یک‌جا انجام دهد.

اگر زیرساخت شما از همین الان روی یک سرور مجازی آریانت اجرا می‌شود، همین امروز می‌توانید وظایف بکاپ و نگهداری را از crontab به systemd timer منتقل کنید و با journalctl مطمئن شوید که هیچ job دیگر بی‌صدا شکست نمی‌خورد.