במאמר הזה מוסבר איך לבצע פריסת כחול-ירוק של אפליקציית storeדוגמה בשני אשכולות GKE. פריסות כחול-ירוק הן אסטרטגיה יעילה להעברת האפליקציות שלכם לאשכולות GKE חדשים עם סיכון מינימלי. על ידי העברת תעבורת הנתונים בהדרגה מהאשכול הנוכחי (כחול) לאשכול החדש (ירוק), אתם יכולים לאמת את הסביבה החדשה בסביבת הייצור לפני שאתם מתחייבים למעבר חד למערכת אחרת (cutover).
במדריך הזה משתמשים באפליקציית store לדוגמה כדי לדמות תרחיש מהעולם האמיתי שבו שירות קניות אונליין נמצא בבעלות ובהפעלה של צוותים נפרדים, ונפרס בצי של אשכולות GKE משותפים.
לפני שמתחילים
כדי לפרוס שערים מרובי-אשכולות, צריך לבצע הכנה מסוימת של הסביבה. לפני שממשיכים, פועלים לפי השלבים שמפורטים במאמר הכנת הסביבה לשימוש בשערי גישה מרובי-אשכולות:
פריסה של אשכולות GKE.
רושמים את האשכולות ב-Fleet (אם הם עדיין לא רשומים).
מפעילים את בקרי השירות מרובי האשכולות והשער מרובה האשכולות.
לבסוף, מומלץ לעיין במגבלות ובבעיות הידועות של GKE Gateway Controller לפני שמשתמשים בבקר בסביבה שלכם.
ניתוב כחול-ירוק בין כמה אשכולות באמצעות Gateway
ל-GatewayClasses gke-l7-global-external-managed-*, gke-l7-regional-external-managed-* ו-gke-l7-rilb-* יש הרבה יכולות מתקדמות של ניתוב תנועה, כולל חלוקת תנועה, התאמת כותרות, שינוי כותרות, שיקוף תנועה ועוד. בדוגמה הזו נראה איך להשתמש בחלוקת תנועה מבוססת-משקל כדי לשלוט באופן מפורש בפרופורציית התנועה בין שני אשכולות GKE.
בדוגמה הזו מפורטים כמה שלבים ריאליים שבעלי שירות יבצעו כדי להעביר את האפליקציה שלהם לאשכול GKE חדש או להרחיב אותה לאשכול כזה. המטרה של פריסות כחולות-ירוקות היא לצמצם את הסיכון באמצעות מספר שלבי אימות שמאשרים שהאשכול החדש פועל בצורה תקינה. בדוגמה הזו מוסברים ארבעה שלבי פריסה:
- 100%-Header-based canary: Use HTTP header routing to send only test or synthetic traffic to the new cluster.
- 100%-Mirror traffic: Mirror user traffic to the canary cluster. בבדיקה הזו, המערכת מעתיקה 100% מתנועת המשתמשים לאשכול הזה כדי לבדוק את הקיבולת שלו.
- 90%-10%: חלוקת תנועה מסוג Canary של 10% כדי לחשוף את האשכול החדש לתנועה בזמן אמת באופן הדרגתי.
- 0%-100%: מעבר חד למערכת אחרת (cutover) מלא לאשכול החדש עם אפשרות לחזור לאשכול הקודם אם מתגלות שגיאות.
הדוגמה הזו דומה לקודמת, אבל במקום זאת היא פורסת שער פנימי מרובה אשכולות. הפעולה הזו פורסת מאזן עומסים פנימי של אפליקציות (ALB) שאפשר לגשת אליו באופן פרטי רק מתוך ה-VPC. תשתמשו באשכולות ובאותה אפליקציה שפרסתם בשלבים הקודמים, אבל תפרסו אותם דרך שער שונה.
דרישות מוקדמות
הדוגמה הבאה מבוססת על חלק מהשלבים במאמר בנושא פריסת שער חיצוני מרובה אשכולות. לפני שממשיכים עם הדוגמה הזו, חשוב לוודא שביצעתם את השלבים הבאים:
-
בדוגמה הזו נעשה שימוש באשכולות
gke-west-1ו-gke-west-2שכבר הגדרתם. האשכולות האלה נמצאים באותו אזור כיgke-l7-rilb-mcGatewayClass הוא אזורי ותומך רק בבק-אנד של אשכולות באותו אזור. פורסים את השירות ואת ServiceExports שנדרשים בכל אשכול. אם פרסתם את Services ו-ServiceExports מהדוגמה הקודמת, סימן שכבר פרסתם חלק מהם.
kubectl apply --context gke-west-1 -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/main/gateway/gke-gateway-controller/multi-cluster-gateway/store-west-1-service.yaml kubectl apply --context gke-west-2 -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/main/gateway/gke-gateway-controller/multi-cluster-gateway/store-west-2-service.yamlהוא פורס קבוצה דומה של משאבים לכל אשכול:
service/store created serviceexport.net.gke.io/store created service/store-west-2 created serviceexport.net.gke.io/store-west-2 created
הגדרת רשת משנה ל-Proxy בלבד
אם עדיין לא עשיתם זאת, הגדירו רשת משנה (subnet) של שרת proxy בלבד לכל אזור שבו אתם פורסים שערים פנימיים. תת-הרשת הזו משמשת להקצאת כתובות IP פנימיות לשרתי ה-proxy של מאזן העומסים, וחובה להגדיר אותה עם --purpose שמוגדר ל-REGIONAL_MANAGED_PROXY בלבד.
לפני שיוצרים שערים שמנהלים מאזני עומסים פנימיים של אפליקציות, צריך ליצור רשת משנה של פרוקסי בלבד. לכל אזור ברשת של ענן וירטואלי פרטי (VPC) שבו אתם משתמשים במאזני עומסים פנימיים של אפליקציות (ALB) צריכה להיות תת-רשת של שרת proxy בלבד.
הפקודה gcloud compute networks subnets create יוצרת רשת משנה של פרוקסי בלבד.
gcloud compute networks subnets create SUBNET_NAME \
--purpose=REGIONAL_MANAGED_PROXY \
--role=ACTIVE \
--region=REGION \
--network=VPC_NETWORK_NAME \
--range=CIDR_RANGE
מחליפים את מה שכתוב בשדות הבאים:
-
SUBNET_NAME: השם של תת-הרשת של ה-proxy בלבד. -
REGION: האזור של תת-הרשת של ה-proxy בלבד. -
VPC_NETWORK_NAME: השם של רשת ה-VPC שמכילה את תת-הרשת. -
CIDR_RANGE: טווח כתובות ה-IP הראשי של רשת המשנה. צריך להשתמש במסכה של רשת משנה בגודל של/26לכל היותר, כדי שיהיו לפחות 64 כתובות IP זמינות לשרתי proxy באזור. מסכה של רשת משנה מומלצת היא/23.
פריסת השער
השער הבא נוצר מ-gke-l7-rilb-mc GatewayClass, שהוא שער פנימי אזורי שיכול לטרגט רק אשכולות GKE באותו אזור.
מחילים את
Gatewayהמניפסטgke-west-1הבא על אשכול ההגדרות,gke-west-1בדוגמה הזו:cat << EOF | kubectl apply --context gke-west-1 -f - kind: Gateway apiVersion: gateway.networking.k8s.io/v1 metadata: name: internal-http namespace: store spec: gatewayClassName: gke-l7-rilb-mc listeners: - name: http protocol: HTTP port: 80 allowedRoutes: kinds: - kind: HTTPRoute EOFמוודאים שהשער הופעל בהצלחה. כדי לסנן רק את האירועים מהשער הזה, משתמשים בפקודה הבאה:
kubectl get events --field-selector involvedObject.kind=Gateway,involvedObject.name=internal-http --context=gke-west-1 --namespace storeאם הפלט דומה לזה שמופיע בהמשך, סימן שהפריסה של שער הנתונים הושלמה בהצלחה:
LAST SEEN TYPE REASON OBJECT MESSAGE 5m18s Normal ADD gateway/internal-http store/internal-http 3m44s Normal UPDATE gateway/internal-http store/internal-http 3m9s Normal SYNC gateway/internal-http SYNC on store/internal-http was a success
גרסת גישוש מבוססת-כותרת
בדיקות קנרי שמבוססות על כותרות מאפשרות לבעלי השירות להתאים תנועת בדיקות סינתטית שלא מגיעה ממשתמשים אמיתיים. זו דרך קלה לוודא שהרשת הבסיסית של האפליקציה פועלת בלי לחשוף את המשתמשים ישירות.
מחילים את
HTTPRouteהמניפסטgke-west-1הבא על אשכול ההגדרות,gke-west-1בדוגמה הזו:cat << EOF | kubectl apply --context gke-west-1 -f - kind: HTTPRoute apiVersion: gateway.networking.k8s.io/v1 metadata: name: internal-store-route namespace: store labels: gateway: internal-http spec: parentRefs: - kind: Gateway namespace: store name: internal-http hostnames: - "store.example.internal" rules: # Matches for env=canary and sends it to store-west-2 ServiceImport - matches: - headers: - name: env value: canary backendRefs: - group: net.gke.io kind: ServiceImport name: store-west-2 port: 8080 # All other traffic goes to store-west-1 ServiceImport - backendRefs: - group: net.gke.io kind: ServiceImport name: store-west-1 port: 8080 EOFאחרי הפריסה, ה-HTTPRoute הזה מגדיר את התנהגות הניתוב הבאה:
- בקשות פנימיות אל
store.example.internalללא כותרת ה-HTTPenv: canaryמנותבות אל תרמיליstoreבאשכולgke-west-1 - בקשות פנימיות אל
store.example.internalעם כותרת ה-HTTPenv: canaryמנותבות אל תרמיליstoreב-clustergke-west-2
כדי לוודא ש-HTTPRoute פועל בצורה תקינה, שולחים תנועה לכתובת ה-IP של Gateway.
- בקשות פנימיות אל
מאחזרים את כתובת ה-IP הפנימית מ-
internal-http.kubectl get gateways.gateway.networking.k8s.io internal-http -o=jsonpath="{.status.addresses[0].value}" --context gke-west-1 --namespace storeמחליפים את VIP בשלבים הבאים בכתובת ה-IP שמתקבלת כפלט.
שליחת בקשה ל-Gateway באמצעות כותרת ה-HTTP
env: canary. כך תוכלו לוודא שתעבורת הנתונים מנותבת אלgke-west-2. כדי לוודא שהבקשות מנותבות בצורה נכונה, אפשר להשתמש בלקוח פרטי באותו VPC כמו אשכולות GKE. צריך להריץ את הפקודה הבאה במכונה שיש לה גישה פרטית לכתובת ה-IP של השער, אחרת היא לא תפעל.curl -H "host: store.example.internal" -H "env: canary" http://VIPהפלט מאשר שהבקשה הוגשה על ידי Pod מהאשכול
gke-west-2:{ "cluster_name": "gke-west-2", "host_header": "store.example.internal", "node_name": "gke-gke-west-2-default-pool-4cde1f72-m82p.c.agmsb-k8s.internal", "pod_name": "store-5f5b954888-9kdb5", "pod_name_emoji": "😂", "project_id": "agmsb-k8s", "timestamp": "2021-05-31T01:21:55", "zone": "us-west1-a" }
שיקוף תנועה
בשלב הזה התנועה נשלחת לאשכול היעד, אבל היא גם משוכפלת לאשכול הקנרי.
שימוש בשיקוף עוזר לקבוע איך עומס התנועה ישפיע על ביצועי האפליקציה בלי להשפיע על התגובות ללקוחות. יכול להיות שלא תצטרכו את זה לכל סוגי ההשקות, אבל זה יכול להיות שימושי כשמשיקים שינויים גדולים שיכולים להשפיע על הביצועים או על הטעינה.
מחילים את
HTTPRouteהמניפסטgke-west-1הבא על אשכול ההגדרות,gke-west-1בדוגמה הזו:cat << EOF | kubectl apply --context gke-west-1 -f - kind: HTTPRoute apiVersion: gateway.networking.k8s.io/v1 metadata: name: internal-store-route namespace: store labels: gateway: internal-http spec: parentRefs: - kind: Gateway namespace: store name: internal-http hostnames: - "store.example.internal" rules: # Sends all traffic to store-west-1 ServiceImport - backendRefs: - name: store-west-1 group: net.gke.io kind: ServiceImport port: 8080 # Also mirrors all traffic to store-west-2 ServiceImport filters: - type: RequestMirror requestMirror: backendRef: group: net.gke.io kind: ServiceImport name: store-west-2 port: 8080 EOFבאמצעות הלקוח הפרטי, שולחים בקשה ל
internal-httpGateway. משתמשים בנתיב/mirrorכדי לזהות באופן ייחודי את הבקשה הזו ביומני האפליקציה בשלב מאוחר יותר.curl -H "host: store.example.internal" http://VIP/mirrorהפלט מאשר שהלקוח קיבל תגובה מ-Pod באשכול
gke-west-1:{ "cluster_name": "gke-west-1", "host_header": "store.example.internal", "node_name": "gke-gke-west-1-default-pool-65059399-ssfq.c.agmsb-k8s.internal", "pod_name": "store-5f5b954888-brg5w", "pod_name_emoji": "🎖", "project_id": "agmsb-k8s", "timestamp": "2021-05-31T01:24:51", "zone": "us-west1-a" }כך מוודאים שהאשכול הראשי מגיב לתנועת נתונים. עדיין צריך לוודא שהתנועה המשוכפלת מגיעה לאשכול שאליו מבצעים את ההעברה.
בודקים את יומני האפליקציות של Pod
storeבאשכולgke-west-2. היומנים צריכים לאשר שה-Pod קיבל תנועה משוכפלת ממאזן העומסים.kubectl logs deployment/store --context gke-west-2 -n store | grep /mirrorהפלט הזה מאשר שגם רכיבי ה-Pod באשכול
gke-west-2מקבלים את אותן בקשות, אבל התגובות שלהם לבקשות האלה לא נשלחות בחזרה ללקוח. כתובות ה-IP שמופיעות ביומנים הן כתובות ה-IP הפנימיות של מאזן העומסים, שמתקשרות עם ה-Pods שלכם.Found 2 pods, using pod/store-5c65bdf74f-vpqbs [2023-10-12 21:05:20,805] INFO in _internal: 192.168.21.3 - - [12/Oct/2023 21:05:20] "GET /mirror HTTP/1.1" 200 - [2023-10-12 21:05:27,158] INFO in _internal: 192.168.21.3 - - [12/Oct/2023 21:05:27] "GET /mirror HTTP/1.1" 200 - [2023-10-12 21:05:27,805] INFO in _internal: 192.168.21.3 - - [12/Oct/2023 21:05:27] "GET /mirror HTTP/1.1" 200 -
חלוקת התנועה
פיצול תנועה הוא אחת השיטות הנפוצות ביותר להטמעה של קוד חדש או לפריסה בסביבות חדשות בצורה בטוחה. בעל השירות מגדיר אחוז מפורש של תנועה שנשלחת לקצה העורפי של גרסה ראשונית (canary). בדרך כלל מדובר בכמות קטנה מאוד של תנועת הגולשים הכוללת, כדי שאפשר יהיה לקבוע את הצלחת ההשקה עם רמת סיכון מקובלת לבקשות של משתמשים אמיתיים.
ביצוע פיצול תעבורה עם חלק קטן מהתעבורה מאפשר לבעלי השירות לבדוק את תקינות האפליקציה ואת התגובות. אם כל האותות נראים תקינים, אפשר להמשיך למעבר חד למערכת אחרת (cutover) המלא.
מחילים את
HTTPRouteהמניפסטgke-west-1הבא על אשכול ההגדרות,gke-west-1בדוגמה הזו:cat << EOF | kubectl apply --context gke-west-1 -f - kind: HTTPRoute apiVersion: gateway.networking.k8s.io/v1 metadata: name: internal-store-route namespace: store labels: gateway: internal-http spec: parentRefs: - kind: Gateway namespace: store name: internal-http hostnames: - "store.example.internal" rules: - backendRefs: # 90% of traffic to store-west-1 ServiceImport - name: store-west-1 group: net.gke.io kind: ServiceImport port: 8080 weight: 90 # 10% of traffic to store-west-2 ServiceImport - name: store-west-2 group: net.gke.io kind: ServiceImport port: 8080 weight: 10 EOFבאמצעות הלקוח הפרטי, שולחים בקשת curl רציפה אל שער
internal- http.while true; do curl -H "host: store.example.internal" -s VIP | grep "cluster_name"; sleep 1; doneהפלט ייראה כך, ויציין שמתבצעת חלוקת תנועה של 90/10.
"cluster_name": "gke-west-1", "cluster_name": "gke-west-1", "cluster_name": "gke-west-1", "cluster_name": "gke-west-1", "cluster_name": "gke-west-1", "cluster_name": "gke-west-1", "cluster_name": "gke-west-1", "cluster_name": "gke-west-1", "cluster_name": "gke-west-2", "cluster_name": "gke-west-1", "cluster_name": "gke-west-1", ...
העברת התנועה
השלב האחרון בהעברה מסוג blue-green הוא מעבר מלא לאשכול החדש והסרת האשכול הישן. אם בעל השירות באמת מפעיל אשכול שני באשכול קיים, השלב האחרון יהיה שונה כי התנועה תעבור לשני האשכולות. במקרה כזה, מומלץ להשתמש ב-store ServiceImport יחיד עם Pods מאשכולות gke-west-1 ו-gke-west-2. כך מאזן העומסים יכול להחליט לאן התנועה צריכה להגיע באפליקציה פעילה-פעילה, על סמך הקרבה, התקינות והקיבולת.
מחילים את
HTTPRouteהמניפסטgke-west-1הבא על אשכול ההגדרות,gke-west-1בדוגמה הזו:cat << EOF | kubectl apply --context gke-west-1 -f - kind: HTTPRoute apiVersion: gateway.networking.k8s.io/v1 metadata: name: internal-store-route namespace: store labels: gateway: internal-http spec: parentRefs: - kind: Gateway namespace: store name: internal-http hostnames: - "store.example.internal" rules: - backendRefs: # No traffic to the store-west-1 ServiceImport - name: store-west-1 group: net.gke.io kind: ServiceImport port: 8080 weight: 0 # All traffic to the store-west-2 ServiceImport - name: store-west-2 group: net.gke.io kind: ServiceImport port: 8080 weight: 100 EOFבאמצעות הלקוח הפרטי, שולחים בקשת curl רציפה אל שער
internal- http.while true; do curl -H "host: store.example.internal" -s VIP | grep "cluster_name"; sleep 1; doneהפלט ייראה בערך כך, ויציין שכל התנועה מועברת עכשיו אל
gke-west-2."cluster_name": "gke-west-2", "cluster_name": "gke-west-2", "cluster_name": "gke-west-2", "cluster_name": "gke-west-2", ...
בשלב האחרון הזה משלימים העברה מלאה של אפליקציה בשיטת blue-green מאשכול GKE אחד לאשכול GKE אחר.
הסרת המשאבים
אחרי שתסיימו את התרגילים במסמך הזה, תצטרכו לפעול לפי השלבים הבאים כדי להסיר משאבים ולמנוע חיובים לא רצויים בחשבון:
ביטול הרישום של האשכולות ב-Fleet אם אין צורך לרשום אותם למטרה אחרת.
השבתת התכונה
multiclusterservicediscovery:gcloud container fleet multi-cluster-services disableהשבתת Multi Cluster Ingress:
gcloud container fleet ingress disableמשביתים את ממשקי ה-API:
gcloud services disable \ multiclusterservicediscovery.googleapis.com \ multiclusteringress.googleapis.com \ trafficdirector.googleapis.com \ --project=PROJECT_ID
פתרון בעיות
אין מקור תקין
התסמין:
יכולה להתרחש הבעיה הבאה כשיוצרים Gateway אבל אין גישה לשירותי ה-Backend (קוד תגובה 503):
no healthy upstream
הסיבה:
הודעת השגיאה הזו מציינת שכלי הבדיקה של בדיקת תקינות לא יכול למצוא שירותי קצה עורפיים תקינים. יכול להיות שהשירותים שלכם ב-Backend תקינים, אבל תצטרכו להתאים אישית את בדיקות התקינות.
פתרון עקיף:
כדי לפתור את הבעיה, מתאימים אישית את בדיקת התקינות בהתאם לדרישות של האפליקציה (לדוגמה, /health) באמצעות HealthCheckPolicy.