שרת NFS ב-Kubernetes: היכן האחסון נשבר
התרשים הטיפוסי נראה כך: שרת NFS אחד ב-Kubernetes מוקם "במהירות" — ייצוא יחיד של תיקייה, גישה לפי כתובת הצומת, מיפוי (mount) לתיקייה משותפת עבור הקונטיינרים.
בסביבת הבדיקה זה עובד, אבל בפרודקשן הדבר הראשון שמתגלה הוא מיפוי תלוי של הכרך: התהליך על הצומת אינו מקבל תשובה ואינו יכול להשלים את הפעולה.
המיפוי התלוי של הכרך אינו בעיה מקומית של אפליקציה אחת. ה-Pod נתקע בהמתנה, הצומת אינו יכול להשלים אותו כראוי ועובר למצב בלתי זמין — יחד איתו מושבתים שירותים שכנים, שמעולם לא פנו לאחסון. ובמקביל, בלוגים הכול נראה תקין: השרת מגיב, אין שגיאות מפורשות.
המוקש השני — אובדן נתונים בעת אתחול (restart) של ה-Pod. אם הכתיבה מתבצעת לכרך משותף ללא תיאום נעילות, האתחול קוטע אותה באמצע, והאפליקציה לאחר העלייה קוראת חצי קובץ כקובץ שלם. מספר רפליקות שכותבות לאותה תיקייה מוסיפות מרוצים (races) על שמות ודריסה של תוצאות של אחרים.
בעומס שיא, צוואר הבקבוק של האחסון מתגלה כתור בקשות גדל: השרת מבזבז זמן על פעולות מטא-דאטה, האפליקציה ממתינה לתשובה ומחזיקה חיבורים. הוספת רפליקות בתרשים כזה לא עוזרת — הן נתקלות באותו צומת יחיד.
כל זה אינו נפתר ב"שרת נכון", אלא בחלוקת תפקידים: מצבי מיפוי (mount), רמות גישה, מגבלות וצפייה (observability) על הכרך. נפרט כיצד זה נסגר.
מה נותן אחסון קבצים מנוהל לאשכול
הסדר באחסון ניכר במהירות הנפקת הכרך. ביצירה דינמית של כרכים ובמחלקת אחסון (StorageClass) המתוארת באופן דקלרטיבי, הכרך מופיע ברגע תזמון ה-Pod, והקונטיינר מתחיל עם תיקייה שכבר ממופּית — ללא ייצוא ידני בשרת והמתנה למנהל תורן. ההשהיה בין "צריך כרך" ל"הכרך מוכן" מפסיקה להיות תלויה באדם.
האפקט השני — מחלקת כשלים צפויה של האחסון. שרת NFS בודד ללא רפליקציה משבית את כל הפודים שמחזיקים עליו נתונים: זמן ההשבתה של האפליקציה שווה לזמן שחזור השרת.
אחסון קבצים מנוהל ב-Kubernetes מפריד בין כשל צומת לכשל שירות — הלקוח מתחבר מחדש לנקודה זמינה, ה-Pod עובר ומקבל את אותו כרך באותו נתיב, והאפליקציה אינה בונה מחדש את המצב מאפס.
השלישי — ביצועי כתיבה תחת עומס. צוואר הבקבוק מתגלה במטא-דאטה לפני הנפח: הרבה קבצים קטנים, רוטציה תכופה של לוגים, כתיבה בו-זמנית מעשרות פודים.
מצב הגישה ופרמטרי המיפוי נבחרים בהתאם לפרופיל — כתיבה משותפת לתיקיות משותפות, בלעדית לבסיסי נתונים ולתורים, כרכים נפרדים ללוגים, כדי שכתיבה כבדה לא תאט את הקריאה.
הסדר נמדד באופן קונקרטי: שניות להנפקת הכרך, מספר הפעולות שנותרו בידניים, שיעור כשלי הצומת שנפתרו ללא השבתת האפליקציה, ואחידות הכתיבה בשיא. נפרט כיצד זה מוגדר עבור האשכול שלכם.
צמתי NFS עצמאיים מול שירות אחסון קבצים בענן
NFS ב-Kubernetes מתחבר כ-PersistentVolume דרך StorageClass ודרייבר, ומצב הגישה קובע כמה צמתים יכולים למפות את הכרך בו-זמנית: ReadWriteMany — עבור תיקיות משותפות, ReadWriteOnce — עבור צרכן אחד. טעות בבחירה מתגלה לא בזמן הפריסה, אלא בזמן ההרחבה (scaling): רפליקה חדשה נתלית בהמתנה לכרך.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| צומת NFS עצמאי | נדרש שליטה על ייצוא, הרשאות וצילומי מצב (snapshots) | נקודת כשל יחידה: המכונה נפלה — כל הפודים עם הכרך המשותף הושבתו |
| שירות מנוהל של ספק, AWS EFS | מספר אזורי זמינות, לא רוצים להחזיק את האחסון בעצמכם | השהיה גבוהה יותר בפעולות קטנות, פחות הגדרות, תלות בספק |
| דיסקים מקומיים של הצמתים | בסיסי נתונים, מטמונים, רגישות למהירות קלט/פלט | רק ReadWriteOnce, הכרך מקושר לצומת ואינו עובר עם ה-Pod |
| כרך בלוקים עם דרייבר | רפליקה אחת לכתיבה, שחזור מהיר מצילומי מצב | אין גישה משותפת: Pod שני לא ימפה את אותו כרך |
צומת עצמאי נותן שליטה מלאה על ייצוא וצילומי מצב, אבל התפעול, העדכונים והגיבויים נשארים בצד שלכם. שירות מנוהל מסיר את העבודה הזו, אך מגביל את ההגדרות ומוסיף השהיה בהמון פעולות קבצים קטנות.
כלל מעשי: נתונים משותפים — על כרך משותף, בסיסי נתונים ומטמונים — על מקומי או בלוקים. את מצב הגישה בודקים לפני העלאה (rollout) — על צומת אחד ReadWriteOnce עובד, אבל על שלוש רפליקות אותו מניפסט כבר לא יעלה.
כיצד אנו מטמיעים אחסון קבצים: שלבי העבודה
סדר העבודה נבנה כך שכל צעד הבא נשען על נתונים מאומתים של הקודם. תחילה ביקורת (אאודיט) של האחסון: בודקים אילו כרכים כבר ממופּים באשכול, אילו בקשות לכרכים נותרו מאפליקציות שהוסרו, מי כותב לתיקייה המשותפת ובאילו מצבי גישה.
בשלב הזה בדרך כלל עולים שניים-שלושה צווארי בקבוק שקטים — ייצוא עמוס מדי ואפליקציה שמחזיקה את המיפוי במצב של המתנות קשיחות.
- ביקורת אחסון. ספירת מלאי של כרכים, פרופילי גישה, נקודות מיפוי והרשאות; מדידת השהיות קריאה וכתיבה תחת עומס אמיתי של האפליקציות.
- תכנון התרשים. בחירה בין ייצוא משותף לייצואים נפרדים לכל אפליקציה, חלוקה למחלקות אחסון, חישוב מרווח בקיבולת ובתפוקה (throughput).
- הכנת צד השרת. פריסת השירות, ייצואים עם הגבלה לפי תת-רשת, מצב כתיבה סינכרונית לתיקיות קריטיות, מכסות (quotas) למשתמש.
- חיבור לאשכול. דרייבר האחסון, מחלקות אחסון ומדיניויות שחרור כרכים, כדי שכרך שהוסר יחזיר מקום ולא יישאר תלוי.
- בדיקת עמידות בכשלים. מכבים צומת עם האפליקציה, מנתקים את הקשר עם האחסון, בודקים כיצד מתנהגים הפודים: מעבר לצומת אחר, מיפוי מחדש, מהירות החזרה לעבודה.
- ריצות עומס. בודקים באיזו כתיבה מקבילה מתחילים להיווצר תורים, ובוחרים פרמטרי מיפוי בהתאם לפרופיל האפליקציה.
- מסירה לתפעול. מדדים על השהיות ומילוי כרכים, התראות על תקלות, נהל הרחבה והוראה קצרה למשמרת התורנית.
ביקורת אורכת יותר זמן מההגדרה, ואנחנו לא מסתירים זאת: עריכת התרשים מחדש לאחר ההרצה עולה יותר מאשר יומיים של ספירת מלאי לפניה. בסופו של דבר נשארים לצוות סטנד עובד, קונפיגורציה ניתנת לשחזור וסדר פעולות מובן בעת כשל של צומת או של האחסון עצמו.
קונפיגורציות, תרשימים ונהלים: מה תקבלו
בסיום תקבלו לא אוסף של מניפסטים מפוזרים, אלא ערכה שלפיה פורסים את האחסון, בודקים ומעבירים למשמרת התורנית. הכול נמצא במאגר (repository) לצד האפליקציות: שינויים עוברים סקירה, ומצב מחלקות האחסון והכרכים משוחזר ללא עריכות ידניות בצמתים.
- קונפיגורציות כרכים — StorageClass ותיאורי PVC לפרופילי גישה שונים: ReadWriteMany לנתונים משותפים, ReadWriteOnce לבסיסי נתונים עמוסים; פרמטרי מיפוי, התיקייה בתוך הכרך ומגבלת הנפח.
- מדיניויות גישה לאחסון — כללי ייצוא בצד השרת, הפרדה לפי תת-רשתות ומרחבי שמות, הרשאות נפרדות לקריאה ולכתיבה, גישה אנונימית חסומה.
- תרשים הפריסה — איזה צומת NFS מספק אילו תיקיות, באיזה אזור הוא יושב, כיצד הכרך מגיע ל-Pod, היכן נקודות הכשל וכיצד הן מופרדות זו מזו.
- נהל גיבוי — מה מגובים, באיזו תדירות, כמה זמן נשמרים העותקים, מי מפעיל שחזור וכיצד בודקים שהעותק אכן נקרא.
- תיעוד התשתית — סדר חיבור כרך חדש, פעולות בעת אי-זמינות של שרת האחסון, מפת מדדים, צ'ק-ליסט לקליטת משמרת.
- צפייה (Observability) — מדדי השהיה של פעולות, נפח תפוס ושגיאות בלקוחות, ספי הפעלה ומסלולי התראות.
- תוכנית הגירה וחזרה לאחור — כיצד להעביר נתונים מהאחסון הנוכחי ללא השבתה וכיצד לחזור לתרשים הקודם אם התנהגות האפליקציה תסטה מהציפיות.
מקרה בוחן: העברנו את האחסון ללא השבתת השירות
לפני המעבר, כל מרחבי השמות באשכול Kubernetes עבדו עם נקודת מיפוי NFS אחת. הכרכים הונפקו ידנית: בקשה, עריכת רשימת הייצוא, המתנה למהנדס — מכמה שעות ועד יממה. כשל של השרת היחיד משמעותו כרכים תלויים: הפודים לא הופעלו מחדש, ותור הבקשות הצטבר עד שהתורן התמודד עם הקונפיגורציה.
מה שינינו
הרכבנו שני צמתי אחסון עם רפליקציה של ייצואים ומעבר אוטומטי, את מחלקות האחסון תיארנו באופן דקלרטיבי — הכרך נוצר וממופה ללא מעורבות אדם. הוספנו מכסות למרחב שמות, ניטור השהיות קלט/פלט ונהל החלפת צומת, שנבדק בכשל תרגיל.
המעבר התנהל במקביל: הקמנו את המעגל החדש לצד הישן והעברנו את הנתונים עם בדיקת סכומי ביקורת. השירותים הועברו אחד-אחד — נקודת המיפוי השתנתה במניפסט, ה-Pod נוצר מחדש, והכרך הקודם נשאר זמין לחזרה לאחור. את המעגל הישן החזקנו במילואים עד שהעומס השלים מחזור מלא על האחסון החדש.
התוצאה במספרים
הנפקת הכרך הצטמצמה מ-4-6 שעות של עבודה ידנית ל-30 שניות אוטומטיות. לא היו כשלי גישה לנתונים במשך חצי שנת תצפיות — לפני המעבר הצטברו 2-3 בחודש. הפעולות הידניות עם הייצואים הצטמצמו בכ-90%, נותרו הרחבת מכסות ועדכונים מתוכננים. זמן השחזור לאחר כשל צומת — מ-40 דקות של בירור ידני למעבר אוטומטי בדקה.
כרכים דינמיים או חיבור סטטי: מה לבחור
ב-Kubernetes אפשר להנפיק כרך בשתי דרכים, וההבדל אינו בנוחות אלא בנקודת השליטה. במצב דינמי הבקשה מפנה למחלקת אחסון, והרכיב האחראי על ההנפקה יוצר בעצמו כרך בגודל הנדרש, מקשר אותו לבקשה וממפה אותו ל-Pod.
בחיבור סטטי מכינים את הייצוא מראש, ולאשכול מגיע תיאור הכרך עם כתובת מדויקת ופרמטרים — האפליקציה מוצאת אותו לפי תווית (label).
הבחירה תלויה במי הבעלים של הנתונים וכמה זמן הם חיים.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| כרכים דינמיים | שירותים עם עומס משתנה, תורים, מטמונים, הרבה סביבות מאותו סוג | נדרשת מחלקת אחסון עובדת והרשאות ליצירת כרכים; את חיי הכרך מנהלת מדיניות ההחזרה |
| חיבור סטטי | ייצוא משותף לקבוצת שירותים, נתונים ששורדים יצירה מחדש של האשכול, מערכים חיצוניים | הגודל והכתובת קבועים; הרחבה ורוטציה — ידנית |
פרט מפתח — ניהול מחזור החיים של הכרך. מחלקת האחסון קובעת את מדיניות ההחזרה: בעת מחיקת הבקשה, הכרך או משתחרר יחד עם הנתונים, או נשמר ודורש חיבור ידני. עבור בסיסי נתונים ותורים זה מכריע אם המצב ישרוד יצירה מחדש של הסביבה.
בפועל שני המצבים מתקיימים זה לצד זה: דינמי — לשירותים חסרי מצב ולסביבות זמניות, סטטי — לנתונים שחייבים לשרוד את האשכול. אנחנו ממיינים אותם לפי מחלקות אחסון כך שאף כרך לא יישאר ללא בעלים.
שאלות טכניות לפני הטמעת האחסון
לפני ההטמעה נשארת לצוות ההנדסי רשימה קצרה של ספקות, שבהם תלויה גורל הפרויקט: תאימות כרכים, כתיבה בו-זמנית, הרשאות גישה והתנהגות בעת כשל. נפרט את אלה שעולים בתהליך התיאום בתדירות הגבוהה ביותר.
האם הכרכים תואמים למניפסטים הקיימים?
בודקים על הסטנד את אותו מניפסט שיילך לפרודקשן: ה-Pod מקבל תיקייה ורואה מערכת קבצים רגילה. ההבדל מכרך בלוקים אחד — אין פורמט, לכן ההעברה והחזרה לאחור נעשות בהעתקת תיקייה, ולא בשחזור תמונת מצב.
האם שני פודים שכותבים לאותה תיקייה הם בטוחים?
מצב ReadWriteMany מתיר מיפוי על מספר צמתים, אבל את העקביות מספקת האפליקציה, לא האחסון. עבור העלאות משותפות וארטיפקטים זה תקין; במקום שבו סדר הכתיבה חשוב, משאירים כותב אחד או מפזרים את הנתונים לתת-תיקיות עם נעילות.
כיצד מונפקות הרשאות הגישה?
דרך מזהי משתמש בתוך ה-Pod ומצב מיפוי הרשאות root בשרת. אם לא מגדירים אותם, הקונטיינר כותב מחשבון לא מיוחס ומקבל סירוב. מגדירים בעלים של התיקייה וקבוצה בתבנית פעם אחת — שירותים חדשים יורשים את התרשים העובד.
מה יקרה בעת כשל של צומת או של שרת האחסון?
בעת כשל צומת, הכרך מנותק ועובר יחד עם ה-Pod — חשוב שנקודת המיפוי תהיה זמינה מהצומת החדש. בעת אי-זמינות של השרת עצמו, הפודים עוברים להמתנה לקלט/פלט ואינם נמחקים כשורה, לכן את פסקי הזמן (timeouts) ואת מצב ההחזרה מגדירים מראש.
נדון במשימה: מה להכין לשיחה
תוכנית העבודה מורכבת מכמה נתוני פתיחה: כיצד בנויה הגישה לכרכים כעת, מהו פרופיל העומס ואילו מגבלות יש בסביבה. ככל שהתשובות קונקרטיות יותר, כך פחות סבבי בירור — ההערכה והתוכנית מתקבלות במעבר אחד, ולא אחרי שלוש שיחות. אין צורך להכין מפרט טכני מלא מראש.
מה כדאי להחזיק בהישג יד:
- גרסת אשכול Kubernetes, אופן ההתקנה, מספר הצמתים ואזורי הזמינות;
- תרשים האחסון הנוכחי — שרת NFS, אחסון קבצים בענן או כרכים מקומיים — ומה בו אינו מספק;
- פרופיל העומס: גדלי הכרכים, מספר הקוראים והכותבים בו-זמנית, האם נדרש כרך משותף למספר פודים;
- רגישות להשהיה ולתפוקה בתרחישים מסוימים;
- דרישות לעמידות בכשלים: מה צריך לשרוד נפילה של צומת או אזור, זמן ההשבתה המותר ונפח האובדן;
- מגבלות הסביבה: מדיניויות רשת, תת-רשתות פרטיות, דרישות ביקורת, הרשאות גישה לתשתית;
- לוחות זמנים וחלון שבו מותר לשנות את הפרודקשן.
עם הסט הזה אנחנו חוזרים עם הערכה ותוכנית עבודה: שלבים, סיכונים וסדר חיבור האחסון ללא עצירת השירותים. אם חלק מהנתונים עדיין אינו ידוע — נבין זאת בשיחה ונשלים תוך כדי.







