קונטיינרים Rootless ב-Podman: מה נשבר בסכימה המוכרת
הסכימה הקלאסית להרצת קונטיינרים מניחה שדמון הניהול פועל מהרשאת superuser, והתהליך בפנים מקבל הרשאות root כברירת מחדל. כל עוד הכול יציב, זה בלתי מורגש: השירות חי בבידוד שלו, הכרכים מותקנים, הפורטים מפורסמים.
אבל די בכך שתימצא פגיעות או שגיאה בהרשאות גישה — והתוקף מוצא עצמו לא בארגז החול, אלא על הצומת עצמו, עם מבט אל הקונטיינרים השכנים, סוקט הניהול ומערכת הקבצים של המארח.
המחיר של ארכיטקטורה כזו הוא נקודת כשל יחידה. קונטיינר אחד שנפרץ שקול לפריצה של כל המכונה: ממנו נחשפים סודות של שירותים שכנים, נקראים כרכים משותפים, נערכות קונפיגורציות. הבידוד של השירותים מפסיק להיות גבול אבטחה ונשאר רק אריזה נוחה.
Podman מוציא מהמשוואה את הדמון המיוחס: במצב rootless הקונטיינרים רצים במרחב שמות נפרד של משתמש, שבו root בפנים מתאים למזהה בלתי מיוחס בחוץ. גם בפריצה מוצלחת, האפליקציה מקבלת הרשאות של משתמש רגיל — בלי כתיבה לספריות מערכת ובלי גישה לתשתית קונטיינרים זרה.
המעבר משנה הרגלים: כרכים ופורטים מתחת ל-1024 מוגדרים אחרת, חלק מהאימג'ים דורשים מיפוי מפורש של מזהים, והניהול ללא דמון מערכתי נשען על מנגנונים אחרים. אנו מפרקים כיצד זה נסגר ומה בדיוק צריך לבנות מחדש בקונטור הקיים.
מה נותן המעבר של שירותים לקונטיינרים ללא הרשאות root
המעבר של שירותים לקונטיינרים שרצים מהרשאת משתמש בלתי מיוחס משנה לא את הנוחות, אלא את סוג הכשל ואת רדיוס הפגיעה. תהליך ללא הרשאות root לא יתקין ספריות של הצומת ולא יכתוב לנתיבי מערכת — פריצת הקונטיינר נשארת בתוך הסביבה שלו.
משטח התקיפה מצטמצם לא באמצעות הגדרות בודדות, אלא במודל ההרצה עצמו: לשירות אין הרשאות על מה שבדרך כלל מנוצל.
מכאן — בידוד הכשל. שגיאה בשירות אחד אינה משמעה עוד נפילה של הצומת כולו: לכל קונטיינר יש מגבלות משלו על זיכרון ומעבד, ומיצוי משאבים על ידי תהליך אחד לא גורר איתו את האחרים.
סוג הכשל "הצומת נפל — כל השירותים קרסו" יוצא מהפרקטיקה, ואיתו גם החלק היקר ביותר של השבתת השירות — הזמן להקמת המארח כולו.
ההפעלה האוטומטית של קונטיינרים מתוארת מראש: השירותים עולים לאחר אתחול בסדר מוגדר, עם מדיניות הפעלה מחדש ותלויות ביניהם. זמן שחזור השירות נמדד בעשרות שניות במקום פירוק ידני של "מה ובאיזה סדר להפעיל" — המהנדס אינו עוסק בשגרה, אלא רק בודק את התוצאה.
וסביבת פיתוח ופרודקשן אחודה: מקומית ובשרת משתמשים באימג' אחד ובפקודת הרצה אחת, ורק פרמטרי הסביבה ונקודות ההתקנה שונים. זה מסיר פערים של "אצלי זה עובד, בשרת לא", ו-Podman במכונת העבודה מתנהג בדיוק כמו בצומת הפרודקשן, ולכן הבדיקה המוקדמת מפסיקה להיות שלב נפרד.
Podman וחלופות: השוואה של מצבי הרצה
מצב ההרצה קובע מה יקרה בעת פריצת הקונטיינר וכמה צפוי שהשירות ישרוד אתחול של הצומת. מנוע מיוחס נוח: הקונטיינר רואה את התקני המארח, תופס פורט מתחת ל-1024 ומתקין כל ספרייה. המחיר — דמון משותף ותהליך עם הרשאות root, שבו שגיאה באימג' הופכת לגישה מלאה לצומת.
קונטיינר בשם משתמש שירות מסיר את המגבלה הזו: הליבה מחזיקה את התהליך במסגרת הרשאותיו של אותו משתמש, וחריגה מהן דורשת שגיאת קונפיגורציה נפרדת. התאימות לפקודות מוכרות נשמרת — Podman מחקה את תחביר ההרצה, ולכן סקריפטים של פריסה מועברים בהחלפת הקריאה, ולא בכתיבה מחדש.
מגבלות המצב נראות בפועל: פורטים מיוחסים דורשים העברה, שכבות נכתבות במרחב המשתמש, ונדרש טווח מוקצה של subuid/subgid.
| אפשרות | מתי מתאים | מגבלות |
|---|---|---|
| מנוע מיוחס על הצומת | גישה להתקנים, לכרכים של המארח, לפורטים מתחת ל-1024; אימג'ים בירושה | דמון משותף — נקודת כשל יחידה; קונטיינר עם root בפנים שווה ל-root על הצומת |
| קונטיינר בשם משתמש שירות | שירותי ווב, תורים, בוני תוכנה, משימות רקע ללא הרשאות מיוחדות | אין פורטים מתחת ל-1024 ללא העברה; כתיבה לשכבות איטית יותר; חלק מיכולות הליבה אינן זמינות |
| שירותים מקוריים מחוץ לקונטיינרים | תהליך בודד, עדכון באמצעות מנהל חבילות, אין צורך בבידוד | תלות בספריות המארח; אין ניידות; חזרה לגרסה קודמת מורכבת יותר |
סכימה מעורבת עובדת: rootless כברירת מחדל בכל הצמתים, ומכונות נפרדות עם מנוע מיוחס — לעומס מוגדר. העיקר — לתעד אילו הרשאות באמת נדרשות, ולא להעניק יותר.
סדר המעבר של שירותים: מאודיט הצומת ועד למסירה
אנו מעבירים שירותים לקונטיינרים rootless (Podman) בשלבים: כל שלב נשען על תוצאת קודמו. השגיאה נראית בשלב שבו הופיעה, ולא ברגע כשל של שירות פרודקשן.
- אודיט הצומת. אוספים מלאי: אילו שירותים רצים, בשם מי, אילו פורטים וספריות הם תופסים, מה עולה מ-systemd וממתזמן המשימות. מתעדים תלויות בין השירותים — בלעדיהן סכימת המיקום תצא שרירותית.
- סכימת מיקום השירותים. מפזרים את השירותים לפי משתמשים: מה עובר לקונטור rootless, מה נשאר מערכתי, היכן נדרשים כרכים משותפים והעברת פורטים. את הסכימה מתאמים איתכם לפני שינויים בצומת.
- הכנת הסביבה. מפעילים מרחבי שמות של משתמש, מגדירים מגבלות משאבים וכללים לסשנים ארוכים. בודקים שהקונטיינרים ממשיכים לחיות לאחר יציאת המנהל מהמערכת — אחרת הם נופלים יחד עם הסשן.
- הגדרת בידוד והפעלה אוטומטית. לכל שירות — משתמש משלו, כרכים ופורטים משלו, והרשאות לקבצים רק היכן שנדרש. את ההפעלה האוטומטית מעצבים כתיאורי שירות עם סדר עלייה מפורש, המשקף את הקשרים האמיתיים בין השירותים.
- בדיקת עמידות בכשלים. מאתחלים את הצומת, מנתקים את הרשת, מכבים שירותים אחד-אחד, ממלאים את הדיסק. רואים מה עולה מעצמו, מה מאבד נתונים, ומה דורש התערבות ידנית.
- מסירה לתפעול. מוסרים קונפיגורציה, סכימת מיקום, הוראות לפעולות שגרתיות ולחזרה לאחור. מריצים את הצוות המלווה בתרחישים, כדי שתפעול הצומת לא יהיה תלוי במבצע ההטמעה.
חבילת המסירה: קונפיגורציות, סכימות, נהלי גישה
המסירה בנויה כך שצוות הלקוח נשאר עם קונטור עובד, ולא עם הסכמות בעל פה. מצב rootless של Podman מוסיף לכך ניואנסים משלו: הרשאות משתמש, מיפוי מזהים, מרחבי שמות נפרדים — כל מה שקל לשבור בשינוי הבא, אם אינו מתועד. לכן אנו מוסרים ערכת ארטיפקטים במאגר שעובר ביקורת קוד ומתעדכן יחד עם השירות.
- קובצי קונפיגורציה. יחידות להרצת קונטיינרים כשירותי משתמש, הגדרות של registry ואחסון אימג'ים, מגבלות משאבים, פרמטרי רוטציה של לוגים — עם הערות מה ולמה משתנה עם גידול העומס.
- סכימות פריסה. מיקום השירותים לפי צמתים וחשבונות משתמש, אילו כרכים ומרחבי רשת משותפים, והיכן עובר הגבול בין סביבות הפיתוח והתפעול.
- מדיניות גישה. אילו עובדים ואילו קבוצות מנהלים את השירותים, מניין מותר לבנות אימג'ים, כללי אימות חתימה, איסור על קונטיינרים מיוחסים ועל התקנת ספריות מערכת.
- נהל עדכון. סדר ההעלאה של גרסה חדשה, בדיקות לפני העברת התעבורה, התנהגות כשכרכים תפוסים, נקודות תיאום וקריטריונים שלפיהם מהדורה נחשבת מוצלחת.
- תיעוד וחזרה לאחור. הוראות צעד-אחר-צעד לפעולות שגרתיות, סכימת תלויות השירותים, סדר החזרה לגרסה קודמת עם בדיקה שהשירות עלה ומגיב לבקשות.
- אבחון והעברת ידע. מערכת פקודות לניתוח תקלות, הסבר קונפיגורציות בסביבה חיה ומענה לשאלות מהנדסי הלקוח — כדי שהתחזוקה לא תהיה תלויה בנו.
מקרה בוחן: מעבר שירותים לקונטיינרים ללא הרשאות
שירותים ב-42 צמתים רצו מהרשאת superuser וחלקו גישה לסוקט של מנוע הקונטיינרים. כל רכיב שנפרץ קיבל הרשאות על המארח, ונפילה של שירות אחד נרפאה לא אחת בהפעלה מחדש של כל המכונה.
שחזור לאחר כשל כזה נמשך 35–40 דקות, עדכונים הועברו לחלון לילה בן 3 שעות, וחלק מההעלאות הוחזרו ידנית. סוג הכשל "הפעלה מחדש של כל המכונה" התרחש בממוצע 9 פעמים ברבעון.
מה שינו
השירותים הועברו למצב rootless של Podman: כל אחד פועל תחת משתמש מערכתי נפרד בלי גישה למנוע המיוחס. הפעלה אוטומטית — יחידות systemd של משתמש עם lingering, ולכן השירות עולה לאחר אתחול הצומת ללא כניסה ידנית.
הכרכים חוברו כ-bind-mount עם תוויות SELinux, פרסום הפורטים הוגבל לרשימת הנדרשים, והאימג'ים — ממאגר פנימי עם אימות חתימה. מדיניות המגבלות והתיעוד נרשמו ברמת השירות, ולא ברמת הצומת.
מה היה לתוצאה
| מדד | לפני | אחרי |
|---|---|---|
| שיעור הצמתים ללא הרשאות superuser | 0 מתוך 42 | 38 מתוך 42 |
| שחזור השירות לאחר כשל | 35–40 דק' | 4–6 דק' |
| כשלים מסוג "הפעלה מחדש של כל המכונה" | 9 ברבעון | 0 |
| חלון עדכון | 3 שעות בלילה | 25 דק' ביום |
"בעבר שירות אחד שנפל גרר אחריו את כל הצומת, כעת מופעל מחדש רק עץ התהליכים שלו", — מהנדס תחזוקה. משתתף שני בקצרה: "חלון העדכון הפסיק להיות לילי — אנחנו מעלים ביום ולא מעירים את התורן".
ארבעה צמתים הושארו עם הרשאות: השירותים שם זקוקים לגישה ישירה לחומרה, וזה מתועד במדיניות נפרדת. השאר סגור — פריצה של תהליך אינה מקנה עוד הרשאות על המארח, וזמן השחזור הצטמצם כמעט בסדר גודל.
מה לעשות אם Podman כבר נפרס באופן חלקי
כל עוד Podman מכסה חלק מהשירותים והשאר עובדים לפי הסכימה הקודמת, זהו מצב עובד, ולא מצב חירום. הסכנה אינה בדו-קיום של שני מודלי הרצה, אלא בתמיכה כפולה: שני סטים של כללי גישה, שתי דרכים לצפות בלוגים, שתי דרכים לעדכן אימג'ים. המשימה — לא להחזיר לאחור את מה שנעשה, אלא לקבוע סדר שבו עובר היתר.
הצעד הראשון — מלאי: מה כבר במצב rootless, מה נותר, וממה תלוי כל שירות — כרכים, פורטים, חשבונות, שמות שכנים ברשת. רוב הסיפורים של "נשבר אחרי המעבר" מוסברים דווקא בקשרים: שירות שכן אינו נפתר לפי שם בסכימה החדשה, או שספרייה הופכת לבלתי נגישה מבחינת הרשאות. את זה מתעדים לפני כל העברה.
מכאן — תור עדיפויות. ראשונים עוברים שירותים ללא מצב משלהם: פרוקסי חיצוני, מטמון, מטפלי תורים — הם עולים בשניות, ושגיאה עולה בדקות. שירותים עם נתונים באים אחרונים: הם זקוקים לחלון מתוכנן, לעותק מתואם ולבדיקת שלמות לאחר מכן.
כל עוד שתי הסכימות חיות, את הכניסה והרשת מוציאים לרמה שאינה תלויה בדרך ההרצה. השמות ונקודות הכניסה נשארים כשהיו, והשירותים אינם יודעים היכן מתגורר השכן — המעבר נמתח בלי עצירת העבודה.
הוא אינו מסתיים כשהכול רץ במצב rootless, אלא כשנשאר לכל שירות דרך הרצה אחת, דרך אחת לאספקת לוגים ודרך אחת לעדכון. התמיכה הכפולה מסתיימת יחד עם הסט הכפול האחרון של כללים.
בידוד, הפעלה אוטומטית ותאימות: ספקות טכניים
כל ההתנגדויות ל-Podman מצטמצמות לארבעה-חמישה תרחישים: כיצד לספק תעבורה לפורטים נמוכים, מה עם הרשאות לנתונים, האם אימג'ים מוכנים יעלו, האם הקונטיינרים ישרדו אתחול של הצומת והאם אפשר להעביר שירותים אחד-אחד. מפרקים כל אחד לגופו — בציון היכן נדרשת הגדרה והיכן המגבלה באמת קיימת.
מה לעשות עם פורטים ברשת מיוחסים?
קונטיינר rootless אינו יכול לתפוס בעצמו פורט מתחת ל-1024. ישנן שתי סכימות עובדות: לאפשר לתהליכים בלתי מיוחסים פורטים נמוכים על הצומת, או להשאיר בחוץ פרוקסי הפוך או מאזן עומסים שמקבל תעבורה על 80/443 ומעביר פנימה לפורט גבוה.
האפשרות השנייה עדיפה: הקונטיינר אינו מקבל הרשאות מיותרות, ונקודת כניסת התעבורה נשארת אחת ומנוהלת על ידי שירות נפרד.
כיצד נפתרת הגישה למערכת הקבצים ולמשאבי רשת?
ספריות הנתונים מותקנות עם בעלים מפורש — המשתמש שתחתיו פועל הקונטיינר, אחרת התהליך בפנים לא יוכל לכתוב. בצמתים עם SELinux הכרכים מתויגים בתוויות הקשר בעת ההתקנה, אחרת הגישה נחסמת ברמת בקרת הכפייה.
רשתות פנימיות נוצרות כרשתות משתמש, השירותים רואים זה את זה לפי שמות, ולחוץ מפורסמים רק הפורטים הנדרשים.
האם האימג'ים הקיימים תואמים?
Podman עובד עם אימג'י OCI, ולכן אימג'ים שנבנו קודם עולים בלי בנייה מחדש — משתנה פקודת ההרצה ודרך תיאור השירותים. התיקונים העיקריים נוגעים לתיאורים: נתיבי התקנה, שמות רשתות, healthcheck וסדר עליית התלויות. פערים אלה מתגלים בשלב ההעברה בסביבת בדיקות, ולא על צומת הפרודקשן.
האם הקונטיינרים יעלו לאחר אתחול הצומת?
מעצמם — לא: תהליכי rootless אינם מורמים על ידי מנהל האתחול המערכתי. הדבר נפתר ביחידות משתמש וב-lingering מופעל, ואז השירותים עולים בעת האתחול, מופעלים מחדש לאחר נפילה ונעצרים כראוי בסדר מוגדר. זה גם נותן תמונה ברורה של תלויות ולוגים.
האם אפשר להעביר חלק מהשירותים ולהשאיר חלק כפי שהם?
כן, וזו הדרך הבטוחה ביותר. צמתים עם סביבות שונות מתקיימים במקביל כל עוד אינם מתנגשים על פורטים וכרכים; פרוקסי חיצוני מנתב את התעבורה לפי שירותים. ההעברה מתבצעת בקבוצות, עם חזרה לאחור של כל קבוצה בנפרד — כשל בשירות אחד אינו פוגע בשאר.
נדון במשימה: אילו נתונים נדרשים להערכה
הערכה מקצועית נבנית מעובדות על התשתית, ולא ממשאלות כלליות. ככל שתתארו ביתר דיוק את המצב הנוכחי ואת המגבלות, כך יהיו פחות הנחות בתוכנית העבודה והלוחות זמנים יהיו ריאליסטיים יותר. להלן — המינימום שלרוב מספיק לנו כדי לחזור עם הערכה ותוכנית.
- רשימת צמתים ושירותים. כמה מארחים ומה תפקידיהם, מה כבר בקונטיינרים ומה יישאר על המארח: דמוני מערכת, מתזמן משימות, בסיסי נתונים מקומיים, שירותי קבצים.
- דרך ההרצה הנוכחית. קובצי Compose, יחידות systemd, סקריפטי פריסה, מאגר אימג'ים עצמאי — מהם רואים מה מועבר כמו שהוא ומה דורש אריזה מחדש.
- דרישות לבידוד. האם נדרשים משתמשים נפרדים וסגמנטים ברשת בין שירותים, גישה לפורטים מיוחסים, כרכים משותפים — מזה תלויה סכימת ההרצה ללא root ב-Podman וסדר חלוקת ההרשאות.
- חלונות עדכון. מתי מותר להפעיל מחדש שירותים, איזו השבתה נחשבת מקובלת, האם יש תקופות אי-זמינות נוקשות ובאיזה סדר אפשר להעלות צמתים.
- גישה לנתונים. היכן נמצאים הכרכים והקונפיגים: דיסקים מקומיים או אחסון ברשת, מה מותקן, כיצד בנויים הגיבויים, והאם יש נתונים שאסור להוציא מחוץ לגבולות הצומת.
- מדדי יעד. מה נחשב לתוצאה: זמן עליית הצומת מאפס, זמן השחזור לאחר כשל, שיעור השירותים ללא root, שיעור הבדיקות האוטומטיות.
תארו את המשימה בצורה חופשית — זה מספיק כדי לחזור עם הערכה ותוכנית עבודה.







