Spring Boot & Kubernetes

Spring Boot includes a number of additional features in the form of actuators. Some of these featuresbare health checks, tracing or monitoring. Let’s explore some of them:

Health Check Actuator

When we created the project, we already set the actuator dependencies, so we don’t need to register them in the pom.xml.

By default some HealthIndicators are registerd to report the state of the application. Some of them are generic and others are specific to a technology. To mention a few:

  • DiskSpaceHealthIndicator

  • PingHealthIndicator

  • DataSourceHealthIndicator (if datasource is used)

  • MongoHealthIndicator (if MongoDB is used)

Sending a request to /actuator/health endpoint returns an agrgegated result of all registered health indicators.

Unresolved include directive in modules/ROOT/pages/04-actuators.adoc - include::partial$package_run.adoc[]

{"status":"UP"}

Custom Health Indicator

A custom health check indicator may be implemented implementing org.springframework.boot.actuate.health.HealthIndicator interface. Create a new class named LuckyHealthIndicator.

package org.acme.hellokubernetes;

import java.time.LocalTime;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component("lucky")
public class LuckyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        final LocalTime currentTime = LocalTime.now();
        final Health.Builder healthBuilder = currentTime.getMinute() % 2 == 0 ?
                                Health.up()
                                :
                                Health.down().withDetail("time", currentTime);

        return healthBuilder.build();
    }

}

Unresolved include directive in modules/ROOT/pages/04-actuators.adoc - include::partial$package_run.adoc[]

Now depending on the minute you do the request, you may receive an UP or DOWN as a result:

< HTTP/1.1 200
< Content-Type: application/vnd.spring-boot.actuator.v3+json
< Transfer-Encoding: chunked
< Date: Thu, 20 May 2021 11:38:32 GMT
{"status":"DOWN"}
< HTTP/1.1 503
< Content-Type: application/vnd.spring-boot.actuator.v3+json
< Transfer-Encoding: chunked
< Date: Thu, 20 May 2021 11:39:04 GMT
{"status":"UP"}

Custom Configuration

There is a special health check configuration parameter that adds details to health check response. Open src/main/resources/application.properties file and add the following property:

application.properties
management.endpoint.health.show-details=always

Unresolved include directive in modules/ROOT/pages/04-actuators.adoc - include::partial$package_run.adoc[]

The output contains some details about all registered health indicators:

{"status":"UP","components":{"discoveryComposite":{"description":"Discovery Client not initialized","status":"UNKNOWN","components":{"discoveryClient":{"description":"Discovery Client not initialized","status":"UNKNOWN"}}},"diskSpace":{"status":"UP","details":{"total":500068036608,"free":77511536640,"threshold":10485760,"exists":true}},"lucky":{"status":"UP"},"ping":{"status":"UP"},"refreshScope":{"status":"UP"}}}

Also, it’s possible to get the status of specific health indicator sending request to /actuator/health/{name}, where name is the prefix part of the health indicator class name. For example, in the case of LuckyHealthIndicator, the name of the health indicator is lucky.

{"status":"DOWN","details":{"time":"13:39:04.458205"}}
Finally delete the LuckyHealthIndicator.java file to not affect the readiness of the application when deployed in the Kubernetes cluster.

Metrics Actuator

Spring Boot Actuator provides support for Micrometer project to provide application metrics.

Prometheus

Micrometer supports several monitoring systems such as Prometheus, Elastic, Dynatrace, …​ For this tutorial, we are going to use Prometheus as a metrics format.

The first thing to do is registering the io.micrometer:micrometer-registry-prometheus dependency:

pom.xml
<dependency>
	<groupId>io.micrometer</groupId>
	<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

Then, we need to update the application.properties to expose metrics endpoints (in fact since we are going to need more endpoints in the course of this tutorial):

application.properties
management.endpoints.web.exposure.include=*

Unresolved include directive in modules/ROOT/pages/04-actuators.adoc - include::partial$package_run.adoc[]

Now, two new endpoints related to monitoring are available:

Sends the current monitoring parameters:

{"names":["http.server.requests","jvm.buffer.count","jvm.buffer.memory.used","jvm.buffer.total.capacity","jvm.classes.loaded","jvm.classes.unloaded","jvm.gc.live.data.size","jvm.gc.max.data.size","jvm.gc.memory.allocated","jvm.gc.memory.promoted","jvm.gc.pause","jvm.memory.committed","jvm.memory.max","jvm.memory.used","jvm.threads.daemon","jvm.threads.live","jvm.threads.peak","jvm.threads.states","logback.events","process.cpu.usage","process.files.max","process.files.open","process.start.time","process.uptime","system.cpu.count","system.cpu.usage","system.load.average.1m","tomcat.sessions.active.current","tomcat.sessions.active.max","tomcat.sessions.alive.max","tomcat.sessions.created","tomcat.sessions.expired","tomcat.sessions.rejected"]

To consume the metrics, we use another endpoint:

And the metrics output is in the Prometheus format:

# HELP process_cpu_usage The "recent cpu usage" for the Java Virtual Machine process
# TYPE process_cpu_usage gauge
process_cpu_usage 0.0
# HELP tomcat_sessions_expired_sessions_total
# TYPE tomcat_sessions_expired_sessions_total counter
tomcat_sessions_expired_sessions_total 0.0
# HELP process_uptime_seconds The uptime of the Java virtual machine
# TYPE process_uptime_seconds gauge
process_uptime_seconds 195.939
....
# HELP tomcat_sessions_active_max_sessions
# TYPE tomcat_sessions_active_max_sessions gauge
tomcat_sessions_active_max_sessions 0.0
# HELP jvm_gc_live_data_size_bytes Size of long-lived heap memory pool after reclamation
# TYPE jvm_gc_live_data_size_bytes gauge
jvm_gc_live_data_size_bytes 0.0
# HELP jvm_gc_memory_promoted_bytes_total Count of positive increases in the size of the old generation memory pool before GC to after GC
# TYPE jvm_gc_memory_promoted_bytes_total counter
jvm_gc_memory_promoted_bytes_total 2834944.0

Customizing Metrics

We can create custom metrics injecting io.micrometer.core.instrument.MeterRegistry in the constructor and registering the metric.

Open HelloController class and add the following content:

package org.acme.hellokubernetes;

import java.util.HashSet;
import java.util.Set;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;

@RestController
public class HelloController {

    private Set<String> names = new HashSet<>();

    public HelloController(MeterRegistry registry) { (1)
      registry.gaugeCollectionSize("names.size", Tags.empty(), names); (2)
    }

    @GetMapping("/hello")
    String hello() {
      return "Hello World";
    }

    @GetMapping("/hello/{name}")
    String helloWithName(@PathVariable("name") String name) {
      names.add(name); (3)
      return "Hello World " + name;
    }

}
1 Injects MeterRegistry
2 Registers a new gauge counting the number of elements inserted at names collection
3 Adds the given name to the collection

Unresolved include directive in modules/ROOT/pages/04-actuators.adoc - include::partial$package_run.adoc[]

Then make a request to /hello/Alex and then get the metrics (optionally filtering by names.size key used to register the gauge):

curl localhost:8080/hello/Alex

curl http://localhost:8080/actuator/prometheus | grep names.size

And metric is updated.

# HELP names_size
# TYPE names_size gauge
names_size 1.0