WIUNIX ACADEMY
زمانبندی با systemd timer؛ نوبت ازدسترفته و اجرای تکراری رو مدیریت کن
یک کار آزمایشیِ تکرارپذیر میسازیم، زمانبندی روزانهٔ UTC و جبران نوبت ازدسترفته رو میبینیم و یاد میگیریم چه وقت فاصله از پایان اجرا انتخاب بهتریه.
محیطت رو انتخاب کنآموزش متناسب با محیط تو
کجا اجرا میکنی؟
سیستمعامل و نسخهٔ سرورت رو انتخاب کن تا مراحل، دستورها و راهحلهای همون محیط رو ببینی.
آموزش متناسب با مقادیر تو
تنظیمات این آموزش
مقادیر نمونه رو با مشخصات سرورت عوض کن تا متن و دستورها با هم تغییر کنن. رمز، کلید خصوصی و توکن اینجا وارد نکن. مقدارها فقط توی همین زبانه میمونن و به سرور یا لینک اشتراکگذاری فرستاده نمیشن. قبل از اجرا، DNS، گواهیها و تنظیمات دسترسی شبکه رو روی سرورت بررسی کن.
اگر آیپی، دامنه یا پورتهای نمونه با سرورت فرق دارن، بخش تنظیمات پایین رو باز کن. مقدارها رو عوض کن و «اعمال در آموزش» رو بزن؛ بعد دستورهای تازه رو کپی کن.
تنظیم مقادیر برای محیط من3 مقدار قابلتغییر
از اینجا شروع کن
آخر این آموزش چی خواهیم داشت؟
یک کار آزمایشیِ تکرارپذیر میسازیم، زمانبندی روزانهٔ UTC و جبران نوبت ازدسترفته رو میبینیم و یاد میگیریم چه وقت فاصله از پایان اجرا انتخاب بهتریه.
سیستمعاملها و نسخههای پوششدادهشده
مسیرهای اوبونتو ۲۶٫۱۰ و LTSهای ۲۶٫۰۴ و ۲۴٫۰۴، دبیان ۱۳ و ۱۲، خانوادهٔ RHEL/راکی/آلما ۱۰ و ۹، آرچ و مانجارو پوشش داده شدهاند. تفاوت ابزار بسته و سیاست میزبان در انتخاب محیط میاد؛ مسیرهای غلتان با تاریخ بررسی و نسخهٔ بسته شناخته میشن. اینها بررسی مستنداتن، نه ادعای اجرای عملی روی همهٔ سیستمها. timer محلی جای صف پایدار یا تضمین برنامه برای جلوگیری از اثر تکراری نیست.
قبل از شروع
- یک میزبان آزمایشی با systemd بهعنوان پردازش شمارهٔ یک، دسترسی مجاز sudo و ویرایشگر آماده برای sudoedit.
- نام تازهای برای واحدهای تمرین و فضای کوچک داده؛ هیچ فایل یا زمانبندی برنامهٔ موجودی جایگزین نمیشه.
- آشنایی با واحدهای systemd، کد خروج برنامه و بررسی لاگ. اجرا و توقف فقط برای نمونهٔ همین آموزش مجازه.
از زمانبندی دقیقاً چه انتظاری داریم؟
فرض کن یک کار نگهداری هر شب انجام میشه. اگر سرور خاموش باشه چه اتفاقی باید بیفته؟ اگر اجرا شکست بخوره، تکرارش بیخطره؟ اگر هنوز تموم نشده باشه و نوبت بعدی برسه چی؟ timer فقط زمان درخواست شروع سرویس رو مشخص میکنه؛ تشخیص کار باقیمونده و نتیجهٔ اجرای قبلی به خود برنامه مربوطه.
برای روشنشدن این تفاوت، یک کار آزمایشی تازه میسازیم که برای هر روز UTC فقط یک فایل کوچک ثبت میکنه. اول زمانبندی روزانه رو میبینیم و بعد سراغ فاصلهای میریم که از پایان کار حساب میشه. این فایل نشونهٔ انجام کار آزمایشیه؛ بکاپ، صورتحساب یا تضمین اجرای دقیقاً یکباره نیست. روی میزبان آزمایشی کار کن و زمانبندی برنامهٔ واقعی رو با این نمونه عوض نکن.
اگر ساعت مشخصی از روز مهمه، تقویم انتخاب مناسبیه. اگر میخوای برنامه بعد از هر اجرا مدتی استراحت کنه، فاصله از پایان کار روشنتره. timer یک ماشین هم هماهنگکنندهٔ کلاستر نیست؛ برای پردازش تکتک رویدادهای ازدسترفته به صف یا سازوکار خود برنامه نیاز داری.
مقادیر نمونه رو قبل از کپیکردن تنظیم کن
فرم مقادیر نمونه رو باز کن، برای کار یک نام تازه انتخاب کن و ساعت UTC رو بین ۰ تا ۲۳ بذار. بعد مقدارها رو اعمال کن. شروع کار ممکنه تا 300 ثانیه بهصورت تصادفی عقب بیفته تا چند میزبان همزمان شلوغ نشن؛ این مقدار تضمین زمان پایان نیست. نام کار هم نام فایلهای سرویس، timer، اسکریپت و پوشهٔ داده رو تعیین میکنه، پس نام یک برنامهٔ موجود رو انتخاب نکن.
مقدارها برای همین آموزش و محیط انتخابشده در همین تب میمونن. تا وقتی تغییرها رو اعمال یا بازنشانی نکرده باشی، یا مقدار نامعتبر باشه، کپی دستورها متوقف میشه. اینجا فقط مقدارهای عمومی وارد کن؛ رمز، توکن و آرگومان خصوصی برنامه جاشون در این فرم نیست. تاریخ فایل آزمایشی همیشه UTC حساب میشه، حتی اگر ساعت اجرا رو عوض کنی.
قدم بعدی
توزیع و ابزارهای همین میزبان رو بررسی کن
از نسخهٔ فرعیِ پشتیبانیشده و بهروز RHEL، راکی یا آلما در شاخهٔ ۱۰ یا ۹ استفاده کن. ابزار کمشده رو از مخزن مجاز RHEL یا مخزن رسمی همان توزیع بگیر. اجرای کار و مدیریت سرویس با systemd نصبشده انجام میشه؛ cron تازه، مخزن بیرونی یا systemd جایگزین لازم نداریم.
فایلهای نمونه در محل واحدهای مدیریتی میرن، نه روی فایل واحدی که بسته نصب کرده. SELinux رو در حالت enforcing نگه دار. برچسب اسکریپت، مجوز فایل و دسترسی هویت موقت به داده هرکدوم میتونن جدا از رسیدن نوبت timer، اجرا رو متوقف کنن. نسخهٔ بسته رو ببین؛ میزبانهای شاخههای ۹ و ۱۰ الزاماً systemd یکسان ندارن.
ترمینال میزبان آزمایشی با دسترسی مجاز
cat /etc/os-release
cat /proc/1/comm
systemd --version
rpm -q systemd coreutils sudo audit policycoreutils libselinux-utils
command -v systemctl systemd-analyze journalctl timedatectl sudo sudoedit date mktemp mv
command -v getenforce ausearch restoreconپردازش شمارهٔ یک systemd است و ابزارهای لازم موجودن؛ اگر انتخاب با میزبان نمیخونه، قبل از ادامه رفعش کن.
قدم بعدی
ساعت سیستم و چند نوبت بعدی رو ببین
ساعت اجرا رو صریحاً با UTC مینویسیم تا تغییر منطقهٔ زمانی میزبان یا ساعت تابستونی، نوبت موردنظر UTC رو جابهجا نکنه. قبل از ذخیرهٔ timer، وضعیت همگامسازی ساعت رو ببین و از خود systemd بخواه چند نوبت بعدی رو حساب کنه. درستبودن عبارت تقویمی فقط نحو اون رو ثابت میکنه؛ سلامت NTP، بیدارشدن سختافزار یا برنامه رو ثابت نمیکنه.
سرویس زمان فعلی میزبان رو نگه دار. قرارگرفتن timer بعد از time-sync.target بهتنهایی نشون نمیده ساعت دقیق شده؛ وضعیت سرویس زمان رو هم باید باهاش تطبیق بدی. برای تمرین جبران نوبت ازدسترفته، ساعت میزبان رو جلو و عقب نبر و منطقهٔ زمانی سیستم رو عوض نکن.
ترمینال میزبان آزمایشی با دسترسی مجاز
timedatectl status
date -u
systemd-analyze calendar --iterations=5 '*-*-* 3:00:00 UTC'عبارت نرمالشده و پنج نوبت بعدی UTC با زمان موردنظرت سازگارن. اگر ساعت همگام نیست، قبل از فعالکردن علتش رو پیدا کن.
قدم بعدی
فایلهای یک کار تازه رو آماده کنیم
برای نمونه، اسکریپت و سرویس و timer مستقل میسازیم. اگر واحد یا پوشهٔ داده از قبل وجود داشته باشه، ادامه نمیدیم؛ پوشهٔ خصوصیای که DynamicUser استفاده میکنه هم بررسی میشه. ساخت انحصاری فایلها روی فایل یا پیوند موجود متوقف میشه. اگر رزرو فایل دوم یا سوم شکست بخوره، ممکنه فایلهای خالی قبلی باقی مونده باشن؛ همان فایلها رو بررسی کن و تا رفع مشکل timer رو فعال نکن.
سه محتوای بعدی رو در فایلهای متناظر ویرایشگر بذار. پوشهٔ مشترک اسکریپتها میتونه از قبل وجود داشته باشه، ولی فقط فایلهای نامبرده به این آموزش مربوط میشن. فایل واحدِ بستهها یا فایل کار دیگری رو تغییر نده. برای نصب فایلها دسترسی مدیر لازم داریم، اما خود کار با هویت موقت و بدون دسترسی مدیر اجرا میشه.
ترمینال میزبان آزمایشی با دسترسی مجاز
test "$(systemctl show -p LoadState --value 'academy-daily-marker.service')" = not-found || exit 1
test "$(systemctl show -p LoadState --value 'academy-daily-marker.timer')" = not-found || exit 1
sudo test ! -e '/var/lib/academy-daily-marker' || exit 1
sudo test ! -e '/var/lib/private/academy-daily-marker' || exit 1
sudo install -d -m 0755 /usr/local/libexec
sudo sh -c 'set -euC; : > "$1"; : > "$2"; : > "$3"' sh '/usr/local/libexec/academy-daily-marker.sh' '/etc/systemd/system/academy-daily-marker.service' '/etc/systemd/system/academy-daily-marker.timer' || exit 1
sudo chmod 0644 '/usr/local/libexec/academy-daily-marker.sh' '/etc/systemd/system/academy-daily-marker.service' '/etc/systemd/system/academy-daily-marker.timer'
sudoedit '/usr/local/libexec/academy-daily-marker.sh' '/etc/systemd/system/academy-daily-marker.service' '/etc/systemd/system/academy-daily-marker.timer'سه فایل تازه با مالک root برای ویرایش باز شدهاند. برخورد با نام موجود، مرحله رو بدون جایگزینی اون فایل متوقف میکنه.
کار تکرارپذیر بنویسیم و پایانش رو با فایل نهایی ثبت کنیم
systemd مسیر پوشهٔ داده رو به اسکریپت میده. اسکریپت تاریخ امروز UTC رو پیدا میکنه و اگر فایل همون روز موجود باشه، با موفقیت خارج میشه. در اجرای اول، محتوا داخل فایل موقت همان پوشه نوشته میشه و بعد با تغییر نام به فایل نهایی تبدیل میشه. قطع معمولی قبل از تغییر نام، فایل تکمیلشده نمیسازه؛ قطع ناگهانی برق ممکنه فایل موقت باقی بذاره.
این روش فقط تکرارپذیری یک خروجی کوچک محلی رو نشون میده. تغییر نام فایل، فراخوانی API یا تغییر دیتابیس رو دقیقاً یکبار نمیکنه و تضمین ذخیرهٔ پایدار روی دیسک هم نیست. برنامهٔ واقعی به تراکنش، شناسهٔ جلوگیری از تکرار یا ثبت پیشرفت قابلبازیابی خودش نیاز داره. این اسکریپت رو فقط از راه همین سرویس اجرا کن؛ اجرای مستقیمِ همزمان، کنترل systemd رو دور میزنه.
محتوای فایل؛ در ترمینال اجرا نکن
#!/bin/sh
set -eu
umask 077
: "${STATE_DIRECTORY:?Start this job through its systemd service}"
day=$(date -u +%F)
report="$STATE_DIRECTORY/$day.txt"
if test -f "$report"; then
printf 'ALREADY_DONE %s\n' "$day"
exit 0
fi
tmp=$(mktemp "$STATE_DIRECTORY/.report.XXXXXX")
trap 'rm -f -- "$tmp"' EXIT HUP INT TERM
printf 'academy daily marker for UTC day %s\n' "$day" > "$tmp"
mv -- "$tmp" "$report"
trap - EXIT HUP INT TERM
printf 'COMPLETED %s\n' "$day"