בלוג
האם כל דייר צריך מכונה וירטואלית משלו?
בחרו בין קונטיינרים לכל דייר, מכונות וירטואליות והגדרות היברידיות עם מסגרת החלטות מבוססת סיכונים ושלבי הידוק שהופכים כל אפשרות למוגנת.
סיכום
אירוח מרובה-דיירים (multi-tenant) מאלץ אותך להחליט עד כמה דיירים יכולים להגיע זה לזה. קונטיינרים משתמשים במרחבי שמות (namespaces) של לינוקס ובקבוצות בקרה (cgroups) כדי לבודד תהליכים ומשאבים, אבל הם חולקים את ליבת המערכת של המארח. מכונות וירטואליות מוסיפות גבול ברמת החומרה, במחיר של מהירות ועומס תפעולי. גישה היברידית—קונטיינרים בתוך מכונות וירטואליות—יכולה לתת לך גם את זה וגם את זה, אבל היא מכפילה את שטח התיקון שעליך לעדכן. מאמר זה מוביל אותך דרך החלטה מבוססת סיכון, השוואה זה לצד זה, ושלבי ההידוק של Docker שחשובים גם בתוך מכונה וירטואלית. בסוף, תדע איזה מודל בידוד מתאים לדיירים שלך ומה להגדיר לפני ההשקה.
אפליקציית ה-multi-tenant שלך כמעט מוכנה. יש לך קובץ Docker Compose שמריץ stack לכל לקוח, וזה מהיר. ואז חבר שמפעיל חברת אחסון שואל: 'האם אתה נותן לכל דייר מכונה וירטואלית משלו?' אתה קופא. לא תיכננת לשאלה הזו. מאמר זה נותן לך דרך לענות עליה היום, בלי צוות אבטחה. אתה עושה את זה לבד, אז ההחלטה צריכה להיות פשוטה מספיק כדי להגן עליה ב-2 לפנות בוקר.
תפסיק לנסות למצוא את המודל ה'טוב ביותר'. התחל ברישום מה קורה אם הקוד של דייר משתלט על המארח שלך. הגדר את רדיוס הפיצוץ לפני שתבחר כל כלי. התרגיל הזה יגיד לך יותר מכל benchmark אי פעם.
ליבת המערכת היא השותף לדירה שאי אפשר לפנות
קונטיינרים יעילים כי הם חולקים את ליבת המערכת של המארח. השיתוף הזה הוא כל הטריק, וכל הסיכון. מרחבי שמות (namespaces) של לינוקס נותנים לכל קונטיינר תצוגה משלו של תהליכים, רשת ומערכת קבצים. קבוצות בקרה (cgroups) מאפשרות לך להגביל CPU, זיכרון וקלט/פלט דיסק כך שדייר אחד לא ירעיב אחרים. אבל אף אחת מהן לא יוצרת חומת חומרה.
חשבו על קונטיינר כתהליך עם תעודת זהות מזויפת באמת טובה. הוא מאמין שהוא על מכונה משלו. ליבת המערכת, עם זאת, היא עותק אחד של לינוקס שרץ על המארח שלך. אם דייר מנצל פרצת אבטחה בליבת המערכת, מרחבי השמות הופכים למטא-דאטה בלבד. תוקף שיכול לקרוא לפונקציות של ליבת המערכת יכול להגיע למרחבי שמות אחרים על אותה ליבה. זו בריחת הקונטיינר ששומעים עליה כל הזמן.
נניח שאתה מארח כלי B2B קטן עם קונטיינר אחד לכל לקוח. לקוח מתקין תוסף חשוד עם באג של ביצוע קוד מרחוק. עם הגדרות Docker כברירת מחדל, התהליך הזה רץ כ-root בתוך הקונטיינר. root בקונטיינר הוא עדיין UID 0, וליבת המערכת לא מבדילה בין UID זה ל-root של המארח אלא אם כן אתה ממפה משתמשים במפורש. התוקף יכול לנסות לפרוץ החוצה, וליבת המערכת המשותפת היא המטרה שלו.
הכשל לא חייב להיות דרמטי. דייר אחד שמדליף זיכרון יכול לדחוף את המארח ל-swap, ולהאט כל דייר אחר. ללא מגבלות cgroup, לולאה אחת מתנהגת רע היא מתקפת זמינות. עם מגבלות, זה תהליך חסום והתראה.
האם זה אומר שקונטיינרים לא בטוחים? לא. זה אומר שאתה חייב להתייחס לליבת המערכת כאזור אמון משותף. לפני שתבחר, כתוב הצהרת סיכון של פסקה אחת: 'אם הקונטיינר של דייר נפרץ, התוקף יכול לגשת ל: [רשימה]. העלות העסקית תהיה: [סכום או השפעה].' אם הפסקה הזו מפחידה אותך, אתה לא פרנואיד. אתה כן.
למבט מעמיק יותר על ספקטרום הבידוד, מקונטיינרים משותפים ועד stacks נפרדים לחלוטין, עיין במדריך שלנו לתכנון ארכיטקטורת Docker מרובת-דיירים.
שלוש דרכים לחתוך את זה (בחר אחת לפני הפריסה)
יש באמת שלוש ארכיטקטורות לבידוד מרובה-דיירים. כל 'שיטה מומלצת' היא שילוב של אלה.
| גישה | מחסום בידוד | הכי מתאים כש | האזהרה הקשה ביותר |
|---|---|---|---|
| קונטיינרים לכל דייר | מרחבי שמות של ליבת המערכת + cgroups | דיירים קטנים רבים, סיכון נמוך לכל דייר, צורך בצפיפות | ניצול אחד של ליבת המערכת יכול לשבור כל דייר על המארח הזה |
| מכונה וירטואלית אחת לכל דייר | היפרוויזר/וירטואליזציה בחומרה | נתונים מוסדרים, דיירים עוינים, ערך גבוה לכל דייר | כבד יותר, איטי יותר לפריסה, אתה מתקן מערכת הפעלה לכל דייר |
| קונטיינרים בתוך מכונות וירטואליות | גבול מכונה וירטואלית סביב עומסי עבודה בקונטיינרים | צפיפות פלוס קליפה קשיחה בין קבוצות | עלויות ועומס תפעולי כמעט כפולים |
קונטיינרים לכל דייר. זוהי ברירת המחדל עבור רוב מייסדי ה-SaaS. כל דייר מקבל קונטיינר משלו או stack קטן של Compose. הפריסה מיידית, התמונות קטנות, CI/CD פשוט. מגבלות משאבים מונעות משכנים רועשים לאכול את השרת. המחיר הוא ליבת המערכת המשותפת. אם אתה יכול לשמור על עומסי עבודה ללא הרשאות ולתקן את המארח באופן קבוע, זו לעתים קרובות הצעד הראשון הנכון.
אל תשים שני דיירים באותו קונטיינר. זהו ליבת מערכת משותפת פלוס runtime משותף פלוס מערכת קבצים משותפת. אם דייר אחד מעלה קובץ שיוצר תהליך, הדייר השני כבר נמצא באותה טבלת תהליכים. קונטיינר הוא יחידת הבידוד שלך; עשה אותו דייר אחד לכל קונטיינר.
מה לגבי בסיס הנתונים? אם כל דייר מתחבר למופע MongoDB או PostgreSQL אחד עם אותם אישורים, כבר הוספת רכיב משותף ענק. תן לכל דייר אישורים נפרדים, ובאופן אידיאלי בסיס נתונים או סכמה נפרדים. קונטיינרים מבודדים את האפליקציה; מסד הנתונים הוא לעתים קרובות הנזילה הראשונה שתוקף יבדוק.
מכונה וירטואלית אחת לכל דייר. תן לכל דייר מכונה וירטואלית מלאה. ההיפרוויזר מוסיף גבול ברמת החומרה, שזה בדיוק מה שניצול של ליבת המערכת חייב לחצות כדי להגיע למארח. זה חשוב בסביבות מוסדרות או כאשר הדיירים אינם מהימנים. המחיר הוא צפיפות וזמן. אתה מנהל כעת צי של מערכות הפעלה, לא רק קונטיינרים. כל מכונה וירטואלית זקוקה לעדכונים, סוכני אבטחה וניטור. עבור מייסד יחיד, זו עבודה אמיתית.
תבניות שעובדות ברמה זו: השתמש בתשתית כקוד (infrastructure-as-code) כדי ליצור מכונה וירטואלית מאותה תמונת בסיס, אפה עדכונים בתמונות חדשות במקום לתקן מערכות חיות, וסיים עומסי עבודה שאינך מזהה. שמור על פורט הניהול של המכונה הווירטואלית סגור לאינטרנט.
קונטיינרים בתוך מכונות וירטואליות. השילוב ההיברידי הזה כמעט ולא נדון במדריכים למתחילים. אתה שם מכונה וירטואלית קטנה סביב כל דייר (או קבוצה קטנה של דיירים), ואז מריץ קונטיינרים בתוך אותה מכונה וירטואלית. המכונה הווירטואלית היא מיכל לרדיוס הפיצוץ; הקונטיינרים הם רק יחידות פריסה. זה נותן לך את הקצה הקשה של הוירטואליזציה ואת היכולת לשחזר תמונות. זה עולה יותר, כי אתה משלם על תקורה של וירטואליזציה וגמישות קונטיינרים, אבל זה יכול להיות המודל השפוי ביותר לטווח ארוך כאשר אתה לא יכול לסמוך לחלוטין על דיירים.
דוגמה מיקרו-נפוצה: דייר מריץ API של Node ו-worker ברקע. במקום קונטיינר אחד ענק עם שני התהליכים, השתמש במכונה וירטואלית אחת, ולאחר מכן שני קונטיינרים עם מגבלות משאבים שונות, רשת משותפת, וללא חשיפה ישירה לאינטרנט עבור ה-worker. המכונה הווירטואלית מספקת את הקצה הקשה; הקונטיינרים מספקים מבנה.
באיזה מהם כדאי לבחור? הטבלה היא הרשימה הקצרה שלך. החלקים הבאים הופכים את ההחלטה לקונקרטית.
אם אתה בוחר בקונטיינרים, עשה את ששת הדברים האלה או אל תטרח
קונטיינרים לכל דייר הם בסדר אם אתה מתייחס לכל קונטיינר כתוקף פוטנציאלי. זה מתחיל בתצורה, לא במשאלת לב.
0. הגבל משאבים לפני שאתה סומך על מישהו. cgroups הם מנגנון הוגנות והגנה על זמינות. הגדר --memory ו---cpus לכל קונטיינר. דייר שמדליף זיכרון צריך לפגוע במגבלה שלו, לא בשרת שלך. זה לא גבול אבטחה, אבל שכן רועש הוא מתקפה בלי שורת קוד אחת. התחלה מעשית: --memory 512m --cpus 0.5. לתהליך worker, התחל נמוך יותר וגדל.
1. הרץ כמשתמש שאינו root. לעולם אל תיתן לתהליך הקונטיינר להשתמש ב-UID 0 אלא אם כן אתה בהחלט צריך את זה. הגדר משתמש ב-Dockerfile והעבר --user כהגנה נוספת. ניצול שרץ כמשתמש ללא הרשאות יש לו הרבה פחות נתיבים לליבת המערכת. ב-Dockerfile, צור משתמש: RUN useradd -u 10001 app ו-USER app. אל תדלג על זה כדי לחסוך זמן.
2. הסר כל יכולת שאינך צריך. יכולות (capabilities) של לינוקס מפצלות את הכוח של root לחתיכות קטנות. רוב אפליקציות ה-web לא צריכות כמעט כלום. התחל עם --cap-drop=ALL והוסף בחזרה רק את מה שאתה יודע שאתה צריך. קונטיינר ללא CAP_SYS_ADMIN יהיה קשה הרבה יותר לשימוש לטריקים של מרחבי שמות. אם האפליקציה שלך מנסה להיקשר לפורט עם הרשאות, הרץ אותה על פורט גבוה והצב proxy מלפנים במקום להעניק NET_BIND_SERVICE.
3. הפוך את מערכת הקבצים לקריאה בלבד. האפליקציה שלך לא צריכה לכתוב לשכבת הקונטיינר שלה. הר tmpfs ל-state. תוקף שלא יכול לכתוב לדיסק מתקשה הרבה יותר לשתול התמדה. אפליקציית PHP שנפרצה שמנסה לכתוב webshell תיכשל כשמערכת הקבצים בשורש היא לקריאה בלבד. אתה יכול להריץ נפח בעל שם עבור ספרייה ניתנת לכתיבה שהאפליקציה שלך באמת צריכה.
4. החל seccomp ו-AppArmor או SELinux. אלה שולחים קריאות מערכת מסוכנות לערימת הפסולת. Docker כולל פרופיל seccomp ברירת מחדל; השתמש בו. הוסף פרופיל AppArmor לשכבה נוספת. אתה לא צריך לשלוט בכל קריאת מערכת. עליך למנוע את הדברים ש-worker אינטרנט רגיל אף פעם לא צריך. לעולם אל תריץ עם --privileged. הדגל הזה משבית כמעט כל הגנה שהקמת.
5. חלק את הרשת. אל תיתן לכל קונטיינר נתיב לכל קונטיינר אחר. ברירת מחדל של דחייה, ואז פתח רק את הפורטים שאתה צריך. קונטיינר מסד נתונים שנפרץ לא אמור להיות מסוגל לסרוק את פאנל הניהול שלך. אם דיירים נמצאים ברשתות נפרדות, פריצה ברשת אחת לא יכולה להתפשט לרוחב.
התחלה מעשית:
docker run --user 10001 --cap-drop=ALL --security-opt no-new-privileges --read-only --tmpfs /tmp:rw,size=64M --security-opt seccomp=default.json --memory 512m --cpus 0.5 myimage
שים את אותם דגלים בקובץ Compose והחל אותם על כל דייר. זה לא מלא, אבל זה כברירת מחדל חזקה בהרבה ממה ש-docker run נותן לך מהקופסה.
להדרכה מעמיקה יותר, השתמש במדריך ההידוק שלנו שלב אחר שלב עבור קונטיינרים של Docker באירוח מרובה-דיירים.
בידוד הקונטיינרים המשופר של Docker הוא החריגה שכדאי להכיר
אם אתה רץ בסביבת Docker מנוהלת, חפש את ה-Enhanced Container Isolation (ECI) של Docker. הוא משתמש בבידוד מרחבי שמות של משתמשים וב-runtime מאובטח לקונטיינרים מתחת למכסה המנוע. root בתוך קונטיינר ממופה למשתמש ללא הרשאות במארח, כך שאפילו קונטיינר שרץ כ-root לא מקבל הרשאות root של המארח. הוא גם חוסם יכולות מסוכנות וקריאות מערכת כברירת מחדל. זה לא משהו שאתה יכול לשחזר עם כמה דגלים על Docker רגיל. אם הפלטפורמה שלך תומכת בכך, הפעל אותו. זה לא מסיר את הצורך במשתמשים שאינם root ובמגבלות משאבים, אבל זה משנה את המתמטיקה של הסיכון.
אתה יכול לקרב חלק מזה עם מיפוי מרחב שמות משתמשים (userns-remap) בדמון של Docker. זה לא מלא כמו runtime מאובטח, אבל עדיף מכלום. אם אתה משתמש בו, ודא שמיפוי ה-UID עובד לפני שאתה סומך עליו.
כשל ה-VM: מעבר למכונות וירטואליות אינו הידוק
הנה החלק הניגודי, וזה החלק שרוב האנשים מדלגים עליו. אם אתה עובר למכונה וירטואלית אחת לכל דייר ולאחר מכן פורס את הקונטיינרים הרגילים שלך בתוכה, לא הסרת את בעיית האבטחה של הקונטיינרים. הוספת כלוב רחב. בריחת הקונטיינר עדיין עובדת; התוקף פשוט נוחת במכונה הווירטואלית במקום על המארח. זה שיפור אמיתי, אבל אתה עדיין צריך את ששת השלבים.
המלכודת השנייה היא להניח שהמכונה הווירטואלית עצמה בטוחה. תמונת ברירת מחדל עם סיסמת SSH חלשה, חבילות בסיס ללא תיקונים, או פורט ניהול פתוח הם מתנה. גבול ההיפרוויזר משנה רק אם הguest מהודק ומעודכן. אחרת, 'המכונה הווירטואלית המאובטחת' שלך היא נתיב מהיר יותר לפריצה כי אתה מרגיש בטוח ומפסיק לבדוק.
מה שמכונה וירטואלית נותנת לך הוא רדיוס פיצוץ שניתן לצמצום. האסון של דייר אחד נשאר במכונה וירטואלית אחת. מה שהיא עולה לך הוא הזמן שלך. אתה הופך למנהל המערכת עבור כמה מערכות הפעלה שיש לך דיירים. אם אתה מייסד יחיד שמשיק מוצר, שאל אם יש לך שעות לתקן ולנטר צי. אם כן, מכונה וירטואלית לכל דייר יכולה להיות ההחלטה הנכונה. אם לא, קונטיינרים עם הידוק חזק עשויים להיות ישר יותר.
זכור גם שהמארח של ההיפרוויזר הוא מטרה קריטית. היפרוויזר שנפרץ יכול לראות את כל ה-guests. תקן את המארח, לא רק את ה-guests. המכונה הווירטואלית לא פוטרת אותך מתיקון המארח; זה מעלה את ההימור על החמצה.
אזהרה על ההיברידית: אל תניח שקונטיינרים בתוך מכונה וירטואלית נותנים לך 'שתי שכבות אבטחה' בחינם. המכונה הווירטואלית מוסיפה גבול; הקונטיינר עדיין צריך non-root, יכולות, ו-seccomp. אחרת, השכבה הראשונה היא רק חזקה כמו הקונטיינר החלש ביותר.
ארבע שאלות שפותרות את הוויכוח בעשר דקות
אל תעשה אופטימיזציה בהפשטה. שאל את עצמך את ארבע השאלות האלה לפי הסדר. רשום את התשובות.
1. לאילו משאבים יש לדייר שלי גישה? אם דייר יכול לגשת רק לאפליקציית ה-web ולמסד הנתונים שלו, קונטיינרים לכל דייר עם כללי רשת מחמירים הם הגנתיים. אם הנתונים של דייר מוסדרים או רגישים מבחינה פיננסית, עבור לכיוון מכונות וירטואליות.
2. כמה תעלה לי פריצה של דייר אחד? סכם לקוחות שאבדו, חשיפה משפטית ואמון. אם המספר גדול מעלות הפעלת מכונות וירטואליות, הוציא את הכסף. אם לא, קונטיינרים הם בחירה רציונלית.
3. כמה דיירים יש לי וכמה הם משלמים? מנויים קטנים רבים: צפיפות קונטיינרים חשובה. כמה חשבונות גדולים: תן לכל אחד מכונה וירטואלית וחייב בהתאם. דיירים שמשלמים לך פחות מכוס קפה לא צריכים כל אחד לדרוש מערכת הפעלה לניהול.
4. האם אני יכול לתקן דברים בלוח זמנים? קונטיינרים חולקים ליבת מערכת מארחת אחת, כך שתיקון המארח מגן על כולם. מכונות וירטואליות מכפילות את יעדי התיקון שלך. אם אתה יודע שתדלג על עדכונים, בחר בארכיטקטורה עם פחות חלקים נעים וברירות מחדל קשוחות יותר.
התשובות שלך יתקבצו. שתי תשובות או יותר הממוקדות במכונות וירטואליות אומרות שאסור לך כברירת מחדל להשתמש בקונטיינרים לכל דייר. שלוש תשובות או יותר הממוקדות בקונטיינרים אומרות שמכונות וירטואליות מוקדמות מדי. תוצאה אחת מנוגדת לאינטואיציה: דייר עם הכנסה נמוכה שיש לו גישה לנתונים רגישים עדיין צריך את המכונה הווירטואלית, כי העלות הרגולטורית אינה קשורה לכמה הוא משלם.
פרסם את המינימום שאתה יכול לסמוך עליו, ואז הרווח יותר בידוד
הארכיטקטורה הראשונה שלך לא חייבת להיות הסופית. התחל עם ההתקנה ההדוקה ביותר שאתה באמת יכול לתחזק, ואז הוסף בידוד ככל שבסיס הדיירים שלך מצדיק זאת. עבור רוב המפעילים היחידים, זה אומר קונטיינרים לכל דייר עם non-root, יכולות מוגבלות, מערכות קבצים לקריאה בלבד, seccomp ופילוח רשת. עבור דיירים מוסדרים או בעלי ערך גבוה, קפוץ ישר למכונה וירטואלית לכל דייר, עם קונטיינרים רק כשכבת אריזה בפנים.
מה שתבחר, רשום את ההחלטה ובקר אותה מחדש רבעונית. כשתקבל את השאלה הראשונה 'האם כדאי להעביר את הדייר הזה למכונה וירטואלית?', תהיה לך תשובה, ותהיה לך רשימת בדיקה לגיבוי. זה מה שבידוד אומר בפועל: פשרה שאתה מנהל, לא טכנולוגיה שאתה קונה.
לפני ההשקה, עברו על רשימת הבידוק המעשית שלנו לאבטחת בידוד Docker — היא הופכת את ההחלטות האלה לרשימה שאפשר לאמת לפני שמציגים עמוד ללקוח.

