cgroups v2 והגבלת משאבים: היכן הפרודקשן נשבר
בסביבת קונטיינרים ללא הגבלת משאבים, הצומת (node) מחזיק מעמד עד הפרץ הראשון. שירות שאין לו תקרת זיכרון, בעת דליפה גדל עד גבול הליבה, דוחק את השכנים לסוואפ, והמתזמן מתחיל לבזבז זמן על שחרור דפים במקום על עבודה מועילה.
הדרדרות תחת עומס מתפשטת על כל הצומת — כולל תהליכים שאין להם קשר לאשם. זה השכן הרועש הקלאסי: פורמלית אשם שירות אחד, בפועל כל הצומת מפסיק להגיב.
עומס יתר על המעבד נראה אחרת, אבל מוביל לאותה תוצאה. תהליך אחד עם לולאה חמדנית צורך את המכסה המוקצית, תורי המתזמן גדלים, וזמן התגובה של שער ה-API יוצא מגבולות הסביר. מערכות חיצוניות, לאחר פקיעת זמן ההמתנה, מתחילות להתייחס לצומת כלא זמין, למרות שהחומרה תקינה והרשת כשורה. האבחון במקרה כזה מתעכב: מדדי רמת הצומת מראים עלייה כללית בעומס, אך לא מצביעים על האשם הספציפי.
cgroups v2 סוגר זאת באמצעות עץ היררכיה אחד. cpu.max קובע את הגבול העליון לזמן המעבד, cpu.weight — את החלק בתחרות, memory.max — גבול קשיח עם סיום התהליך, memory.high — סף רך עם האטה במקום תקלה, io.max — רוחב פס בהתקן דיסק, memory.swap.max — איסור swap לשירותים שהדבר אסור להם.
את התמונה משלים PSI: הוא מראה כמה זמן משימות עמדו בחוסר מעש בהמתנה למשאב, כלומר האות מגיע לפני הכשל.
בהמשך נפרט כיצד להגדיר הגבלת משאבים על הצומת כך שהפרודקשן לא יישבר: מאילו מדדים להתחיל, אילו מגבלות לקבוע וכיצד להבחין בין דליפת זיכרון בשירות לבין גידול אמיתי בעומס.
אפקט מדיד: השהיות, זמן השבתה, בידוד שירותים
כל עוד לשירותים יש מאגר משאבים משותף, תהליך אחד עם דליפת זיכרון או פרץ קלט/פלט משתלט על הצומת כולו: השכנים מאבדים תגובתיות, הליבה מפעילה שחרור זיכרון חירום, והכול מדרדר. עם cgroups v2 כל מודול חי בענף היררכיה משלו ואינו יכול לחרוג מהחלק שהוקצה לו — כשל מדורג כקטגוריה נעלם.
זמן התגובה תחת עומס מפסיק להיות לוטו. משקל המתזמן (cpu.weight) והתקרה על זמן המעבד נותנים לשירות חלק מובטח גם כשהשכן עמוס — הוא אינו צריך להמתין לשחרור הליבה. זה ניכר בזנב ההתפלגות: השאילתות האיטיות ביותר מפסיקות לפגר אחרי החציון פי כמה, ולא פי עשרות.
זיכרון ופעולות דיסק אנחנו מגבילים ברכות. הסף memory.high מפעיל דחיסת דפים במקום הרג קשיח של התהליך, ו-io.max לא נותן לגיבוי או לאינדוקס מחדש לחנוק שאילתות טרנזקציוניות. הצריכה הופכת לצפויה — לפיה אפשר לחשב את צפיפות פריסת השירותים על הצומת בלי מרווח «בעין».
בידוד שירותים הופך את הצומת מסיר אחד משותף למערכת של מעגלים עצמאיים: אחד מדרדר, האחרים שומרים על רמת תגובה. משימות רקע ופסגות ערב מפסיקות להיות סיבה לחקירות לילה — יציבות הצומת מפסיקה להיות תלויה בשכן הרועש ביותר.
מצבי הגבלה: מגבלות קשיחות, רזרבות, משקלות
תקרה קשיחה ומשקל פותרים משימות שונות, וערבוב ביניהם הוא מקור לתקלות. מגבלה קשיחה (cpu.max, memory.max) קובעת גבול שהשירות לא יחרוג ממנו גם כשמשאבי המארח פנויים: חריגה בזיכרון מפעילה מנגנון דחיקה, ולא הדרדרות של השכנים.
רזרבה מובטחת (memory.min, memory.low) פועלת להפך — היא מגנה על תהליכים מפני דחיקה כשמתחיל לחץ זיכרון על הצומת.
עדיפות לפי משקלות (cpu.weight, io.weight) אינה מגבילה דבר: היא מופעלת רק בתחרות ורק מקצה מחדש משאבים שהתפנו. לכן משקלות הן הבחירה הנכונה למשימות אצווה שצריכות לפנות מקום לעומס מקוון, אך יכולות לנצל זמן סרק.
קבוצה נפרדת — הגבלת מספר התהליכים (pids.max) ותקרת קלט/פלט (io.max): לעתים קרובות מדלגים עליהן, אבל דווקא הן מחזיקות את הצומת כששירות מיצה את מגבלת מתארי הקבצים או חולל מפולת תהליכים.
| אפשרות | מתי מתאים | מגבלות |
|---|---|---|
| מגבלה קשיחה על מעבד וזיכרון | שירותים עם פרופיל צריכה צפוי | בחישוב שגוי — הפעלות מחדש תקופתיות; תעבורת שיא לא תיכנס |
| רזרבה מובטחת | שירותים קריטיים לצד שכנים רועשים | שומרת זיכרון גם לתהליכים שאינם פעילים, ומפחיתה את ניצולת הצומת הכוללת |
| עדיפות לפי משקלות | עיבוד אצווה, משימות רקע | אינה נותנת ערובות לתגובה: בעומס מלא על הצומת השירות ממתין |
| הגבלת מספר תהליכים | שירותים ציבוריים ומשתמשים | דורשת התאמה לפי מספר העובדים בפועל, אחרת כשל במקום הדרדרות |
| תקרת קלט/פלט | מסדי נתונים, שכפול, ייצוא | הגבלה ברמת ההתקן, יש לחשב לפי רוחב הפס של הדיסק |
בפועל משלבים בין המצבים: לשירות קריטי קובעים גם רזרבה וגם תקרה בו-זמנית, ולשירות רקע — רק משקלות, כדי שלא יתחרה על המשאב בשעות עומס.
כיצד אנו מטמיעים מגבלות: מהאודיט ועד למסירה
מגבלות שנקבעות לפי תיעוד ולא לפי המצב בפועל או חונקות את השירות או לא פועלות: מערך הזיכרון העובד מתברר כגדול פי שלושה מהמוצהר, והאתחול נכנס ל-throttling. לכן איננו משנים הגדרות עד שלא מדדנו פרופילי צריכה אמיתיים.
- אודיט צריכה. בודקים היכן תהליכים חיים בעץ cgroups v2, מה נכנס לחתך השורש ואיזה מהשירותים באותה מכונה למעשה אוכל את המשאבים.
- פרופילי עומס. מדדנו פרופיל יומי ושבועי: מערך הזיכרון העובד, חלונות שיא, מספר תהליכונים ופעולות דיסק, מדדי לחץ בעת הדרדרות השכנים.
- תכנון מדיניות. מפרידים בין סף רך לקשיח בזיכרון, קובעים משקל ותקרה לזמן מעבד, מגבלות קלט/פלט עם מרווח לפרץ, בונים היררכיית חתכים.
- הגדרה. מעבירים ערכים לקונפיגורציית החתכים, מטפלים בנפרד בשירותי מערכת ויישום, בודקים שהאתחול ומשימות הרקע אינם נתקלים בתקרה.
- בדיקה תחת עומס. חוזרים על תרחיש השיא: throttling לא פוגע בתגובה, הגנת הזיכרון פועלת כצפוי, שירותים שכנים לא מדרדרים.
- תוכנית rollback. מתעדים אילו פרמטרים ובאיזה סדר מוחזרים, היכן נקודת האל-חזור וכיצד להסיר במהירות מגבלה משירות מסוים.
- מסירה לתפעול. מוסרים נוהל: ערכים, מדדים והתראות על התקרבות לתקרה, סדר שינוי מגבלות ואחריות לעדכון הפרופילים.
מה נשאר בידיים: קונפיגורציות, תרשימים, נהלים
תוצאת העבודה — לא שרת מורץ, אלא ערכה שלפיה צוות התחזוקה שלכם מנהל את המערכת בלעדינו וללא הסברים בעל פה. כל מה שהוגדר ב-cgroups v2 נמצא במאגר (repository): המגבלות מתוארות באופן דקלרטיבי, השינויים עוברים בדיקה, היסטוריית העריכות נשמרת וניתן לחזור ממנה בפקודה אחת.
- קובצי קונפיגורציה עם מגבלות לכל שירות:
cpu.max,memory.max,memory.high,io.max— עם הערך והערה מאילו מדידות הוא התקבל. - תרשים חלוקת משאבים: היררכיית קבוצות, מה מוכל בכל קבוצה, היכן הוקצתה רזרבה לתהליכי מערכת, והיכן הושאר מרווח לעומס שיא.
- מערכי ערכים למעגלים: פרופילים נפרדים לסביבת ייצור, קדם-ייצור ופיתוח מקומי, כדי שהבדיקה לא תחזור על מגבלות הייצור.
- נוהל תחזוקה: תדירות בדיקות, ספי הפעלה של
memory.high, תגובה לגידול מתמשך בצריכה, סדר איסוף סטטיסטיקות לפי קבוצות. - הוראות לתורנים: תרחישים שלב-אחר-שלב לאירועים טיפוסיים — קבוצה נתקלת במגבלה, תהליך הוסר בגלל חוסר זיכרון, תת-מערכת הדיסקים מאטה בגלל תחרות.
- סדר שינוי מגבלות: מי מאשר את העריכה, כיצד נמדדת הצריכה האמיתית, איך הערך מגיע לצמתים ובכמה זמן ניתן לחזור ממנו.
- מפת ניטור: אילו מדדי קבוצות מוצגים בלוחות ומאילו התראות תלויים סימנים מוקדמים להתקרבות לגבול.
הערכה מיועדת למהנדס שרואה את המערכת בפעם הראשונה: לפיה אפשר לנתח תקלה בלי להעלות היסטוריית התכתבות, ולהעביר משמרת תורנות.
מקרה בוחן: הצומת הפסיק ליפול תחת עומס שיא
לפני: כשל של שירות אחד עצר את הצומת
שירות אחד עם דליפת זיכרון איטית אכל בהדרגה את הזיכרון של שכניו על הצומת. כשהזיכרון הפנוי נגמר, הליבה הפעילה סיום חירום של תהליכים, ולא התהליך הבעייתי נפל, אלא כל הצומת יחד עם השירותים הפעילים.
פסגות עומס הגיעו כמה פעמים בשבוע, ובכל פעם השחזור ארך שעות: הפעלה מחדש ידנית, בדיקת שלמות נתונים, חימום מחדש.
מה שינינו
העברנו את הצומת ל-cgroups v2 עם היררכיה אחת במקום בקרים מפוזרים. קבענו גבולות קשיחים לזיכרון ולמספר תהליכים, משקלות יחסיות לזמן מעבד ומכסות לקלט/פלט דיסק לכל שירות. העיקר — סיום קבוצתי: בעת חריגה בזיכרון מכבה תהליך בעייתי אחד, ולא כל הצומת. ההתנהגות מחזיקה גם כשהשכנים מגיעים לשיא בו-זמנית.
תוצאה
תדירות הכשלים של הצומת ירדה מכמה פעמים בשבוע לאפס בתקופת התצפית, וזמן השחזור — משעות לדקות: השירות הבעייתי מופעל מחדש לפי מדיניות restart, והאחרים לא שמים לב. המדדים נאספו ב-Prometheus — בעת שיאים הצריכה נתקלת בגבול שהוגדר, ולא גדלה עד למיצוי.
סוג הכשל «דליפה של שירות אחד מפילה שכנים» נסגר, והמגבלות חיות בקונפיגורציה ומתעדכנות בלי לבנות מחדש אימג'ים.
מה לעשות אם המגבלות כבר הוגדרו בגרסת cgroups הקודמת?
לא ניתן להעביר את המדיניות «כמו שהן»: בענף הקודם כל בקר חי בהיררכיה משלו, ובחדש יש עץ אחד והפרמטרים בו מאוחדים.
cpu.shares מפנה מקום ל-cpu.weight עם טווח ערכים אחר, המכסה והתקופה מתמזגות ל-cpu.max, והגבלות הדיסק — ל-io.max. כלומר, ההעברה היא חישוב מחדש של ערכים והפעלה מפורשת של הבקרים הנדרשים ב-cgroup.subtree_control, ולא החלפת שמות לפי תבנית.
תאימות לאחור מספק מצב משולב: חלק מהבקרים נשארים בהיררכיה הישנה, חלק עוברים לחדשה. כך הצומת שורד אתחול מחדש בחלון נוח, אבל כסכמה קבועה זה לא מתאים — התהליך מתחיל להיספר בשני עצים, ובקר שכבר הועבר לא חוזר להיררכיה הישנה.
העברה ללא השבתה בנויה על הפעלה הדרגתית: תחילה צומת אחד, אחר כך אחד-אחד השאר. כל עוד הצומת מוצא מאיזון, העומס עובר לשכנים, ולאחר אתחול השירותים תחת systemd הם עולים כבר בעץ החדש. בכל שלב רק צומת אחד נמצא תחת השינוי, והחזרה עולה אתחול אחד.
הבדיקה לאחר המעבר — לא רק «השירות עלה». מוודאים שהבקרים פעילים ושהמגבלות הוחלו על התהליכים, אוספים מדדי לחץ memory.pressure, cpu.pressure, io.pressure — בענף הקודם הם פשוט לא קיימים. לאחר מכן פרופיל עומס קרוב לפרודקשן מאשר שההגבלות לא פוגעות בעבודה המועילה.
ספקות והתנגדויות טכניות נפוצות
לפני תחילת העבודה על מגבלות cgroups v2, השאלות של מהנדסים ומנהלים חופפות: האם לא נחנוק את העומס, עד כמה ההגדרות הפיכות ומי אחראי להתנהגות המערכת לאחר המסירה. להלן תשובות ישירות בלי מילים כלליות.
האם לא נחנוק את השירות במגבלה קשיחה?
מגבלת זמן המעבד אינה מכבה את התהליך, אלא מגבילה את חלקו: הפרץ מעובד לאט יותר, אך אינו קורס. המסוכן הוא דווקא גבול הזיכרון הקשיח. לכן תחילה מודדים פרופיל צריכה תחת עומס אמיתי, קובעים מגבלה עם מרווח ומפעילים סף רך לפני הקשיח — השירות מפנה מטמון במקום לקרוס.
האם ההגדרות הפיכות אם משהו ישתבש?
הפרמטרים הם קובצי היררכיה, ואנחנו מתעדים את מצבם לפני העריכות. החזרה היא השבת כמה ערכים, ואין צורך באתחול. את השינויים אנחנו שומרים בצורה דקלרטיבית במערכת ניהול קונפיגורציה, לכן כל שלב ניתן לשחזור וחוזר על עצמו במכונה אחרת בלי עבודה ידנית.
איך להבין שהצוואר בקבוק אינו במגבלות?
לפני העריכות אוספים מדדים: זמן הגבלה כפויה, אירועי זיכרון, סטטיסטיקת קלט/פלט, מדדי לחץ על משאבים. אם ההגבלה לא גדלה אבל ההשהיות קיימות — הסיבה היא בדיסק, ברשת או בקוד עצמו, והמגבלות לא יצרו אותה.
האם זה תואם לקונטיינרים?
סביבות קונטיינרים ומתזמרים עובדים עם cgroups v2 דרך דרייבר המערכת, לכן הכללים והמגבלות שלנו מהמניפסטים פועלים בהתאמה. אין קונפליקטים: מוחלת המגבלה המחמירה יותר ברמת השירות.
מה קורה לאחר מסירת העבודה?
נשארים בקשר לפי נוהל מוסכם: מנתחים תקלות הקשורות למגבלות, מחשבים מחדש פרמטרים בעת גידול בעומס, מעדכנים תיעוד. מוסרים לוחות ניטור, התראות והוראות שלב-אחר-שלב לחזרה לאחור.
נדון במשימה: ממה נתחיל ומה צריך מכם
כדי לחזור עם הערכה ותוכנית, ולא עם שאלות הבהרה, די לנו בכמה נתוני פתיחה. ככל שתארו את המצב הנוכחי בצורה מדויקת יותר, כך יקטן הזמן שיוקדש לבדיקה ראשונית והעבודה תתחיל מהר יותר.
- מה פרוס על הצומת: כמה שירותים, אילו קריטיים להשהיה ואילו יכולים להמתין בתור.
- כיצד מוגדרות המגבלות כיום: מגבלות קשיחות, מקדמי משקל או שתהליכים חיים בלי גבולות.
- תסמינים: פרצי השהיות, סיום חירום של תהליכים, גידול בתור פעולות הדיסק.
- פלטפורמה: ענף הליבה, ההפצה, האם יש ניהול קבוצות אחיד דרך systemd או מתזמר.
- מגבלות: חלונות תחזוקה, דרישות לזמינות רציפה, מי מספק גישות לצמתים.
- מי מצדכם מקבל החלטות ובודק את התוצאה לאחר השינויים.
להלן הסדר פשוט: דנים במשימה, מתעדים יעדים למעבד, לזיכרון ולפעולות דיסק, ומסכימים על תוכנית עבודה בשלבים עם נקודות בדיקה. הצעד הבא הראשון — לאסוף פרופילי קבוצות על צומת בדיקה; לאחר מכן קובעים מגבלות ומריצים את הקונפיגורציה תחת עומס. אם המדדים אינם עומדים ביעד, מחזירים את המערכת הקודמת — החזרה נשארת בידיים שלכם.
את פורמט שיתוף הפעולה אתם בוחרים: עבודה חד-פעמית על אזור צר או ליווי מתמשך עם עדכון פרופילים לפי גידול העומס.







