המעבר ממונוליטי ל-Microservices

Print Friendly, PDF & Email

כל מי שנמצא ברמה ניהולית של IT (מנמ"ר/CTO/CIO וכו') בוודאי מכיר את הדבר הבא: חברה מעוניינת לפתח מוצר גדול, "הדבר הבא" בתחום שלהם. מתקיימות מספר ישיבות עם גורמים שונים בחברה ותוך כדי כותבים מפרט ארוך מה הולך להיות בתוך המוצר, במה הולכים לתמוך בתוך המוצר וכו' וכו'.

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

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

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

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

פרויקט גדול מורכב מחלקים רבים שצריך לכתוב. בשיטה המונוליטית כל החלקים משתלבים אחד עם השני (Linking) כך שאי אפשר לשלב קוד בחופשיות של מפתחים שונים. צריך לבדוק כל חלק שמבצעים לו Commit שהוא לא שובר חלקים אחרים במוצר. בשיטת ה-Microservices (אני אקרא לזה מ"ש במשך פוסט זה) עושים דברים בשיטה הפוכה: כל חלק שצריך לכתוב, יכתב באופן עצמאי לחלוטין, הוא יכול להיות כתוב בשפה אחרות או עם פלטפורמה/Framework שונה מחלקים אחרים – כל עוד לאותו חלק יהיה ממשק RESTful API שאליו נוכל לשלוח פרמטרים (דרך YAML, JSON וכו') ונוכל לקבל נתונים בחזרה מאותו חלק בפורמט שנרצה.

וכך, בשיטה זו הצוותים השונים עובדים בצורה עצמאית לחלוטין והדבר היחיד שהם צריכים לשמור, זה פורמט API שמוסכם בין כל הצוותים ומתועד. זו בדיוק ההזדמנות גם להשתמש בטכנולוגיות חדשות, או לקחת מפתחים מבחוץ שיודעים לבנות לדוגמא UI בכלים מודרניים, אפשר להשתמש בכלי CI/CD לבדוק ולקמפל כל חלק באופן עצמאי, לכתוב טסטים ולבצע Stress testing לכל חלק.

לאחר שהחלקים השונים נכתבו (או במהלכם) – אנחנו נשתמש במערכת אורקסטרציה לקונטיינרים (כמו Kubernetes/OpenShift) בכדי להריץ כל חלק בקונטיינר/POD והתקשורת בין החלקים תהיה דרך HTTP/HTTPS ודרך פרוטוקולים אלו נשתמש ב-API כך שכל חלק יוכל לדבר עם חלקים אחרים.

במתודה המונוליטית, כשאנחנו צריכים לבצע Scaling, אנחנו בעצם נשכפל מכונות VM ונגדיר את ה-Load Balancer שידע להפנות פניות למכונות ה-VM השונות. הבעיה המהותית בשיטה הזו, היא שאנחנו משתמשים במשאבים רבים כשברוב המקרים רק חלק מסויים או חלקים מסויימים צריכים את ה-Scaling ושאר החלקים רק תופסים זכרון מבלי לעשות כמעט כלום. במתודת ה-מ"ש לעומת זאת, אנחנו יכולים לבצע Scaling דינמי לאותו חלק שמשאביו נגמרים וה-Scaling עצמו יבוצע תוך שניות בודדות (בניגוד להקמת VM נוסף), כך שברוב המקרים, כמות המשאבים שנצטרך לבצע Scaling – תהיה נמוכה בהרבה בהשוואה למתודות הרצה של אפליקציות מונוליטיות (הוספת עוד ועוד מכונות VM).

בכל הקשור לשדרוגי חלקים, HA, אחסון ושליפת נתונים, תקשורת ואבטחה – עבודה עם Kubernetes/Openshift תהיה הרבה יותר טובה ויעילה בהשוואה לשיטות העבודה הקלאסיות. שדרוגים לדוגמא מבוצעים בפקודה אחת מבלי להפסיק את כל החלקים השונים, ובמקרה הצורך, אותו דבר מתבצע בשנמוכים. את ה-HA מקבלים כברירת מחדל עם Kubernetes/Openshift, ובכל הקשור לאחסון – אותן מערכות יודעות "לדבר" עם כל אחסון מקומי או שקיים בענן ציבורי. מה עם אבטחה? כיום עם istio אפשר לעשות דברים רבים שבמערכות קלאסיות מצריכות תוכנות מסחריות (יקרות) מצד ג', ויש כמובן כלים נוספים, רובם בקוד פתוח זמינות לציבור.

אנסה לסכם את הפוסט כך: כיום, אם יש צורך בפיתוח אפליקציות גדולות ומורכבות, עדיף לעבוד במתודות ה-Microservices (ואגב, לחובבי ה-Mainframe – כן, אפשר לעשות זאת בקרוב גם על Mainframe של IBM עם Z/OS) שנותנות יתרונות רבים מאוד על פני המתודה המונוליטית. נכון, Kubernetes הוא לא בדיוק דבר קליל ללימוד אך מצד שני, המאמץ שווה, מה גם שאם אתם הולכים להשתמש בעננים ציבוריים, החיים הרבה יותר קלים עם שרותי הקונטיינרים הטבעיים שאותם ספקי ענן ציבורי מציעים.

להלן מצגת (קצת ישנה) על הנושא (ותודה ליבגני זיסליס על הלינק):

Comments

comments

8 תגובות בנושא “המעבר ממונוליטי ל-Microservices”

  1. היי חץ

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

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

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

    ולכן גם המבט על קוברנטיס ודוקר/קונטיינר שהם טכנולוגיות מדהימות, צריכות להיות באותה צורה – הן לא מתאימות לכל דבר.

    וזה גם בסדר לכתוב קוד שבסוף יזרק, כי צריך להתחיל איכשהו ומשהו.

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

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

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

        יתרונות
        1. חלוקה לרכיבים עצמאיים מאפשרת מספר release queues. כל צוות יכול לשחרר גרסאות בצורה עצמאית ללא קורדינציה עם צוותים אחרים (בשינויים שלא שוברים contract/interface קיים). זה פתרון לבעיה של scale ארגוני – כשיש המון מפתחים שעובדים על חלקים שהם seperable אחד מהשני, זה מאפשר לארגון פיתוח לצמוח בלי להאט את קצת הדליברי. זאת הסיבה העיקרית לשלם את המחירים הכבדים ולהטמיע כזאת ארכיטקטורה.

        2. מקרים שבהם יש פרופילי עבודה שונים מאד לחלקים שונים במוצר וקיים צורך ממשי לעשות להם scale בנפרד. נניח ויש קומפוננטה שהיא מאד IO bound וצריכה SSD מאד מהיר, וקומפוננטה אחרת שהיא מאד cpu bound וצריכה המון ליבות עיבוד. אם הרכיב שמנצל המון מעבד עובד בסקייל גדול ומצריך המון שרתים, בארכיטקטורה מונוליטית זה יגרור עלות של SSD מהיר על כל אחד.
        ברוב המקרים, זאת סיבה גרועה מאד לשנות ארכיטקטורה כי ה complexity cost של microservices גדול משמעותית ברוב התרחישים מאשר העלות הכספית של utilization נמוך.

        חסרונות:

        1. אובדן consistency – אם כל מיקרוסרוויס מחזיק את הדאטה של עצמו (ואם לא אז יש לנו מונוליט מבוזר שלא נהנה מהיתרונות לעיל), לפתע נפתחים המון failure domains שיכולים לגרום לכך שסרוויסים יכילו תמונת עולם שלא תואמת לעצמה. נסיונות להכיל טרנזאקציות מעל מספר סרוויסים בדרך באים עם tradeoff גדול על performance או על availability, תלוי במימוש

        2. תמיכה לאחור – בהנתן שכל סרוויס מורשה לתקשר עם סרוויסים אחרים, אם שירות A רוצה לשנות API הוא צריך לסנכרן את השינוי עם הצרכנים שלו. להם יש אינטרס מובהק לשחרר פיצ׳רים ולתת time to market מהיר (בגלל זה הרי הלכנו על ארכיטקטורה כזאת), ולא לשנות עכשיו את הצורה שבה הם קוראים לסרוויסים אחרים. די מהר מגיעים ככה ל״פקקים״ בהם יש לנו פתאום תלות גדולה באחרים שממנה ניסינו להמנע. אפשר להטמיע deprecation policy אבל זה גורר גם שינוי תרבותי ושינוי בצורת עבודה שקשה ארגונית לנהל.

        3. visibility and debug-ability – כבר אין stack trace אחד שאפשר להסתכל עליו ולהבין איפה ב lifecycle של בקשה משהו נשבר. יש גרף תלויות מורכב שכולל מעבר של בקשות מעל רשת (שהיא אסינכרונית כידוע). להכניס את הכלים הדרושים ל distributed tracing ומוניטורינג של תשתית כזאת, זאת השקעה אדירה ומתמשכת. וזה לפני שמדברים על lifecycle של פיתוח וטסטים (בהצלחה לכתוב integration tests כשאתה אפילו לא יודע מי הצרכנים שלך ומה הם מנסים לעשות עם ה API שאתה מספק).

        4. ביצועים – לא רלוונטי ל95% מהאפליקציות, אבל יש overhead לכל קריאות ה RPC שבקשה עוברת בדרך. הרבה זמן מבוזבז על סריאליזציה, כתיבה וקריאה לרשת ודסריאליזציה בצד השני. במערכות שבהן ה latency הוא קריטי, זאת מגבלה.

        5. יציבות. – מערכת מבוזרת מעלה את ה surface area של דברים שעלולים להשבר. סומכים יותר על הרשת, יש הרבה יותר כלים ותשתיות שאנחנו צריכים בשביל שהיא תעבוד (כלי דיפלויימנט, orchestration, service discovery וכו׳). כשל בכל אחד מהם עלול להביא את המערכת למצב שבור. וזה לפני שנדבר על גרף התלויות הענק של סרוויסים שמדברים אחד עם השני – אם סרוויס A נופל, הוא באופן ישיר פוגע בסרוויס B שבאופן ישיר פוגע בסרוויסים X and Y וכן הלאה. ברור שיש דרכים לעשות מיטיגציה לכל הדברים האלו, אבל שוב – בהשקעה אדירה של זמן ומשאבים שלא מושקעים באופן ישיר במתן ערך ליוזרים.

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

          1. זה בסדר, בהרבה מקרים אני מקבל הודעות למה אני לא מפרט/לא נכנס יותר לעומק ולפעמים מהצד השני שהחומר יותר מדי "גיקי". ככה זה..

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

  3. אחלה כתבה והסבר, רק חידוד אחד… בדרך כלל תמצא בארכיטקטורת מיקרושירותים בוגרת סוג של queue שאיתו המיקרושירותים מתקשרים אחד עם השני , התקשורת הפנימית תהיה על ידי כתיבה קוריאה מהqueues השונים.
    זה הסיבה שבדרך כלל מוצאים אץתrabbitmq, Kafka ו nats ( התמודדת החדשה מבית הcncf)
    התקשורת בingress החיצוני תהיה ב https אבל הרבה פעמים נמצא (או נכתוב) תקשורת בתוך תורים בdistributed software projects

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

This site uses Akismet to reduce spam. Learn how your comment data is processed.