פריסת שער מרובה אשכולות לאיזון עומסים לפי קיבולת

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

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

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

לפני שמתחילים

כדי לפרוס שערים מרובי-אשכולות, צריך לבצע הכנה סביבתית. לפני שממשיכים, פועלים לפי השלבים שמפורטים במאמר הכנת הסביבה לשימוש בשערי רשת מרובי-אשכולות:

  1. פריסה של אשכולות GKE.

  2. רושמים את האשכולות ב-Fleet (אם הם עדיין לא רשומים).

  3. מפעילים את בקרי השירות מרובה האשכולות והשער מרובה האשכולות.

לסיום, כדאי לעיין במגבלות ובבעיות הידועות של בקר GKE Gateway לפני שמשתמשים בבקר בסביבה שלכם.

פריסת איזון עומסים מבוסס-קיבולת

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

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

תנועה שגולשת מקלאסטר אחד לקלאסטר אחר

הכנת הסביבה

  1. כדי להכין את הסביבה, פועלים לפי ההוראות במאמר הכנת הסביבה לשערי כניסה בכמה אשכולות.

  2. מוודאים שמשאבי GatewayClass מותקנים באשכול ההגדרות:

    kubectl get gatewayclasses --context=gke-west-1
    

    הפלט אמור להיראות כך:

    NAME                                  CONTROLLER                  ACCEPTED   AGE
    gke-l7-global-external-managed        networking.gke.io/gateway   True       16h
    gke-l7-global-external-managed-mc     networking.gke.io/gateway   True       14h
    gke-l7-gxlb                           networking.gke.io/gateway   True       16h
    gke-l7-gxlb-mc                        networking.gke.io/gateway   True       14h
    gke-l7-regional-external-managed      networking.gke.io/gateway   True       16h
    gke-l7-regional-external-managed-mc   networking.gke.io/gateway   True       14h
    gke-l7-rilb                           networking.gke.io/gateway   True       16h
    gke-l7-rilb-mc                        networking.gke.io/gateway   True       14h
    

פריסת אפליקציה

פורסים את שרת אפליקציית האינטרנט לדוגמה לשני האשכולות:

kubectl apply --context gke-west-1 -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/master/gateway/docs/store-traffic-deploy.yaml
kubectl apply --context gke-east-1 -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/master/gateway/docs/store-traffic-deploy.yaml

הפלט אמור להיראות כך:

namespace/store created
deployment.apps/store created

פריסה של Service,‏ Gateway ו-HTTPRoute

  1. מחילים את המניפסט הבא של Service על האשכולות gke-west-1 ו-gke-east-1:

    cat << EOF | kubectl apply --context gke-west-1 -f -
    apiVersion: v1
    kind: Service
    metadata:
      name: store
      namespace: traffic-test
      annotations:
        networking.gke.io/max-rate-per-endpoint: "10"
    spec:
      ports:
      - port: 8080
        targetPort: 8080
        name: http
      selector:
        app: store
      type: ClusterIP
    ---
    kind: ServiceExport
    apiVersion: net.gke.io/v1
    metadata:
      name: store
      namespace: traffic-test
    EOF
    
    cat << EOF | kubectl apply --context gke-east-1 -f -
    apiVersion: v1
    kind: Service
    metadata:
      name: store
      namespace: traffic-test
      annotations:
        networking.gke.io/max-rate-per-endpoint: "10"
    spec:
      ports:
      - port: 8080
        targetPort: 8080
        name: http
      selector:
        app: store
      type: ClusterIP
    ---
    kind: ServiceExport
    apiVersion: net.gke.io/v1
    metadata:
      name: store
      namespace: traffic-test
    EOF
    

    השירות מסומן בהערה max-rate-per-endpoint עם הערך 10 בקשות לשנייה. עם 2 רפליקות לכל אשכול, לכל שירות יש קיבולת של 20 RPS לכל אשכול.

    מידע נוסף על בחירת רמת הקיבולת של השירות זמין במאמר קביעת הקיבולת של השירות.

  2. מחילים את המניפסט הבא של Gateway על אשכול ההגדרות, gke-west-1 בדוגמה הזו:

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: Gateway
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: store
      namespace: traffic-test
    spec:
      gatewayClassName: gke-l7-global-external-managed-mc
      listeners:
      - name: http
        protocol: HTTP
        port: 80
        allowedRoutes:
          kinds:
          - kind: HTTPRoute
    EOF
    

    קובץ המניפסט מתאר שער חיצוני גלובלי מרובה-אשכולות שמפריס מאזן עומסים של אפליקציות (ALB) חיצוני עם כתובת IP שנגישה לכולם.

  3. מחילים את מניפסט HTTPRoute הבא על אשכול ההגדרות, gke-west-1 בדוגמה הזו:

    cat << EOF | kubectl apply --context gke-west-1 -f -
    kind: HTTPRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: store
      namespace: traffic-test
      labels:
        gateway: store
    spec:
      parentRefs:
      - kind: Gateway
        namespace: traffic-test
        name: store
      rules:
      - backendRefs:
        - name: store
          group: net.gke.io
          kind: ServiceImport
          port: 8080
    EOF
    

    המניפסט מתאר HTTPRoute שמגדיר את השער עם כלל ניתוב שמפנה את כל התנועה אל ServiceImport של החנות. ‫store ServiceImport מקבץ את ה-Pods של store Service בשני האשכולות, ומאפשר למאזן העומסים להתייחס אליהם כאל שירות אחד.

    אחרי כמה דקות, אפשר לבדוק את האירועים של השער כדי לראות אם הפריסה הסתיימה:

    kubectl describe gateway store -n traffic-test --context gke-west-1
    

    הפלט אמור להיראות כך:

    ...
    Status:
      Addresses:
        Type:   IPAddress
        Value:  34.102.159.147
      Conditions:
        Last Transition Time:  2023-10-12T21:40:59Z
        Message:               The OSS Gateway API has deprecated this condition, do not depend on it.
        Observed Generation:   1
        Reason:                Scheduled
        Status:                True
        Type:                  Scheduled
        Last Transition Time:  2023-10-12T21:40:59Z
        Message:
        Observed Generation:   1
        Reason:                Accepted
        Status:                True
        Type:                  Accepted
        Last Transition Time:  2023-10-12T21:40:59Z
        Message:
        Observed Generation:   1
        Reason:                Programmed
        Status:                True
        Type:                  Programmed
        Last Transition Time:  2023-10-12T21:40:59Z
        Message:               The OSS Gateway API has altered the "Ready" condition semantics and reservedit for future use.  GKE Gateway will stop emitting it in a future update, use "Programmed" instead.
        Observed Generation:   1
        Reason:                Ready
        Status:                True
        Type:                  Ready
      Listeners:
        Attached Routes:  1
        Conditions:
          Last Transition Time:  2023-10-12T21:40:59Z
          Message:
          Observed Generation:   1
          Reason:                Programmed
          Status:                True
          Type:                  Programmed
          Last Transition Time:  2023-10-12T21:40:59Z
          Message:               The OSS Gateway API has altered the "Ready" condition semantics and reservedit for future use.  GKE Gateway will stop emitting it in a future update, use "Programmed" instead.
          Observed Generation:   1
          Reason:                Ready
          Status:                True
          Type:                  Ready
        Name:                    http
        Supported Kinds:
          Group:  gateway.networking.k8s.io
          Kind:   HTTPRoute
    Events:
      Type    Reason  Age                  From                   Message
      ----    ------  ----                 ----                   -------
      Normal  ADD     12m                  mc-gateway-controller  traffic-test/store
      Normal  SYNC    6m43s                mc-gateway-controller  traffic-test/store
      Normal  UPDATE  5m40s (x4 over 12m)  mc-gateway-controller  traffic-test/store
      Normal  SYNC    118s (x6 over 10m)   mc-gateway-controller  SYNC on traffic-test/store was a success
    

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

אישור התנועה

כדי לוודא שהתנועה מועברת לאפליקציה, בודקים את כתובת ה-IP של השער באמצעות פקודת curl:

curl GATEWAY_IP_ADDRESS

הפלט אמור להיראות כך:

{
  "cluster_name": "gke-west-1",
  "host_header": "34.117.182.69",
  "pod_name": "store-54785664b5-mxstv",
  "pod_name_emoji": "👳🏿",
  "project_id": "project",
  "timestamp": "2021-11-01T14:06:38",
  "zone": "us-west1-a"
}

בפלט הזה מוצגים המטא-נתונים של ה-Pod, שמציינים את האזור שממנו הבקשה טופלה.

אימות התנועה באמצעות בדיקות עומס

כדי לוודא שמאזן העומסים פועל, אפשר לפרוס מחולל תנועה באשכול gke-west-1. מחולל התנועה יוצר תנועה ברמות עומס שונות כדי להדגים את הקיבולת ואת יכולות הגלישה של מאזן העומסים. השלבים הבאים מראים שלוש רמות של עומס:

  • ‫10 בקשות לשנייה, פחות מהקיבולת של שירות החנות ב-gke-west-1.
  • ‫30 בקשות לשנייה, שחורגות מהקיבולת של שירות החנות gke-west-1 וגורמות לעומס תנועה ב-gke-east-1.
  • ‫60 בקשות לשנייה, שזה מעבר לקיבולת של השירותים בשני האשכולות.

הגדרת לוח הבקרה

  1. משיגים את השם של ה-URLmap הבסיסי של השער:

    kubectl get gateway store -n traffic-test --context=gke-west-1 -o=jsonpath="{.metadata.annotations.networking\.gke\.io/url-maps}"
    

    הפלט אמור להיראות כך:

    /projects/PROJECT_NUMBER/global/urlMaps/gkemcg1-traffic-test-store-armvfyupay1t
    
  2. נכנסים לדף Metrics explorer במסוף Google Cloud .

    כניסה ל-Metrics Explorer

  3. בקטע Select a metric (בחירת מדד), לוחצים על CODE: MQL (קוד: MQL).

  4. מזינים את השאילתה הבאה כדי לראות את מדדי התנועה של שירות החנות בשני האשכולות:

    fetch https_lb_rule
    | metric 'loadbalancing.googleapis.com/https/backend_request_count'
    | filter (resource.url_map_name == 'GATEWAY_URL_MAP')
    | align rate(1m)
    | every 1m
    | group_by [resource.backend_scope],
        [value_backend_request_count_aggregate:
            aggregate(value.backend_request_count)]
    

    מחליפים את GATEWAY_URL_MAP בשם של מיפוי ה-URL מהשלב הקודם.

  5. לוחצים על Run query. אחרי שמפעילים את מחולל העומסים (בקטע הבא), צריך להמתין לפחות 5 דקות עד שהמדדים יוצגו בתרשים.

בדיקה עם 10 בקשות לשנייה

  1. פורסים Pod באשכול gke-west-1:

    kubectl run --context gke-west-1 -i --tty --rm loadgen  \
        --image=cyrilbkr/httperf  \
        --restart=Never  \
        -- /bin/sh -c 'httperf  \
        --server=GATEWAY_IP_ADDRESS  \
        --hog --uri="/zone" --port 80  --wsess=100000,1,1 --rate 10'
    

    מחליפים את GATEWAY_IP_ADDRESS בכתובת ה-IP של השער מהשלב הקודם.

    הפלט אמור להיראות כך, ולציין שמחולל התנועה שולח תנועה:

    If you don't see a command prompt, try pressing enter.
    

    מחולל העומס שולח באופן רציף 10 בקשות לשנייה אל השער. למרות שתעבורת הנתונים מגיעה מתוך אזור Google Cloud , מאזן העומסים מתייחס אליה כתעבורת נתונים של לקוחות שמגיעה מהחוף המערבי של ארה"ב. כדי לדמות מגוון לקוחות ריאליסטי, מחולל העומסים שולח כל בקשת HTTP כחיבור TCP חדש, מה שאומר שהתנועה מתחלקת בין ה-Pods של ה-Backend בצורה אחידה יותר.

    יכול להיות שיחלפו עד 5 דקות עד שהתנועה תופיע במרכז הבקרה.

  2. פותחים את לוח הבקרה של Metrics explorer. מופיעות שתי שורות שמציינות כמה תנועה מאוזנת בעומס לכל אחד מהאשכולות:

    תרשים שבו מוצג עומס התנועה שמאוזן באשכולות

    אפשר לראות ש-us-west1-a מקבל כ-10 בקשות לשנייה של תנועה, וש-us-east1-b לא מקבל תנועה בכלל. מכיוון שגנרטור התנועה פועל ב-us-west1, כל התנועה נשלחת לשירות באשכול gke-west-1.

  3. מפסיקים את מחולל העומסים באמצעות Ctrl+C, ואז מוחקים את ה-pod:

    kubectl delete pod loadgen --context gke-west-1
    

בדיקה עם 30 בקשות לשנייה

  1. פורסים שוב את מחולל העומס, אבל מגדירים אותו לשלוח 30 בקשות לשנייה:

    kubectl run --context gke-west-1 -i --tty --rm loadgen  \
        --image=cyrilbkr/httperf  \
        --restart=Never  \
        -- /bin/sh -c 'httperf  \
        --server=GATEWAY_IP_ADDRESS  \
        --hog --uri="/zone" --port 80  --wsess=100000,1,1 --rate 30'
    

    למחולל לוקח עד 5 דקות ליצור תנועה למרכז הבקרה.

  2. צפייה במרכז הבקרה של Cloud Ops.

    תרשים שמראה תנועה שגולשת אל gke-east-1

    אמורים לראות שכ-20 בקשות לשנייה נשלחות אל us-west1-a ו-10 בקשות לשנייה נשלחות אל us-east1-b. המשמעות היא שהשירות ב-gke-west-1 נמצא בשימוש מלא, ו-10 בקשות לשנייה של תעבורת נתונים מועברות לשירות ב-gke-east-1.

  3. עוצרים את מחולל העומס באמצעות Ctrl+C ומוחקים את ה-Pod:

    kubectl delete pod loadgen --context gke-west-1
    

בדיקה עם 60 בקשות לשנייה

  1. פורסים את מחולל העומסים שהוגדר לשליחת 60 בקשות לשנייה:

    kubectl run --context gke-west-1 -i --tty --rm loadgen  \
        --image=cyrilbkr/httperf  \
        --restart=Never  \
        -- /bin/sh -c 'httperf  \
        --server=GATEWAY_IP_ADDRESS  \
        --hog --uri="/zone" --port 80  --wsess=100000,1,1 --rate 60'
    
  2. ממתינים 5 דקות וצופים בלוח הבקרה של Cloud Ops. עכשיו אמור להופיע ששני האשכולות מקבלים בערך 30 RPS. מכיוון שכל השירותים נמצאים בשימוש יתר ברחבי העולם, אין תנועה שגולשת מעבר לקיבולת והשירותים סופגים את כל התנועה שהם יכולים.

    תרשים שבו מוצג שימוש יתר בשירותים

  3. מפסיקים את מחולל העומסים באמצעות Ctrl+C, ואז מוחקים את ה-Pod:

    kubectl delete pod loadgen --context gke-west-1
    

הסרת המשאבים

אחרי שתסיימו את התרגילים במסמך הזה, בצעו את השלבים הבאים כדי להסיר את המשאבים ולמנוע חיובים לא רצויים בחשבון:

  1. מחיקת האשכולות.

  2. לבטל את הרישום של האשכולות מה-Fleet אם לא צריך לרשום אותם למטרה אחרת.

  3. השבתת התכונה multiclusterservicediscovery:

    gcloud container fleet multi-cluster-services disable
    
  4. השבתת Multi Cluster Ingress:

    gcloud container fleet ingress disable
    
  5. משביתים את ממשקי ה-API:

    gcloud services disable \
        multiclusterservicediscovery.googleapis.com \
        multiclusteringress.googleapis.com \
        trafficdirector.googleapis.com \
        --project=PROJECT_ID
    

פתרון בעיות

אין מקור בריא

הסימפטום:

הבעיה הבאה עשויה להתרחש כשיוצרים שער אבל אין גישה לשירותי הקצה העורפי (קוד תגובה 503):

no healthy upstream

הסיבה:

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

פתרון עקיף:

כדי לפתור את הבעיה, צריך להתאים אישית את בדיקת תקינות בהתאם לדרישות של האפליקציה (לדוגמה, /health) באמצעות HealthCheckPolicy.

המאמרים הבאים