פתרון בעיות שקשורות לאירועי OOM

אם אתם נתקלים בסגירות לא צפויות של קונטיינרים, בחוסר יציבות או בשגיאות CrashLoopBackOff באפליקציות של Google Kubernetes Engine‏ (GKE), יכול להיות שהבעיה נובעת מאירועים של חוסר זיכרון (OOM). אירועי OOM מתרחשים כשאין יותר זיכרון בקונטיינר או בצומת, וכלי ה-OOM Killer של לינוקס מפסיק תהליכים כדי לפנות משאבים.

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

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

סיבות נפוצות לאירועי OOM

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

התרחישים הבאים יכולים לגרום לאירוע OOM:

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

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

איך Kubernetes מטפל באירועי OOM

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

בסביבת Kubernetes, תהליך OOM Killer פועל בשני היקפים שונים: קבוצת הבקרה (cgroup), שמשפיעה על קונטיינר אחד, והמערכת, שמשפיעה על הצומת כולו.

הפסקת פעולה של קונטיינר בגלל חוסר זיכרון (OOM)

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

כשמפסיקים את התהליך הראשי במאגר בדרך הזו, Kubernetes מתעד את האירוע ומסמן את הסטטוס של המאגר כ-OOMKilled. ההגדרה של ה-Pod‏ restartPolicy קובעת את התוצאה:

  • Always או OnFailure: הקונטיינר מופעל מחדש.
  • Never: הקונטיינר לא מופעל מחדש ונשאר במצב סיום.

על ידי בידוד הכשל בקונטיינר הבעייתי, OOM Killer מונע מ-Pod פגום יחיד להפיל את הצומת כולו.

איך גרסת cgroup משפיעה על ההתנהגות של OOM Killer

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

  • ב-cgroup v1, אירוע OOM בתוך קבוצת בקרת זיכרון (cgroup) של קונטיינר עלול להוביל להתנהגות בלתי צפויה. ה-OOM Killer עשוי לסיים כל תהליך בתוך קבוצת הבקרה הזו, כולל תהליכי צאצא שלא שייכים לתהליך הראשי של הקונטיינר (PID 1).

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

  • cgroup v2 מציע התנהגות צפויה יותר של OOM Killer.

    כדי להבטיח את שלמות עומס העבודה בסביבת cgroup v2, מנגנון ה-OOM killer מונע השבתות חלקיות ומבטיח אחד משני תרחישים: או שכל המשימות ששייכות ל-cgroup ולצאצאים שלו מסתיימות (מה שמאפשר ל-Kubernetes לראות את הכשל), או שאם בעומס העבודה אין משימות שצורכות יותר מדי זיכרון, עומס העבודה לא מושפע והוא ממשיך לפעול בלי סיום לא צפוי של תהליכים פנימיים.

    בתרחישים שבהם רוצים את ההתנהגות של cgroup v1 סיום תהליך יחיד, ‏ kubelet מספק את הדגל singleProcessOOMKill עבור cgroup v2. הדגל הזה מאפשר לכם שליטה מפורטת יותר, ומאפשר לסיים תהליכים ספציפיים במהלך אירוע OOM, במקום את כל קבוצת הבקרה.

ביטול OOM ברמת המערכת

מחיקה של תהליך בגלל חוסר זיכרון ברמת המערכת היא אירוע חמור יותר שמתרחש כשנגמר הזיכרון הזמין בכל הצומת, ולא רק במאגר יחיד. האירוע הזה יכול לקרות אם השימוש המשולב בזיכרון של כל התהליכים (כולל כל ה-Pods והדמונים של המערכת) חורג מהקיבולת של הצומת.

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

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

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

בדיקת אירועי OOM

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

בדיקת הסטטוס של ה-Pod לאירועי OOM גלויים

השלב הראשון באישור אירוע OOM הוא לבדוק אם Kubernetes זיהה את אירוע ה-OOM. ‫Kubernetes מתעד את האירוע כשהתהליך הראשי של הקונטיינר מופסק, וזו התנהגות רגילה בסביבות cgroup v2.

  • בודקים את הסטטוס של ה-Pod:

    kubectl describe pod POD_NAME
    

    מחליפים את POD_NAME בשם של ה-Pod שרוצים לבדוק.

    אם התרחש אירוע OOM גלוי, הפלט ייראה כך:

    ...
      Last State:     Terminated
        Reason:       OOMKilled
        Exit Code:    137
        Started:      Tue, 13 May 2025 19:05:28 +0000
        Finished:     Tue, 13 May 2025 19:05:30 +0000
    ...
    

אם מופיע OOMKilled בשדה Reason, סימן שאשרתם את האירוע. גם הודעת השגיאה Exit Code of 137 מציינת שהייתה השבתה בגלל חריגה מזיכרון. אם בשדה Reason יש ערך שונה, או אם ה-Pod עדיין פועל למרות שגיאות באפליקציה, צריך להמשיך לקטע הבא כדי לבדוק את הבעיה לעומק.

חיפוש ביומנים של אירועי OOM בלתי נראים

הפסקת פעולה בגלל חוסר זיכרון היא 'בלתי נראית' ל-Kubernetes אם תהליך צאצא מופסק אבל תהליך המכולה הראשי ממשיך לפעול (תרחיש נפוץ בסביבות cgroup v1). כדי למצוא הוכחות לאירועים האלה, צריך לחפש ביומנים של הצומת.

כדי למצוא מקרים של סגירת תהליכים בגלל חוסר זיכרון שלא נראים, משתמשים ב-Logs Explorer:

  1. במסוף Google Cloud , עוברים אל Logs Explorer.

    כניסה לדף Logs Explorer

  2. בחלונית השאילתה, מזינים אחת מהשאילתות הבאות:

    • אם כבר יש לכם Pod שלדעתכם חווה אירוע OOM, תוכלו לשלוח שאילתה ל-Pod הספציפי הזה:

      resource.type="k8s_node"
      jsonPayload.MESSAGE:(("POD_NAME" AND "ContainerDied") OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      

      מחליפים את מה שכתוב בשדות הבאים:

      • POD_NAME: השם של ה-Pod שרוצים לשלוח אליו שאילתה.
      • CLUSTER_NAME: השם של האשכול שה-Pod שייך לו.
    • כדי לגלות באילו פודים או צמתים התרחש אירוע OOM, מריצים שאילתה על כל עומסי העבודה של GKE:

      resource.type="k8s_node"
      jsonPayload.MESSAGE:("ContainerDied" OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      
  3. לוחצים על Run query.

  4. בפלט, מחפשים אירועי OOM על ידי חיפוש רשומות ביומן שמכילות את המחרוזת TaskOOM.

  5. אופציונלי: אם חיפשתם אירועי OOM בכל עומסי העבודה של GKE ואתם רוצים לזהות את ה-Pod הספציפי שחווה את אירועי ה-OOM, מבצעים את השלבים הבאים:

    1. לכל אירוע, מציינים את מזהה מאגר התגים שמשויך אליו.
    2. כדי לזהות עצירות של קונטיינרים, מחפשים רשומות ביומן שמכילות את המחרוזת ContainerDied ושקרו זמן קצר אחרי אירועי ה-OOM. משווים את מזהה מאגר התגים מהאירוע OOM לשורה המתאימה ContainerDied.

    3. אחרי שמתאימים את container IDs, השורה ContainerDied בדרך כלל כוללת את שם ה-Pod שמשויך לקונטיינר שנכשל. ה-Pod הזה הושפע מאירוע ה-OOM.

שימוש ב-journalctl לקבלת מידע בזמן אמת

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

  1. מתחברים לצומת באמצעות SSH:

    gcloud compute ssh NODE_NAME --location ZONE
    

    מחליפים את מה שכתוב בשדות הבאים:

    • NODE_NAME: השם של הצומת שרוצים לבדוק.
    • ZONE: אזור Compute Engine שאליו שייך הצומת.
  2. במעטפת, בודקים את הודעות הליבה מיומן המערכת של הצומת:

    journalctl -k
    
  3. מנתחים את הפלט כדי להבחין בין סוגי האירועים:

    • הרג ברמת הקונטיינר: רשומת היומן מכילה מונחים כמו memory cgroup,‏ mem_cgroup או memcg, שמציינים שאכפה מגבלה של cgroup.
    • הפסקת פעולה ברמת המערכת: רשומת היומן היא הודעה כללית כמו Out of memory: Killed process... בלי לציין קבוצת cgroup.

פתרון אירועים של חוסר זיכרון

כדי לפתור אירוע OOM, אפשר לנסות את הפתרונות הבאים:

  • הגדלת מגבלות הזיכרון: זה הפתרון הכי ישיר. עורכים את קובץ המניפסט של ה-Pod כדי לספק ערך resources.limits.memory גבוה יותר שמתאים לשימוש המקסימלי באפליקציה. מידע נוסף על הגדרת מגבלות זמין במאמר ניהול משאבים עבור Pods וקונטיינרים במקורות המידע של Kubernetes.
  • הוספה או שינוי של בקשות זיכרון: במניפסט של ה-Pod, מוודאים שהשדה resources.requests.memory מוגדר לערך ריאלי לשימוש טיפוסי. ההגדרה הזו עוזרת ל-Kubernetes לתזמן את ה-Pod לצומת עם מספיק זיכרון.
  • הגדלת הקיבולת של עומס העבודה: כדי לחלק את עומס התנועה ולהפחית את העומס על הזיכרון בכל Pod בנפרד, מגדילים את מספר הרפליקות. כדי ש-Kubernetes יתאים את עומס העבודה באופן פרואקטיבי, כדאי להפעיל התאמה אופקית של קבוצות Pod לעומס.
  • הגדלת הקיבולת של הצמתים: אם הרבה פודים בצומת מתקרבים למגבלות שלהם, יכול להיות שהצומת עצמו קטן מדי. כדי להגדיל את הגודל של הצמתים, מעבירים את עומסי העבודה למאגר צמתים עם יותר זיכרון. כדי ש-Kubernetes יבצע מידרוג יזום של הצמתים, כדאי להפעיל התאמה אנכית של קבוצות Pod לעומס.
  • אופטימיזציה של האפליקציה: כדאי לבדוק את האפליקציה כדי לזהות ולפתור בעיות של דליפת זיכרון, ולבצע אופטימיזציה של קוד שצורכת כמויות גדולות של זיכרון במהלך עליות פתאומיות בתנועה.
  • מחיקת עומסי עבודה בעייתיים: כמוצא אחרון לעומסי עבודה לא קריטיים, מוחקים את ה-Pod כדי להקל באופן מיידי על העומס על האשכול.

המאמרים הבאים