פודים נתקעים ב-Pending: היכן נשבר Kubernetes StorageClass
פריסה נראית בריאה: המניפסטים הוחלו, השירות נוצר, אבל הפודים נתקעים ב-Pending ולא זזים. באירועי הפוד — FailedScheduling עם הפניה לתביעת אחסון שלא סופקה, ובבקר — שגיאת הקצאת ווליום. האפליקציה עצמה לא נופלת: היא פשוט לא עולה, כי Kubernetes לא מספק לה אחסון.
הסיבה כמעט תמיד בקונפיגורציה של מחלקת האחסון. אם ב-StorageClass מוגדר מצב קישור מיידי, הווליום נוצר לפני שהמתזמן בחר נוד — ולכן בקלות נוחת באזור שבו אין את הדיסק הדרוש או שהוא לא תואם לנוד הזה.
בקישור מושהה הווליום מוקצה עבור נוד ספציפי, ופריסה תקועה מעידה לעיתים קרובות שאין היכן לשבץ את הפוד, ולא שהאחסון לא זמין.
ענף נפרד — פער בין התביעה למחלקה: ה-PVC מפנה לשם שלא קיים בקלאסטר, למחלקה בלי תווית ברירת המחדל, או מבקש מצב גישה שהדרייבר לא יודע לספק. אז הווליום לא נוצר כלל, והאוטוסקיילר לא יכול להוסיף אף רפליקה: כל חדשה ממתינה לווליום שלה, והסקיילינג נעצר יחד עם הפריסה.
האבחון כאן צפוי: מסתכלים על אירועי ה-PVC והפוד, משווים את שם המחלקה, מצב הקישור ומדיניות ההחזרה, ובודקים שהדרייבר רשום ושיש קיבולת פנויה באזור הנבחר. מכאן המשימה אינה להעיר פוד תקוע אחד, אלא להביא את מדיניות האחסון לסדר — ומפרקים איך זה נסגר.
מה נותן פרוביז'נינג דינמי של ווליומים בקלאסטר
פרוביז'נינג דינמי של ווליומים נשען על חיבור אחד: StorageClass מתאר מחלקת אחסון ומדיניות שחרור שלה, ו-PersistentVolumeClaim במניפסט של האפליקציה פשוט מפנה למחלקה הזו. משם בקר האחסון יוצר בעצמו את הווליום, מקשר אותו לתביעה ומחבר אותו לפוד.
הצעד הידני "להזמין דיסק, להמתין להקצאה, לרשום מזהה במפרט ולפרוס מחדש" נעלם מהתהליך כולו — הווליום מופיע בשניות, לא בשעות של אישורים.
עבור זמן הפריסה זה משנה יותר ממה שנדמה. תביעת האחסון נוסעת באותו קומיט עם האפליקציה עצמה, ולכן סביבה חדשה במעגל הבדיקות עולה בהחלה אחת של מניפסטים. המפתח לא ממתין לבקשת תשתית, והסביבה לא חיה במצב "הפוד כבר קיים, הדיסק עוד לא".
סקיילינג של העומס מקבל את אותו אפקט: עם גידול במספר הרפליקות של StatefulSet, כל פוד חדש יוצר אוטומטית ווליום מהמחלקה ומעלה אותו, בלי עבודה ידנית לכל עותק.
מחלקת הכשלים כמו "ווליום לא הוקצה", "התביעה תלויה בהמתנה", "הדיסק מקושר לאזור הלא נכון" יוצאת מהתפעול, כי הבקר סוגר אותן, לא מהנדס תורן.
ווליומים סטטיים קיימים ממשיכים לעבוד: מחלקות ותביעות עבורם מתקיימות במקביל, ולכן המעבר יכול להיעשות בהדרגה, אפליקציה אחת בכל פעם.
אם בקלאסטר כבר מוגדרות StorageClass עם מדיניות שימור נתונים, תביעות חדשות משתמשות בהן כמו שהן — התאימות למניפסטים ישנים נשמרת, ואין צורך לשנות את פריסת האחסון.
חלק הפעולות הידניות יורד למקרים בודדים: ידניים נשארים רק ההחלטות שבהן נדרש אדם — גודל המכסה, תוחלת החיים, מדיניות מחיקת הווליום לאחר הסרת התביעה. כל השאר — יצירה, קישור, העלאה — קורה אוטומטית, ורדיוס הכשל מצטמצם לתביעה אחת במקום שרשרת של צעדים ידניים.
השוואה של מצבי הקצאה וקישור של ווליומים
הבחירה בין הקצאה סטטית לדינמית היא בחירה בין חיזוי למהירות. מצבי הקצאת הווליומים קובעים מי יוצר את המדיה: המנהל מתאר מראש ווליומים ומחזיק אותם במאגר, או שהבקר יוצר את המדיה ברגע שהבקשה מגיעה מהפוד. פרוביז'נינג סטטי נותן שליטה מלאה על היכן יגיעו הנתונים, דינמי — מסיר עבודה ידנית לכל שירות חדש.
על קישור הווליומים משפיע לא רק המצב, אלא גם מדיניות המיחזור של הווליום: לאחר מחיקת הבקשה המדיה חוזרת למאגר, נשמרת כמו שהיא, או נמחקת יחד עם הווליום. מזה תלויה גם היכולת לבצע רולבק, וגם באיזו מהירות גדל נפח האחסון.
מתי איזה מצב מתאים
| אפשרות | מתי מתאים | מגבלות |
|---|---|---|
| הקצאה סטטית | נתונים ששורדים את הקלאסטר: בסיסי נתונים, ארכיונים, ווליומים עם מחזור חיים חיצוני | ווליומים נוצרים ידנית, יש להחזיק את המאגר עם עודף, והקישור לפי שם שביר |
| דינמית דרך מחלקת ברירת המחדל | עומס אחיד בלי דרישות לפרופיל המדיה | כל הבקשות מקבלות פרופיל אחד; החלפתו משפיעה על הקלאסטר כולו, לא על צוות אחד |
| מחלקות בעלות שם לפי פרופילים | דרישות שונות: דיסקים מהירים לטרנזקציות, קיבולת גדולה ללוגים ולאנליטיקה | יותר אובייקטים לתחזוקה, לכל מחלקה מדיניות מיחזור משלה |
בנפרד ראוי לציין קישור מושהה: הווליום נוצר רק כשהפוד כבר שובץ לנוד. אחרת, בקלאסטר עם כמה אזורים, המדיה עלולה להימצא באזור הלא נכון, והפוד ייתקע בהמתנה, כשהאבחון של מצב כזה גוזל שעות. מפרקים איך לבחור מצב לפי פרופיל העומס ולא לאבד נתונים בעת רולבק.
איך אנחנו מגדירים Kubernetes StorageClass: שלבי העבודה
סדר העבודות נבנה כך שמחלקות האחסון ייווצרו מפרופילי עומס אמיתיים, ולא "לפי העין". קודם מתעדים מה כבר מורכב ואיך ווליומים מתנהגים תחת עומס, אחר כך מתכננים את סכימת המחלקות, ורק לאחר מכן משנים את הקלאסטר. כל שלב נסגר בארטיפקט שניתן לבדוק — לפני המעבר לשלב הבא.
- אודיט של ווליומים ב-Kubernetes: אוספים PVC ו-PV לפי מרחבי שמות, בודקים דרייבר, מדיניות שחרור, מצב קישור, ניצולת בפועל וטופולוגיה של הנודים. בפלט — טבלת עומסים עם דרישות לשהיה, גישה משותפת ועצמת קלט-פלט.
- תכנון סכימת המחלקות: מגדירים 3–5 פרופילים — ווליומים מהירים לבסיסי נתונים, קיבולת גדולה ללוגים, ארכיוניים עם שכבה קרה — וקובעים לכל אחד StorageClass משלו עם פרמטרים מפורשים.
- הגדרת StorageClass: מגדירים מדיניות שחרור, הרשאה להרחבה, קישור מושהה לווליומים במספר אזורים וגדלים מקסימליים, כדי ש-PVC אחד לא ימשוך את כל המאגר.
- הכנה והעברת עומסים: מגדירים מחלקת ברירת מחדל, מעבירים ווליומים קיימים בהחלפת מחלקה דרך יצירה מחדש או סנאפשוטים — בלי עצירה ארוכה של שירותים.
- בדיקה בסביבת בדיקות: מריצים כשל נוד, מיצוי מכסה, הרחבת ווליום תחת עומס חי ורולבק למחלקה הקודמת. מתעדים איך המתזמן מתנהג בכל תרחיש.
- הטמעת האחסון בפרודקשן והעברה לצוות: פורסים לפי מרחבי שמות, מוסרים את סכימת המחלקות, את התקנון לבקשות ווליומים ואת ההוראות למשמרת התורנית.
מדיניות גישה, מכסות ואובייקטים בקלאסטר
לאחר ההטמעה נשארים לצוות הלקוח לא רק מחלקות אחסון עובדות, אלא גם ארטיפקטים שלפיהם הקלאסטר מתופעל בלעדינו. להלן מה שנמסר לידיים ונבדק בקבלה.
- תיאורים של כל StorageClass: ייעוד, פרמטרים (מצב קישור הווליום, מדיניות החזרת נתונים במחיקת הבקשה, סוג הדיסק) ופרופיל העומס שלשמו נוצר — המפתח בוחר מחלקה לפי התיאור, לא באקראי.
- מדיניות גישה: הפרדת הרשאות לפי תפקידים — מי יוצר ועורך מחלקות, מי רק מבקש ווליומים, מי קורא סטטוס; תפקידים נפרדים לחשבונות שירות.
- מכסות מרחבי שמות: ResourceQuota ו-LimitRange לכל מרחב — תקרה לנפח האחסון הכולל, למספר הבקשות ולגודל ווליום בודד. במיצוי המכסה הצוות מקבל שגיאה מובנת, לא משימה תקועה.
- כללי מחזור חיים של ווליומים: אילו מחלקות שומרות נתונים לאחר מחיקת הבקשה ואילו משחררות; כיצד לקחת ווליום לחקירת תקלה.
- תקנון תחזוקה: סדר הוספת מחלקה או עריכת פרמטרים, בדיקה בסביבת בדיקות, חלון החלת שינויים, נקודות רולבק.
- תיעוד הקלאסטר: סכימת הפריסה, רשימת המחלקות, המניפסטים וההיסטוריה שלהם במערכת בקרת גרסאות, יומן שינויים.
- ניטור והתראות: אילו מטריקות על בקשות ווליומים ומקום פנוי נאספות, ובאילו ספים מגיעה התראה.
- העברה לצוות: מפגש פירוט עם מהנדסי הלקוח, הרצת תרחישים טיפוסיים, אנשי קשר להסלמה בתקופת הליווי.
קייס: ביטלנו תקיעת ווליומים בעומס שיא
לפני הפרויקט ווליומים בקלאסטר הונפקו ידנית: לפי בקשה המהנדס יצר דיסק והכניס אותו למניפסט, והפודים חיכו לתורם. במבצעי שיא התור גדל, חלק מהפודים נשארו במצב Pending עד הבוקר, ומדור בקטלוג היה לא זמין.
המתזמן Kubernetes לא יכול היה לעזור — בלי StorageClass אין לו במה לקשר את הווליום, ולכן התורן טיפל בתקלות כאלה בלילה, בידיים.
מה שינינו
תיארנו StorageClass לפי פרופילי עומס: מחלקה מהירה כברירת מחדל לבסיסי נתונים ולתורים, מחלקה קרה ללוגים ולייצואים.
הפעלנו פרוביז'נינג דינמי, קישור הווליום לאזור של הפוד, allowVolumeExpansion לגידול הווליום בלי יצירה מחדש ו-reclaimPolicy Retain לנתונים קריטיים — הווליום כבר לא הולך יחד עם הבקשה.
מעל זה הצבנו מכסות על גודל הבקשה, כדי שפוד אחד לא יאכל את כל המאגר, וסנאפשוטים של הווליום לפני ריליס לרולבק מהיר.
מטריקות לפני ואחרי
| מדד | לפני | אחרי |
|---|---|---|
| ווליומים ב-Pending בשיא | עד 18% מהבקשות | אפס |
| הנפקת ווליום | 4-6 דקות, בשיא — עשרות | שניות |
| פעולות ידניות של התורן | 10-12 בשבוע | 0 |
| תקלות בגלל אחסון | 3-4 בחודש | 0 בחצי שנה |
מחלקת כשלים שלמה נעלמה: העומס כבר לא עומד בתור לווליום, אלא מפעיל סקיילינג אוטומטי. שעות הלילה התורן מקדיש לעבודות מתוכננות, לא לקישור ידני של דיסקים, והריליס כבר לא נדחה בגלל האחסון.
מה לעשות אם הווליום כבר נוצר סטטית
השדה storageClassName של PVC קיים אינו ניתן לשינוי — לא ניתן לערוך אותו בקלאסטר חי. לכן המעבר לפרוביז'נינג דינמי נעשה לא בעריכת מניפסט, אלא במיגרציה: נוצרת StorageClass עם הדרייבר ומדיניות ההחזרה הדרושים, מכינים PVC חדש, הנתונים מועברים, ורק אז העומס עובר לווליום החדש.
שיטת ההעברה תלויה ביכולות של דרייבר האחסון. המסלול הקפדני — סנאפשוט של הווליום או שכפול עם שחזור ל-PVC חדש: העותק נוצר ברמת האחסון, האפליקציה ממשיכה לעבוד על הווליום המקורי ואינה יודעת על ההעתקה. אם אין סנאפשוטים, מרימים רפליקה שנייה שממלאת את הווליום החדש בזמן שהשירות עונה לתעבורה.
לא ניתן לעשות שימוש חוזר בווליום הישן כמו שהוא: PV סטטי מקושר ל-PVC מסוים. StatefulSet לא יאמץ PVC זר אוטומטית — מתקנים את volumeClaimTemplates, והפודים מוחלפים אחד אחד עם בדיקה שהרפליקה הדביקה ושהנתונים על הווליום החדש עקביים. הווליום הישן נשאר מורכב לרולבק, עד שהסכימה החדשה תעבוד תחת עומס מחזור מלא.
בקלאסטר חי אי אפשר לשנות accessModes ואת הקישור למחלקה ב-PVC, ואת הגודל — רק כלפי מעלה ורק אם ההרחבה מופעלת ב-StorageClass; חלק מהדרייברים עושים זאת תוך כדי תנועה, לאחרים נדרש אתחול הפוד.
מדיניות החזרת המשאבים נקבעת ב-StorageClass לפני יצירת הווליום: לערוך אותה על PV פעיל זה מסוכן — מחיקת ה-PVC תישא עימה נתונים שהאפליקציה עוד מפנה אליהם.
ספקות טכניים לפני תחילת העבודה
הספקות בדרך כלל אינם לגבי Kubernetes כשלעצמו, אלא לגבי האופן שבו מחלקה מסוימת תתיישב על האחסון, הווליומים וההרשאות של הצוותים. להלן השאלות שצוותי הנדסה שואלים לפני התחלה בשכיחות הגבוהה ביותר.
המחלקה תסתדר עם האחסון שלנו?
StorageClass הוא פרופיל, ואת העבודה עם הדיסק מבצע דרייבר האחסון. בבדיקה אנחנו בודקים אילו מצבי גישה הוא תומך ואיך הוא מתנהג בהחלפת נוד, ומרימים מחלקת בדיקה במעגל מבודד — לפני כל שינוי בסביבת הפרודקשן.
מה יקרה לווליומים קיימים?
בקשות ווליומים שנוצרו נשארות מקושרות למחלקה שלהן — מחלקה חדשה לא כותבת אותן מחדש. המעבר נעשה באמצעות העתקת נתונים ובקשה חדשה, לא בעריכת ווליום חי. בנפרד עוקבים אחרי מחלקת ברירת המחדל: כל עוד התווית קיימת, כל הווליומים בלי ציון מפורש הולכים אליה.
למה אי אפשר להסתפק במחלקה אחת להכול?
את המגבלות קובע הדרייבר: ווליום אחד — נוד אחד, גישה משותפת, התנהגות במחיקת בקשה שהוסרה. לכן מפצלים מחלקות לפי פרופילים. מחלקה אוניברסלית או שאינה נותנת את מצב הגישה הדרוש, או שמוחקת נתונים במקום שבו היה צריך לשמר אותם.
אילו הרשאות גישה נדרשות לצוותים?
מחלקה היא משאב ברמת הקלאסטר, ויוצר ועורך אותה צוות הפלטפורמה, לא כל מפתח. לצוותי האפליקציה נותנים הרשאות לבקשות ווליומים בתוך מרחב השמות שלהם ומכסות לנפח: המחלקות נשארות מנוהלות, והגישה לאחסון לא מתפשטת.
נדבר על המשימה: מה להכין לשיחה
כדי לדון במשימה באופן ענייני, כדאי להחזיק תמונה עדכנית של האחסון והדרישות ממנו. אנחנו לא מבקשים לנסח מסמך דרישות טכני מלא — די ברשימה שלהלן, ולפיה כבר נאספת הערכת העבודה ותוכנית ההטמעה.
- סכימת המחלקות הנוכחית: אילו StorageClass מוצהרות בקלאסטר, איזו מסומנת כברירת מחדל בשימוש, אילו מדיניות החזרת ווליום מוגדרות ולאן נעלם הדיסק הפיזי לאחר מחיקת הבקשה.
- פרופילי עומס: אילו שירותים כותבים ברצף (לוגים, ארכיונים), לאילו נדרשת כתיבה אקראית (בסיסי נתונים), ולאילו מספיק דיסק רשתי — מזה תלוי סוג הפרוביז'נינג ומצב קישור הווליום לנוד.
- דרישות גישה: האם נדרשת כתיבה בו-זמנית מכמה נודים, האם יש קישור קשיח לאזור זמינות, ואילו הרשאות יש לצוותים ולמרחבי השמות.
- התמודדות עם גידול ועותקים: האם מותרת הרחבת ווליום תוך כדי תנועה, האם נוצרים סנאפשוטים, ואיך בנויים גיבויים ושחזור מהם.
- סדר בדיקת התוצאה: באיזה מעגל בדיקות משווים את ההתנהגות, ולפי אילו תרחישים — יצירת ווליום, העברת עומס, שחרור דיסק לאחר מחיקה, כשל נוד.
- אופן התפעול: חלונות עבודה, דרישות לזמינות גבוהה, מי מצד הלקוח מאשר שינויים וחותם על המעבר לסכימה החדשה.
אם חלק מהנתונים חסר, אנחנו משחזרים אותו בעצמנו בשלב הסקר — זה בא לידי ביטוי בתוכנית ההטמעה בשורה נפרדת, ולא מוטל על הצוות שלכם. שלחו את מה שיש: גם על בסיס סט חלקי נחזור עם הערכה וסדר עבודה ברור.







