زمان رسیدن، به معنی انجام شدن نیست
فرض کنید تهیه گزارش روزانه باید هر شب انجام شود، اما سرور هنگام موعد خاموش بوده است. روز بعد روشن شدن ماشین به تنهایی تضمین نمیکند گزارش تولید شده باشد. از سوی دیگر ممکن است زمانبند کار را آغاز کرده باشد ولی فرمان به دلیل نبودن دسترسی یا پر شدن دیسک شکست بخورد. طراحی درست باید سه پرسش را جدا پاسخ دهد: موعد چه زمانی است، کدام کار باید آغاز شود و از کجا میفهمیم خروجی معتبر تولید شده است. در systemd پاسخ اول معمولاً در واحد timer و پاسخ دوم در واحد service قرار میگیرد. پاسخ سوم نیازمند بررسی وضعیت سرویس و خود نتیجه کسبوکار است.
این جدایی یک مزیت عملی دارد. همان سرویس را میتوان برای آزمایش دستی اجرا کرد و سپس به زمانبند سپرد، بدون اینکه منطق اصلی در عبارت زمانبندی پنهان شود. سرویس میتواند کاربر اجرا، مسیر کاری، محدودیت زمان و وابستگیها را مشخص کند. واحد timer فقط تصمیم میگیرد چه زمانی درخواست فعال شدن آن سرویس را بدهد. داشتن وضعیت active برای timer نشان میدهد زمانبند فعال است؛ چنین وضعیتی گواه موفقیت آخرین تهیه نسخه پشتیبان یا تحویل گزارش نیست. این سوءبرداشت یکی از دلایل رایج کشف دیرهنگام شکست کارهای دورهای است.
دو خانواده زمان و دو قرارداد متفاوت
عبارت OnCalendar بر ساعت دیواری تکیه دارد: مثلاً هر روز در یک ساعت مشخص. این مدل برای کارهایی مناسب است که با تقویم سازمان یا زمان محلی معنا پیدا میکنند. در مقابل، گزینههایی مانند OnBootSec و OnUnitInactiveSec فاصله را نسبت به یک نقطه شروع تعریف میکنند. اولی نسبت به راهاندازی ماشین و دومی نسبت به آخرین غیرفعال شدن واحد هدف سنجیده میشود. انتخاب میان این مدلها صرفاً سلیقه نوشتن نیست. گزارش روزانه و بررسی سلامت هر چند دقیقه قراردادهای زمانی متفاوت دارند و باید با گزینه مناسب بیان شوند.
OnUnitActiveSec فاصله را از آخرین فعال شدن واحد هدف میسنجد. اگر کاری مدت متغیر داشته باشد، این نقطه شروع با زمان پایان آن یکسان نیست. برای نمونه، فاصله پنج دقیقه پس از شروع کاری که چهار دقیقه طول میکشد با پنج دقیقه استراحت پس از پایان همان کار تفاوت دارد. پیش از نوشتن تنظیمات، جمله فارسی قرارداد را روشن کنید: آیا هدف اجرا در ساعت معین است، فاصله میان شروعها اهمیت دارد یا لازم است پس از پایان کار مدتی صبر شود؟ این جمله ساده جلوی بسیاری از تنظیمات ظاهراً درست ولی رفتاری نامناسب را میگیرد.
یک مثال کوچک و قابل آزمایش
نمونه زیر برای برنامهای محلی است که گزارش را تولید میکند و هنگام پایان باید خارج شود. مسیر برنامه فرضی است و باید پیش از استفاده با فایل واقعی جایگزین شود. کاربر گزارش نیز باید وجود داشته باشد و فقط دسترسی لازم را دریافت کند. ابتدا سرویس را مستقلاً آزمایش کنید؛ اگر فرمان در محیط سرویس کار نمیکند، افزودن timer مشکلی را حل نخواهد کرد. محیط اجرای سرویس لزوماً محیط پوسته تعاملی مدیر نیست و متغیرها، مسیر جستوجوی برنامه و دسترسی فایلها باید صریح باشند.
# /etc/systemd/system/daily-report.service
[Unit]
Description=Generate daily report
[Service]
Type=oneshot
User=report
WorkingDirectory=/var/lib/report
ExecStart=/usr/local/bin/generate-report
TimeoutStartSec=20min
# /etc/systemd/system/daily-report.timer
[Unit]
Description=Schedule daily report
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=10min
[Install]
WantedBy=timers.target
وقتی نام پایه دو واحد یکسان باشد، timer به طور پیشفرض سرویس همنام را فعال میکند. برای هدف دیگر میتوان Unit را مشخص کرد، اما نامگذاری ساده بررسی عملیات را آسانتر میکند. زمان دو بامداد در این مثال باید همراه منطقه زمانی ماشین فهمیده شود و تأخیر تصادفی نیز به آن افزوده میشود. بنابراین قرارداد مثال «آغاز دقیق در دو بامداد» نیست. اگر موعد بیرونی سختی وجود دارد، بازه مجاز و مدت اجرای کار باید در طراحی لحاظ شوند و انتخاب تصادفی نباید از فرصت تحویل تجاوز کند.
فعالسازی و مشاهده به ترتیب درست
پس از نوشتن فایلها، مدیر سرویس باید تعریف تازه را بارگذاری کند. سپس اجرای دستی سرویس، خطای مسیر یا مجوز را زود آشکار میکند. فعال کردن timer برای شروع هنگام راهاندازی با آغاز کردن همان واحد در نشست فعلی فرق دارد؛ ترکیب enable و now هر دو را درخواست میکند. با این حال موفقیت این فرمان هنوز نیازمند بررسی زمان بعدی و نتیجه سرویس است. تغییر فایل نیز تا وقتی بارگذاری مجدد انجام نشده ممکن است با تعریف در حافظه متفاوت باشد.
systemctl daemon-reload
systemctl start daily-report.service
systemctl status daily-report.service
systemctl enable --now daily-report.timer
systemctl list-timers --all
journalctl -u daily-report.service
این فرمانها فقط در سامانهای اجرا شوند که واحد نمونه برای آن آماده شده است. هنگام عیبیابی، وضعیت timer و service را کنار هم ببینید. ممکن است timer موعد بعدی را درست نشان دهد ولی آخرین سرویس شکست خورده باشد. همچنین خروجی موفق برنامه باید با فایل یا رکورد مورد انتظار تطبیق داده شود؛ برنامهای که خطا را میبلعد و با کد صفر خارج میشود میتواند وضعیت ظاهراً سالم بسازد. زمان تولید آخرین خروجی معتبر معمولاً شاخص مفیدتری از صرف وجود فرایند یا فعال بودن زمانبند است.
Persistent چه چیزی را جبران میکند؟
برای timer تقویمی، Persistent زمان آخرین فعالسازی را روی دیسک نگه میدارد. وقتی واحد دوباره فعال شود و در فاصله غیرفعال بودن دستکم یک موعد از دست رفته باشد، اجرای جبرانی درخواست میشود. این رفتار برای لپتاپ یا میزبانی که مدتی خاموش بوده مفید است. اما نباید آن را صفی از همه اجراهای فوتشده تصور کرد. خاموشی چندروزه لزوماً به اجرای جداگانه برای تکتک روزها تبدیل نمیشود. برنامه باید خودش بداند کدام بازه داده هنوز پردازش نشده و چگونه عقبماندگی را جبران کند.
همچنین تأخیر تصادفی همچنان میتواند بر اجرای جبرانی اثر بگذارد. اگر همه ماشینهای یک مرکز همزمان روشن شوند، اجرای فوری هزار کار سنگین ممکن است سرویس مقصد را اشباع کند. فاصله تصادفی میتواند این تجمع را کاهش دهد، ولی ضمانت توزیع دقیق بار نیست. برای کارهای مالی یا پردازش صف، نشانگر آخرین بازه موفق باید در داده برنامه ثبت شود؛ مهر زمانی timer جای ثبت تراکنش کاری را نمیگیرد. این تفکیک باعث میشود روشن شدن ماشین پس از وقفه، خروجی تکراری یا دورههای حذفشده تولید نکند.
دقت اجرا با پخش بار یکی نیست
AccuracySec بازهای برای همزمانسازی بیدار شدنهای سیستم فراهم میکند و میتواند به کاهش بیدار شدن پردازنده کمک کند. RandomizedDelaySec هدف دیگری دارد: افزودن تأخیر تصادفی تا کارهای مشابه در یک لحظه جمع نشوند. این دو تنظیم را نباید مترادف دانست. کوچک کردن دقت، برنامه را از تأخیر ناشی از بار سیستم یا آماده نبودن وابستگیها مصون نمیکند. systemd timer برای زمانبندی کار مدیریتی مناسب است، اما انتخاب آن به معنی تعهد اجرای سخت بلادرنگ در یک میلیثانیه مشخص نیست.
برای ناوگان ماشینها، بازه تصادفی را با ظرفیت مقصد و مهلت اتمام کار بسنجید. اگر انتقال پشتیبان در پنجره یکساعته باید تمام شود، پخش شروعها در تمام همان یک ساعت بدون توجه به مدت انتقال ممکن است مهلت را نقض کند. اندازه فایل، پهنای باند و تعداد میزبانها باید وارد برآورد شوند. در محیط آزمایش میتوان زمان شروع واقعی چندین اجرا را ثبت کرد و توزیع بار را دید. مشاهده این داده از انتخاب یک مقدار بزرگ و امیدوار بودن به توزیع مناسب قابل اتکاتر است.
همپوشانی و رفتار سرویس طولانی
اگر هنگام رسیدن موعد، واحد هدف از قبل فعال باشد، timer همان سرویس فعال را به یک نمونه تازه تبدیل نمیکند. این رفتار برای جلوگیری از برخی اجراهای همزمان مفید است، ولی قرارداد صف کاری کامل نیست. اگر برنامه پسزمینه شود، فرایند دیگری راه بیندازد یا وضعیت سرویس با عمر واقعی کار هماهنگ نباشد، نتیجه میتواند با انتظار مدیر تفاوت داشته باشد. نوع سرویس باید با شیوه زندگی برنامه سازگار باشد و پایان واحد باید معنای پایان کار قابل پیگیری داشته باشد.
برای سرویس oneshot که قرار است در موعدهای بعدی دوباره اجرا شود، RemainAfterExit معمولاً انتخاب مناسبی نیست؛ باقی ماندن واحد در حالت فعال میتواند مانع فعالسازی مجدد مورد انتظار شود. همچنین قفل در سطح خود برنامه برای مواردی که اجرای دستی یا مسیر دیگری همان کار را آغاز میکند ارزشمند است. قفل باید شکست و خروج غیرعادی را نیز در نظر بگیرد. علاوه بر منع همپوشانی، عملیات بهتر است در برابر تکرار مقاوم باشد تا اجرای جبرانی یا تلاش دوباره، رکورد تکراری و فایل ناقص ایجاد نکند.
ساعت، تعلیق و منطقه زمانی
موعد تقویمی به صحت ساعت دیواری وابسته است. ترتیب وابستگی به هدف همگامسازی زمان به تنهایی نباید با تضمین اندازهگیری دقیق ساعت اشتباه گرفته شود؛ پیکربندی واقعی سرویس زمان اهمیت دارد. روی ماشینی که ساعت سختافزاری قابل اعتماد ندارد، شروع زودهنگام timer پیش از تصحیح زمان میتواند رفتار غیرمنتظره بسازد. منطقه زمانی و تغییر ساعت فصلی نیز باید در قرارداد کار روشن باشند. آزمون عبارت تقویمی با systemd-analyze calendar کمک میکند برداشت مدیر با تفسیر systemd مقایسه شود.
تعلیق سیستم موضوع جداگانهای است. WakeSystem در صورت پشتیبانی سیستم و وجود مجوز لازم میتواند برای بیدار کردن ماشین استفاده شود، اما خودش مسئول بازگرداندن سیستم به خواب پس از پایان کار نیست. انتخاب میان بیدار کردن و جبران پس از بیداری به نیاز واقعی بستگی دارد. برای یک لپتاپ، مصرف باتری و دسترسی شبکه هنگام بیداری اهمیت دارند؛ برای سرور، پنجره نگهداری و ترتیب راهاندازی سرویسها تعیینکننده است. هیچ گزینه واحدی جای بررسی این شرایط محیطی را نمیگیرد.
طراحی پایش و پذیرش نهایی
پایش را با موعد کسبوکار تعریف کنید. اگر گزارش باید تا صبح آماده باشد، هشدار باید نبودن گزارش تازه تا زمان مشخص را تشخیص دهد، حتی اگر timer هر شب فعال شده باشد. مدت اجرا، کد خروج، آخرین خروجی موفق و حجم داده پردازششده کنار هم تصویر بهتری میسازند. در تست پذیرش، اجرای عادی، شکست برنامه، خاموش بودن در موعد و طولانی شدن کار را آزمایش کنید. نتیجه هر حالت باید با قرارداد از پیش نوشتهشده تطبیق داشته باشد تا رفتار جبرانی تصادفاً در محیط واقعی کشف نشود.
مهاجرت از cron نیز با حذف فوری زمانبند قبلی آغاز نمیشود. ابتدا فرمان، محیط و خروجی را در سرویس جدید بررسی کنید؛ سپس در پنجره کنترلشده تنها یک مسیر زمانبندی را فعال نگه دارید تا کار دوبار اجرا نشود. مسیر بازگشت و محل گزارش خطا ثبت شوند. ارزش timer در این است که موعد، وضعیت و گزارش سرویس را در یک چارچوب قابل مشاهده قرار میدهد. کیفیت نهایی همچنان به برنامهای وابسته است که پایان درست را اعلام کند، داده را سازگار بنویسد و اجرای دوباره را با منطق روشن مدیریت کند.