Autoescalado con Prometheus y Spring Boot en Kubernetes

Horizontal Pod Autoscaler with Prometheus and Spring Boot

Horizontal Pod Autoscaler with Prometheus and Spring Boot


En esta entrada de autoescalado con Prometheus y Spring Boot en Kubernetes, vamos a ver como através del Horizontal Pod Autoscaler de kubernetes, podemos hacer un autoescalado propio basado en diferentes métricas. El Horizontal Pod Autoscaler o escalado de Pod Horizontal de kubernetes se basa, por defecto en CPU y memoria, por lo que para muchos casos en los que se busca alguna métrica más, esto no es suficiente. Por eso en nuestro ejemplo vamos a hacer uso de Prometheus através de actuator de Spring Boot para obtener métricas diferentes y realizar un escalado en función de estas métricas.

Aunque con el autoscaler por defecto de Kubernetes, obtenemos dos de las métricas más usadas (CPU y memoria), a la hora de realizar un escalado horizontal, en algunos casos no es suficiente. Por ello se creo la version autoscaling/v2beta2, en la que se incorpora autoescalado en función de métricas propias. Por ejemplo podemos basar nuestro autoescalado en función del número de peticiones por segundo y crear nuevas réplicas si superamos un umbral. Para esos casos Prometheus através de Spring Boot actuator nos ayudará a recoger esas métricas.

Autoescalado en Kubernetes con Prometheus y Spring Boot

El Horizontal Pod Autoscaler o escalado horizontal de kubernetes, escala automáticamente el número de Pods en un replication controller, deployment, replica set o stateful en función de la CPU, memoria utilizada u otras métricas previamente definidadas.

Para realizar nuestro ejemplo de autoescalado con Spring Boot, vamos a levantar una instancia de nuestra aplicación de Kubernetes, a partir de la cual Prometheus comenzará a reunir métricas a través del endpoint de actuator. Momento en el que en función del número de peticiones establecidas, veremos un escalado horizontal de nuestros Pods.

Autoescalado con Prometheus en Kubernetes | Autoescalado con Prometheus y Spring Boot en Kubernetes
Autoescalado con Prometheus en Kubernetes

En el diagrama anterior vemos la arquitectura que vamos a aplicar en nuestro ejemplo. Por un lado, tendremos una aplicación realizada en Spring Boot, a la que le vamos a añadir las dependencias de Prometheus y de Actuator para poder exponer las métricas; y por otro lado tenemos el Horizontal Pod Autoscaler de Kubernetes que en función de unas métricas que va a ir obteniendo a través de Prometheus realizará un escalado de Pods. La pieza de Prometheus adapter será necesaria ya que actúa como un intermediario, es decir, obtiene las métricas y las expone.

El escalado que realiza kubernetes es en función de una fórmula:

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

Esta fórmula indica el número total de réplicas que queremos que tener, de manera que será el número máximo de la multiplicación del número actual de réplicas entre el valor actual de la métrica entre el deseado. Por ejemplo, si el valor de la métrica actual es de 200m y el deseado de 100 y tenemos una única réplica, será 1*(200/100) = 2

Hands On

A continuación vamos a comenzar a realizar los pasos necesarios para poder realizar un Autoescalado con Spring Boot en Kubernetes, para ello vamos a empezar con desplegar en nuestro clúster de Kubernetes Prometheus. Para ello vamos a utilizar el repositorio oficial de Prometheus através de Helm Chart:

Helm utiliza un formato de empaquetado llamado gráficos. Un gráfico es una colección de archivos que describen un conjunto relacionado de recursos de Kubernetes. Se puede usar un solo gráfico para implementar algo simple, como un módulo de memoria caché, o algo complejo, como podría ser un conjunto de aplicaciones que hacen uso de diferentes recursos, como en el ejemplo de este artículo Prometheus.

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add stable https://charts.helm.sh/stable
helm repo update
helm install prometheus prometheus-community/prometheus

Una vez hemos introducido los comando anteriores tendremos instalado prometheus en nuestro clúster de kubernetes. El siguiente paso es hacer un port forward al puerto 9090 para el pod de prometheus server para poder hacer pruebas desde nuestro local:

Pods de Prometheus
Pods de Prometheus
kubectl port-forward prometheus-server-68f878ff7d-492l9 9090:9090

De esta manera podremos levantar nuestra aplicación de Spring Boot en local y conectarnos a Prometheus.

Prometheus en Kubernetes | Autoescalado con Prometheus y Spring Boot en Kubernetes
Prometheus en Kubernetes

Dependencias Maven

A continuación vamos a crear una simple aplicación Spring Boot en dónde haciendo uso de las dependencias de Prometheus y de Actuator vamos a conectarnos con nuestro Prometheus instalado en Kubernetes.

Lo primero que vamos a hacer es añadir las dependencias maven a nuestra aplicación:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
   <groupId>io.micrometer</groupId>
   <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

Las dependencias anteriores son las únicas dependencias necesarias para conectarnos con Prometheus, através de /actuator/prometheus.

Dependencias de base de datos:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>

Construcción aplicación Spring Boot con Prometheus

Partiendo del proyecto spring boot que hemos creado, vamos a crear un controlador que se conectará a una base de datos en memoria H2 para poder integralo y conectarlo con la instancia de Prometheus que hemos levantado en nuestro kubernetes.

Si quieres el código completo lo puedes descargar de aquí.

@RestController
@RequestMapping("/cars")
@RequiredArgsConstructor
public class CarController {

    private final CarRepository carRepository;

    @GetMapping
    public ResponseEntity<Iterable<Car>> findAllCars() {
        return ResponseEntity.ok().body(carRepository.findAll());
    }

    @GetMapping("/{id}")
    public ResponseEntity<Car> findCarById(@PathVariable("id") String id) {
        return ResponseEntity.ok().body(carRepository.findById(UUID.fromString(id)).orElseThrow());
    }

    @PutMapping
    public ResponseEntity<Car> updateCar(@RequestBody Car car) {
        return new ResponseEntity<>(carRepository.save(car),HttpStatus.CREATED);
    }

    @PostMapping
    public ResponseEntity<Car> addCar(@RequestBody Car car) {
        return new ResponseEntity<>(carRepository.save(car),HttpStatus.CREATED);
    }

    @DeleteMapping("/{id}")
    public void deleteCar(@PathVariable("id") String id) {
        carRepository.deleteById(UUID.fromString(id));
    }
}

A continuación añadimos el repositorio:

public interface CarRepository extends JpaRepository<Car, UUID> {

}

Y finalmente la entidad de base de datos que se corresponde con el objeto de salida:

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Car {

    @Id
    @GeneratedValue
    private UUID id;

    private String model;

    private String brand;

    private String color;

    private String type;
}

Es muy importante indicar mediante las propiedades de nuestra aplicación (application.yml) que vamos a exponer métricas:

spring:
  application:
    name: kubernetes-custom-autoscaler

management:
  endpoints:
    web:
      exposure:
        include: "*"

Deploy aplicación Spring Boot con Prometheus en Kubernetes

Una vez hemos creado nuestra aplicación Spring Boot, vamos a realizar un despliegue en Kubernetes, para ello lo primero es crear la imágen docker.

Si estamos usando kubernetes en local, con minikube, no te olvides de añadir, eval $(minikube -p minikube docker-env)

Para crear la imágen con Spring Boot en maven usamos el siguiente comando:

mvn spring-boot:build-image

Una vez tenemos la imágen creada, es el momento de crear el deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: spring-boot-kubernetes-autoscaler
  name: spring-boot-kubernetes-autoscaler
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-boot-kubernetes-autoscaler
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: spring-boot-kubernetes-autoscaler
      annotations:
        prometheus.io/path: /actuator/prometheus
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
    spec:
      containers:
      - image: kubernetes-custom-autoscaler:0.0.1-SNAPSHOT
        name: spring-boot-kubernetes-autoscaler
        ports:
        - containerPort: 8080

El anterior es un deployment bastante común a excepción de la parte de annotations dentro de metadata. Ya que en este punto le indicamos la comunicación al pod con las métricas de Prometheus.

Una vez los el pod ha sido creado, podemos hacer un port forward con el pod y veremos la comunicación ya con Prometheus. Si haces la siguiente petición localhost:8080/cars y miras en Prometheus podrás ver que se realiza una petición:

Peticiones a Spring Boot con Actuator
Peticiones a Spring Boot con Actuator

Una vez la aplicación esta desplegada y Prometheus está recopilando métricas, es necesario conectarlo con el adaptador que hace de puente con API de métricas de kubernetes. En este adapter que vamos a crear vamos a añadir la métrica por la que queremos controlar que es http_server_requests_seconds_count, este adapter creará un configmap , un deployment y service haciendo uso de helm. Para ello vamos realizar una pequeña modificación en el que añadimos una regla y especificamos puerto y url de prometheus.

En la regla que vamos a añadir, le decimos que sobre escriba el namespace y el pod que es necesario para poder recolectar las métricas de Prometheus, para ello ejecutamos el siguiente fichero:

prometheus:
  url: http://prometheus-server.default.svc
  port: 80
  path: ""

rules:
  default: true
  custom:
    - seriesQuery: '{__name__=~"^http_server_requests_seconds_.*"}'
      resources:
        overrides:
          kubernetes_namespace:
            resource: namespace
          kubernetes_pod_name:
            resource: pod
      name:
        matches: "^http_server_requests_seconds_count(.*)"
        as: "http_server_requests_seconds_count_sum"
      metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>,uri=~"/cars.*"}) by (<<.GroupBy>>)

Para ejecutar el fichero anterior usamos:

helm install -f helm-prometheus.yaml prometheus-adapter prometheus-community/prometheus-adapter

Si cometes algún error puedes realizar un update con el siguiente comando:

 helm upgrade --install -f helm-prometheus.yaml prometheus-adapter prometheus-community/prometheus-adapter

Si a continuación ejecutamos el siguiente comando, podrás ver las métricas y el pod asociado:

kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/http_server_requests_per_second" | jq .

Una vez el adapter esta funcioando y configurado es momento de crear un Horizontal Pod Autoscaler, que es un tipo de objeto de kubernetes que creará nuevas réplicas en función de unos valores.

Horizontal Pod Autoscaler en Kubernetes para Prometheus

Para crear nuestro HorizontalPodAutoscaler vamos a utilizar la versión autoscaling/v2beta2, ya que esta es la versión que tiene incluido las métricas custom. Este objeto se asociará a un deployment, en nuestro caso será el deployment que hemos creado para ejecutar nuestra aplicación Spring Boot. Una vez el número de peticiones se supere a la media que hemos establecido, se crearán nuevas réplicas.

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: spring-boot-prometheus-autoscaler
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: spring-boot-kubernetes-autoscaler
  minReplicas: 1
  maxReplicas: 5
  metrics:
    - type: Pods
      pods:
        metric:
          name: http_server_requests_seconds_count_sum
        target:
          type: AverageValue
          averageValue: 10

A continuación ejecutamos el HPA (Horizontal Pod Autoscaler) con el siguiente comando:

kubectl apply -f HorizontalPodAutoscaler.yaml

Ejecutamos el comando, para ver que se ha creado bien:

kubectl get hpa

y tras unas cuantas peticiones debería de empezar a crear réplicas y podrás ver algo así:

Detalle de HPA
Detalle de HPA

Lo que podemos ver en la imágen anterior es que hemos ejecutado una petición 91 veces contra el servicio de Spring Boot. Por lo que a partir de 10 se crea una nueva réplica habiendo llegado al máximo establecido de 5.

Una vez hemos hemos visto como podemos crear un escalador horizontal con métricas custom en kubernetes vamos a proceder a borrar todo lo que hemos creado en el entorno de trabajo.

helm uninstall prometheus
helm uninstall prometheus-adapter
kubectl delete hpa spring-boot-prometheus-autoscaler
kubectl delete deployment spring-boot-kubernetes-autoscaler
kubectl delete svc spring-boot-kubernetes-autoscaler

Conclusión

Una de las herramientas o utilidades más necesarias en arquitecturas de microservicios es poder hacer uso de un autoescalado horizontal. Kubernetes nos facilita esta tarea através de su Horizontal Pod Autoscaler para CPU, memoria y métricas, por lo que haciendo uso de Spring Boot Actuator y Prometheus podemos crear un autoescaler custom para el número de peticiones de una aplicación.

Si quieres descargar este ejemplo así como los ficheros de kubernetes puedes hacerlo en nuestro github.

No te olvides de seguirnos en nuestras redes sociales Facebook o Twitter para estar actualizado.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com y te ayudaremos encantados!



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *