Multi-Stage Dockerfile: איפה האימג' מתנפח ומאט את הפריסה
בנייה טיפוסית מתבצעת בשלב אחד: אימג' בסיס, התקנת קומפיילר, מנהל חבילות, קובצי כותרת וכלי עזר עבור מודולים נייטיביים, העתקת קוד המקור וקאש התלויות. כל זה נשאר בשכבה הסופית, גם אם בסוף ה-Dockerfile יש ניקוי.
מחיקת קובץ בשכבה הבאה לא מקטינה את גודל האימג': השכבה בלתי משתנה, ו-rm רק מוסיף תווית מעל, בעוד שהבתים עצמם ממשיכים לשבת בשכבת האב.
מכאן שתי מסקנות. גודל האימג' גדל פי כמה ביחס לרנטיים: קומפיילרים, סמלי דיבאג, ספריות בדיקה וכלי שירות — אלו תלויות מיותרות ברנטיים, שלא משתתפות בעבודת האפליקציה, אבל נמשכות לכל נודה.
זמן הבנייה גדל מסיבה אחרת: שינוי בקוד מעל התקנת התלויות מאפס את הקאש, והחבילות מותקנות מחדש בכל ריצה.
הפריסה מתעכבת לא בגלל הקוד, אלא בגלל התעבורה: כל נודה מושכת את האימג' בשלמותו, הקונטיינר לוקח יותר זמן להגיע למוכנות, והסקייל האוטומטי תחת עומס מפגר. בנוסף, משטח התקיפה מתרחב — קומפיילר, דיבאגר ומנהל חבילות ברנטיים נותנים סט כלים מוכן למי שהשיג גישה פנימה לקונטיינר.
Multi-stage dockerfile מפריד בין התפקידים האלה: שלב אחד בונה את הארטיפקט, השני מקבל רק אותו ואת סביבת ההרצה המינימלית. לשכבה הסופית לא נכנסים לא כלי הבנייה ולא הקאש — נפרק איך זה בנוי ומה זה נותן בפועל.
מה נותנת הפרדת הבנייה לשלבים: זמן, שכבות, רולבקים
הפרדת הבנייה לשלבים (multi-stage Dockerfile) מוציאה מהאימג' הסופי כל מה שנדרש רק לקומפילציה: קומפיילרים, קובצי כותרת, מנהלי חבילות וארטיפקטים ביניים. נשאר רק מה שנדרש להרצה: האפליקציה הבנויה והספריות שלה בזמן ריצה. בפועל, 60–90% מהתוכן יוצא מהאימג' — אלו עשרות חבילות שלא אמורות להיות בקונטיינר העבודה.
פחות חבילות — פחות משטח תקיפה: תלויות בנייה מפסיקות להיכנס לקונטיינר העבודה יחד עם הפגיעויות שלהן. פחות גודל — פריסה מהירה יותר: הצומת מושך את האימג' מהרישום בשניות, לא בדקות, והפעלה מחדש של השירות נכנסת לחלון העדכון.
השחזוריות נשענת על שני דברים: אימג' הבסיס מוצמד לפי digest, וסדר ההוראות נבנה כך ששכבות שמשתנות לעתים רחוקות יבואו קודם — תלויות, ואחר כך קוד. Docker עושה שימוש חוזר בקאש השכבות כל עוד הקלט לא השתנה: עריכה של קובץ אחד בונה מחדש שכבה אחת, לא את כל האימג'. בנייה מחדש טיפוסית אחרי קומיט אורכת שניות לעומת דקות בבנייה בשלב אחד.
עבור רולבקים זה נותן חיזוי: האימג' הקודם נשאר ברישום עם התג הקודם, החזרה היא החלפת תג והפעלה מחדש, בלי בנייה מחדש. סוג הכשל "העלינו מצב קוד מקור שונה ממה שנבדק" נעלם: הארטיפקט המאומת הוא הארטיפקט בפרודקשן.
השוואת גישות: Dockerfile רגיל מול בנייה multi-stage
הבחירה בין multi-stage dockerfile לבנייה בשלב אחד היא לא "נכון ולא נכון", אלא מה בדיוק אתם מייעלים: מהירות איטרציה, גודל אימג' או שחזוריות. בנייה בשלב אחד היא מה שרוב הפרויקטים על Docker מתחילים ממנו, ולהלן רואים באיזה שלב היא מפסיקה להשתלם.
| אפשרות | מתי מתאימה | במה מוגבלת |
|---|---|---|
| בנייה בשלב אחד | שירות קטן, תלויות מחבילות מוכנות | לאימג' הסופי נכנסים קומפיילרים וקאש מנהל החבילות: הגודל גדל, ואיתו משטח התקיפה |
| Multi-stage Dockerfile | סטאק שמתקמפל, חשובים גודל אימג' ומהירות פריסה | צריך להעביר ארטיפקטים במפורש בין השלבים, ואת קאש התלויות להוציא לשכבה נפרדת |
| אימג' בנייה נפרד | כמה שירותים עם טולצ'יין משותף, מתאר מבודד | צריך לנהל גרסאות ולעדכן אימג'י הבנייה — זה תהליך תקופתי נפרד |
| בנייה בצד סוכן הבנייה | עריכות מקומיות, מחזור פיתוח מהיר | התוצאה תלויה בסביבת הסוכן, שחזוריות הבנייה נמוכה יותר |
האפקט המרכזי של multi-stage — עבודה עם קאש התלויות. השכבה עם התקנת החבילות מונחת לפני העתקת קוד המקור, ולכן עריכת קוד לא מאפסת אותה: בנייה חוזרת אורכת שניות, לא מתחילה מאפס. לאימג' הסופי נכנסים רק ארטיפקטים ורנטיים — כלי עזר וקוד מקור נשארים בשלב הביניים.
גם המגבלות אמיתיות. אם רנטיים הבסיס מושך ספריות מיותרות, multi-stage לא יציל את הגודל — צריך אימג' בסיס אחר. אימג' בנייה נפרד מוצדק כשהבניות רבות ולהן טולצ'יין משותף: אז עדכון הקומפיילר נעשה במקום אחד, ולא בכל קובץ.
סדר העבודות: מביקורת שכבות ועד מסירת נהלים
לפני שמשנים את הבנייה, מפרקים ממה מורכב האימג' הנוכחי: אילו שכבות נותנות את עיקר הנפח, מה מושך תלויות מיותרות, אילו קבצים נכנסים לארטיפקט בלי לשים לב. ביקורת אימג' מראה איפה multi-stage dockerfile ייתן רווח אמיתי, ואיפה צוואר הבקבוק נמצא בסדר ההוראות או בקאש.
- ביקורת אימג' — מסירים את הרכב השכבות, מוצאים את הכבדות ביותר, מסמנים חבילות וקבצים שלא נחוצים בהרצה: קאשים של מנהלי חבילות, קומפיילרים, נתוני בדיקה, ספריות שירות.
- סכמת שלבי בנייה — מפרידים בין בנייה להרצה, מחליטים מה עובר לשכבה הסופית: הארטיפקט הבנוי, קונפיגורציות, תעודות, ומה נשאר רק בשלב הקומפילציה.
- העברת הבנייה — כותבים מחדש את ההוראות לקובץ חדש, מקבעים אימג'י בסיס על הענף העדכני, מגדירים משתמש לא-פריבילגי ותיקיית עבודה.
- בדיקת שחזוריות — בונים פעמיים על קאש ריק ומשווים תוצאה: הבנייה לא צריכה להיות תלויה בקבצים מקומיים, בסדר הרשת ובמצב מכונת הבונה.
- השוואה לפני ואחרי — משווים גודל אימג', זמן בנייה, מספר שכבות וזמן עליית קונטיינר, מוודאים שהשירות עובר את בדיקות התקינות שלו.
- מסירת נוהל בנייה — מוסרים את הקובץ, סכמת השלבים, כללי עדכון אימג'י בסיס וסדר רולבק לתג הקודם.
מה הלקוח מקבל: קונפיגורציות, סכמות שכבות, נהלים
התוצאה של העבודה על multi-stage dockerfile היא לא קובץ אחד, אלא מארז ארטיפקטים שלפיו הצוות בונה ומעדכן אימג'ים בלי השתתפותנו. הכול עובר לריפו שלכם, נבדק בסביבת הבדיקה שלכם ומלווה בפירוק: מה שונה ולמה דווקא כך.
המארז סוגר את המחזור מהקונטקסט של הבנייה ועד רולבק: מתעדים אילו שכבות נשמרות בקאש ואיפה האימג' מתחיל לגדול.
- Dockerfile עם הפרדה מפורשת של שלבים — בניית תלויות, עיבוד ביניים, אימג' סופי; עם הערות מה נכנס לכל שכבה.
- סכמת שלבי בנייה: מה מועתק בין השלבים ואילו קבצים לא מגיעים לאימג' הסופי.
- קובץ החרגות וכללי הקונטקסט של הבנייה — המיותר לא נשלח לבנייה מההתחלה.
- נוהל עדכון אימג'י בסיס: תדירות, אחראי, בדיקת תאימות וסדר רולבק.
- הנחיה לצוות: איך להוסיף תלות בלי לנפח את השכבה הסופית, ואיך לבנות אימג' מקומית.
- סדר הבדיקות: השוואת גודל לפני ואחרי, פירוק הרכב השכבות, בדיקת הרצה ותקינות.
- קונבייר בנייה מוגדר עם קאשינג שכבות ופרסום תגים.
- רשומת פירוק עם הצוות: איפה צווארי הבקבוק, מה הושאר בכוונה ואיזה מרווח לצמיחה.
תארו את המשימה — נחזור עם הערכה ותוכנית ונראה אילו ארטיפקטים נחוצים למתאר שלכם, ומה כבר מכוסה בתהליכים שלכם.
קייס: איך עיבוד מחדש של האימג' קיצר את זמן הפריסה
לפני העיבוד מחדש השירות נבנה Dockerfile אחד: אימג' בסיס עם סט מלא של כלי בנייה, מעל — קוד מקור, קומפיילרים וקאש מנהלי חבילות. האימג' שקל בערך 1.2 ג'יגה-בייט, כל נודה בפריסה משכה אותו בשלמותו, ועריכת שורה אחת בתלויות איפסה את הקאש והפעילה בנייה מאפס.
מה שינו
פירקנו את התהליך לשלבים. הראשון — בנייה: מתקין תלויות, מקמפל, מריץ בדיקות. השני — רנטיים: בסיס דק, שאליו מועתק רק התוצאה דרך COPY --from. הוספנו .dockerignore, קבענו תג אימג' בסיס והרצה ממשתמש לא-פריבילגי — השלבים נבנים על הענף העדכני של Docker, וזה מספיק לקאש שכבות צפוי.
מה נהיה
הבנייה הצטמצמה מאחת-עשרה דקות לארבע: שכבת התלויות נשמרת בקאש בנפרד ולא נבנית מחדש בעריכת קוד. גודל האימג' ירד מ-1.2 ג'יגה-בייט ל-190 מגה-בייט — הנודה מושכת אותו בשניות, ולכן הפריסה הפסיקה להיות חלון המתנה, והרולבק לגרסה הקודמת הפך לפעולה רגילה.
היציבות עלתה לא בגלל המהירות, אלא בגלל הרכב האימג': לרנטיים כבר לא נכנסים קומפיילרים, קובצי כותרת וקאש חבילות, שבהם ישבו התלויות ה"צפות". שיעור הפריסות שהיה צריך להחזיר ידנית ירד ברבעון מ-18% לאפס, וגודל הרישום הפסיק לגדול לינארית עם כל ריליס.
מה לעשות אם אימג' הבסיס כבר בשימוש בפרודקשן
אפשר להעביר שירות ל-multi-stage dockerfile בלי לעצור את העבודה: הסכמה החדשה נבנית במקביל לקיימת, מאותו ריפו ואותו טריגר בנייה, רק התוצאה מקבלת תג נפרד. הפרודקשן ממשיך לחיות על האימג' הישן, עד שהחדש עובר בדיקה בסביבת הבדיקה והשוואת מדדים.
הצעד הראשון ב-multi-stage dockerfile — להוציא את התקנת התלויות והבנייה לשלב נפרד, ולהשאיר בסופי רק ארטיפקטים וספריות רנטיים. את אימג' הבסיס בשלב הזה לא משנים ומקבעים לפי digest, כדי שהתוצאה תתאים לפרודקשן: אז רולבק הוא החזרת התג הקודם.
הטמעה מדורגת עוברת דרך סביבת בדיקה ופריסת קנארי על חלק מהאינסטנסים. משווים לא רק גודל אימג', אלא גם זמן עלייה, צריכת זיכרון ועבודה עם תעודות — התג החדש הופך לענף הראשי רק כשהמדדים תואמים.
עדכון אימג' הבסיס — שלב נפרד ומאוחר יותר: החלפת שכבת רנטיים שוברת בדרך כלל לא את הקוד, אלא ספריות מערכת ותעודות שורש, ולכן האימג' מקבעים לפי digest, לא לפי תג צף.
הסיכונים המרכזיים במעבר — אי-התאמה של נתיבים והרשאות בין השלבים, אובדן קאש שכבות בגלל שינוי סדר ההוראות ופסולת בנייה שנכנסה קודם לאימג'. לכן מוסיפים לקונבייר בדיקה: הרצה תחת משתמש לא-פריבילגי והשוואת רשימת קבצים ופורטים עם הפרודקשן.
ספקות טכניים: קאש, תלויות, אימג'י בסיס
לפני ההתחלה מהנדסים שואלים אותן שאלות: מה קורה לקאש כשמשנים קוד, איך להעביר גישה לרישומים סגורים ובאיזו תדירות לבנות מחדש את האימג' כדי ששכבת הבסיס לא תתיישן. להלן איך זה בנוי בבניות שלנו.
איך סדר ההוראות משפיע על קאש השכבות?
הקאש עושה שימוש חוזר בשכבות עד להוראה הראשונה שהשתנתה. מניפסטים של תלויות מועתקים לפני קוד המקור — עריכה של מודול אחד לא מבטלת את התקנת החבילות, ובנייה חוזרת אורכת דקות, לא עשרות דקות. הפרדת שלבי הבנייה והרנטיים מוציאה קומפיילרים וקבצי ביניים מהאימג' הסופי.
איך להעביר תלויות פרטיות בלי להשאיר אותן באימג'?
אין להעביר פרטי גישה כארגומנטים של בנייה — הם נרשמים בהיסטוריית השכבות. גישה לרישום סגור נותנים על ידי mount של secret בזמן ההתקנה: קונפיגורציה זמנית חיה בתוך הצעד ולא נכנסת לאימג'. בודקים זאת בפירוק האימג' המוגמר — טוקנים ומפתחות לא נשארים בו.
באיזו תדירות לעדכן את אימג' הבסיס ולבנות מחדש?
את אימג' הבסיס מקבעים לפי digest: הבנייה משוחזרת, אבל פגיעויות לא נמשכות מעצמן. מעדכנים לפי תוכנית — בודקים מה השתנה בשכבת הבסיס, משנים את הקיבוע, מריצים בדיקות. בנייה מחדש מתבצעת בכל קומיט ובנוסף לפי לוח זמנים, ולכן עדכוני אבטחה מגיעים לפרודקשן בתוך יממה.
אם יש לכם רישום סגור מחוץ לפרימטר, כמה ארכיטקטורות חומרה או דרישות מחמירות לשחזוריות — את המגבלות האלה מפרקים בשלב התכנון, לפני שהבנייה יוצאת לפרודקשן.נדון במשימה: נפרק את האימג' שלכם ונחזור עם הערכה
כדי לחזור עם הערכת זמנים, ולא עם ניסוחים כלליים, אנחנו צריכים מינימום של נתוני מקור: Dockerfile נוכחי עם כל השלבים, קובץ תלויות, פקודת בנייה מהקונבייר ולוג של הריצה האחרונה עם גודל האימג' הסופי.
לפיהם רואים מיד אילו ארטיפקטי בנייה נשארים בשכבה הסופית, כמה שוקלים כלי הקומפילציה ואיפה הולך לאיבוד מקום.
לאחר מכן הפירוק מתבצע בשלבים: בכל שלב ברור מה אנחנו מוסרים ומה נדרש מצדכם.
- איסוף קונטקסט: Dockerfile, מניפסטים של תלויות, משתני סביבה, הוראת הרצה.
- בדיקת הבנייה הנוכחית: התפלגות הגודל לפי שכבות וזמן בנייה על קאש נקי.
- מגבלות: פלטפורמות יעד, דרישות לכלי דיבאג באימג' העבודה, כללי סריקת פגיעויות.
- תוכנית עבודה: אילו שלבים מפרידים, מה מעבירים לשלב הבנייה, מה משאירים בסופי.
- מה מוסרים: multi-stage dockerfile מעובד, סכמת שלבים והסבר לכל שינוי.
- הערכת זמנים: פירוט לפי שלבים, נקודות חזרה לאישור וסדר בדיקת התוצאה.
תארו את המשימה וצרפו את מה שכבר יש ביד — נחזור עם הערכה ותוכנית עבודה. אם הנתונים עדיין מעטים, נסביר מה לאסוף קודם כדי שהפירוק יתבצע לפי עובדות, לא לפי הנחות.







