אם באפליקציות שלכם ב-Google Kubernetes Engine (GKE) יש סגירות לא צפויות של קונטיינרים, חוסר יציבות או שגיאות CrashLoopBackOff, יכול להיות שהסיבה לכך היא אירועי מחסור בזיכרון (OOM). אירועי OOM מתרחשים כשנגמר הזיכרון בקונטיינר או בצומת, ובעקבות זאת Linux 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 kill ברמת מאגר מתרחשת כשמאגר מנסה לחרוג ממגבלת הזיכרון שהוגדרה לו מראש. Kubernetes מקצה כל קונטיינר ל-cgroup ספציפי עם מגבלת זיכרון קשיחה. כשהשימוש בזיכרון של קונטיינר מגיע למגבלה הזו, הליבה מנסה קודם להחזיר את הזיכרון בתוך ה-cgroup הזה. אם הליבה לא מצליחה לפנות מספיק זיכרון באמצעות התהליך הזה, מופעל ה-OOM Killer של ה-cgroup. הוא מסיים תהליכים בתוך ה-cgroup הספציפי הזה כדי לאכוף את גבולות המשאבים.
כשהתהליך הראשי בקונטיינר מסתיים בדרך הזו, Kubernetes מזהה את האירוע ומסמן את סטטוס הקונטיינר כ-OOMKilled. התוצאה נקבעת לפי הערך של restartPolicy שמוגדר ב-Pod:
-
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 עוקב בעיקר אחרי התקינות של תהליך הקונטיינר הראשי, הוא לא מודע לסגירות החלקיות האלה בגלל OOM. יכול להיות שתהליך הקונטיינר הראשי ימשיך לפעול, גם אם תהליכי משנה קריטיים הסתיימו. ההתנהגות הזו יכולה לגרום לכשלים קלים באפליקציה שלא גלויים באופן מיידי ל-Kubernetes או למפעילים, אבל עדיין גלויים ביומן המערכת של הצומת (
journalctl).
cgroup v2מציע התנהגות צפויה יותר של OOM Killer.כדי להבטיח את תקינות עומס העבודה בסביבת cgroup v2, מנגנון ה-OOM killer מונע סגירות חלקיות ומבטיח אחת משתי תוצאות: או שכל המשימות ששייכות לאותו cgroup ולצאצאים שלו מסתיימות (כך שהכשל גלוי ל-Kubernetes), או שכאשר בעומס העבודה אין משימות שמשתמשות ביותר מדי זיכרון, עומס העבודה נשאר ללא שינוי וממשיך לפעול בלי סגירות לא צפויות של תהליכים פנימיים.
בתרחישים שבהם רוצים את ההתנהגות של
cgroup v1לסיום תהליך יחיד, ה-kubelet מספק את הדגלsingleProcessOOMKillל-cgroup v2. הדגל הזה נותן לכם שליטה מפורטת יותר, ומאפשר לסיים תהליכים ספציפיים במהלך אירוע OOM, במקום את כל ה-cgroup.
סגירת תהליכים ברמת המערכת בגלל מחסור בזיכרון (OOM)
סגירת OOM ברמת המערכת היא אירוע חמור יותר, שמתרחש כשנגמר הזיכרון הזמין בכל הצומת, ולא רק בקונטיינר אחד. האירוע הזה יכול לקרות אם השימוש המשולב בזיכרון של כל התהליכים (כולל כל ה-Pods וה-daemons של המערכת) חורג מהקיבולת של הצומת.
כשהזיכרון של הצומת הזה נגמר, ה-OOM Killer הגלובלי בודק את כל התהליכים בצומת ומסיים תהליך כדי לפנות זיכרון לכל המערכת. התהליך שנבחר הוא בדרך כלל תהליך קצר שמשתמש בכמות גדולה של זיכרון.
כדי למנוע מצבים חמורים של OOM, Kubernetes משתמש בפינוי עומס מהצומת כדי לנהל את המשאבים של הצומת. בתהליך הזה, פודים פחות קריטיים מפונים מצומת כשהמשאבים, כמו זיכרון או מקום בכונן, כמעט נגמרים. אם התהליך נסגר בגלל מחסור בזיכרון ברמת המערכת, סימן שתהליך הפינוי לא הצליח לפנות זיכרון מספיק מהר כדי למנוע את הבעיה.
אם 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 היא "בלתי נראית" ל-Kubernetes אם תהליך צאצא נסגר אבל תהליך הקונטיינר הראשי ממשיך לפעול (תרחיש נפוץ בסביבות cgroup v1). כדי למצוא ראיות לאירועים האלה, צריך לחפש ביומנים של הצומת.
כדי למצוא פעולות סגירה בלתי נראות של OOM, משתמשים ב-Logs Explorer:
במסוף Google Cloud , נכנסים לדף Logs Explorer.
בחלונית השאילתה, מזינים אחת מהשאילתות הבאות:
אם כבר יש לכם 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"
לוחצים על Run query.
בפלט, מאתרים אירועי OOM על ידי חיפוש רשומות ביומן שמכילות את המחרוזת
TaskOOM.אופציונלי: אם חיפשתם אירועי OOM לכל עומסי העבודה ב-GKE ואתם רוצים לזהות את ה-Pod הספציפי שבו התרחשו אירועי ה-OOM, בצעו את הפעולות הבאות:
- לכל אירוע, רושמים את מזהה המאגר שמשויך אליו.
כדי לזהות עצירות של קונטיינרים, חפשו רשומות ביומן שמכילות את המחרוזת
ContainerDiedושהתרחשו זמן קצר אחרי אירועי ה-OOM. מתאימים את מזהה הקונטיינר מאירוע ה-OOM לשורה המתאימה ב-ContainerDied.אחרי שמוצאים את
container IDs, השורהContainerDiedבדרך כלל כוללת את שם ה-Pod שמשויך לקונטיינר שנכשל. ה-Pod הזה הושפע מאירוע ה-OOM.
התאמה בין אירועי OOM לבין הפעלות מחדש של קונטיינרים
כש-OOM kill מסיים את התהליך הראשי של קונטיינר, Kubernetes מפעיל מחדש את הקונטיינר על סמך restartPolicy של ה-Pod, כפי שמתואר בקטעים OOM kill ברמת הקונטיינר ו-OOM kill ברמת המערכת. כדי להציג אירועים של OOMKilling ואת הפעלות הקונטיינרים שבאות אחריהם ברשימה כרונולוגית אחת, משתמשים ב-Logs Explorer:
במסוף Google Cloud , נכנסים לדף Logs Explorer.
בחלונית השאילתה, מזינים את השאילתה הבאה:
log_id("events") resource.labels.cluster_name="CLUSTER_NAME" ( ( resource.type="k8s_pod" resource.labels.namespace_name="NAMESPACE_NAME" jsonPayload.message:"Started container" ) OR ( resource.type="k8s_node" jsonPayload.reason="OOMKilling" ) )מחליפים את מה שכתוב בשדות הבאים:
-
CLUSTER_NAME: שם האשכול שרוצים לשלוח אליו שאילתה. -
NAMESPACE_NAME: מרחב השמות של ה-Pods שרוצים לבדוק.
אירועי
OOMKillingמדווחים לצומת (סוג המשאבk8s_node), ולכן אין להם תווית של מרחב שמות. המסנן של מרחב השמות חל רק על אירועיStarted container, שמדווחים עבור Pod (סוג המשאבk8s_pod).-
לוחצים על Run query.
בפלט, מחפשים אירועי
Started containerשמתרחשים זמן קצר אחרי אירועOOMKillingבאותו צומת. הודעת האירועOOMKillingמזהה את התהליך שהופסק, והאירועStarted containerמזהה את ה-Pod והקונטיינר שהופעלו.לא כל אירוע
Started containerנגרם על ידי סגירת OOM. כדי לוודא שהקונטיינר הופעל מחדש בגלל אירוע OOM, בודקים את הסטטוס של ה-Pod כמו שמתואר בקטע בדיקת הסטטוס של ה-Pod לאיתור אירועי OOM גלויים.
שימוש ב-journalctl לקבלת מידע בזמן אמת
אם אתם צריכים לבצע ניתוח של המערכת בזמן אמת, השתמשו בפקודות journalctl.
מתחברים לצומת באמצעות SSH:
gcloud compute ssh NODE_NAME --location ZONEמחליפים את מה שכתוב בשדות הבאים:
-
NODE_NAME: שם הצומת שרוצים לבדוק. -
ZONE: האזור ב-Compute Engine שאליו שייך הצומת.
-
במעטפת, בודקים את הודעות הליבה מיומן המערכת של הצומת:
journalctl -kמנתחים את הפלט כדי להבחין בין סוגי האירועים:
- סגירה ברמת הקונטיינר: רשומת היומן מכילה מונחים כמו
memory cgroup,mem_cgroupאוmemcg, שמציינים שנאכפה מגבלת cgroup. - סגירה ברמת המערכת: הרשומה ביומן היא הודעה כללית כמו
Out of memory: Killed process...בלי אזכור של cgroup.
- סגירה ברמת הקונטיינר: רשומת היומן מכילה מונחים כמו
פתרון אירועי OOM
כדי לפתור אירוע OOM, נסו את הפתרונות הבאים:
- הגדלת מגבלות הזיכרון: זה הפתרון הכי ישיר. עורכים את המניפסט של ה-Pod כדי לספק ערך
resources.limits.memoryגבוה יותר שמתאים לשימוש בשיא של האפליקציה. למידע נוסף על הגדרת מגבלות, קראו את המאמר ניהול משאבים לפודים ולמאגרים במסמכי התיעוד של Kubernetes. - הוספה או התאמה של בקשות זיכרון: במניפסט של ה-Pod, מוודאים שהשדה
resources.requests.memoryמוגדר לערך ריאלי לשימוש אופייני. ההגדרה הזו עוזרת ל-Kubernetes לתזמן את ה-Pod לצומת עם מספיק זיכרון. - להגדיל את עומס העבודה באופן אופקי: כדי לפזר את עומס התנועה ולהפחית את העומס על הזיכרון בכל Pod, מגדילים את מספר הרפליקות. כדי ש-Kubernetes יתאים את עומס העבודה באופן יזום, כדאי להפעיל התאמה אופקית של קבוצות Pod לעומס.
- הגדלת הצמתים: אם הרבה Pods בצומת קרובים למגבלות שלהם, יכול להיות שהצומת עצמו קטן מדי. כדי להגדיל את הצמתים, מעבירים את עומסי העבודה למאגר צמתים עם יותר זיכרון. כדי ש-Kubernetes יתאים את קנה המידה של הצמתים באופן יזום, כדאי להפעיל התאמה אנכית של קבוצות Pod לעומס.
- אופטימיזציה של האפליקציה: בודקים את האפליקציה כדי לזהות ולפתור דליפות זיכרון, ולבצע אופטימיזציה של קוד שצורך כמויות גדולות של זיכרון במהלך עלייה חדה בתנועה.
- מחיקת עומסי עבודה בעייתיים: כמוצא אחרון לעומסי עבודה לא קריטיים, אפשר למחוק את ה-Pod כדי להקל מיד על הלחץ באשכול.
המאמרים הבאים
אם לא מצאתם פתרון לבעיה במסמכי התיעוד, תוכלו לקבל עזרה נוספת במאמר קבלת תמיכה, כולל עצות בנושאים הבאים:
- לפתוח בקשת תמיכה באמצעות פנייה ל-Cloud Customer Care.
- לקבל תמיכה מהקהילה על ידי פרסום שאלות ב-StackOverflow ושימוש בתג
google-kubernetes-engineכדי לחפש בעיות דומות. אתם יכולים גם להצטרף לערוץ Slack של#kubernetes-engineכדי לקבל תמיכה מהקהילה. - דיווח על בעיות או שליחת בקשות להוספת תכונות באמצעות הכלי הציבורי למעקב אחר בעיות.