קונטיינרים – שלושה מרכיבים מרכזיים

קונטיינרים – שלושה מרכיבים מרכזיים
זהו הפוסט השלישי בסדרה. קדמו לו פוסט מבוא עם רקע כללי לקונטיינרים ופוסט שני שעסק התפתחות הטכנולוגיה של הקונטיינרים. בפוסט הזה ארחיב מעט על הרכיבים המרכזיים של הטכנולוגיה ועל המאפיינים שלהם. אפשר לפרוט את הטכנולוגיה של קונטיינרים לשלושה מרכיבים מרכזיים: אימג'ים (Images), רכיב Runtime והקונטיינר עצמו.

Container Image & Registries

האימג' של קונטיינר כולל, באופן טבעי, את הקבצים שהקונטיינר צריך בשביל לרוץ. לגודל האימג' יש השלכות על מספר אספקטים שונים ולכן באופן כללי השאיפה היא לצמצם אותו ככל האפשר ולכלול בו את הקבצים ההכרחיים להרצת היישום בלבד.

ברמה הבסיסית, קובץ אימג' של קונטיינר צריך ליישם פונקציונאליות מקבילה לזו של קובץ ארכיון (כמו tar, לדוגמה), כלומר לאפשר אריזה של מבנה התיקיות עם הקבצים הנדרשים בקובץ אחד שיאפשר להעביר את הארכיון בקלות למערכת, לפרוש אותו ולהריץ את היישום. בפועל, דוקר פיתחו פורמט חכם לשמירת אימג'ים שחוסך במקום ומאפשר שימוש חוזר במידע באמצעות שימוש בשכבות.

בפורמט של דוקר (או, כמו שנכון יותר לומר כיום, בפורמט OCI Image) לכל אימג' יש הגדרת אימג' בסיס (מוגדרת כ-FROM), מקור שאליו האימג' מתווסף בתור סט של שינויים. נניח שאנחנו יוצרים שני אימג'ים שכוללים סט שינויים זהה בעוד שאימג' המקור שונה. באחד מוגדר כמקור אימג' ריק ובשני האימג' הרשמי של הפצת Ubuntu. האימג' שאנחנו יוצרים מוסיף למקור שכבה, סט של שינויים, שלצורך הדוגמה נניח שהיא כוללת קובץ טקסט יחיד שממוקם בתיקיית /home/user. התוצאה של שני האימג'ים שיווצרו תהיה שונה, כמובן. האימג' שמבוסס על מקור ריק יכלול רק את תיקיית /home/user ובתוכה קובץ הטקסט. באימג' השני, שמבוסס על האימג' של ubuntu, האימג' שנוצר יכיל את מערכת הקבצים של הפצת ubuntu עם התוספת של קובץ הטקסט בתוך תיקיית user שנוספה לתיקיית /home.  מכיוון שהשכבה של האימג' עצמו זהה בשני המקרים – היא מכילה את הקובץ היחיד שהוספנו, הגודל שלה יהיה זהה.



השיטה הזו מאפשרת חסכון בזמני הורדה והעתקה של אימג'ים ובמקום הנדרש לאחסונם. לדוגמה, הרצה של ארבעה קונטיינרים עם אימג'ים שונים שמבוססים על ubuntu דורשת עותק אחד של האימג' של ubnutu, כאשר בכל אימג' מהארבעה יתווספו לשכבה הזו השינויים הרלוונטיים בלבד. ה-runtime מזהה באמצעות חתימת ה-hash את השכבות של האימג' וידע להוריד את השכבות הנדרשות ולאחסן אותן פעם אחת בלבד.

לשימוש באימג'ים מבוססי שכבות יש יתרונות מעבר לנפחי אחסון ותעבורה. השימוש באימג'ים מוכרים חוסך זמן ומאמץ בהתקנה ותחזוקה של אימג'ים. ההסתמכות על אימג' של ubuntu, לדוגמה, חוסך את הצורך ביצירת אימג' עם מערכת הפעלה ולתחזק אותו כאשר רוצים להכין אימג' לקונטיינר עבור יישום זה או אחר – האימג' מגיע מוכן מ-ubuntu עם מה שההפצה מגדירה כמכלול המינימלי של קבצי מערכת ההפעלה הנדרשים לשימוש, ומתעדכן באופן שוטף. אם אני מגדיר כאימג' מקור את ubuntu בגרסת latest, ה-runtime יעדכן את אימג' המקור כך שהקונטיינר שאני מריץ יתבסס על גרסה עדכנית מבלי שאני אצטרך לדאוג לתחזק את מערכת ההפעלה של האימג' ולעדכן אותה. אבל מערכת הפעלה זה רק התחלה למה שניתן לצרוך כיחידת בסיס ארוזה באימג' ומוכנה לשימוש.

על מנת להפיץ ולצרוך אימג'ים בקלות פיתחו בדוקר את הרג'יסטרי (Container image registry), אחד הפיצ'רים המשמעותיים ביותר להצלחה ולפופולאריות של דוקר מתחילת הדרך. הרג'יסטרי הוא שירות שמאפשר לשמור אימג'ים, לנהל את הגרסאות שלהם, להפיץ ולצרוך אותם. דוקר אימצו עבור הרג'יסרטי טרמינולוגיה דומה לזו של git – מסמנים אימג' בשם וגרסה באמצעות Tagging, מעלים אותו לרג'יסטרי עם Push, מורידים אותו באמצעות Pull וכן הלאה. דוקר הקימו שירות רג'יסטרי ציבורי בשם Docker Hub שבדומה ל-GitHub מאפשר לכל מי שרוצה לפתוח חשבון חינמי ולאחסן ולהפיץ קונטיינרים פומביים. אפשר למצוא ב-Docker Hub אימג'ים של רוב פרוייקטי הקוד הפתוח המשמעותיים, חברות רבות מחזיקים אימג'ים רשמיים ב-Official Repositories ומעבר לכך, המוני משתמשים עושים שימוש בשירות לפרוייקטים אישיים ולשיתוף פתרונות שונים.

הבשורה שמביאות תכונות אלו של הטכנולוגיה, רג'יסטרי ציבורי שמכיל אימג'ים בפורמט שניתן להרכיב עליהן שכבות, היא מהפכנית. מפתח שרצה להפיץ תוכנה שהוא כתב בפייתון באופן מסורתי היה צריך לעטוף אותה בקובץ התקנה שיוסיף למערכת את כל התלויות וימקם את כל הקבצים שלו במיקומים הנכונים. לעומת זאת הפצה בקונטיינרים אפילו לא מצריכה מהמפתח אפילו לבנות קונטיינר עם ה-runtime של פייתון. כל מה שהוא צריך זה בשורה אחת להגדיר כאימג' מקור את האימג' הרשמי של ולהוסיף את החלק המשמעותי– היישום שהוא מפתח.

Container Runtime

רג'יסטרי ואימג'ים זה יפה מאוד, אבל בלי תוכנה שתאפשר להוריד ולהריץ אותם אין בהם הרבה תועלת, וכאן נכנס לתמונה ה-Runtime. התפקיד הבסיסי של ה-Runtime הוא לקחת אימג' ולהריץ אותו בתור קונטיינר. כדי לעשות את זה ה-Runtime צריך לעבוד מול הרג'יסטרי ומול מערכת ההפעלה. מהרג'יסטרי הוא מוריד את האימג' ומול מערכת ההפעלה הוא יוצר את הקונטיינר, עם המשאבים הנדרשים להרצה.

כמו שציינתי בפוסט הראשון בסדרה, מבחינת מערכת ההפעלה  לא קיים אובייקט מסוג קונטיינר, כשאנחנו מדברים על קונטיינר למעשה אנחנו מדברים על אבסטרקציה של תהליך שרץ תחת מגבלות מסוימות ובמסגרת משאבים נפרדת. את ההגבלות האלו עושה ה-Runtime, שמהבחינה הזו הוא למעשה כלי עם העדפות קשיחות, פחות או יותר, בנוגע לאילו מההגבלות שהקרנל מספק ליישם וכיצד (לפחות כל עוד אנחנו מדברים על לינוקס).

Container

לאחר שה-Runtime הוריד את האימג', עשה לו mount במערכת הקבצים והריץ את הקובץ הבינארי לפי ההגדרות המפורטות באימג', התוצר הוא קונטיינר – תהליך שרץ באותה מערכת הפעלה בה רץ ה-Runtime, שמופעל באמצעות אותו קרנל, ועם זאת, למרות שהוא לא מבודד במכונה וירטואלית, הוא מבודד מיתר התהליכים במערכת ולא יכול לראות אותם לגשת אליהם או להשפיע עליהם באופן ישיר. כמעט כמו שהיינו מריצים אותו במערכת אחרת.

כאשר קונטיינר נוצר, ה-Runtime מוסיף מעל לשכבה העליונה ביותר של האימג' שכבה נוספת שמיועדת לכתיבה. השכבות שמתחת לשכבה זו הינם לקריאה בלבד, גם פעולת מחיקה של קובץ לא משנה את השכבות של האימג' עצמו, המחיקה היא רק סימון בשכבה העליונה. כאשר מוחקים את הקונטיינר ה-Runtime מוחק את השכבה העליונה על כל השינויים שבה.
נדרש רק אימג' אחד (RO) לכל הקונטיינרים שמריצים את אותו אימג',  לכל קונטיינר נוספת מעל האימג' שיכבה פרטית המיועדת לכתיבה
נדרש רק אימג' אחד (RO) לכל הקונטיינרים שמריצים את אותו אימג',
לכל קונטיינר נוספת מעל האימג' שיכבה פרטית המיועדת לכתיבה.

 

סביבת הקונטיינר מבודדת מהמערכת שעליה הוא רץ, רמת ההרשאות של התהליך שרץ בקונטיינר והגישה שלו למשאבי מערכת מוגבלות. ניתן לחשוף שירות שרץ בקונטיינר לרשת בפורט מוגדר. במידה והתהליך שבקונטיינר זקוק לאחסן מידע קבוע ניתן למפות לו אמצעי אחסון שונים. ניתן להגביל את צריכת משאבי המעבד והזיכרון שהקונטיינר יכול לצרוך. ניתן להעמיס אותו, להפיל אותו או לאתחל אותו ולצפות שיציבות המערכת מחוץ לקונטיינר לא תושפע. זמן העלייה והירידה של קונטיינר נובע כמובן ממאפייני התהליך הספציפי, מכיוון שמדובר בתהליך ולא במערכת הפעלה מלאה בדרך כלל מדובר בפעולות מהירות ביותר שאורכות שניות בודדות.

כאמור, מי שאחראי על תכונות הקונטיינר הוא ה-Runtime, שהוא למעשה כלי שמיישם העדפות מובנות בנוגע לאופן הרצת תהליכים בבידוד. לבחירה ב-Runtime של דוקר או של יצרן אחר יש השלכות על המאפיינים שיהיו לקונטיינר. ההגבלות שיוחלו על הקונטיינר, רמת הבידוד שלו, תצורת חיבור הרשת והתקני האחסון הנתמכים, מושפעים כולם מה-Runtime בו נעשה שימוש. בפוסט הבא אציג את התכונות העיקריות של מערכת ההפעלה שבאמצעותם ה-Runtime מייצר את הקונטיינר.