Autoescalado de Liferay Portal/DXP por custom metrics en Kubernetes

Check english version here


Una de las funcionalidades estrella del mundo de Kubernetes es, sin lugar a dudas, la del Autoescalado Horizontal de Pods. Con ella, podemos beneficiarnos de una capacidad de rendimiento superior de nuestro servicio en momentos puntuales, donde el dimensionamiento origen de nuestro cluster pueda no ser suficiente, evitando degradamiento o incluso pérdida del servicio.

Kubernetes nos ofrece out-of-the-box autoescalado por consumo de recursos, como CPU y Memoria. Aunque una situación de carga temporal en el servicio puede hacer que aumente el consumo de estos recursos en nuestros pods y por tanto que dispare nuestro autoescalado, en ocasiones y más en relación al autoescalado por memoria, puede no ser de mucha ayuda y más en aplicaciones Java por el simple hecho de cómo gestiona la JVM la memoria. Por estos motivos y desde la experiencia con clientes, en ocasiones, el autoescalado horizontal encaja mejor por otro tipo de métricas, como la monitorización del pool http del servidor de aplicaciones, ya que normalmente cuando el sistema empieza a estresarse, se empieza a manifestar por saturación de los pools del servidor de aplicaciones o el pool de conexiones a Elasticsearch, en el caso de Liferay Portal/DXP.
 

¿Qué necesito para escalar automáticamente mi Liferay Portal/DXP teniendo en cuenta una métrica a medida?
 

  1. Será necesario apoyarnos en herramientas como JMX Exporter para indicar qué métricas queremos exportar hacia el exterior de nuestro pod de Liferay Portal/DXP. Para ello, se puede consultar cómo configurarlo en mi anterior Blog "Monitorizando Liferay Portal/DXP en Kubernetes"
     
  2. Una vez nuestros pods de Liferay Portal/DXP estén exportando nuestras métricas, será necesario disponer de Prometheus con el fin de recolectar nuestras métricas. Para instalarlo en nuestro cluster de Kubernetes, nuevamente hago referencia a mi anterior Blog "Monitorizando Liferay Portal/DXP en Kubernetes" donde describo detalladamente cómo instalarlo.
     
  3. Con Prometheus instalado y funcional, ahora lo que necesitaremos es implementar la API de custom-metrics de Kubernetes con el fin de obtener un endpoint dentro de nuestro cluster, con el que exponer nuestras métricas a medida necesarias para realizar el autoescalado y para ello utilizaremos Prometheus Adapter. Para instalarlo podremos utilizar HELM:

    $ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    $ helm repo update
    $ helm install --name my-release prometheus-community/prometheus-adapter


    La parte interesante a configurar en Prometheus Adapter para llevar a cabo nuestro acometido es:

    a) Configurar el endpoint donde se expone Prometheus y su puerto: Para ello se puede modificar el values del Chart con los valores correctos.

    b) Configurar la query que extraerá las métricas a medida de Prometheus para exponerlas en el endpoint custom.metrics de k8s: Para ello, se puede modificar el values del Chart con las queries deseadas. En nuestro caso, dado que queremos escalar Liferay Portal/DXP dependiendo del tamaño actual del pool de conexiones http, extraeremos la media de las series expuestas por el threadpool de cada Tomcat de Liferay Portal/DXP a través de Prometheus:

            - seriesQuery: 'tomcat_threadpool_currentthreadcount{kubernetes_namespace!=\"\",kubernetes_pod_name!=\"\"}'
              resources:
                overrides:
                  kubernetes_namespace: {resource: \"namespace\"}
                  kubernetes_pod_name: {resource: \"pod\"}
              name:
                matches: \"^(.*)_currentthreadcount\"
                as: \"${1}_threadcount_avg\"
              metricsQuery: 'avg(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)'

    Una vez desplegado y configurado Prometheus Adapter, podremos comprobar si nuestra métrica está siendo exportada a través del custom.metrics endpoint con
     kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | jq .



     

  4. Lo último que necesitaremos es configurar el HorizontalPodAutoscaler para vincular nuestro Deployment de Liferay Portal/DXP al mismo con el fin de escalar cuando la métrica alcance un umbral deseado (y desescalar cuando éste disminuya):

    apiVersion: autoscaling/v2beta1
    kind: HorizontalPodAutoscaler
    metadata:
      name: liferay-autoscaler
      namespace: liferay
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: liferay
      minReplicas: 2
      maxReplicas: 5
      metrics:
      - type: Pods
        pods:
          metricName: tomcat_threadpool_threadcount_avg
          targetAverageValue: "100"

    Con el manifesto anterior, estaremos utilizando nuestra métrica "tomcat_threadpool_threadcount_avg" exportada a través del endpoint custom.metrics y cuando ésta alcance el valor de 100 (la mitad del valor máximo, 200 para el pool por defecto en Tomcat) , Kubernetes incrementará el número de replicas del Deployment hasta un máximo de 5.
    Desde las versión 1.18 de Kubernetes, es posible configurar el comportamiento del HPA en cuanto a scaleUp y scaleDown (escalado y desescalado) así como las ventanas de estabilización para realizarlos:

    behavior:
      scaleDown:
        stabilizationWindowSeconds: 300
        policies:
        - type: Percent
          value: 100
          periodSeconds: 15
      scaleUp:
        stabilizationWindowSeconds: 0
        policies:
        - type: Percent
          value: 100
          periodSeconds: 15
        - type: Pods
          value: 2
          periodSeconds: 15
        selectPolicy: Max


    Con la configuración anterior, el desescalado se realiza con una ventana de estabilización de 5 minutos. Sólo se configura una política para realizar el desescalado la cual permite eliminar el 100% de las réplicas adicionales.
    Para el escalado no se configura una ventana de estabilización (0). Cuando la métrica alcanza el threshold, el número de réplicas se aumenta inmediatamente. En policies se configuran 2 políticas con las que se agregarán 2 pods o el 100% de las réplicas que se están ejecutando actualmente cada 15 segundos hasta que el HPA alcance su estado estable de nuevo.

    Ejecutando kubectl describe hpa -n=liferay podremos ver el estado de nuestro nuevo HPA

    
    
     
    
    

Probando nuestro HorizontalPodAutoscaler
 

Con ayuda de un JMeter, inyectaremos carga a nuestro cluster de Liferay Portal/DXP para probar el autoescalado y el autodesescalado. Podremos utilizar nuestra instancia de Prometheus para monitorizar el threadpool de nuestros pods mientras inyectamos carga:




Una vez el umbral es alcanzado, vemos como se incrementa el número de instancias de Liferay Portal/DXP:



A los 5 minutos de detener la carga, podemos ver cómo las instancias adicionales son eliminadas hasta dejar nuestro cluster de Liferay Portal/DXP con nuestros 2 nodos como mínimo:

 

Conclusión

El autoescalado es una funcionalidad de mucho valor sobre una infraestructura en Kubernetes, pero dicha funcionalidad tiene que adaptarse al uso de cada proyecto. La decisión del escalado por una métrica u otra tiene que ser analizada para ver que encaje en cómo utilizamos Liferay Portal/DXP y posteriormente, con la ayuda de pruebas de rendimiento, ajustar los comportamientos de escalado y desescalado, así como los thresholds, de la manera más adecuada para beneficiar con ello al máximo a nuestra solución construida sobre Liferay Portal/DXP y Kubernetes.