ליאור בר-און לפני 6 שנים כ- 9 דקות קריאה
קוברנטיס: Deployments ו ReplicaSets
בפוסט הקודם על קוברנטיס, הגדרנו Pod – יחידת ההרצה הקטנה ביותר בקוברנטיס.
סיימנו בכך ש Pod שנוצר בגפו, מה שקרוי ״Naked Pod״ – ואיננו בעל שרידות גבוהה: אם הוא קרס, או ה node שבו הוא רץ קרס / נסגר – קוברנטיס לא יחדש אותו.
בפוסט הזה נציג סמנטיקה מרכזית נוספת בעבודה עם קוברנטיס: ה Deployment – המכמיסה בתוכה סמנטיקה בשם ReplicaSet אותה נסביר גם כן. הסמנטיקות הללו הן מה שמאפשרות להגדיר/לעדכן pods כך שיהיו resilient. זוהי תכונה קריטית שאנו מקבלים מקוברנטיס, ולא היינו מקבלים מ Dokcer לבדו.
בואו נתחיל.
Resilient Pods
בכדי ליהנות מיכולת מתקדמות של קוברנטיס כגון Self-Healing ו Auto-Scaling – עלינו להימנע מהגדרה של naked pods, ולהשתמש בסמנטיקה בשם ReplicaSet.
ReplicaSet הוא המקבילה של AWS Auto-Scaling-Group, מנגנון המוודא שמספר ה Pods שרצים:
- לא יורד ממספר מסוים (Auto-Healing)
- עולה בצורה דינאמית כאשר יש עומס על ה Pods (למשל: CPU utilization מעל 60%) – עד גבול מסוים שהגדרנו, ויורד בחזרה – כאשר העומס חולף.
למי שעבד עם שירותי-ענן, הצורך הזה אמור להיות ברור כשמש.
בעבודה עם קוברנטיס, אנחנו לא נעבוד בד"כ עם ReplicaSet, אלא עם סמנטיקה גבוהה יותר בשם Deployment.
Deployment היא בעצם הסמנטיקה לעבוד איתה.
![]() תרשים קונספטואלי ולא מדויק. נדייק אותו בהמשך. |
מה Deployment מוסיף על ReplicaSet? במחשבה ראשונה נשמע שאנו זקוקים רק ל ReplicaSet.
ובכן, בואו ניקח תסריט מאוד נפוץ ויומיומי: עדכון Pod לגרסה חדשה יותר. ביצענו שינוי במיקרו-שירות הרץ כ Pod, ואנו רוצים לעדכן את הקוד שלו.
כפי שאנחנו זוכרים, צורת העבודה (המומלצת) עם קוברנטיס היא הגדרה דקלרטיבית:
- אנו מגדירים מצב רצוי
- קוברנטיס דוגם כל הזמן את המצב המערכת, ואם יש פעם בין המצב הנוכחי למצב הרצוי – היא פועלת לסגירת הפער.
נניח שיש לנו 3 Pod replicas של מיקרו-שירות, הרצים כולם בגרסה 11 (למשל: build number). אנו רוצים לעדכן אותם לגרסה 12.
אם פשוט נעדכן את קוברניס שאנו רוצים את ה Pod replicas בגרסה 12, עלול לקרות המקרה המאוד-לא-רצוי הבא:
- קוברנטיס רואה שלא רוצים יותר את גרסה 11 – הוא מכבה את כל ה Pod replicas בגרסה הזו.
- לא טוב! מרגע זה אנחנו ב downtime.
- קוברנטיס רואה שרוצים את גרסה 12 – הוא מתחיל להריץ Pod replicas של הגרסה הזו.
- אבוי! בגרסה 12 יש באג בקוד, והמיקרו-שירות לא מצליח לרוץ.
- קוברנטיס יוצר Log מפורט וברור של הבעיה – אבל עד שלא נטפל בה בעצמנו – אנחנו ב Full Downtime.
זה בהחלט לא תסריט שאנו רוצים שיהיה אפשרי בסביבת Production!
Deployment, לשמחתנו, מוסיף את היכולות / האחריות הבאה:
- ביצוע סדר הפעולות בצורה נכונה – כך שתמיד יהיו מספיק Pods שרצים.
- בחינת (probing) ה Pod replicas החדשים – ו rollback במקרה של בעיה.
- למשל: בדוק שה pod replica החדש שעלה הוא תקין (ע״פ תנאים מסוימים, מה שנקרא Readiness check) – לפני שאתה מעלה עוד pod replicas מהגרסה החדשה או מוריד עוד ישנים.
- ישנן מגוון הגדרות בכדי לשלוט בהתנהגות המדויקת.
בתרשים למעלה ערבבתי, לצורך הפשטות, בין entities שהם קונפיגורציה (באפור: deployment, replica set) לבין entities של זמן-ריצה (בכתום: Pod replica). אני רוצה כעת לדייק את הדברים בעזרת התרשים הבא:
קונספט ארכיטקטוני מרכזי בקוברנטיס הוא ה Controllers, שהם בעצם מעין Modules או Plug-Ins של סביבת הניהול של קוברנטיס (ה Control Plane). ה Controllers מאזינים ל API Server הפנימי של קוברנטיס אחר שינויים על ה Resource הרלוונטי להם (Deployment, Service, וכו') או שינויים בדרישות – ואז מחילים את שינויים נדרשים.
בעיקרון ה Controllers רצים ב Reconciliation loop (כמו event loop) שנקרא כך בגלל שכל פעם שיש אירוע (שינוי שנדרש) הם מבצעים פעולה על מנת "ליישר קו" (reconcile) וכך, לעתים בצעדים, ה loop דואג שכל הזמן המצב בשטח יגיע במהרה למצב שהוגדר. מדי כמה זמן הם בודקים את סה"כ ההגדרות ומגיבים לפער – אם קיים. כלומר: גם אם event מסוים פוספס / לא טופל – תוך זמן קצר ה controller יגלה את הפער וישלים אותו.
בניגוד להפשטה המוצגת בתרשים למעלה, Controllers לעולם לא יתקשרו ישירות אחד עם השני (אחרת: הם לא יהיו Pluggable). כל קריאה היא ל API Server, למשל "צור משאב מסוג Replication", שבתורו יפעיל אירוע מתאים שייקלט ע"י ה Replication Controller ויוביל לפעולה.
אנחנו נראה בהמשך את ההגדרות של ה Deployment ואת ה template המדובר.
הנה תיאור דינאמי של תהליך ה deployment:
ה Deployment ישמור את ה replica set הקודם (ועליו כל ההגדרות), על מנת לאפשר תהליך של Rollback.
כמובן שההתנהגות המדויקת של שלב ה Mid (ליתר דיוק: סדרת שלבי ה mid) תלויה מאוד באסטרטגיית ה Deployment שנבחרת.
אני מניח שאתם מכירים את 2 האסטרטגיות המרכזיות, המקובלות בכלל בתעשייה: Rolling deployment ו Blue/Green deployment.
קוברנטיס לא תומך היום (בעת כתיבת הפוסט) ב Blue/Green deployments כיכולת-ליבה (אם כי יש מדריכים שמראים כיצד ניתן להשיג זאת, על בסיס יכולות קיימות של קוברנטיס – הנה דוגמה לאחד).
בכלל, כל נושא ה deployments הוא GA רק מגרסה 1.9 (תחילת 2018). כיום קוברנטיס תומך רק באסטרטגיות: "Recreate" (אסטרטגיה סופר פשוטה, בה יש downtime בהגדרה) ו "RollingUpdate" (ברירת המחדל).
הנה שני מקורות, המציגים כ"א כמה תסריטי deployment אפשריים, בהתבסס על יכולות הליבה של קוברנטיס:
- מצגת של Cloud Native Computing Foundation (בקיצור: CNCF) – הארגון שהוקם על מנת לקחת אחריות על פרויקט קוברנטיס, ולנהל אותו כ Open Source בלתי-תלוי. יש גם סיכום one-pager של האסטרטגיות שהוא מציג.
- פוסט של ארגון בשם Container Solutions – שגם הוא מציג בצורה ויזואלית יפה, את משמעות האסטרטגיות השונות (סט דומה, אך לא זהה לקודם).
הגדרת Deployment בפועל
בלי לקשקש הרבה, נגדיר manifest של deployment:
apiVersion: apps/v1 kind: Deployment metadata: name: hello-deploy labels: app: hello-world-deployment spec: replicas: 4 selector: matchLabels: app: hello-world strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 minReadySeconds: 5 template: metadata: labels: app: hello-world env: dev version: v1 spec: containers: - name: hello-pod image: hello-world:latest ports: - containerPort: 8080 protocol: TCP
אתם כבר אמורים להכיר את 3 המפתחות הראשונים, ולכן נתמקד ב spec:
- את הגדרת ה pod ניתן למצוא תחת ה template והן זהות ל Pod שהגדרנו בפוסט הקודם (מלבד label אחד שהוספנו). שימו לב שה metadata של ה Pod מופיע תחת template, ולא במפתח ה metadata של ה manifest.
- replicas הוא מספר העותקים של ה pod שאנו רוצים להריץ.
- ה selector משמש לזיהוי חד משמעי של סוג ה pod שאנו מתארים. חשוב שניתן label "יציב" שבעזרתו קוברנטיס יידע לקשר ולהשוות אם היו שינויים בין pod templates. אם היה שינוי – עליו לבצע deploy.
- למשל: אם שינינו גם את label הגרסה וגם את ה image – איך אפשר לקשר בוודאות שמדובר באותו האפליקציה, ולא בחדשה?
- מקובל להשתמש ב label בשם app או app.kubernetes.io/name.
- ל labels ניתן להוסיף namespace וקוברנטיס מציע כמה שמות "סטנדרטים" ל labels. אני לא יודע כמה השימוש בהם באמת נפוץ.
- על אסטרטגיית ה deployment כבר דיברנו. הנה 2 פרמטרים חשובים של אסטרטגית rolling deployment:
- maxUnavailable – הכמות המרבית המותרת של pods שאינם זמינים תוך כדי פעולת deploy. הדבר משפיע כמה pods קוברנטיס יכול "להוריד במכה" כאשר הוא מתקין גרסה חדשה. המספר מתייחס למספר ה pods התקינים בגרסה הישנה + החדשה ביחד, וברירת המחדל היא 25%. ניתן גם לקבוע 0.
- maxSurge – הוא פחות או יותר הפרמטר ההופכי: כמה pods חדשים ניתן להרים "במכה". ככל שהמספר גדול יותר – כך ה rolling deployment עשוי להיות מהיר יותר. גם כאן ברירת המחדל היא 25%.
בקיצור גמור: אם יש לנו 4 pod replicas, הערכים שקבענו ב manifest יבטיחו שה cluster תמיד יכיל בין 3 ל 5 pod replicas בזמן deployment.
- minReadySeconds – שדה רשות (ערך ברירת מחדל = 0) שמציין כמה שניות לחכות לאחר שה pod מוכן ("ready") לפני שמעבירים לו תעבורה. ההמתנה הזו היא פרקטיקה מקובלת, מכיוון שהנזק מ pod בעייתי שמחובר ל production traffic – עשוי להיות משמעותי. אפשר להיתקל גם בערכים של 20 ו 30 שניות. חשוב להזכיר שערך גבוה יאט את תהליך ה rolling deployment מכיוון שאנו ממתינים ל minReadySeconds – לפני שאנו ממשיכים להחליף עוד pods.
- כאן שווה להזכיר את ה Readiness & Liveliness Probes של קוברנטיס. קוברנטיס מריץ ב nodes רכיב טכני הרץ כ container ומבצע בדיקות Health-check על ה pods השונים ב node ומדווח את התוצאות הלאה. כל pod צריך לענות ל2 קריאות: well-known/live./ ו well-known/ready./
- מצב ה live הוא אות חיים בסיסי. המימוש המומלץ הוא פשוט להחזיר HTTP 200 OK מבלי לבצע פעולות נוספות. אם התשובה המתקבלת היא לא 2xx – קוברנטיס יאתחל את ה pod הזה מיד.
- מצב ה ready אמור להיות עשיר יותר, בד"כ בודקים גישה לבסיס הנתונים ("SELECT 1") או גישה למשאבים קריטיים אחרים ל Pod (למשל: גישה לרדיס, או שירותים אחרים הקריטיים לפעילות ה pod). אם האפליקציה עוברת עדכון (למשל: הטמעת קונפיגורציה חדשה / עדכון caches ארוך) – הדרך הנכונה היא להגדיר אותה כ "לא ready" בזמן הזה.
אם התשובה ל ready היא שלילית, קוברנטיס עשויה לנתק אותו מתעבורה נכנסת עד שיסתדר. אם המצב מתמשך (ברירת המחדל = 3 כישלונות רצופים) – ה pod יעבור restart.- שגיאה נפוצה היא להגדיר את live ו ready אותו הדבר – אבל אז מערבבים פעולות live יקרות מדי, ו restarts מיותרים של pods (כי עשינו restart בעקבות live אחד שכשל, נניח – מתקלת רשת נקודתית ואקראית).
- כאן שווה להזכיר את ה Readiness & Liveliness Probes של קוברנטיס. קוברנטיס מריץ ב nodes רכיב טכני הרץ כ container ומבצע בדיקות Health-check על ה pods השונים ב node ומדווח את התוצאות הלאה. כל pod צריך לענות ל2 קריאות: well-known/live./ ו well-known/ready./
את המניפסט, כמובן, מחילים כרגיל:
$ kubectl apply -f my-deployment.yaml
נוכל לעקוב אחר מצב ה deployment בעזרת הפקודה:
$ kubectl get deployment hello-deploy
אם אנו מגלים שהגרסה לא טובה (נניח: עלה באג חדש ומשמעותי לפרודקשיין), אנו יכולים לבצע rollback. הפקודה:
$ kubectl rollout history deployment hello-deploy
תציג לנו רשימה של revisions של ה deployment. נניח שאנו רוצים לחזור לגרסה 1, עלינו לפקוד:
$ kubectl rollout undo deployment hello-deploy --to-revision=1
כאשר מדובר ב RollingDeployment, ה rollback כמובן הוא deploy בפני עצמו שייקח זמן, ויעבוד לפי אותם הכללים של deploy חדש. תאורטית אנו יכולים פשוט לשנות את ה deployment.yaml בחזרה למצב הקודם ולבצע deploy חדש – אי כי זה פחות מומלץ, אפילו רק מטעמי תיעוד.
סיכום
סקרנו את משאבי ה ReplicaSet וה Deployment בקוברנטיס – הדרך העיקרית לעדכן את המערכת ב pods חדשים / מעודכנים, בצורה Resilient.
על הדרך, הרחבנו מעט את ההבנה כיצד קוברנטיס עובד.
עדיין, לאחר שביצענו deployment לא נוכל לגשת ל pods שלנו (בקלות הרצויה). לשם כך עלינו להגדיר עוד משאב-ליבה בקוברנטיס בשם Service, המתייחס ל"קבוצה של Pod replicas בעלי אותו הממשק".
סמנטיקת ה Service מתוכננת להיות הנושא לפוסט הבא בסדרה.
שיהיה בהצלחה!