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

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

בדף הזה מוסבר איך לזהות אם אירוע 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)

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

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

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

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

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

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

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

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

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

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

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

סגירת תהליכים ברמת המערכת בגלל מחסור בזיכרון (OOM)

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

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

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

אם תהליך של קונטיינר מסתיים על ידי 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 מתוך 137 מציין גם סיום פעולה עקב חוסר זיכרון. אם בשדה Reason מופיע ערך שונה, או אם ה-Pod עדיין פועל למרות שגיאות באפליקציה, צריך להמשיך לקטע הבא כדי לבדוק את הבעיה לעומק.

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

פעולת OOM kill היא "בלתי נראית" ל-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.
    • כדי לגלות אילו Pods או צמתים חוו אירוע 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 גבוה יותר שמתאים לשימוש השיא באפליקציה. למידע נוסף על הגדרת מגבלות, קראו את המאמר ניהול משאבים לפודים ולמאגרים במסמכי התיעוד של Kubernetes.
  • הוספה או התאמה של בקשות זיכרון: במניפסט של ה-Pod, מוודאים שהשדה resources.requests.memory מוגדר לערך ריאלי לשימוש טיפוסי. ההגדרה הזו עוזרת ל-Kubernetes לתזמן את ה-Pod לצומת עם מספיק זיכרון.
  • להגדיל את עומס העבודה באופן אופקי: כדי לפזר את עומס התנועה ולהפחית את העומס על הזיכרון בכל Pod, מגדילים את מספר הרפליקות. כדי ש-Kubernetes יבצע התאמה לעומס העבודה באופן יזום, כדאי להפעיל התאמה אופקית של קבוצות Pod לעומס.
  • שינוי קנה המידה של הצמתים באופן אנכי: אם הרבה Pods בצומת קרובים למגבלות שלהם, יכול להיות שהצומת עצמו קטן מדי. כדי להגדיל את הגודל של הצמתים, מעבירים את עומסי העבודה למאגר צמתים עם יותר זיכרון. כדי ש-Kubernetes יתאים את קנה המידה של הצמתים באופן יזום, כדאי להפעיל התאמה אנכית של קבוצות Pod לעומס.
  • אופטימיזציה של האפליקציה: בודקים את האפליקציה כדי לזהות ולפתור דליפות זיכרון, ולבצע אופטימיזציה של קוד שצורך כמויות גדולות של זיכרון במהלך עלייה חדה בתנועה.
  • מחיקת עומסי עבודה בעייתיים: כמוצא אחרון לעומסי עבודה לא קריטיים, מוחקים את ה-Pod כדי להקל באופן מיידי על העומס באשכול.

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