Health Check SE Guide

This guide describes how to create a sample Helidon SE project that can be used to run some basic examples using both built-in and custom health-checks.

What you need

About 15 minutes
Helidon Prerequisites

Create a sample SE project

Generate the project sources using the Helidon SE Maven archetype. The result is a simple project that can be used for the examples in this guide.

Run the Maven archetype:
mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-se \
    -DarchetypeVersion=1.4.12 \
    -DgroupId=io.helidon.examples \
    -DartifactId=helidon-quickstart-se \
    -Dpackage=io.helidon.examples.quickstart.se
Copied

Using the built-in health-checks

Helidon has a set of built-in health-checks that can be optionally enabled to report various health-check statuses that are commonly used:

  • deadlock detection

  • available disk space

  • available heap memory

The following example will demonstrate how to use the built-in health-checks. These examples are all executed from the root directory of your project (helidon-quickstart-se).

Notice that the built-in health-check dependency is already in the project’s pom.xml file:
<dependency>
    <groupId>io.helidon.health</groupId>
    <artifactId>helidon-health-checks</artifactId>
</dependency>
Copied
Modify Main.java, import HealthCheckReponse, and replace the createRouting method:
import org.eclipse.microprofile.health.HealthCheckResponse;  

...

private static Routing createRouting(Config config) {

    HealthSupport health = HealthSupport.builder()
      .addLiveness(HealthChecks.healthChecks())  
      .build();

    return Routing.builder()
      .register(JsonSupport.create()) 
      .register(health)  
      .build();
}
Copied
  • Import HealthCheckResponse.
  • Add built-in health-checks (requires the helidon-health-checks dependency).
  • Register the JSON-P support in the WebServer routing.
  • Register the created health support with web server routing (adds the /health endpoint).
Build the application, skipping unit tests, then run it:
mvn package -DskipTests=true
java -jar target/helidon-quickstart-se.jar
Copied
Verify the health endpoint in a new terminal window:
curl http://localhost:8080/health
Copied
JSON response:
{
  "outcome": "UP",
  "status": "UP",
  "checks": [
    {
      "name": "deadlock",
      "state": "UP",
      "status": "UP"
    },
    {
      "name": "diskSpace",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "319.58 GB",
        "freeBytes": 343144304640,
        "percentFree": "68.63%",
        "total": "465.63 GB",
        "totalBytes": 499963174912
      }
    },
    {
      "name": "heapMemory",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "196.84 MB",
        "freeBytes": 206404016,
        "max": "3.56 GB",
        "maxBytes": 3817865216,
        "percentFree": "98.66%",
        "total": "245.50 MB",
        "totalBytes": 257425408
      }
    }
  ]
}
Copied

In MicroProfile Health 2.0 outcome and state were replaced by status in the JSON response wire format. Helidon currently provides both fields for backwards compatibility, but use of outcome and state is deprecated and will be removed in a future release. You should rely on status instead.

Custom liveness health-checks

You can create application specific custom health-checks and integrate them with Helidon using the HealthSupport class, which is a WebServer service that contains a collection of registered HealthCheck instances. When queried, it invokes the registered health-check and returns a response with a status code representing the overall state of the application.

Notice the custom health-checks dependency is already in the project’s pom.xml file:
<dependency>
    <groupId>io.helidon.health</groupId>
    <artifactId>helidon-health</artifactId>
</dependency>
Copied
Replace the HealthSupport builder in the Main.createRouting method:
HealthSupport health = HealthSupport.builder()
  .addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
      .up()
      .withData("time", System.currentTimeMillis())
      .build()) 
  .build();
Copied
  • Add a custom liveness health-check. This example returns UP and current time.
Build and run the application, then verify the custom health endpoint:
curl http://localhost:8080/health
Copied
JSON response:
{
    "outcome": "UP",
    "checks": [
        {
            "name": "LivenessCheck",
            "state": "UP",
            "data": {
                "time": 1546958376613
            }
        }
    ]
}
Copied

Custom readiness health-check

You can add a readiness check to indicate that the application is ready to be used. In this example, the server will wait five seconds before it becomes ready.

Add a readyTime variable to the Main class, then set it five seconds after the application starts:
import java.util.concurrent.atomic.AtomicLong; 

public final class Main {

  private static AtomicLong readyTime = new AtomicLong(0); 
  ...

    static WebServer startServer() throws IOException {
    ...
      server.start() ...

        // Server threads are not daemon. No need to block. Just react.
      try {
        Thread.sleep(5000); 
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }

      readyTime.set(System.currentTimeMillis()); 
      return server;
Copied
  • Import AtomicLong.
  • Declare the readyTime variable.
  • Sleep five seconds.
  • Set the readyTime to the time when the server became ready.
Add a readiness check to the HealhSupport builder in the Main.createRouting method:
HealthSupport health = HealthSupport.builder()
  .addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
      .up()
      .withData("time", System.currentTimeMillis())
      .build())
  .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")
      .state (readyTime.get() != 0 )
      .withData( "time", readyTime.get())
      .build()) 
  .build();
Copied
  • Add the readiness check.
Build and run the application. Issue the curl command with -v within five seconds and you see the application is not ready:
curl -v  http://localhost:8080/health/ready
Copied
HTTP response:
...
< HTTP/1.1 503 Service Unavailable 
...
{
  "outcome": "DOWN",
  "status": "DOWN",
  "checks": [
    {
      "name": "ReadinessCheck",
      "state": "DOWN",
      "status": "DOWN",
      "data": {
        "time,": 0
      }
    }
  ]
}
Copied
  • The HTTP status is 503 since the application is not ready.
After five seconds you will see the application is ready:
curl -v http://localhost:8080/health/ready
Copied
JSON response:
...
< HTTP/1.1 200 OK 
...
{
  "outcome": "UP",
  "status": "UP",
  "checks": [
    {
      "name": "ReadinessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time,": 1566243562097
      }
    }
  ]
}
Copied
  • The HTTP status is 200 indicating that the application is ready.

When using the health-check URLs, you can get the following health-check data

Get both liveness and readiness data from a single query:
curl http://localhost:8080/health
Copied
JSON response:
{
  "outcome": "UP",
  "status": "UP",
  "checks": [
    {
      "name": "LivenessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time": 1566244094548
      }
    },
    {
      "name": "ReadinessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time,": 1566244093012
      }
    }
  ]
}
Copied

Combine built-in and custom health-checks

You can combine built-in and custom health-checks using the same HealthSupport builder.

Register a custom health-check in the Main.createRouting method:
HealthSupport health = HealthSupport.builder()
    .addLiveness(HealthChecks.healthChecks())  
    .addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
      .up()
      .withData("time", System.currentTimeMillis())
      .build())
    .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")
      .state (readyTime.get() != 0 )
      .withData( "time", readyTime.get())
      .build())
    .build();
Copied
  • Add the built-in health-checks back to HealthSupport builder.
Build and run the application, then verify the health endpoint. You will see both the built-in and custom health-check data:
curl http://localhost:8080/health
Copied
JSON response:
{
  "outcome": "UP",
  "status": "UP",
  "checks": [
    {
      "name": "LivenessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time": 1566245527673
      }
    },
    {
      "name": "ReadinessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time,": 1566245527620
      }
    },
    {
      "name": "deadlock",
      "state": "UP",
      "status": "UP"
    },
    {
      "name": "diskSpace",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "326.17 GB",
        "freeBytes": 350224424960,
        "percentFree": "70.05%",
        "total": "465.63 GB",
        "totalBytes": 499963174912
      }
    },
    {
      "name": "heapMemory",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "247.76 MB",
        "freeBytes": 259791680,
        "max": "4.00 GB",
        "maxBytes": 4294967296,
        "percentFree": "99.80%",
        "total": "256.00 MB",
        "totalBytes": 268435456
      }
    }
  ]
}
Copied

Custom health-check URL path

You can use a custom URL path for heath checks by setting the WebContext. In the following example, only the liveness URL is changed, but you can do the same for the readiness and default health-checks.

Register a custom URL path with the custom health-check in the Main.createRouting method:
HealthSupport health = HealthSupport.builder()
    .webContext("/probe/live")
    .addLiveness(() -> HealthCheckResponse.named("livenessProbe")
      .up()
      .withData("time", System.currentTimeMillis())
      .build())
    .build();
Copied
  • Change the liveness URL path using a WebContext.
Build and run the application, then verify that the liveness endpoint is using the /probe/live:
curl http://localhost:8080/probe/live
Copied
JSON response:
{
  "outcome": "UP",
  "checks": [
    {
      "name": "livenessProbe",
      "state": "UP",
      "data": {
        "time": 1546958376613
      }
    }
  ]
}
Copied

Using Liveness and Readiness health-checks with Kubernetes

The following example shows how to integrate the Helidon health API in an application that implements health endpoints for the Kubernetes liveness and readiness probes.

Change the HealthSupport builder in the Main.createRouting method to use the built-in liveness checks, a custom liveness check, and a readiness check:
HealthSupport health = HealthSupport.builder()
    .addLiveness(HealthChecks.healthChecks()) 
    .addLiveness(() -> HealthCheckResponse.named("LivenessCheck")  
      .up()
      .withData("time", System.currentTimeMillis())
      .build())
    .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")  
      .state (readyTime.get() != 0 )
      .withData( "time", readyTime.get())
      .build())
    .build();
Copied
  • Add built-in health-checks.
  • Add a custom liveness check.
  • Add a custom readiness check.
Build and run the application, then verify the liveness and readiness endpoints:
curl http://localhost:8080/health/live
curl http://localhost:8080/health/ready
Copied
Stop the application and build the docker image:
docker build -t helidon-quickstart-se .
Copied
Create the Kubernetes YAML specification, named health.yaml, with the following content:
kind: Service
apiVersion: v1
metadata:
  name: helidon-health 
  labels:
    app: helidon-health
spec:
  type: NodePort
  selector:
    app: helidon-health
  ports:
    - port: 8080
      targetPort: 8080
      name: http
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: helidon-health 
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: helidon-health
        version: v1
    spec:
      containers:
        - name: helidon-health
          image: helidon-quickstart-se
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /health/live 
              port: 8080
            initialDelaySeconds: 5 
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /health/ready 
              port: 8080
            initialDelaySeconds: 5 
            periodSeconds: 2
            timeoutSeconds: 3
---
Copied
  • A service of type NodePort that serves the default routes on port 8080.
  • A deployment with one replica of a pod.
  • The HTTP endpoint for the liveness probe.
  • The liveness probe configuration.
  • The HTTP endpoint for the readiness probe.
  • The readiness probe configuration.
Create and deploy the application into Kubernetes:
kubectl apply -f ./health.yaml
Copied
Get the service information:
kubectl get service/helidon-health
Copied
NAME             TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
helidon-health   NodePort   10.107.226.62   <none>        8080:30116/TCP   4s 
Copied
  • A service of type NodePort that serves the default routes on port 30116.
Verify the health endpoints using port '30116', your port may be different:
curl http://localhost:30116/health
Copied
Delete the application, cleaning up Kubernetes resources:
kubectl delete -f ./health.yaml
Copied

Summary

This guide demonstrated how to use health-check in a Helidon SE application as follows:

  • Access the default health-check

  • Create and use custom readiness and liveness checks

  • Customize the health-check root path

  • Integrate Helidon health-check with Kubernetes

Please refer to the following reference for additional information: