מסביר ביטויי cron
מסביר כל ביטוי cron בניבים Vixie, Quartz, EventBridge ו-Jenkins, מציג את הריצות הבאות בכל אזור זמן ומסמן את המלכודות.
נקרא כניב Vixie cron.
מה זה אומר
בשעה 00:00 ביום 13 בחודש או ביום שישי
שדה אחרי שדה
| שדה | נכתב | מתאים ל |
|---|---|---|
| דקה | 0 | 0 |
| שעה | 0 | 0 |
| יום בחודש | 13 | 13 |
| חודש | * | כל ערך |
| יום בשבוע | 5 | שישי |
למה כדאי לשים לב
- שני שדות היום מוגבלים, ולכן cron מריץ את זה כשאחד מהם מתאים: היום בחודש או היום בשבוע, ולא שניהם יחד. בגלל זה 13 עם שישי פירושו ה-13 בחודש וגם כל יום שישי, ולא "שישי ה-13".
הריצות הבאות
מה הכלי הזה עושה
הוא קורא לכם בחזרה ביטוי cron במילים, מציג את הזמנים הבאים שבהם הוא באמת יופעל באזור הזמן שבו המשימה רצה, ונוקב בדברים שהביטוי מרמז עליהם אבל לא אומר. החלק האחרון הוא העיקר: ביטוי cron לעולם אינו שגוי באופן שמפיק שגיאה. הוא פשוט רץ בימים הלא נכונים, בשקט, עד שמישהו שם לב.
הכלי מבין ארבעה ניבים ולא אחד: הצורה בת חמישה השדות ש-crontab מקבל, Quartz על שדה השניות וכללי לוח השנה שלו, AWS EventBridge עם שנה בסוף, ו-Jenkins עם הגיבוב שלו. הדבקה של אחד מהם בכלי שמכיר רק ניב אחר היא הדרך שבה רוב האנשים מגלים לראשונה שיש יותר מאחד.
השדות, וכמה מהם יש
לביטוי הקלאסי יש חמישה שדות, מופרדים ברווחים, בסדר קבוע: דקה, שעה, יום בחודש, חודש, יום בשבוע. כל אחד מהם הוא כוכבית שפירושה "כל", מספר, רשימה מופרדת בפסיקים, טווח עם מקף, או צעד עם לוכסן. חודשים וימים אפשר לכתוב בשמות בני שלוש אותיות, וזה כמעט תמיד ברור יותר מהספרות.
- כוכבית פירושה כל ערך שהשדה יכול לקבל.
- טווח כמו 9-17 פירושו כל ערך מ-9 עד 17 ועד בכלל.
- צעד כמו */15 פירושו כל ערך חמישה-עשר החל מתחתית השדה; 9-17/2 מדלג בתוך טווח, ו-5/15 מדלג מ-5 עד ראש השדה.
- רשימה כמו 0,30 פירושה בדיוק הערכים האלה.
- טווח מנוון כמו 13-13 הוא פשוט 13 — כמה ספריות טועות בזה ומתייחסות אליו ככוכבית.
שדה שישי הוא המקום שבו הניבים נפרדים. Quartz שם אותו בהתחלה וקורא אותו כשניות; EventBridge שם אותו בסוף וקורא אותו כשנה. שישה שדות חשופים הם לכן דו-משמעיים באמת, והכלי אומר איך הוא קרא אותם במקום לבחור בשקט. עטיפה של הביטוי ב-cron(...), התחביר של EventBridge עצמו, מכריעה את העניין.
המקרואים הם קיצורים: @hourly, @daily, @midnight, @weekly, @monthly, @yearly ו-@annually. הם מתרחבים לביטויים רגילים, שהכלי מציג. @reboot הוא היוצא מן הכלל ואינו לוח זמנים כלל.
שני שדות היום, וה"או" שנראה כמו "וגם"
אפשר לבחור תאריך בשתי דרכים שונות — לפי היום בחודש ולפי היום בשבוע — ול-cron יש את שני השדות. כששניהם מוגבלים, המשימה רצה כשאחד מהם מתאים. לא כששניהם מתאימים.
לכן 0 0 13 * 5 אינו "שישי ה-13". זה ה-13 בכל חודש, וגם כל יום שישי: בערך 64 ימים בשנה במקום אחד או שניים. הביטוי נראה כמו חיתוך ומתנהג כמו איחוד, שום דבר לא מדווח על שגיאה, והמשימה פשוט רצה יותר ממה שהתכוונו.
לכלל יש חצי שני, מוזר יותר, וכמעט אף אחד לא מכיר אותו. cron אינו בודק אם שדה יום מוגבל; הוא בודק אם התו הראשון של השדה הוא כוכבית. לכן צעד כמו */14 נחשב ככוכבית למרות שהוא מגביל את השדה ל-1, ל-15 ול-29 — ונוכחותו הופכת את שני שדות היום מ"או" חזרה ל"וגם". שני ביטויים שנראים מוגבלים באותה מידה מתנהגים אז אחרת לגמרי, והכלי מדווח איזה כלל בתוקף.
Quartz ו-EventBridge נמנעים מכל השאלה בכך שהם מסרבים לתת לשני השדות לומר משהו: בדיוק אחד מהם חייב להיות סימן שאלה, שפירושו "אין ערך מסוים, השדה השני מכריע".
צעדים שאינם מחלקים את השדה שלהם
צעד נכתב כאילו היה מרווח, ובתוך מחזור אחד של השדה הוא אכן כזה. מעבר לגבול, בדרך כלל לא.
*/7 בשדה השעה פירושו השעות 0, 7, 14 ו-21. אחרי 21 השדה נגמר, ולכן הריצה הבאה היא בשעה 0 למחרת — שלוש שעות אחר כך, לא שבע. כל מחזור נגמר בפער קצר אחד, ואותו דבר קורה עם */7 בדקות (פער של ארבע דקות אחרי 56) ועם כל צעד אחר שאינו מחלק את הטווח שלו בשלמות. */15 בדקות ו-*/6 בשעות בטוחים; רוב המספרים שאנשים בוחרים אינם.
זה חשוב כשהצעד נבחר כדי לפרוס עבודה. משימה על */7 שעות אינה רצה כל שבע שעות, ומגבלת קצב שנבנתה לשבע תיפרץ פעם ביום.
ימי השבוע ממוספרים אחרת במקומות שונים
Vixie cron ממספר את הימים 0 עד 7 כשיום ראשון נמצא בשני הקצוות, ולכן 0 ו-7 הם אותו יום ו-1 הוא יום שני. Quartz ו-EventBridge ממספרים אותם 1 עד 7 כשיום ראשון הוא 1, ולכן שם 1 הוא יום ראשון ו-2 הוא יום שני.
ביטוי שהועתק מתצורת Quartz לתוך crontab רץ לכן יום אחד מוקדם מדי, ואף אחד לא מתלונן, כי שני הכתיבים הם מספרים תקפים בשתי המערכות. שמות בני שלוש אותיות — MON, FRI — פירושם אותו יום בכל מקום, וזו ההגנה הפשוטה.
שעון קיץ: שני הבקרים בשנה
דמון cron אינו מתזמן רגעים. הוא מסתכל על שעון הקיר המקומי בכל דקה ושואל אם הביטוי מתאים. פעמיים בשנה השעון הזה אינו סדרה מנומסת.
כשהוא קופץ קדימה, שעה שלמה של קריאות פשוט לא קורית. למשימה שנקבעה ל-02:30 אין 02:30 באותו יום. כשהוא חוזר אחורה, שעה של קריאות קורית פעמיים, ולמשימה שנקבעה ל-01:30 יש שתיים מהן.
Vixie cron מבחין כאן בין שני סוגי משימות, וההבחנה שווה הכרה כי היא קובעת אילו מהמשימות שלכם מושפעות. משימה שנעוצה בשעת קיר — שעה קבועה ודקה קבועה — רצה בדיוק פעם אחת בכל מקרה: אחרי קפיצה קדימה היא רצה מיד, ואחרי חזרה אחורה cron נזהר לא לחזור עליה. משימה עם כוכבית בשעה או בדקה היא משימת מרווח, והיא פשוט הולכת אחרי השעון: היא מאבדת שעה של ריצות באביב וחוזרת על שעה של ריצות בסתיו.
כך שגיבוי שרץ כל שעה אכן רץ פעמיים בלילה אחד בשנה, ומשימה לילית ב-02:30 אכן רצה ברגע חריג בלילה אחר. הכלי בודק את שנים-עשר החודשים הקרובים של אזור הזמן שנבחר מול הביטוי שלכם ואומר איזה מהשניים חל, עם התאריך.
הדרך האמינה להיחלץ היא לתזמן את המשימה ב-UTC, או מחוץ לשעות 01:00 עד 03:00 המקומיות, שבהן אף מעבר לא נוחת.
על איזה שעון המשימה באמת נמצאת
קובץ crontab רץ באזור המקומי של המכונה, או במה שאומרים CRON_TZ או TZ בראש הקובץ. זה כמעט אף פעם לא אזור הזמן של מי שקורא את הביטוי, ולכן אזור הזמן כאן הוא הגדרה ולא אזור הזמן של הדפדפן שלכם.
משימת CronJob ב-Kubernetes שונה שוב: היא קוראת את לוח הזמנים שלה ב-UTC אלא אם המניפסט מגדיר שדה timeZone, לא משנה מה השעון של הצומת אומר. לוח זמנים שנכתב לשעות עבודה מקומיות ונפרס בלי השדה הזה רץ בשעה הלא נכונה ביום בכל מקום מחוץ ל-UTC — ואינו משתנה עם העונות, מה שהוא באג או הקלה, תלוי במשימה.
Jenkins וה-H
Jenkins מוסיף סימן שאין לאף ניב אחר: H, שנראה כמו אקראיות ואינו כזה. Jenkins מגבב את שם המשימה לערך קבוע בתוך טווח השדה, ולכן המשימה רצה באותה שעה בכל יום — השעה שלה — בעוד שמאה משימות שנכתבו H * * * * מתפזרות באופן אחיד על פני השעה במקום שכולן יתחילו בדיוק בדקה הראשונה.
מכיוון שהערך מגיע משם המשימה, ושם המשימה אינו חלק מהביטוי, שום כלי לא יכול לחשב את הזמנים המדויקים. הכלי הזה מפרש כל H לתחתית הטווח שלו ואומר זאת: מבנה לוח הזמנים — כל כמה זמן, באילו ימים — מדויק, ורק ההיסט בתוך כל תקופה הוא ממלא מקום.
Jenkins גם מגדיר מחדש את המקרואים. ה-@daily שלו הוא @midnight, שאינו חצות אלא דקה מגובבת אי-שם בשלוש השעות הראשונות של היום.
מה cron אינו עושה
- הוא אינו משלים פערים. אם המכונה ישנה או הדמון היה מושבת כשריצה הייתה אמורה לקרות, הריצה הזאת לא תקרה מאוחר יותר — היא פשוט הוחמצה. anacron קיים בדיוק בשביל זה והוא תוכנית אחרת.
- הוא אינו מונע ריצות חופפות. אם משימה נמשכת יותר מהמרווח, הבאה מתחילה בכל זאת, ואחרי זמן מה יש כמה מהן. קובץ נעילה הוא הפתרון המקובל.
- אין לו מושג של שניות בצורה הקלאסית. הרזולוציה הדקה ביותר היא דקה אחת; כל דבר מהיר יותר דורש Quartz, systemd, או לולאה בתוך המשימה עצמה.
- @reboot אינו לוח זמנים. הוא רץ פעם אחת כשהדמון עולה, ולכן אין לו ריצה באה, ובמכונה שלא מאותחלת לעולם הוא לא רץ בכלל.
- ביטוי rate ב-EventBridge נספר מהרגע שבו הכלל נוצר, והרגע הזה אינו בביטוי, ולכן שום כלי לא יכול לומר מתי הוא ירוץ בפעם הבאה.
שאלות נפוצות
- איך מריצים משימה ביום שני הראשון של החודש?
- לא בביטוי cron רגיל — זה דורש "וגם" בין שני שדות היום, ו-cron נותן "או". העקיפה המקובלת היא לתזמן 0 0 1-7 * * ולתת לפקודה עצמה לבדוק את היום בשבוע. Quartz ו-EventBridge יכולים לבטא את זה ישירות, כ-0 0 12 ? * MON#1.
- למה 0 0 13 * 5 רץ כל כך הרבה?
- כי פירושו ה-13 בחודש או כל יום שישי, ולא "שישי ה-13". כששני שדות היום מוגבלים ואף אחד מהם אינו מתחיל בכוכבית, cron מריץ את המשימה כשאחד מהם מתאים. אין דרך לכתוב "וגם" בניב הקלאסי.
- מה ההבדל בין */5 לבין 0-59/5?
- שום דבר, בשדה הדקות: שניהם נותנים 0, 5, 10 וכן הלאה. ההבדל מתגלה בשני שדות היום, שם cron מכריע בין "וגם" ל"או" לפי השאלה אם השדה מתחיל בכוכבית — ולכן */5 ו-0-59/5 בוחרים אותם ימים אבל מתחברים לשדה היום השני אחרת.
- האם המשימה שלי רצה פעמיים כשהשעון חוזר אחורה?
- אם יש בה כוכבית בשעה או בדקה, כן: היא הולכת אחרי השעון, והשעה הזאת קורית פעמיים. אם יש בה שעה ודקה קבועות, לא — Vixie cron מריץ משימה כזאת פעם אחת. לוח הממצאים אומר איזה מהמקרים הוא שלכם, עם התאריך שבו זה חל.
- באיזה אזור זמן ביטוי cron משתמש?
- באזור של המכונה, אלא אם CRON_TZ או TZ מוגדרים ב-crontab. משימת CronJob ב-Kubernetes משתמשת ב-UTC אלא אם המניפסט מגדיר timeZone. מכיוון שאזור הזמן אינו חלק מהביטוי, הכלי הזה מבקש אותו במקום להניח את זה של הדפדפן שלכם.
- אפשר לתזמן משהו כל 90 דקות?
- לא כביטוי אחד, כי כל שדה מחזורי בפני עצמו ו-90 דקות לא נכנסות בשלמות לתוך שעה. הכתיב המקובל הוא שני ביטויים, 0 0,3,6,9,12,15,18,21 * * * ו-30 1,4,7,10,13,16,19,22 * * *, שיחד נותנים ריצה כל 90 דקות.
- האם משהו שאני מדביק נשלח לשרת?
- לא. הניתוח, ההסבר וכל חישוב לוח הזמנים רצים בדפדפן שלכם; שום דבר אינו מועלה או נרשם, וזה עובד בלי חיבור רשת.