2017-07-13

על סביבת הריצה החדשה: ה Docker Orchestration

מהפכה מתרחשת מסביבנו: מהפכת הקונטיינרים.

לפני רק עשור וחצי, סביבת הריצה המשמעותית של התוכנה (צד-שרת) שכתבנו הייתה מערכת ההפעלה. כמעט כל תואר אקדמי שכיבד את עצמו הכיל קורס משמעותי על סביבת הריצה הזו: תהליכים, IPC, ניהול זיכרון, socket, files, ומערכת קבצים (inodes - מישהו?).

באה ג'אווה ומיד אחריה סביבת NET. והפכו את סביבת הריצה שהיא מערכת ההפעלה - לשניה ל VM וסט הספריות שלו. ניהול זיכרון, ניהול תהליכים, ו IPC - כבר הפכו לפחות רלוונטיים.

משם המשכנו לענן - ופתאום סביבת הריצה המשמעותית השתנתה פעם נוספת:
Autoscaling, התנהגות בוירטואליזציה, תקשורת בין Availability Zones או בתוכם? רשת משותפת? VPC? Spot instances? - כל השיקולים הללו התווספו לסביבת הריצה של האפליקציה שלנו.

עכשיו סביבת הריצה מקבלת הרחבה נוספת, בדמות ה Linux Containers והמימוש המאוד פופולרי שלהם: Docker.


סביבת הריצה העכשווית של מערכות צד-שרת


----

תזכורת קצרה על Docker


מי שמכיר - שידלג.

להלן 2 דרכים מקובלות להעביר קוד ממכונת הפיתוח לפרודקשיין:

דרך #1 (המקובלת יותר): להעביר את קוד המקור (או ה binary המקומפל) למכונה.

הבעיה: לקוד יש תלויות שצריך להתקין. חלק מהתלויות צריך לקמפל על המכונה (להתאים לארכיטקטורת המעבד / מערכת ההפעלה). התהליך הזה עשוי להיות מסובך, error prone, ולארוך זמן רב (5 וגם 10 דקות לתהליך התקנה של מכונה - הוא לא זמן מופרך).


דרך #2: ליצור Virtual Machine ולהריץ אותו על Hypervisor / ספק ענן

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

הקשיים: ה image הוא לרוב גדול (כמה GB) ולוקח זמן להוריד ולאתחל אותו.
לבדוק את ה image על מכונת פיתוח - לא כ"כ נוח: צריך הרבה זיכרון וזו מערכת הפעלה נפרדת לחלוטין, מה שמקשה מעט על תהליך debugging / בדיקת לוגים וכו׳.

על גבי המכונה שנוצרה מה image נרצה לבצע עדכוני תוכנה (patches) וגם עדכונים של הקוד שלנו - ועם הזמן קל לאבד שליטה על מה שקורה בו. זוהי גישה שנקראת convergence.

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

מקור

גישת ה Containerization (להלן Docker) היא גישת אמצע:
  • נארוז את כל ה user space של המכונה: ה distro של מערכת ההפעלה + ספריות שהותקנו + קוד האפליקציה ל docker image.
  • את ה docker image נעביר למערכת המארחת בה יש hypervisor וגם Linux Kernel.
    • שימו לב ש docker image שנבנה היום עלול לא לרוץ כשורה עוד כ 5 שנים: ה Kernel (של המערכת המארחת) מתעדכן עם הזמן, ואז קריאות מערכת (system calls) - עלולות להפסיק ולהיתמך. הנה דוגמה להמחשה.
    • מכיוון שכל ה user space ארוז ב docker image - אין בעיה להריץ הפצות שונות זו לצד זו: Debian לצד OpenSUSE, לצד CentOS.
  • ה docker image ירוץ כ container על המכונה המארחת:
    • יכולת ה Linux Containers (בקיצור LXC - זו שהחלה את המפכה) מאפשרת להריץ תהליך נפרד עם isolation מסוים ברמת מערכת ההפעלה: 
      • namespaces - הפרדה ברמת המודעות לתהליכים אחרים, תקשורת, ומערכת קבצים. pid, hostname, וה ip שהתהליך ייחשף אליהם הם וירטואליים - ומבודדים משאר העולם.
      • cgroups - הגבלה ברמת צריכת המשאבים. לא ייתכן שתהליך שנמצא ב cgroup אחר ישמש ביותר מ %x של CPU.
    • Docker הוסיפה אבסטרקציה נוספת למערכת הקבצים ול Network stack.
  • ה overhead של כל container שרץ הוא זניח: כמעט ורק זיכרון (בניגוד ל VM). אין בעיה להריץ 15 וגם 50 containers על אותה המכונה ביעילות - ממש כפי שניתן להריץ 15 או 50 תהליכים על אותה המכונה ביעילות.

ה tradeoff של פחות isolation (יחסית ל VM) - אבל ניצולת גבוהה של משאבים קוסמת מאוד לחברות השונות: "על ה images שלנו אנחנו סומכים!". כל עוד המערכת המארחת מריצה רק containers שלי - הדאגה מבחינת אבטחה ו/או "חטיפת משאבים" היא נסבלת לחלוטין.

עוד דבר נחמד ש Docker עושה הוא בנייה של ה docker image בשכבות.



כאשר דוקר הוריד למכונה את ה docker image של שירות #1, הוא הוריד כל שכבה בנפרד. כעת, את שירות #2 הוא יכול להוריד במהירות, ממש כמו התקנה של source code / binary - כי רק השכבה של MyService 2 השתנתה.

כנ"ל לגבי שירות #3 - שעושה שימוש חוזר ב layer הגדול של אובונטו.

גישה זו היא יפה, אבל צריך גם לדעת איך לעבוד איתה. למשל:
  • שכבה שדורשת עדכון - תגרור הבאה מחדש של כל השכבות מעליה (כי מדובר בדלתאות), ולכן יעיל יותר להגדיר את השכבות באופן שמירב העדכונים יהיו בשכבות העליונות.
  • שכבה שמסירה קבצים, גדולים ומיותרים ככל שיהיו, לא תחסוך את הורדת הקבצים הללו - שוב, כי מדובר בדלתא. אם הקובץ גדול כדאי לבנות image ייעודי ורזה יותר של מערכת ההפעלה / אותה שכבה עם קבצים מיותרים.
  • ה docker images של מערכות ההפעלה השונות הם מינימליים ולרוב כוללים סט כלים מצומצם ביותר (bash, grep, package manager, ורק מעט מעבר) ולכן יהיה עליכם להוסיף שכבות משלכם.


ישנן כמה מוטיבציות עיקריות ליישום Docker:
  • ניצול טוב יותר של משאבים - ובמיוחד בסביבה של מיקרו-שירותים. 
    • חשבו על שירות הגיוני ונחוץ, אך שצורך לא יותר מ 1% CPU ממכונה קטנה של ספק הענן שלכם.
  • שיפור תהליך ה Continuous Deployment - כאשר יש פחות סבירות לתקלות שנובעות מחוסר התאמה בין סביבות הפיתוח, הבדיקות, והפרודקשיין. למשל: "אופס! שכחנו לעדכן ב deployment script עדכון של libssl שאיזו ספרייה שלנו משתמשת בה"
    • עקרון חשוב כאן הוא immutable infrastructure (או servers) - אותה הסברתי בפוסט נפרד.
  • Easier) Multi-Cloud deployment) - אם לצורך גיבוי, פריסה גאוגרפית טובה יותר, זמינות שאינה תלויה בספק ענן יחיד, גישה לשירותים של ספקי ענן שונים (למשל: BigQuery) או לצורך צמצום ה Lock-in.

הסיכונים, גם הם קיימים:
  • קונספט חדש, שעדיין לא הבשיל לרמה של הקונספטים הקיימים.
  • שיתוף משאבים הוא נהדר, אבל לא לכל טכנולוגיה. למשל: 
    • ברובי on ריילס צריכת הזיכרון היא גבוהה למדי - מה שמגביל על הרצה של הרבה containers על אותה המכונה. 
    • ג'אווה אגב, גם לא טמנה את ידה בצלחת: ובשל שיקול תכנוני שנראה נכון בזמנו - גם תהליך פשוט של ג'אווה יכול לצרוך מאות MB של זיכרון.
    • מערכות שעושות שימוש אינטנסיבי בזיכרון (Redis, בסיסי נתונים in-memory)
    • מערכות שעושות שימוש אינטנסיבי ב I/O (למשל: בסיסי נתונים. אפרט עוד בהמשך).
  • עלויות מעבר והתאמות הנדרשות לסביבה החדשה: התאמה של תהליכי deploy, תהליכי CI/CD וה debugging. עד שלא תעבדו חודשיים-שלושה עם Docker בפרודקשיין - לא תגלו הכל.

סיימנו עם דוקר, ובחזרה לפוסט...

----


הכירו את ה Container Orchestration Frameworks


Docker עצמו אינו מספיק, במיוחד לא לסביבת production מורכבת.
  • מישהו צריך להרים את ה containers הנכונים ובזמן. איך מתבצע עדכון גרסה של התוכנה?
  • מישהו צריך לשמור שאם container נפל - יקום במקומו אחד אחר.
  • מישהו צריך לדאוג שאם מכונה עמוסה מדי, יזיזו container רעב למשאבים - למכונה אחרת.
  • מישהו צריך לדאוג שאם יש דרישה לעוד כוח מחשוב (high traffic?) - מישהו ירים עוד containers על מנת לספק את הצורך.

ה"מישהו" הזה - נקרא בימנו Container Orchestration Framework.

נכון, Docker הוא כ"כ דומיננטי כיום, שאני בכלל לא מתייחס לאלטרנטיבות, ובראשן rkt (נקרא כמו: "rock-it") של coreOS. בהמשך ארחיב על קוברנטיס - Framework שמספק גם תמיכה ניסיונית ב rkt. גם Lock-In לדוקר הוא משהו שיש ומנסים להימנע ממנו.

Container Orchestration Frameworks (בקיצור: COF) יש כבר לא מעט - והן מתפתחות במהירות:
  • הכל התחיל ב Kubernetes ו Mesos, כך נדמה לי.
  • אמזון ומייקרוסופט נבהלו שהנה "אובד" ה Lock-in לעננים שלהן, ומיהרו לפתח את ECS ו ACS - בהתאמה.
  • HashiCorp, בעלי המוניטין המרשים בעולם ה DevOps הציגו את Nomad - החזון שלהם לעולם ה COF.
  • Docker, שמאחוריה חברה מסחרית הבינה שהפוטנציאל המסחרי נמצא ב COF הרבה יותר מאשר הוא נמצא ב Docker - והצטרפה לחגיגה בעזרת Swarm, הממותג כ ״COF הרשמי".

אפשר לעסוק עד בלי די בכוחות הפועלים מסביב ל Ecosystem של ה Linux, ובתחרות העזה שמתנהלת בין ה COFs השונים. זה לא יקרה בפוסט הזה :-)

אני רוצה להציג COF לדוגמה, וקצת לצלול לאופי של Framework שכזה.
אני אציג את Kubernetes שהוא היום הפתרון המוביל מבחינת יכולות ובשלות.

השוואה בין ה Docker Orchestration Frameworks מלפני שנה. מקור.
חשוב להזכיר שהתחום מתפתח במהירות, וסביר שההשוואה כבר איננה מדוייקת לגמרי.


קדימה לדרך, קוברנטיס!


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

נתחיל בהגדרת המונחים הלוגים העיקריים בעולם של קוברנטיס:

Pod - היא יחידת ה deploy הקטנה ביותר. בד"כ זה יהיה קונטיינר יחיד, אבל יכולים גם להיות 2 או יותר קונטיינרים בפוד - שרוצים להריץ אותם ביחד.

Service - הוא הפשטה לקבוצה לוגית של Pods (מסוג זהה) והמדיניות המתירה גישה אליהם. לקוח של המערכת ניגש ל virtual IP של ה service ומשם המערכת מבצעת routing + load balancing ל pod מתאים כלשהו.

Volume - הוא בעצם Mount של Storage ל Pod. ב Docker יש קונספט של Volume, אבל הוא פחות או יותר mount של folder מה host - ואינו כולל lifecycle מנוהל (למשל: ניקוי הנתונים לאחר שה Container ירד). קוברנטיס מציעה Persistent Volume שיחייה גם לאחר שה Pod ירד.

Namespace - יחידה לוגית שמרכזת משאבים כמו הנ"ל (Pods, שירות, ו Replica Set), ומבודדת אשכולים שונים שלהם זה מזה. למשל: production ו staging.


הנה תרשים ויזואלי של האלמנטים הללו:



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

חשוב להדגיש שזהו תרשים גנרי:
  • לקוברנטיס יש תצורות מעט שונות בעננים השונים: Azure, AWS EC2, IBM Blumix, GCE, Open Stack  ועוד.
  • יש לא מעט מקום לגמישות ו customization, אני אנסה לתאר תצורה פשוטה וסטנדרטית ככל האפשר.

הרכיבים הכחולים הם רכיבי Runtime, בעוד הרכיבים האדומים הם רכיבי Management


ה Kubernetes Cluster מורכב מ Nodes ו Master. המכונות עצמן מנוהלות ו provisioned ע"י פתרון הענן / פתרון שאינו קוברנטיס. ברגע שה Master עולה ומזהה ברשת את ה kubelets המותקנים על ה nodes - הוא מתחיל לפעול.

ה Master עושה scheduling של ה pods שנדרשים ממנו ל nodes השונים, ע"פ כללים שונים. הוא מעביר לכל kubelet את ה podspecs הרלוונטיים (בד"כ: קבצי xxx_pod.yaml) וה kubelet פועל מול ה container engine (בד"כ: Docker) על מנת להוריד את ה docker images המתאימים, לקנפג אותם, לחבר להם volumes - אם קיימים, ולהריץ אותם.
ל Kubelet יש עצמאות לעשות restart ל containers שאינם מגיבים.

כל ה Pods על אותו node יכולים לתקשר זה עם זה בעזרת localhost או IPC. קונטיינרים באותו ה pod משתפים ביניהם גם storage.

ה Pods השונים מזוהים ע"י Labels (צמד key/value - לצורך זיהוי) ו Annotations (מחרוזות המשמשות כ metadata לצורך קלות הניהול).
Replica-Set מוגדרת כקבוצה של Pods התואמים ל Label Selector, או פרדיקט, בנוסח:
role = webserver, application != honeypot.
כאשר role ו application הם ה keys של ה Labels, ו webserver ו honeypot - הם ערכים אפשריים.
המערכת תדאג שה Replica-Set תשאר בגודל הרצוי - ואם למשל נפל node וחסרים Pods ב replica-set - המערכת תשלים אותם אוטומטית.

מי שמנהל מרכזית את ה Replica-Set הוא רכיב בשם Controller Manager - הרץ כחלק מה Master. ה Master נדרש בעיקר לשינויים ולא לריצה שוטפת. אם הוא נופל, אי אפשר יהיה לבצע שינויי קונפיגורציה - אבל ההתנהלות בתוך ה nodes תמשיך לפעול כרגיל. עבור צרכים של Higher Availability, ניתן להפעיל יותר מ Master אחד.

הרכיבים המרכזיים ב Master הם:
  • API Service, החושף את ה REST API בו משתמשים ה kubectl ו/או ה Management UI של קוברנטיס. הוא אחראי ל input validation ובדיקה שהתצורה המבוקשת תמיד תקינה.
  • ה Controller Manager הוא רכיב הניהול המרכזי. הוא רץ בלופ תמידי שבודק את מצב המערכת (נתונים מגיעים מה cAdvisors דרך ה Kubelet וה API Service) מול התצורה הרצויה וכל הזמן מנסה להביא את המצב הקיים למצב הרצוי.
    • Replication Controller, Endpoints Controller, Namespace Controller, וכו' - הם תתי הרכיבים של ה Controller Manager.
  • ה Scheduler שהוא רכיב המנהל שורה של כללים עסקיים ("Policies") ומבצע שינויי תצורה ב Cluster בכדי לספק High Availability, ביצועים מספיק טובים, ו Quality of Service. כאן בעצם מגיבים למצב המערכת (high / low load, ביצועים לא מספיקים, מחסור במשאבים וכו') - ומשנים את התצורה הרצויה בכדי לשפר את תגובת המערכת.
  • etcd - פרויקט אחר של גוגל לניהול key/value בצורה מבוזרת המשמש לאחסון כל ה state של ה cluster.
    • ניתן להריץ את ה etcd או על המאסטר או כ cluster נפרד (עבור higher availability). אני בחרתי לצייר אותו בתרשים מחוץ ל Master.

עבור צרכני המערכת, מה שנגיש הוא ה Services. הם אלו שדרכם ניגשים ל APIs / Endpoints של המערכת.
את ה load balancing / service discovery הראשוני של ה services נעשה בעזרת DNS. קוברנטיס מספקת DNS משלה שהוא bulit-in בשם Kube-DNS. ניתן להשתמש בחלופות אחרות ל service discovery ו load balancing - אבל Kube-DNS היא הדרך הפשוטה.

בתוך ה Node, יהיו פעמים רבות מספר Pods מאותו הסוג (המזוהים ע"י אותם ה Labels). את ה load balancing והחיווט הפנימי בתוך ה Node - עושה רכיב בשם ה kube-proxy.


אם יש פתרון multi-cloud (ולכן multi-kubernetes-cluster) - אזי load balancing מוקדם יותר יעשה ע״י DNS חיצוני, למשל כמו זה של Dyn או Akamai.

Kubernetes Management Dashboard. מקור: Kubernetes



מדוע Stateful Pods הם בעייתים בקוברנטיס?

אחד הרעיונות הדומינטטים בקוברנטיס ו Docker בכלל הם Stateless Services: שירותים שיכולים ליפול ולעלות בכל רגע, בלי לפגוע בשירות.
מה קורה כאשר אנחנו רוצים שירות שהוא לא כזה? למשל: Database?

ל Database בקוברנטיס (וב Docker בכלל) יש כמה בעיות. קוברנטיס פותרת כמה בעיות, כמו היכולת לנהל lifecycle מורכב יותר ל Pod - אבל זה רחוק מלהספיק:
  • שיתוף של דיסק בין כמה Pods הוא בעייתי. ה Database זקוק למקסימום I/O בכדי לפעול. אפשר לחשוב על דיסק משותף כ Bandwith משותף שמתחלק - אבל זה יהיה לא מדויק בכלל. הבעיה היא ב latency. אם מדובר בדיסק מכאני, שבו ה Database צריך להתחרות על התור בהזזת הראש המגנטי - מצב ה latency יהיה חמור, אבל גם ב SDD - יש עניין של latency כאשר יש ריבוי קריאות. רבות מהאופטימיזציות של ה Databases היום - לא יעבדו בצורה טובה בכלל, כאשר הדיסק משותף עם עוד Pods.
  • אפשר להגדיר Pods "ענק" שרץ לבד על המכונה - וכך פותרים את בעיית שיתוף הדיסק. מצד שני - זה לא מצב טבעי (היום) בקוברנטיס, וזה פותח פתח לקונפיגורציות מורכבות יותר לאורך כל המערכת.
  • מה קורה כאשר רוצים לעשות Replicas לבסיס הנתונים (מצב דיי שכיח)? - הקוניפוגרציות של קוברנטיס גם לא חינניות למצב הזה - והן הולכות להסתבך עוד יותר. 
  • נפילה של master ו/או Replica היא לא עניין זניח - ופה כמה הנחות יסוד של קוברנטיס לא עובדות יפה.
בקיצור: ההמלצה הגורפת היא לא לנהל בסיסי נתונים בקוברנטיס ו/או Docker - במיוחד לא ב Production. כנ"ל לגבי מערכות שהן memory hungry וזקוקות למלוא רוחב ה memory bus על מנת לפעול בצורה יעילה.


שווה להזכיר את Minikube - סביבת הרצה לקוברנטיס שאפשר להריץ על המחשב האישי. המגבלה: יש רק Node אחד. היא מתאימה לפיתוח ולבדיקות פונקציונליות.


סיכום


Container Orchestration Frameworks לוקחים את עולם ה Containers לרמה הבאה - והמאוד מוחשית. ה Frameworks הללו מציעים כמה יתרונות חשובים - אבל הם עדיין לא הגיעו לרמת הבגרות של פתרונות הענן הקיימים.

קוברנטיס, שקצת יותר סקרנו לעומק - הוא ה Framework הבשל והמפותח ביותר כיום. אחד הרשמים החשובים, שאני מקווה שהצלחתי להעביר, הוא שהוא לא משהו פשוט. קוברנטיס הוא לא עוד AWS Lambda, אלא פתרון ברמת מורכבות של... חצי ספק ענן. אם ללמוד לעבוד עם עוד ספק ענן לוקח X מאמץ, לעבוד עם קוברנטיס עשוי לקחת X/2 מאמץ.

ה Sweet Spot הנוכחי של קוברנטיס (או Frameworks אחרים) הוא מערכת של Stateless Services, ללא בסיסי נתונים, וללא תלויות מורכבות בין השירותים ו/או ה Pods השונים.
רבים מאלו שמאמצים Container Orchestration Frameworks היום, עדיין משתמשים בהם רק בסביבות הפיתוח והבדיקות - ועדיין לא בפרודקשיין.


שיהיה בהצלחה!



2 תגובות:

  1. אדיר!
    אני לא כ"כ מהתחום ובזמן האחרון ניסיתי ללא הצלחה להבין על מה הרעש (בעקבות הרצאה של Alex Ionescu).
    עשית לי סדר, תודה רבה!

    השבמחק
  2. יש לציין כי כל התחום מתקדם ומתפתח בסביבות LINUX. מפתחי windows נותרים מאחור. אמנם מיקרוסופט הוציאו windows native containers במערכות ההפעלה החדשות שלהן אבל - מנסיון - שומר נפשו יירחק. רמת בשלות נמוכה, orchestration כמעט ולא קיים (למעט swarm וגם זה לא באופן מלא), וכמובן - כמיטב המסורת של מיקרוסופט - כל נושא הרישוי נע בין שערורייתי ללא הגיוני בעליל. לא נותר לנו אלא להמשיך לקרוא כתבות כאלה ולקנא...

    השבמחק