Autoescalado con Prometheus y Spring Boot en Kubernetes
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.
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:
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.
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.
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:
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í:
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!