התאמה אופקית של קבוצות Pod לעומס (HPA)

במאמר הזה מוסבר איך להפעיל שינוי אוטומטי של מספר העותקים של Pod אופקית (HPA) בשירות המנוהל של Google Cloud ל-Prometheus. כדי להפעיל את HPA, מבצעים אחת מהפעולות הבאות:

אי אפשר להשתמש ב-Stackdriver Adapter וב-Prometheus Adapter ביחד באותו אשכול כי יש חפיפה בהגדרות המשאבים שלהם, כמו שמתואר בקטע פתרון בעיות. מומלץ לבחור רק פתרון אחד ל-HPA.

שימוש ב-KEDA

‫KEDA (Kubernetes Event-driven Autoscaling) הוא הכלי האחרון להתאמה אוטומטית לעומס שמשתמש במדדים של Prometheus, והוא הופך לפתרון מועדף בקהילת Prometheus.

כדי להתחיל, אפשר לעיין במסמכי העזרה של KEDA בנושא שילוב עם השירות המנוהל של Google Cloud ל-Prometheus.

שימוש במתאם של Stackdriver למדדים מותאמים אישית

המתאם של Stackdriver למדדים מותאמים אישית תומך בשליחת שאילתות למדדים מ-שירות מנוהל ל-Prometheus החל מגרסה v0.13.1 של המתאם.

כדי להגדיר דוגמה להגדרת HPA באמצעות Custom Metrics Stackdriver Adapter, מבצעים את הפעולות הבאות:

  1. מגדירים אוסף מנוהל באשכול.
  2. מתקינים את Custom Metrics Stackdriver Adapter באשכול.

    kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter_new_resource_model.yaml
    
  3. פורסים כלי לייצוא מדדים של Prometheus לדוגמה ומשאב HPA:

    kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/examples/prometheus-to-sd/custom-metrics-prometheus-sd.yaml
    

    הפקודה הזו פורסת אפליקציית ייצוא שפולטת את המדד foo ומשאב HPA. ה-HPA משנה את קנה המידה של האפליקציה הזו ל-5 רפליקות כדי להשיג את ערך היעד של המדד foo.

  4. אם אתם משתמשים ב-Workload Identity Federation ל-GKE, אתם צריכים גם להעניק את התפקיד 'צפייה ב-Monitoring' לחשבון השירות שבו פועל המתאם. אפשר לדלג על השלב הזה אם לא הפעלתם איחוד זהויות של עומסי עבודה ל-GKE באשכול Kubernetes.

    export PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format 'get(projectNumber)')
    gcloud projects add-iam-policy-binding projects/PROJECT_ID \
      --role roles/monitoring.viewer \
      --member=principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/custom-metrics/sa/custom-metrics-stackdriver-adapter
    
  5. מגדירים משאב PodMonitoring על ידי הצבת ההגדרה הבאה בקובץ בשם podmonitoring.yaml.

    apiVersion: monitoring.googleapis.com/v1
    kind: PodMonitoring
    metadata:
      name: prom-example
    spec:
      selector:
        matchLabels:
          run: custom-metric-prometheus-sd
      endpoints:
      - port: 8080
        interval: 30s
    
  6. פורסים את משאב PodMonitoring החדש:

    kubectl -n default apply -f podmonitoring.yaml
    

    תוך כמה דקות, השירות המנוהל ל-Prometheus מעבד את המדדים שנאספו מה-exporter ומאחסן אותם ב-Cloud Monitoring באמצעות שם ארוך. המדדים של Prometheus מאוחסנים לפי המוסכמות הבאות:

    • הקידומת prometheus.googleapis.com.
    • הסיומת הזו היא בדרך כלל אחת מהאפשרויות הבאות: gauge,‏ counter,‏ summary או histogram. עם זאת, למדדים לא מוקלדים יכולה להיות הסיומת unknown או unknown:counter. כדי לוודא את הסיומת, מחפשים את המדד ב-Cloud Monitoring באמצעות Metrics Explorer.
  7. מעדכנים את ה-HPA שנפרס כדי לשלוח שאילתה למדד מ-Cloud Monitoring. המדד foo נקלט כמדד prometheus.googleapis.com/foo/gauge. כדי שאפשר יהיה להריץ שאילתות על המדד באמצעות משאב HorizontalPodAutoscaler שנפרס, צריך להשתמש בשם הארוך ב-HPA שנפרס, אבל צריך לשנות אותו ולהחליף את כל הקווים הנטויים (/) בתו צינור (|): prometheus.googleapis.com|foo|gauge. מידע נוסף זמין בקטע Metrics available from Stackdriver במאגר Custom Metrics Stackdriver Adapter.

    1. מריצים את הפקודה הבאה כדי לעדכן את ה-HPA שנפרס:

      kubectl edit hpa custom-metric-prometheus-sd
      
    2. משנים את הערך של השדה pods.metric.name מ-foo ל-prometheus.googleapis.com|foo|gauge. המקטע spec צריך להיראות כך:

      spec:
         maxReplicas: 5
         metrics:
         - pods:
             metric:
               name: prometheus.googleapis.com|foo|gauge
             target:
               averageValue: "20"
               type: AverageValue
           type: Pods
         minReplicas: 1
      

    בדוגמה הזו, הגדרת ה-HPA מחפשת את הערך הממוצע של המדד prometheus.googleapis.com/foo/gauge שיהיה 20. מכיוון שהפריסה מגדירה את ערך המדד כ-40, בקר ה-HPA מגדיל את מספר הפודים עד לערך של השדה maxReplicas (5) כדי לנסות להקטין את הערך הממוצע של המדד בכל הפודים ל-20.

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

  8. כדי לראות את שינוי הגודל של עומס העבודה, מריצים את הפקודה הבאה:

    kubectl get hpa custom-metric-prometheus-sd --watch
    

    הערך של השדה REPLICAS משתנה מ-1 ל-5.

    NAME                          REFERENCE                                TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
    custom-metric-prometheus-sd   Deployment/custom-metric-prometheus-sd   40/20          1         5         5          *
    
  9. כדי לצמצם את הפריסה, צריך לעדכן את ערך מדד היעד כך שיהיה גבוה יותר מערך המדד המיוצא. בדוגמה הזו, הפריסה מגדירה את הערך של המדד prometheus.googleapis.com/foo/gauge ל-40. אם מגדירים את ערך היעד למספר גבוה מ-40, הפריסה תצטמצם.

    לדוגמה, אפשר להשתמש ב-kubectl edit כדי לשנות את הערך של השדה pods.target.averageValue בהגדרת ה-HPA מ-20 ל-100.

    kubectl edit hpa custom-metric-prometheus-sd
    

    משנים את קטע המפרט כך שיתאים לערכים הבאים:

    spec:
      maxReplicas: 5
      metrics:
      - pods:
          metric:
            name: prometheus.googleapis.com|foo|gauge
          target:
            averageValue: "100"
            type: AverageValue
      type: Pods
      minReplicas: 1
    
  10. כדי לראות את צמצום העומס, מריצים את הפקודה הבאה:

    kubectl get hpa custom-metric-prometheus-sd --watch
    

    הערך של השדה REPLICAS משתנה מ-5 ל-1. כך זה אמור להיות, והתהליך הזה איטי יותר מאשר הגדלת מספר ה-pods:

    NAME                          REFERENCE                                TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
    custom-metric-prometheus-sd   Deployment/custom-metric-prometheus-sd   40/100          1         5         1          *
    
  11. כדי לנקות את הדוגמה שנפרסה, מריצים את הפקודות הבאות:

    kubectl delete -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter_new_resource_model.yaml
    kubectl delete -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/examples/prometheus-to-sd/custom-metrics-prometheus-sd.yaml
    kubectl delete podmonitoring/prom-example
    

מידע נוסף זמין בדוגמה ל-Prometheus במאגר של מתאם Stackdriver למדדים מותאמים אישית, או במאמר בנושא שינוי קנה מידה של אפליקציה.

שימוש ב-Prometheus Adapter

אפשר להשתמש בהגדרות קיימות של prometheus-adapter כדי להגדיר שינוי גודל אוטומטי עם כמה שינויים בלבד. להגדרת קנה מידה של prometheus-adapter באמצעות השירות המנוהל ל-Prometheus יש שתי הגבלות נוספות בהשוואה להגדרת קנה מידה באמצעות Prometheus במעלה הזרם:

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

כל השאילתות שמונפקות על ידי prometheus-adapter הן גלובליות בהיקף שלהן. המשמעות היא שאם יש לכם אפליקציות בשני מרחבי שמות שפולטות מדדים עם שמות זהים, הגדרת HPA שמשתמשת במדד הזה תבצע שינוי גודל על סמך נתונים משתי האפליקציות. מומלץ להשתמש תמיד במסננים namespace או cluster ב-PromQL כדי להימנע מסקיילינג באמצעות נתונים שגויים.

כדי להגדיר דוגמה להגדרת HPA באמצעות prometheus-adapter ואיסוף מנוהל, פועלים לפי השלבים הבאים:

  1. מגדירים אוסף מנוהל באשכול.
  2. פורסים את שרת ה-Proxy של ממשק המשתמש של Prometheus באשכול. אם אתם משתמשים באיחוד שירותי אימות הזהות של עומסי עבודה ב-GKE, אתם צריכים גם להגדיר חשבון שירות ולתת לו הרשאה.
  3. פורסים את קובצי המניפסט בספרייה examples/hpa/ במאגרprometheus-engine:
    • example-app.yaml: פריסה ושירות לדוגמה שפולטים מדדים.
    • pod-monitoring.yaml: משאב שמגדיר גירוד של מדדי הדוגמה.
    • hpa.yaml: משאב ה-HPA שמגדיר את שינוי הגודל של עומס העבודה.
  4. מוודאים שהתוסף prometheus-adapter מותקן באשכול. כדי לעשות את זה, צריך לפרוס את המניפסט לדוגמה להתקנה באשכול. קובץ המניפסט הזה מוגדר כך:

    • שליחת שאילתה לפרוקסי של קצה קדמי שנפרס במרחב השמות default.
    • מריצים שאילתת PromQL כדי לחשב ולהחזיר את המדד http_requests_per_second מהפריסה לדוגמה.
  5. מריצים את הפקודות הבאות, כל אחת בסשן מסוף נפרד:

    1. יוצרים עומס HTTP על שירות prometheus-example-app:
      kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://prometheus-example-app; done"
    2. צופים ב-Horizontal Pod Autoscaler:
      kubectl get hpa prometheus-example-app --watch
    3. צופים בהגדלת עומס העבודה:
      kubectl get po -lapp.kubernetes.io/name=prometheus-example-app --watch
  6. כדי להפסיק את יצירת העומס של HTTP, משתמשים ב-Ctrl+C וצופים בעומס העבודה מצטמצם.

פתרון בעיות

המתאם Custom Metrics Stackdriver Adapter משתמש בהגדרות משאבים עם אותם שמות כמו אלה שבמתאם Prometheus Adapter,‏ prometheus-adapter. החפיפה בשמות אומרת שהפעלת יותר ממתאם אחד באותו אשכול גורמת לשגיאות.

התקנת Prometheus Adapter באשכול שבו הותקן בעבר Custom Metrics Stackdriver Adapter עשויה להוביל לשגיאות כמו FailedGetObjectMetric בגלל שמות מתנגשים. כדי לפתור את הבעיה, יכול להיות שתצטרכו למחוק את v1beta1.external.metrics.k8s.io, את v1beta1.custom.metrics.k8s.io ואת v1beta2.custom.metrics.k8s.io apiservices שנרשמו קודם על ידי Custom Metrics Adapter.

טיפים לפתרון בעיות:

  • חלק ממדדי המערכת של Cloud Monitoring, כמו מדדי Pub/Sub, מתעדכנים באיחור של 60 שניות או יותר. הכלי Prometheus Adapter מריץ שאילתות באמצעות חותמת הזמן הנוכחית, ולכן יכול להיות ששאילתות על המדדים האלה באמצעות הכלי יחזירו תוצאה שגויה של 'אין נתונים'. כדי לשלוח שאילתות על מדדים מושהים, משתמשים במשנה offset ב-PromQL כדי לשנות את היסט הזמן של השאילתה בסכום הנדרש.

  • כדי לוודא שה-proxy של ממשק המשתמש בקצה הקדמי פועל כמצופה ואין בעיות בהרשאות, מריצים את הפקודה הבאה בטרמינל:

    kubectl -n NAMESPACE_NAME port-forward svc/frontend 9090
    

    לאחר מכן, פותחים טרמינל נוסף ומריצים את הפקודה הבאה:

    curl --silent 'localhost:9090/api/v1/series?match%5B%5D=up'
    

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

    curl --silent 'localhost:9090/api/v1/series?match%5B%5D=up' | jq .
    {
      "status": "success",
      "data": [
         ...
      ]
    }
    

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

  • כדי לוודא ששרת ה-API של המדדים בהתאמה אישית זמין, מריצים את הפקודה הבאה:

    kubectl get apiservices.apiregistration.k8s.io v1beta1.custom.metrics.k8s.io
    

    כשה-apiserver זמין, התגובה תיראה כך:

    $ kubectl get apiservices.apiregistration.k8s.io v1beta1.custom.metrics.k8s.io
    NAME                            SERVICE                         AVAILABLE   AGE
    v1beta1.custom.metrics.k8s.io   monitoring/prometheus-adapter   True        33m
    
  • כדי לוודא שה-HPA פועל כמצופה, מריצים את הפקודה הבאה:

    $ kubectl describe hpa prometheus-example-app
    Name:                                  prometheus-example-app
    Namespace:                             default
    Labels:                                
    Annotations:                           
    Reference:                             Deployment/prometheus-example-app
    Metrics:                               ( current / target )
    "http_requests_per_second" on pods:  11500m / 10
    Min replicas:                          1
    Max replicas:                          10
    Deployment pods:                       2 current / 2 desired
    Conditions:
    Type            Status  Reason              Message
    ----            ------  ------              -------
    AbleToScale     True    ReadyForNewScale    recommended size matches current size
    ScalingActive   True    ValidMetricFound    the HPA was able to successfully calculate a replica count from pods metric http_requests_per_second
    ScalingLimited  False   DesiredWithinRange  the desired count is within the acceptable range
    Events:
    Type     Reason               Age                   From                       Message
    ----     ------               ----                  ----                       -------
    Normal   SuccessfulRescale    47s                   horizontal-pod-autoscaler  New size: 2; reason: pods metric http_requests_per_second above target
    

    אם התשובה מכילה הצהרה כמו FailedGetPodsMetric, אז ה-HPA נכשל. הדוגמה הבאה ממחישה תגובה לקריאה describe כש-HPA נכשל:

    $ kubectl describe hpa prometheus-example-app
    Name:                                  prometheus-example-app
    Namespace:                             default
    Reference:                             Deployment/prometheus-example-app
    Metrics:                               ( current / target )
      "http_requests_per_second" on pods:   / 10
    Min replicas:                          1
    Max replicas:                          10
    Deployment pods:                       1 current / 1 desired
    Conditions:
      Type            Status  Reason               Message
      ----            ------  ------               -------
      AbleToScale     True    ReadyForNewScale     recommended size matches current size
      ScalingActive   False   FailedGetPodsMetric  the HPA was unable to compute the replica count: unable to get metric http_requests_per_second: unable to fetch metrics from custom metrics API: the server could not find the metric http_requests_per_second for pods
      ScalingLimited  False   DesiredWithinRange   the desired count is within the acceptable range
    Events:
      Type     Reason               Age                   From                       Message
      ----     ------               ----                  ----                       -------
      Warning  FailedGetPodsMetric  104s (x11 over 16m)   horizontal-pod-autoscaler  unable to get metric http_requests_per_second: unable to fetch metrics from custom metrics API: the server could not find the metric http_requests_per_second for pods
    

    אם ה-HPA נכשל, צריך לוודא שאתם יוצרים מדדים באמצעות load-generator. אפשר לבדוק את ה-API של המדדים המותאמים אישית ישירות באמצעות הפקודה:

    kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/" | jq .
    

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

    $ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/" | jq .
      {
      "kind": "APIResourceList",
      "apiVersion": "v1",
      "groupVersion": "custom.metrics.k8s.io/v1beta1",
      "resources": [
         {
            "name": "namespaces/http_requests_per_second",
            "singularName": "",
            "namespaced": false,
            "kind": "MetricValueList",
            "verbs": [
            "get"
            ]
         },
         {
            "name": "pods/http_requests_per_second",
            "singularName": "",
            "namespaced": true,
            "kind": "MetricValueList",
            "verbs": [
            "get"
            ]
         }
      ]
      }
    

    אם אין מדדים, לא יוצגו נתונים בקטע "resources" בפלט, לדוגמה:

    kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/" | jq .
    {
    "kind": "APIResourceList",
    "apiVersion": "v1",
    "groupVersion": "custom.metrics.k8s.io/v1beta1",
    "resources": []
    }