Kubernetes CronJob: הגדרה, נהלים מומלצים וניטור

משימות מתוזמנות נשברות בשקט: הדוח לא הגיע, עותק הנתונים לא נאסף, ואין אשמים. אנו מגדירים Kubernetes CronJob כמסלול מקצה לקצה: מנתחים לוחות זמנים, סוגרים נקודות כשל, מציבים ניטור ומעבירים לצוות נהלים ברורים. שיעור ההרצות שאבדו מצטמצם פי כמה, והתורן מפסיק לבדוק משימות ידנית.

היכולות שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

Kubernetes CronJob: מדוע משימות אינן מופעות לפי לוח הזמנים

CronJob נראה כמו הישות הפשוטה ביותר באשכול: לוח זמנים, image, פקודה — והסביבה עצמה מרימה את המשימה. בדיוק משום כך מזהים כשל בלוח הזמנים באיחור, כשבדוחות צץ הרצה שהוחמצה של משימה או כפילות של משימות שגרתיות: אותו ייצוא, התאמה או תפוצה פועלת פעמיים.

המתזמן לא מעיר את המשימה בדיוק לפי לוח הזמנים — הוא בודק את הזמן בלולאה. אם control plane הופעל מחדש, הצומת היה לא זמין או לוח הזמנים נקבע בתדירות גבוהה מצעד הבדיקה, חלון ההרצה מתפספס.

הפרמטר startingDeadlineSeconds קובע כמה איחור עוד מותר, ו-concurrencyPolicy: Forbid לא מאפשר למופע חדש להתחיל מעל מופע פעיל.

כפילות נגרמת לרוב ממדיניות Allow וממשימה ארוכה: ההרצה הבאה מגיעה כשהקודמת עדיין כותבת לאותה טבלה. אותה תוצאה נובעת מכמה רפליקות של control plane בלי מנהיג נבחר והשלמות ידניות לאחר תקרית.

תהליכים תקועים באשכול — קטגוריה נפרדת. בלי activeDeadlineSeconds והגבלת ניסיונות חוזרים, משימה יכולה להיתקע ימים: המופע לא יוצא מ-Pending בגלל מכסות, ה-image לא עולה, הקונטיינר לא מסתיים לפי אות. תהליך כזה מחזיק משאבים וחוסם את ההרצה הבאה לפי מדיניות האיסור.

ולבסוף, תוצאות שאבדו: מופע שהצליח נמחק, הלוגים נעלמים יחד איתו, וההיסטוריה שומרת רק כמה הרצות אחרונות. מכאן התמונה "המשימה רצה, אבל אין תוצאה". נבחן כיצד כל סוג כשל נסגר באמצעות קונפיגורציה, ולא באמצעות תורנות.

משימות מנוהלות לפי לוח זמנים: האפקט על הצוות

משימות שגרתיות מפסיקות להיות נקודת סיכון כאשר לוח הזמנים, המגבלות ומדיניות ההפעלה מחדש מתוארים במניפסטים של Kubernetes, ולא משוחזרים ידנית אחרי כל כשל. האפקט מדיד: שיעור ההרצות שאבדו, זמן השחזור ומספר ההתערבויות הידניות של התורן.

צמצום הפעלות מחדש ידניות

הגבלת זמן הריצה ומדיניות הניסיונות החוזרים לא מאפשרות למשימה להיתקע ולחסום את החלון הבא. סיבת הכשל נראית בהיסטוריית ההרצות, ולכן כשל טיפוסי נסגר בתיקון המניפסט, ולא בהפעלה מחדש "על מזל". מספר הבדיקות הידניות מצטמצם מכמה בשבוע לבודדות ברבעון.

עומס צפוי מראש על האשכול

לוחות הזמנים מפוזרים בזמן, עבור המשימות הוגדרו בקשות ומגבלות משאבים, וההרצות המקבילות מוגבלות. שיאי העומס מפסיקים ליפול לשעות העבודה: משימות לילה לא מתחרות בשירותים, והאשכול לא נכנס לתור לתחזוקה.

שחזור מהיר לאחר כשל

החזרה לפעולה — rollback של המניפסט והרצה חוזרת של החלון שהוחמץ, ולא שחזור מגיבוי. זמן השחזור יורד מעשרות דקות ליחידות: סדר הפעולות ידוע מראש ואינו תלוי במי שנמצא בתורנות.

חלון שהוחמץ מפסיק להיות בלתי מורגש — הפער נראה במוניטורינג באותו יום. לוח הזמנים הופך לארטיפקט ניתן לשחזור: אפשר להעביר אותו לצוות אחר או לפרוס אותו בסביבה חדשה בלי הוראות בעל פה.

Kubernetes CronJob: אפשרויות הרצה והמגבלות שלהן

משימה שגרתית באשכול לעתים רחוקות היא בודדת: ייצוא דוח, רוטציית מפתחות, ניקוי רשומות מיושנות, חישוב מחדש של אגרגטים. CronJob מטפל בלוח הזמנים, אבל לא עונה על שתי שאלות — כמה עותקים של המשימה מותרים בו-זמנית ומה לעשות אם המעבר הקודם טרם הסתיים. מכאן ארבעה מצבים שביניהם בוחרים בפועל.

השאלה אינה "איזה כלי טוב יותר", אלא היכן חי הנעילה ומי אחראי להרצה שהוחמצה. גבולות הישימות של כל אפשרות:

אפשרות מתי מתאימה מגבלות
הרצה בודדת — משימה עם איסור עותקים מקבילים משימה אידמפוטנטית, שנכנסת למרווח לוח הזמנים מעבר ארוך דוחק את הבא; חלון שהוחמץ אינו נדחה
הרצה מבוזרת עם נעילה כמה רפליקות או אשכולות מתחרים על אותה עבודה נדרש מתאם חיצוני ושכירת נעילה עם timeout, אחרת בכשל של המחזיק המשימה נעצרת
הרצה מחוץ לאשכול משימה כבדה עם חלון ארוך וגישה למערכות חיצוניות אין היסטוריית הרצות וניסיונות חוזרים באמצעי האשכול; מוניטורינג וסודות נאספים בנפרד
מתזמן סטנדרטי בלי נעילה משימות תקופתיות קלות בתוך האשכול רזולוציית לוח זמנים — דקות; החמצות נחתכות לפי startingDeadlineSeconds, אזור זמן מוגדר במפורש

בפועל מערבבים בין המצבים: את לוח הזמנים וההרצה משאירים למתזמן, ואת הנעילה וההגנה מפני חפיפה מוציאים למעבד עצמו — אז העברת המשימה מחוץ לאשכול לא שוברת את הלוגיקה.

כדאי לעקוב לא אחרי העובדה ש"ה-pod רץ", אלא אחרי התוצאה: מדד של מעברים מוצלחים, זמן הסיום האחרון ומספר החלונות שהוחמצו.

סדר היישום: מביקורת לוחות זמנים ועד העברה

היישום מתחיל לא במניפסטים, אלא במיפוי: cron בשרתים, טיימרים, מתזמן במערכת הבנייה ומשימות שכבר חיות באשכול. כבר כאן רואים אילו מהן מתחרות על משאבים, היכן לוחות הזמנים חופפים בזמן ואילו כשלים איש לא הבחין בהם במשך חודשים.

  1. ביקורת לוחות הזמנים הנוכחיים. מרכזים את המשימות בטבלה: תדירות, משך בפועל, תלויות בנתונים, בעלים. מסמנים כפילויות ואלה שמופעות לעתים קרובות מהנדרש.
  2. סכמת הרצת משימות. עבור כל אחת קובעים מדיניות מקביליות, זמן ריצה מקסימלי, מגבלות משאבים ועומק שמירת ההיסטוריה של הרצות שהסתיימו.
  3. הגדרת ניסיונות חוזרים. קובעים מספר ניסיונות, השהיה ביניהם ומדיניות הפעלה מחדש של הקונטיינר; בנפרד מחליטים מה לעשות עם משימה שלא נכנסה לחלון שהוקצה.
  4. בדיקת עמידות בכשלים. מפילים pod, מנתקים תלות חיצונית, מעמיסים על הצומת — המשימה צריכה להיות מופעלת מחדש, לא ליצור כפילויות ולא לחסום את הבאה לפי לוח הזמנים.
  5. נראות. מגדירים מדדים של הרצות מוצלחות וכושלות, התראה על חלון שהוחמץ ויומן שלפיו סיבת הכשל נראית בלי בדיקה ידנית.
  6. העברת התחזוקה. מוסרים את סכמת לוחות הזמנים, הקונפיגורציות, ההוראות לכשלים טיפוסיים וסדר ביצוע השינויים.

כל שלב מסתיים בארטיפקט שניתן לבדוק: לוח זמנים מעודכן, קונפיגורציה, דוח על הרצת בדיקות כשל. כך מסירים את הסיכון העיקרי — כשהמשימות חיות לבד ומידע על כשל מגיע מצוות סמוך, ולא מהתראה.

מערך ארטיפקטים: מניפסטים, מדיניות, נהלים

העבודה מסתיימת לא ב"אשכול מוגדר", אלא במערך שאפשר להעביר לצוות אחר ולשחזר מאפס. הכול נמצא במאגר עם היסטוריית שינויים: המניפסטים מיושמים באופן דקלרטיבי, ההבדלים בין הסביבות מתוארים במפורש, ולא מתוקנים ידנית בסביבת הייצור.

  • תיאור קונפיגורציית המשימות — מניפסטים עם concurrencyPolicy, backoffLimit, מגבלות משאבים ומדיניות שמירת pods שהסתיימו; בנפרד מתועד למה נבחרו דווקא הערכים האלה.
  • מדיניות גישה ותפקידים — חשבונות שירות לכל משימה, הרשאות בגבולות מרחב השמות, מדיניות רשת, סודות ממאגר חיצוני במקום ערכים במניפסטים.
  • סכמת סביבות — הפרדה בין סביבות פיתוח, קדם-ייצור וייצור, דריסות ערכים לפי סביבה, סדר קידום שינויים ו-rollback.
  • מוניטורינג והתראות — מדדי הרצות ומשכן, כללי התראה על לוח זמנים שהוחמץ, כשלים חוזרים ומשימות תקועות.
  • נוהל טיפול בכשלים — מה עושה התורן כשמשימה נופלת, כיצד להשהות לוח זמנים דרך suspend ולהפעיל מחדש הרצה שהוחמצה בלי כפילויות.
  • תיעוד תחזוקה — ייעוד כל לוח זמנים, תלויות במערכות חיצוניות, פעולות טיפוסיות ונקודות שבהן נדרשת התערבות אדם.

את המערך מעבירים יחד עם הסבר: המהנדס של הלקוח עובר עליו ומשחזר את הפריסה בסביבת בדיקות, ולאחר מכן אפשר לקיים תחזוקה בכוחות עצמם.

תרגול: כיצד צמצמנו את שיעור ההרצות שאבדו

לקוח הגיע עם צוות פלטפורמה שאצלו לפי לוח זמנים פעלו כ-120 משימות: ייצוא לילה, התאמות יתרות, חישוב מחדש של תנאים, תפוצות. חלק מההרצות אבדו — ונודע על כך מתלונות של העסק, ולא מאות מוניטורינג.

מה הראו המדדים לפני

שיעור ההרצות שאבדו עמד על 8–9% ביום. בערך מחציתן — החמצות שקטות: המשימה לא התחילה או לא הסתיימה, אבל אף התראה לא נורתה. זמן התגובה הממוצע לכשל היה כמה שעות, משום שהאות היחיד היה שיחה ממחלקה שחיכתה לדוח.

מה שינינו

רכזנו את לוחות הזמנים ב-control plane אחד על Kubernetes: הגדרנו startingDeadlineSeconds כדי שמשימה שהורמה מאוחר מדי לא תחפוף לחלון הבא, concurrencyPolicy: Forbid למשימות חופפות ו-backoffLimit עם activeDeadlineSeconds במקום ניסיונות חוזרים אינסופיים.

התראה הגדרנו לא על נפילה, אלא על היעדר סיום מוצלח בחלון, בנוסף ל-TTL להיסטוריית משימות שהסתיימו. את המשימות עצמן הפכנו לאידמפוטנטיות — ניסיון חוזר אחרי כשל לא שכפל עוד חיובים ושליחות.

מה היה בסוף

שיעור ההרצות שאבדו ירד מ-8–9% ל-0.2–0.4%, זמן התגובה לכשל — מכמה שעות ל-1–2 דקות. סוג הכשל "הרצה שהוחמצה שאיש לא ידע עליה" נסגר: החמצת החלון עצמה הפכה לאירוע בקרת ביצוע משימות.

במה נבדלות משימות לפי לוח זמנים מתורי עבודה?

הגבול עובר לפי מקור האמת. משימה לפי לוח זמנים מופעלת על ידי הזמן: המתזמן מחליט בעצמו שהרגע הגיע ומתחיל תהליך, בלי לבדוק אם יש עבודה עבורו. תור מופעל על ידי אירוע: עד שמגיעה משימה חדשה, המעבד ממתין ולא תופס משאבים.

מכאן גם סוגי כשל שונים. אצל לוח זמנים — חפיפות והחמצות: שתי הרצות של אותו חישוב מחדש חופפות זו לזו, מעבר ארוך אוכל את הבא, ועצירה של המתזמן נשארת בלתי מורגשת. זה נסגר במדיניות מקביליות, חלון איחור מותר ושמירת היסטוריית הרצות, שממנה רואים גם הצלחה וגם כשל. אצל תור — מסירה חוזרת ומשימות "תקועות" שנפלו ולא חזרו לעבודה בלי בדיקה נפרדת.

גם שם וגם שם חובה אידמפוטנטיות של פעולות. חישוב מחדש לילי חוזר או אירוע שנמסר שוב לא אמורים לחייב פעמיים, לשלוח מכתב שני או ליצור כפילות הזמנה — מפתח אידמפוטנטיות והשוואה למצב הנוכחי במסד נתונים פותרים זאת בלי תלות בדרך ההרצה.

הבחירה פשוטה: שגרה בלי טריגר חיצוני — דוחות, סנכרון ספרים, ניקוי וארכוב — הולכת לפי לוח זמנים. זרם אירועים — הזמנות, webhooks, קבצים — עובר דרך תור, שבו מספר המעבדים גדל יחד עם אורך התור.

פשרה מעשית — היברידי: משימה ב-Kubernetes לא מבצעת את העבודה בעצמה, אלא רק ממלאת תור, ואת העיבוד והניסיונות החוזרים מובילים מבצעים נפרדים.

שאלות טכניות שנשאלות לפני ההתחלה

לפני ההתחלה כמעט תמיד צצים אותם ספקות: איך לוח הזמנים ישרוד עדכון אשכול, מה לעשות עם משימה שמתארכת ומאין מגיעות הרצות כפולות. להלן ארבע שאלות שמשפיעות לרוב על סכמת הפריסה.

משימה עלולה לרוץ פעמיים — איך לשלול זאת?

כפילויות מופיעות כשההרצה הקודמת לא הספיקה להסתיים עד הבאה. את ההתנהגות קובעת מדיניות המקביליות: Forbid מדלגת על הרצה חדשה, Replace עוצרת את הנוכחית ומתחילה חדשה. עבור ייצוא וכתיבה לאותה טבלה שמים Forbid.

מה לעשות אם משימה נתקעה?

תקיעה תופסים ב-timeout של המשימה עצמה: activeDeadlineSeconds מסיר את ה-pod בכפייה, backoffLimit מגביל ניסיונות חוזרים, ttlSecondsAfterFinished מסיר אובייקטים שסיימו. בנוסף עוקבים אחרי זמן ההרצה המוצלחת האחרונה — הוא גדל לפני שה-timeout פועל.

האם המשימה תשרוד עדכון אשכול?

במהלך עדכון הצמתים מוצאים מכלל פעולה וה-pod נפסק. משימות קצרות שורדות זאת בזכות ניסיונות חוזרים, ארוכות מעבירים לחלונות ללא תחזוקה ועושים אותן אידמפוטנטיות כדי שניסיון חוזר לא יקלקל נתונים. את תיאור המשימה מחזיקים בגרסה עדכנית — אחרת האשכול המעודכן לא יקבל אותו.

איך להגביל את זמן הריצה?

הגבלת זמן לא מאפשרת למשימה להיתלות לנצח ולהיכנס בחפיפה להרצה הבאה. אם העבודה לא נכנסת למרווח לוח הזמנים, מוציאים אותה ל-control plane נפרד או מאריכים את התקופה — אחרת חלק מההרצות יוחמצו.

נדון במשימה: נבחן לוחות זמנים ונקודות כשל

כדי לחזור עם הערכה ותוכנית עבודה ללוחות זמנים, אנחנו צריכים פרוסה של הסביבה, ולא ניסוח "משימות תקופתיות לפעמים לא פועלות". תארו את המשימה — נשווה אותה לסכמה ונציע קונפיגורציה שאפשר ליישם בלי לעצור שירותים.

מה לשלוח כנתוני קלט להערכה:

  • סכמת הסביבה הנוכחית: אשכולות וסביבות, במה נבדלים פרמטרי ההרצה ביניהם;
  • רשימת המשימות התקופתיות: לוח זמנים, משך טיפוסי, תלויות בין שלבים, חלון השהיה מותר;
  • איך ההרצה בנויה כיום: מתזמן בתוך האפליקציה, טיימר מערכת או סקריפט ידני;
  • דרישות להתנהגות לאחר כשל: מה יקרה בביצוע כפול של אותה משימה;
  • כמה זמן לשמור היסטוריית ניסיונות מוצלחים וכושלים והאם נדרש rollback לגרסה הקודמת;
  • כללי התראה פעילים ונקודות כשל ידועות: לאן מגיעים האותות ומי קורא אותם מחוץ לשעות העבודה;
  • מגבלות הסביבה: מדיניות רשת, גישות למשאבים חיצוניים, חלונות תחזוקה.

בסוף תקבלו מפת משימות עם הערכת סיכון, תוכנית עבודה ללוחות זמנים לפי שלבים ומערכת בדיקות שמוכיחות שההרצה אכן עברה והתוצאה נרשמה. שלחו את נתוני הקלט — נחזור עם הערכה ותוכנית.