במסמך הזה מוסבר איך לבצע פריסת כחול-ירוק (blue-green deployment) של אפליקציית storeדוגמה בשני אשכולות GKE. פריסות כחול-ירוק הן אסטרטגיה יעילה להעברת האפליקציות שלכם לאשכולות GKE חדשים עם סיכון מינימלי. באמצעות העברה הדרגתית של תעבורת הנתונים מהאשכול הנוכחי (כחול) לאשכול החדש (ירוק), אתם יכולים לאמת את הסביבה החדשה בסביבת הייצור לפני שאתם מתחייבים למעבר מלא.
במדריך הזה משתמשים באפליקציית store לדוגמה כדי לדמות תרחיש מהעולם האמיתי שבו שירות קניות אונליין נמצא בבעלות ובהפעלה של צוותים נפרדים, ונפרס בצי של אשכולות GKE משותפים.
לפני שמתחילים
כדי לפרוס שערים עם כמה אשכולות, צריך להכין את הסביבה. לפני שממשיכים, פועלים לפי השלבים שמפורטים בקטע הכנת הסביבה לשערים עם כמה אשכולות:
פריסה של אשכולות GKE.
רושמים את האשכולות ב-Fleet (אם הם עדיין לא רשומים).
מפעילים את בקרי השירות מרובי האשכולות והשער מרובי האשכולות.
לסיום, לפני שמשתמשים בבקר בסביבה שלכם, כדאי לעיין במגבלות ובבעיות הידועות של בקר GKE Gateway.
ניתוב כחול-ירוק מרובה אשכולות עם 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%-שיקוף תנועה: שיקוף תנועת המשתמשים לאשכול ה-canary. במהלך הבדיקה, 100% מתנועת המשתמשים מועתקת לאשכול הזה כדי לבדוק את הקיבולת שלו.
- 90%-10%: חלוקת תנועה מסוג Canary של 10% כדי לחשוף את האשכול החדש לתנועה חיה באופן הדרגתי.
- 0%-100%: מעבר חד למערכת אחרת (cutover) מלא לאשכול החדש עם אפשרות לחזור לאשכול הקודם אם מתגלות שגיאות.
הדוגמה הזו דומה לקודמת, אבל במקום זאת היא פורסת שער פנימי מרובה אשכולות. הפעולה הזו פורסת מאזן עומסים פנימי של אפליקציות (ALB) שאפשר לגשת אליו באופן פרטי רק מתוך ה-VPC. תשתמשו באשכולות ובאותו אפליקציה שפרסתם בשלבים הקודמים, אבל תפרסו אותם דרך שער שונה.
דרישות מוקדמות
הדוגמה הבאה מבוססת על חלק מהשלבים במאמר בנושא פריסת שער חיצוני מרובה אשכולות. לפני שמתחילים ליישם את הדוגמה הזו, חשוב לוודא שביצעתם את הפעולות הבאות:
-
בדוגמה הזו נעשה שימוש באשכולות
gke-west-1ו-gke-west-2שכבר הגדרתם. האשכולות האלה נמצאים באותו אזור כיgke-l7-rilb-mcGatewayClass הוא אזורי ותומך רק בבק-אנד של אשכולות באותו אזור. פורסים את Service ואת 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 בלבד
אם עדיין לא עשיתם זאת, הגדירו רשת משנה של שרתי proxy בלבד לכל אזור שבו אתם פורסים שערים פנימיים. תת-הרשת הזו משמשת להקצאת כתובות IP פנימיות לשרתי ה-proxy של מאזן העומסים, וחובה להגדיר אותה עם --purpose שמוגדר ל-REGIONAL_MANAGED_PROXY בלבד.
לפני שיוצרים שערים שמנהלים מאזני עומסים של אפליקציות (ALB) פנימיים, צריך ליצור רשת משנה של פרוקסי בלבד. לכל אזור ברשת של ענן וירטואלי פרטי (VPC) שבו אתם משתמשים במאזני עומסים של אפליקציות (ALB) פנימיים, צריכה להיות תת-רשת של פרוקסי בלבד.
הפקודה gcloud compute networks subnets create יוצרת רשת משנה של שרתי proxy בלבד.
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בדוגמה הזו: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בדוגמה הזו: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" }
שיקוף תנועה
בשלב הזה, תעבורת הנתונים נשלחת לאשכול המיועד, אבל גם משוכפלת לאשכול ה-Canary.
שיקוף תנועה עוזר לקבוע איך עומס התנועה ישפיע על ביצועי האפליקציה, בלי להשפיע על התגובות ללקוחות. יכול להיות שלא תצטרכו להשתמש בזה בכל סוגי ההשקות, אבל זה יכול להיות שימושי כשמשיקים שינויים גדולים שיכולים להשפיע על הביצועים או על העומס.
מחילים את המניפסט הבא של
HTTPRouteעל אשכול ההגדרות,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 -
חלוקת התנועה
פיצול תנועה הוא אחת מהשיטות הנפוצות ביותר להשקת קוד חדש או לפריסה בסביבות חדשות בצורה בטוחה. בעל השירות מגדיר אחוז תנועה מפורש שנשלח לשרתי הקצה של קנרי. בדרך כלל מדובר בכמות קטנה מאוד מכלל התנועה, כדי שאפשר יהיה לקבוע את הצלחת ההשקה עם סיכון מקובל לבקשות של משתמשים אמיתיים.
פיצול תעבורה עם חלק קטן מהתעבורה מאפשר לבעלי השירות לבדוק את תקינות האפליקציה ואת התגובות. אם כל האותות נראים תקינים, אפשר להמשיך למעבר המלא.
מחילים את מניפסט
HTTPRouteהבא על אשכול ההגדרות,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 הוא מעבר מלא לאשכול החדש והסרת האשכול הישן. אם בעלי השירות היו מוסיפים אשכול שני לאשכול קיים, השלב האחרון היה שונה כי התנועה הייתה מגיעה לשני האשכולות. במקרה כזה, מומלץ להשתמש ב-ServiceImportstore יחיד שיש לו Pods משני האשכולות gke-west-1 ו-gke-west-2. כך מאזן העומסים יכול להחליט לאן להפנות את התנועה באפליקציה פעילה-פעילה, על סמך הקרבה, התקינות והקיבולת.
מחילים את המניפסט הבא של
HTTPRouteעל אשכול ההגדרות,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", ...
השלב האחרון הזה משלים העברה מלאה של אפליקציה כחולה-ירוקה מאשכול 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
פתרון בעיות
אין שרת upstream תקין
הסימפטום:
הבעיה הבאה יכולה להתרחש כשיוצרים שער אבל לא מצליחים לגשת לשירותים לקצה העורפי (קוד תגובה 503):
no healthy upstream
הסיבה:
הודעת השגיאה הזו מציינת שכלי הבדיקה של בדיקת תקינות לא יכול למצוא שירותי קצה עורפיים תקינים. יכול להיות שהשירותים שלכם בבק-אנד תקינים, אבל תצטרכו להתאים אישית את בדיקות התקינות.
פתרון עקיף:
כדי לפתור את הבעיה, התאימו אישית את בדיקת התקינות בהתאם לדרישות של האפליקציה (לדוגמה, /health) באמצעות HealthCheckPolicy.