Ajuste de escala automático horizontal de Pods (HPA)

En este documento, se describe cómo habilitar el ajuste de escala automático horizontal de pods (HPA) de Google Cloud Managed Service para Prometheus. Para habilitar el HPA, sigue uno de estos pasos:

Debes elegir un enfoque. No puedes usar ambos porque sus definiciones de recursos se superponen, como se describe en Solución de problemas.

Usa el adaptador de Stackdriver para métricas personalizadas

El adaptador de métricas personalizadas de Stackdriver admite las consultas de métricas del servicio administrado para Prometheus a partir de la versión v0.13.1 del adaptador.

A fin de establecer un ejemplo de configuración de HPA con el adaptador de métricas personalizadas de Stackdriver, haz lo siguiente:

  1. Configurar la recopilación administrada para tu clúster.
  2. Instala el adaptador de métricas personalizadas de Stackdriver en tu clúster.

    kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter_new_resource_model.yaml
    
  3. Implementa un exportador de métricas de Prometheus de ejemplo y un recurso de 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
    

    Con este comando, se implementa una aplicación de exportador que emite la métrica foo y un recurso de HPA. El HPA escala esta aplicación hasta 5 réplicas a fin de lograr el valor objetivo para la métrica foo.

  4. Si usas Workload Identity Federation for GKE, también debes otorgar el rol de visualizador de Monitoring a la cuenta de servicio en la que se ejecuta el adaptador. Omite este paso si no tienes Workload Identity Federation for GKE habilitado en tu clúster de 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. Para definir un recurso PodMonitoring, coloca la siguiente configuración en un archivo llamado 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. Implementa el recurso nuevo de PodMonitoring:

    kubectl -n default apply -f podmonitoring.yaml
    

    En unos minutos, Managed Service para Prometheus procesa las métricas recopiladas del exportador y las almacena en Cloud Monitoring mediante un nombre de forma larga. Las métricas de Prometheus se almacenan con las siguientes convenciones:

    • El prefijo prometheus.googleapis.com.
    • Este sufijo suele ser uno de los siguientes: gauge, counter, summary o histogram, aunque las métricas sin tipo podrían tener el sufijo unknown o unknown:counter. Para verificar el sufijo, busca la métrica en Cloud Monitoring mediante el Explorador de métricas.
  7. Actualiza el HPA implementado para consultar la métrica desde Cloud Monitoring. La métrica foo se transfiere como prometheus.googleapis.com/foo/gauge. Para que el recurso HorizontalPodAutoscaler implementado pueda consultar la métrica, usa el nombre con formato largo en el HPA implementado, pero debes modificarlo reemplazando todas las barras diagonales (/) por el carácter de barra vertical (|): prometheus.googleapis.com|foo|gauge. Para obtener más información, consulta la sección de Métricas disponibles de Stackdriver del repositorio de adaptadores de métricas personalizadas de Stackdriver.

    1. Actualiza el HPA implementado mediante la ejecución del siguiente comando:

      kubectl edit hpa custom-metric-prometheus-sd
      
    2. Cambia el valor del campo pods.metric.name de foo a prometheus.googleapis.com|foo|gauge. La sección spec debería ser como la siguiente:

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

    En este ejemplo, la configuración del HPA busca que el valor promedio de la métrica prometheus.googleapis.com/foo/gauge sea 20. Debido a que la implementación establece el valor de la métrica en 40, el controlador de HPA aumenta la cantidad de Pods hasta el valor del campo maxReplicas (5) para tratar de reducir el valor promedio de la métrica entre todos los Pods.20 .

    La consulta del HPA se limita al espacio de nombres y al clúster en el que se instala el recurso de HPA, por lo que las métricas idénticas en otros clústeres y espacios de nombres no afectan el ajuste de escala automático.

  8. Para mirar cómo se escala verticalmente la carga de trabajo, ejecuta el siguiente comando:

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

    El valor del campo REPLICAS cambia de 1 a 5.

    NAME                          REFERENCE                                TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
    custom-metric-prometheus-sd   Deployment/custom-metric-prometheus-sd   40/20          1         5         5          *
    
  9. Para reducir la escala de la implementación verticalmente, actualiza el valor de la métrica de destino para que sea más alto que el valor de la métrica exportada. En este ejemplo, la implementación establece el valor de la métrica prometheus.googleapis.com/foo/gauge en 40. Si estableces el valor objetivo en un número mayor que 40, la implementación reducirá la escala verticalmente.

    Por ejemplo, usa kubectl edit para cambiar el valor del campo pods.target.averageValue en la configuración del HPA de 20 a 100.

    kubectl edit hpa custom-metric-prometheus-sd
    

    Modifica la sección de especificaciones para que coincida con la siguiente información:

    spec:
      maxReplicas: 5
      metrics:
      - pods:
          metric:
            name: prometheus.googleapis.com|foo|gauge
          target:
            averageValue: "100"
            type: AverageValue
      type: Pods
      minReplicas: 1
    
  10. Para ver cómo se reduce verticalmente la escala de la carga de trabajo, ejecuta el siguiente comando:

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

    El valor del campo REPLICAS cambia de 5 a 1. Por diseño, esto sucede con más lentitud que cuando se escala verticalmente la cantidad de Pods:

    NAME                          REFERENCE                                TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
    custom-metric-prometheus-sd   Deployment/custom-metric-prometheus-sd   40/100          1         5         1          *
    
  11. Para limpiar el ejemplo implementado, ejecuta los siguientes comandos:

    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
    

Para obtener más información, consulta el ejemplo de Prometheus en el repositorio del adaptador de las métricas personalizadas de Stackdriver o consulta Escala una aplicación.

Usa el adaptador de Prometheus

Las configuraciones existentes de prometheus-adapter se pueden usar para realizar un ajuste de escala automático con solo unos pocos cambios. La configuración de prometheus-adapter para escalar mediante el servicio administrado para Prometheus tiene dos restricciones adicionales en comparación con el escalamiento mediante Prometheus:

  • Las consultas deben enrutarse a través del proxy de IU de frontend de Prometheus, al igual que cuando consultas el servicio administrado para Prometheus mediante la API o la IU de Prometheus. Para prometheus-adapter, debes editar la implementación prometheus-adapter para cambiar el valor prometheus-url de la siguiente manera:

    --prometheus-url=http://frontend.NAMESPACE_NAME.svc:9090/
    

    donde NAMESPACE_NAME es el espacio de nombres en el que se implementa el frontend.

  • No puedes usar un comparador de regex en un nombre de métrica en el campo .seriesQuery de la configuración de reglas. En su lugar, debes especificar por completo los nombres de las métricas.

Como los datos pueden tardar un poco más en estar disponibles dentro del servicio administrado para Prometheus en comparación con el procesamiento ascendente de Prometheus, la configuración de un ajuste de escala automático demasiado fácil puede provocar un comportamiento no deseado. Aunque no hay garantía de que los datos se actualicen, por lo general, los datos están disponibles para consultar entre 3 y 7 segundos después de enviarlos al servicio administrado para Prometheus, sin incluir ninguna latencia de red.

Todas las consultas emitidas por prometheus-adapter son de alcance global. Esto significa que si tienes aplicaciones en dos espacios de nombres que emiten métricas con nombre idéntico, una configuración de HPA que usa esa métrica se escala con datos de ambas aplicaciones. Recomendamos usar siempre los filtros namespace o cluster en tu PromQL para evitar el escalamiento con datos incorrectos.

Para establecer una configuración de HPA de ejemplo con prometheus-adapter y la colección administrada, sigue estos pasos:

  1. Configurar la recopilación administrada para tu clúster.
  2. Implementa el proxy de la IU del frontend de Prometheus en tu clúster. Si usas Workload Identity Federation for GKE, también debes configurar y autorizar una cuenta de servicio.
  3. Implementa los manifiestos en el directorio examples/hpa/ dentro del repositorio de prometheus-engine:
    • example-app.yaml: Un ejemplo de implementación y servicio que emite métricas.
    • pod-monitoring.yaml: Un recurso que configura la recopilación de las métricas de ejemplo.
    • hpa.yaml: El recurso de HPA que configura el escalamiento para tu carga de trabajo.
  4. Asegúrate de que prometheus-adapter esté instalado en tu clúster. Para ello, implementa el manifiesto de instalación de ejemplo en tu clúster. Este manifiesto se configuró para lo siguiente:

    • Consultar un proxy de frontend implementado en el espacio de nombres default.
    • Emitir PromQL para calcular y mostrar la métrica http_requests_per_second de la implementación de ejemplo.
  5. Ejecuta los siguientes comandos, cada uno en una sesión de terminal independiente:

    1. Genera una carga HTTP en el servicio 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. Mira el escalador automático horizontal del pod:
      kubectl get hpa prometheus-example-app --watch
    3. Mira el escalamiento vertical de la carga de trabajo:
      kubectl get po -lapp.kubernetes.io/name=prometheus-example-app --watch
  6. Detén la generación de carga HTTP mediante Ctrl+C y observa cómo se reduce la escala de la carga de trabajo.

Soluciona problemas

El adaptador de métricas personalizadas de Stackdriver usa definiciones de recursos con los mismos nombres que los del adaptador de Prometheus, prometheus-Adapter. Esta superposición en los nombres significa que ejecutar más de un adaptador en el mismo clúster genera errores.

Instala el adaptador de Prometheus en un clúster que antes tenía las métricas personalizadas instaladas. El adaptador de Stackdriver instalado podría generar errores como FailedGetObjectMetric debido a los nombres con conflictos. Para resolver esto, es posible que debas borrar los apiservices v1beta1.external.metrics.k8s.io, v1beta1.custom.metrics.k8s.io y v1beta2.custom.metrics.k8s.io que registró previamente el adaptador de métricas personalizadas.

Sugerencias para solucionar problemas:

  • Algunas métricas del sistema de Cloud Monitoring, como las métricas de Pub/Sub, se retrasan 60 segundos o más. A medida que el adaptador de Prometheus ejecuta consultas con la marca de tiempo actual, consultar estas métricas mediante el adaptador de Prometheus podría generar datos de forma incorrecta. Para consultar las métricas retrasadas, usa el modificador offset en PromQL para cambiar la compensación de tiempo de tu consulta en la cantidad necesaria.

  • Para verificar que el proxy de la IU de frontend funcione según lo previsto y que no haya problemas con los permisos, ejecuta el siguiente comando en una terminal:

    kubectl -n NAMESPACE_NAME port-forward svc/frontend 9090
    

    A continuación, abre otra terminal y ejecuta el siguiente comando:

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

    Cuando el proxy de IU de frontend funciona de forma correcta, la respuesta en la segunda terminal es similar a la siguiente:

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

    Si recibes un error 403, entonces el proxy de la IU de frontend no está configurado de forma correcta. Para obtener información sobre cómo resolver un error 403, consulta la guía Configura y autoriza una cuenta de servicio.

  • Para verificar que el apiserver de métricas personalizadas esté disponible, ejecuta el siguiente comando:

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

    Cuando el apiserver está disponible, la respuesta es similar a la siguiente:

    $ 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
    
  • Para verificar que tu HPA funcione según lo previsto, ejecuta el siguiente comando:

    $ 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
    

    Cuando la respuesta contiene una sentencia como FailedGetPodsMetric, el HPA falla. A continuación, se ilustra una respuesta a la llamada describe cuando el HPA falla:

    $ 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
    

    Cuando el HPA falle, asegúrate de generar métricas con el load-generator. Puedes verificar la API de métricas personalizadas directamente con el siguiente comando:

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

    Un resultado correcto debería verse de la siguiente manera:

    $ 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"
            ]
         }
      ]
      }
    

    Si no hay métricas, no habrá datos en "resources" en el resultado, por ejemplo:

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