הגדרת systemd timers: היכן נשבר cron הקלאסי
מתזמן המשימות ב-Linux מצטמצם לרוב לשורה ב-crontab: cron מפעיל פקודה לפי לוח זמנים וזה הסוף. הוא לא יודע כיצד הסתיים ההפעלה הקודמת, מה לעשות במקרה של תקלה והאם הייתה הפעלה מחדש של המחשב בחלון המשימה.
לכן השאלה "מדוע משימות מתוזמנות לא פועלות" מתחילה בדרך כלל בסריקה ידנית של יומני המערכת בחיפוש אחר השורה הבודדת שנכשלה.
בהמשך - ארבע תקלות אופייניות שצפות בכל חוות שרתים. השגיאה נעלמת: ללא נמען מוגדר, פלט הפקודה שוקע במייל המקומי של root, שאף אחד לא בודק במשך חודשים. הפעלה מחדש או אי-זמינות של הצומת ברגע ההפעלה פירושה שהמשימה פשוט לא תתבצע — cron לא שומר מצב ולא משלים את המוחמץ.
בין המשימות אין קשרים: הייצוא מתחיל לפני שהסתיים הכנת הנתונים, ושני התהליכים לא מודעים לכך. היומנים מפוזרים — חלק במייל, חלק בקובצי מערכת, חלק אבד ללא תקנה.
הגדרת systemd timers מסירה זאת ברמת הארכיטקטורה: טיימר ומטען שימושי מתוארים על ידי זוג קובצי unit, ולא על ידי שורה אחת. מצב ההפעלה נשמר על ידי systemd עצמו, תוצאת ההרצה הקודמת נראית לפי unit ספציפי, ההנחיה OnFailure= מגדירה מטפל בנפילה, ו-After= ו-Requires= בונים סדר בין המשימות.
המפתח Persistent=true מחזיר הפעלה שהוחמצה מיד לאחר הדלקת המחשב, כך שהפעלה מחדש בלילה כבר לא "אוכלת" את המשימה.
החלפת cron ב-systemd נותנת נקודת אמת אחת: רואים מה ומתי הופעל, איך זה הסתיים ומה בדיוק נשבר. להלן ננתח כיצד מועבר לוח זמנים ספציפי ומה לבדוק לפני השבתת השורות הישנות.
מה משתנה לאחר העברת משימות: אפקט מדיד
ב-cron הסימן היחיד לתקלה הוא שורה במייל של root או רשומה ב-syslog ללא סטטוס יציאה. systemd מנהל כל משימה כ-unit נפרד עם מצב משלו, לכן שיעור ההפעלות האבודות יורד לאפס: Persistent=true משלים חלון שהוחמץ לאחר הדלקת המחשב, ולא מאבד אותו בשקט. כבר בשלב זה רואים שהמשימה או הסתיימה עם קוד 0, או השאירה עקבות ביומן — אין מצב שלישי.
תלויות בין משימות מפסיקות להיות הסכם "להמתין שתי דקות" בתוך הסקריפט. צירופי After= ו-Requires= נותנים סדר הפעלה מפורש: הייצוא לא מתחיל עד שהסתיים הכנת הנתונים, ונפילה של שלב אחד לא מאפשרת לבא לעבוד על נתונים חלקיים.
הפעלה חוזרת במקרה של תקלה מוגדרת באופן סטנדרטי — Restart=on-failure עם השהיה בין ניסיונות ו-OnFailure= להתראה, במקום לולאות תוצרת בית ב-bash.
יומן הפעלות אחיד מסיר את הצורך לחפש את הסיבה ביומנים שונים. journalctl -u שם.timer תוך שניות מספק קוד יציאה, פלט stderr וזמן התחלה, לכן התקלה מתגלה לא בבוקר לפי תלונה, אלא תוך דקות לאחר החלון. בפועל, הזמן עד לגילוי התקלה מצטמצם מכמה שעות לעשרות דקות.
חיזוי חלון הביצוע נקבע על ידי OnCalendar ו-RandomizedDelaySec: לוח הזמנים נקרא כמחרוזת רגילה, והפיזור בזמן ההתחלה מסיר עומס בו-זמני על הדיסק והרשת. סחיפה שהצטברה ב-cron במשימות ארוכות לא מתרחשת.
בסך הכל, מספר ההתערבויות הידניות בחודש יורד משמונה-עשר לאחת-שתיים — בדרך כלל מדובר בניתוח סיבה חיצונית, ולא בהפעלה מחדש של המשימה ביד.
אפשרויות תזמון: השוואת גישות ומגבלות
בחירת לוח זמנים אינה תלויה בתחביר, אלא בערבויות הנדרשות למשימה: תלות בתוצאת ההפעלה הקודמת, התנהגות לאחר השבתת הצומת, יומן אחיד ופיזור בזמן ההתחלה.
ל-cron אין מצב בין הפעלות — הפעלה שהוחמצה בגלל מכונה כבויה פשוט הולכת לאיבוד, והשגיאה נשלחת בדואר לשום מקום. ל-systemd timers יש ביטויים קלנדריים, השהיית התחלה אקראית והשלמת הפעלה שהוחמצה, אך אין נעילה מבוזרת: אם משימה צריכה לרוץ פעם אחת על מספר צמתים, המתזמן ישכפל אותה.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| להשאיר את המתזמן הנוכחי | משימות פשוטות ללא תלויות ודרישות ליומן | אין השלמת החמצות, קשר ל-units אחרים ומצב |
| העברה מלאה למתזמן המערכת | נדרשות תלויות, יומן אחיד, השלמת הפעלות שהוחמצו | יש לתאר כל משימה כ-unit; במספר צמתים ייתכן שכפול |
| סכמה היברידית | משימות קריטיות מועברות, השאר עובד כמו קודם | שני מקומות אמת — נדרש מרשם מרוכז של לוחות זמנים |
| מתזמן משימות חיצוני | ההפעלה מבוזרת, חשובים הסדר והחזרה על הפעלה במקרה של תקלה | מערכת נפרדת שצריך לתחזק ולעדכן |
תזמון היברידי — פשרה עובדת בתקופת המעבר: מעבירים משימות אחת אחת, משווים את היומן להתנהגות הקודמת ורק לאחר מכן מסירים את השורה מקובץ לוח הזמנים הישן. כך הגדרת systemd timers לא הופכת למעבר חד-פעמי ומסוכן של כל הצומת.
שלבי יישום: כיצד אנו מעבירים משימות ל-systemd
לפני החלפת המתזמן צריך לראות את התמונה המלאה: כמה משימות באמת פועלות, אילו תלויות זו בזו ומה יקרה אם חלון ההפעלה יוחמץ. לכן ההעברה מתבצעת לפי סדר קבוע — ממיפוי ועד מסירה לתפעול, עם בדיקה בכל שלב.
- מיפוי לוח הזמנים של המשימות. מייצאים רשומות מטבלאות המתזמן המערכתיות והמשתמשיות, מוסיפים הפעלות מסקריפטים של אפליקציות ומשווים הכל ליומנים. חלק מהמשימות נכשלות מזמן, חלקן משוכפלות, ולחלקן אבד הבעלים — זה מתברר בדיוק כאן.
- סכמת תלויות בין משימות. בונים גרף: מה בא בהחלט אחרי מה, מה ניתן להקביל, והיכן החמצת חלון מותרת. כך מתעדים צירופים כמו "גיבוי לאחר ייצוא", שאותם אסור להעביר בנפרד.
- העברת תיאורים. המשימה מתפצלת ל"מה מפעילים" ו"מתי": unit שירות נפרד וטיימר. לוח הזמנים מתואר על ידי ביטוי קלנדרי, מופעלת השלמה של חלונות שהוחמצו לאחר השבתה — אם המשימה מאפשרת זאת.
- הגדרת הרשאות. רושמים משתמש, ספריית עבודה, משתני סביבה ומגבלות משאבים. המשימה מפסיקה להיות תלויה בסשן של מישהו ולא תגרור אחריה את כל המארח.
- בדיקה בתקלות. הפעלות כפויות, ניתוח יומנים, סימולציה של הפעלה מחדש והשבתה ממושכת, התנהגות כאשר מסד הנתונים אינו זמין. צריך לוודא שהמשימה או מתבצעת, או מדווחת במפורש על תקלה.
- מסירת לוח הזמנים לתפעול. מוסרים מפת משימות, תיעוד יומנים והתראות על הפעלות לא מוצלחות, הוראות להוספה והשבתה של משימות. לוח הזמנים נראה במקום אחד ומשתנה ללא עריכת סקריפטים של אחרים.
תצורות, סכמות ותקנות שנשארות אצלכם
לאחר העברת לוחות הזמנים ל-systemd-טיימרים, לצוות נשארת לא רק תצורה עובדת, אלא גם ערכת מסמכים שלפיה ניתן לתחזק אותה ללא המחבר. בלעדיה, בעוד חצי שנה איש לא יזכור מדוע המשימה מתחילה ב-03:17, בשם מי, ומה לעשות אם היא נפלה.
- תיאורי משימות ו-units — מה עושה כל טיימר, איזה סקריפט הוא מפעיל, אילו פרמטרים הוא מקבל ובמה שונה הביטוי הקלנדרי מהשורה הקודמת ב-cron.
- סכמת תלויות בין משימות — מה חייב להסתיים קודם: עיגון אחסון, העלאת מסד נתונים, ייצוא ממערכת חיצונית.
- כללי גישה — בשם מי מבוצעות ההפעלות, אילו קבוצות והרשאות נדרשות, היכן מאוחסנים פרטי הזדהות, ובמה מוגבלות יכולות ה-unit.
- תיעוד לוח הזמנים של המשימות — טבלה מרוכזת "משימה — תדירות — חלון ביצוע — בעלים", כולל הפעלות נדירות וקלנדריות.
- נוהל תגובה לתקלה — כיצד מגיעה ההתראה, מי ובאיזה פרק זמן בודק את היומן, מתי המשימה מופעלת מחדש אוטומטית ומתי היא נחסמת עד לניתוח.
- סדר שינוי לוח הזמנים — כיצד להוסיף או להשבית משימה כך שהעריכה לא תפגע בטיימרים שכנים ולא תיצור הפעלה כפולה.
- בדיקות בקרה — פקודות שבאמצעותן התורן מאשר: הטיימר פעיל, ההפעלה הבאה מתוכננת, הקודמת הסתיימה בהצלחה.
את החומרים אנחנו מסדרים לצד התצורה, כך שהתיאור וה-unit משתנים בשינוי אחד, ולא חיים במקומות שונים. הצוות המלווה מקבל נקודת כניסה: לפרוס לוח זמנים מאפס, למצוא את הבעלים של המשימה, לנתח תקלה לפי היומן.
מקרה בוחן: כיצד משימות מתוזמנות הפסיקו ללכת לאיבוד
מה היה לפני
משימות לילה שגרתיות — ייצוא, חישוב מחדש של תצוגות, תחזוקת אינדקסים — פעלו על cron קלאסי בשלוש סביבות. איש לא עקב אחר ההפעלה: אם המכונה הופעלה מחדש בזמן החלון, המשימה הוחמצה בשקט, ודוחות הבוקר נאספו על נתונים מיושנים.
מדי חודש אבדו 6-9% מההפעלות הליליות, חיפוש סיבת התקלה ארך בממוצע 45 דקות, והתורן הפעיל מחדש משימות ידנית עד 15 פעמים בשבוע.
מה שינו
כל משימה תוארה על ידי זוג units — טיימר ושירות — והופצה מתבנית כללית דרך Ansible, כך שלוח הזמנים עובר גרסאות יחד עם התצורה, ולא חי בטבלה של מישהו אחר על השרת. המפתח היה Persistent=true: חלון שהוחמץ בגלל מכונה כבויה מושלם מיד עם ההדלקה, אך בעבודה שגרתית אין שכפול.
הצירוף OnFailure= עם unit מטפל שולח התראה לערוץ התורן, ופלט המשימה נכתב ליומן המערכת עם קישור ל-unit — היסטוריית ההרצות נראית ללא סוכן נפרד.
מה נהיה
| מטריקה | לפני | אחרי |
|---|---|---|
| הפעלות לילה שהוחמצו | 6-9% | 0 |
| חיפוש סיבת תקלה | 45 דקות | 2-3 דקות |
| הפעלות מחדש ידניות | עד 15 בשבוע | מקרים בודדים |
ההחמצות נסגרו על ידי מכניקה, ולא על ידי קריאה "להסתכל טוב יותר": הפעלה שהוחמצה מושלמת, זו שנפלה נראית מיד ביומן ובהתראה. האבחון הצטמצם לסינון לפי שם ה-unit, לכן האירוע מנותח לפני תחילת יום העבודה.
המעבר הפיך: המשימות נשארות units נפרדים, וכל אחת מהן יכולה לחזור ללוח הזמנים הקודם מבלי לפגוע באחרות.
במה שונה הפעלה לפי לוח זמנים משירות קבוע
Unit של systemd — זו יחידת ניהול, והסוג שלו קובע את מחזור החיים: תהליך אחד מחזיק מצב באופן קבוע, אחר מתבצע ומסתיים. מכאן גם ההבדל בין שירות לטיימר — השירות עונה על השאלה "מה צריך לעבוד", הטיימר — "מתי להפעיל את מה שמסתיים". בהגדרת systemd timers ההבחנה הזו חשובה יותר מתחביר לוח הזמנים.
שירות קבוע מנהל תהליך: עוקב אחריו, מעלה אותו לאחר נפילה, אוסף את הפלט ליומן. טיימר לא מחזיק תהליך בזיכרון — הוא מפעיל unit משויך לפי לוח זמנים קלנדרי או לפי מרווח לאחר האתחול. את העבודה עצמה מארגנים כשירות oneshot: הקוד לא מסתובב ברקע, אלא נקרא בדיוק לזמן הביצוע.
התנהגות לאחר הפעלה מחדש — ההבדל המרכזי משיטת ההפעלה הישנה לפי דקות. עם השלמת הפעלות שהוחמצו מופעלת, המערכת מבצעת את המשימה מיד לאחר האתחול, אם המכונה הייתה כבויה באותו רגע; בלעדיה ההחמצה אובדת בשקט. אצל שירות קבוע הפעלה מחדש היא אתחול התהליך, ומצב שלא נשמר בדיסק צריך לשחזר ידנית.
עוד שני פרמטרים שאין ב-cron הקלאסי: חלון פיזור אקראי ודיוק ההפעלה. הם מפזרים את העומס כאשר עשרות משימות זהות מתחילות באותה דקה.
הבחירה פשוטה: אם תהליך חי ומחזיק חיבור, מטמון או תור — זה שירות; אם המשימה סופית (ייצוא, גיבוי, רוטציית יומנים) — טיימר פלוס oneshot. אפשרות משולבת גם עובדת: שירות קבוע שמפעיל בעצמו טיימר לפי האירוע שלו.
ספקות טכניים לפני העברת לוחות זמנים
העברת לוחות זמנים אינה מחייבת עצירת משימות נוכחיות: cron וטיימרים פועלים במקביל, בזמן שאנחנו מעבירים סקריפטים אחד אחד.
העבודה העיקרית היא לא לכתוב מחדש את הלוגיקה, אלא לשחזר את סביבת ההפעלה: cron מרים משימה עם PATH מצומצם וללא ספריית עבודה, לכן ב-unit מגדירים במפורש Environment=, WorkingDirectory= ו-User=. כל עוד שורת ההפעלה נותנת את אותה תוצאה בשני המתזמנים, המשימה הישנה נשארת פעילה.
סקריפטים נכתבו עבור cron — יהיה צורך לכתוב אותם מחדש?
לא. הסקריפט נקרא כמו שהוא, משתנה רק העטיפה. סיבה נפוצה ל"נשבר אחרי ההעברה" היא לא הלוגיקה, אלא ספריית עבודה אחרת ומשתני סביבה, לכן מעבירים קודם משימה בסיכון נמוך ומשווים את התוצאה לקודמת.
משימות מופעלות תחת משתמשים שונים — כיצד זה מועבר?
תפקידים מתוארים ב-unit: User= ו-Group=, בנוסף הרשאות לספריות פלט ויומנים. שם גם נפתר המקרה של "הסקריפט כתב לספריית הבית" — הנתיב מוגדר במפורש, ולא עובר בירושה ממי שהפעיל את המשימה.
איך לנפות תקלות לילה אם הצוות ישן?
כל הפעלה נרשמת ביומן עם חותמת זמן, קוד החזרה ופלט — מסתכלים על ההיסטוריה של משימה מסוימת, ולא מנחשים. מצב ההשלמה מבצע הפעלה שהוחמצה לאחר הדלקת השרת, כך שתקלת לילה לא הופכת ליום בלי נתונים.
ומה אם המשימה תלויה ברשת או במשאבים מעוגנים?
תלויות מתוארות במפורש, לכן ההפעלה לא מתחילה לפני שהרשת עלתה או שהאחסון התחבר. עבור משימות שפוגעות באותו משאב, מוסיפים פיזור בזמן — שיא העומס על השרת מתפזר.
נדון במשימה: מה צריך לדעת לפני תחילת העבודה
כדי שההערכה של העברת לוח הזמנים תהיה מדויקת, ולא "לפי אנלוגיה", לנו מספיק מיפוי של המשימות הנוכחיות והבנה של הסביבה. אם חלק מהנתונים כבר ביד, הניתוח מהיר יותר, ומקומות שנויים במחלוקת נדון לגופם.
- היכן נמצאים כעת לוחות הזמנים: crontab של משתמשים, קבצים ב-
/etc/cron.dו-/etc/cron.*, משימות במתזמנים פנימיים של אפליקציות; - רשימת משימות: מה מופעל, באיזו תדירות, כמה זמן אורך בדרך כלל ובשיא;
- תלויות וסדר: מה חייב להסתיים לפני תחילת הבא, היכן נדרשות נעילות מפני הפעלה מקבילה;
- סביבה: הפצות, הרשאות ליצירת קובצי unit, הפעלה תחת משתמש לא מיוחס, גישה לסודות;
- יומנים והתראות: לאן נכתב הפלט כעת, מי קורא אותו, לאן צריכות להגיע הודעות שגיאה;
- התנהגות בעת החמצה: אם הצומת היה כבוי ברגע ההפעלה — להשלים את המשימה או לוותר;
- חלונות לעבודה: מתי ההעברה לא פוגעת בתהליכים עסקיים.
זה מספיק כדי לחזור עם תוכנית יישום לוח זמנים וסימון קובצי unit וטיימרים. המהנדסים שלנו ינתחו בנפרד מקרים פרטיים — חפיפות בזמן, הפעלה תחת משתמשים שונים, העברה ללא אובדן הפעלות שהוחמצו — ויציעו סכמה עם נסיגה ללוח הזמנים הקודם.
נדון במשימת הגדרת לוח הזמנים: תארו את המפורט לעיל, ונחזור עם הערכת עבודה ותוכנית.







