- Helidon SE Health Check 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
For this 15 minute tutorial, you will need the following:
| A Helidon SE Application | You can use your own application or use the Helidon SE Quickstart to create a sample application. |
| Java SE 17 (Open JDK 17) | Helidon requires Java 17+. |
| Maven 3.6.1+ | Helidon requires Maven 3.6.1+. |
| Docker 18.09+ | You need Docker if you want to build and deploy Docker containers. |
| Kubectl 1.16.5+ | If you want to deploy to Kubernetes, you need kubectl and a Kubernetes cluster (you can install one on your desktop. |
java -version
mvn --version
docker --version
kubectl version# On Mac
export JAVA_HOME=`/usr/libexec/java_home -v 17`
# On Linux
# Use the appropriate path to your JDK
export JAVA_HOME=/usr/lib/jvm/jdk-17Create 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.
mvn -U archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-se \
-DarchetypeVersion=3.2.16 \
-DgroupId=io.helidon.examples \
-DartifactId=helidon-quickstart-se \
-Dpackage=io.helidon.examples.quickstart.seUsing 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).
<dependency>
<groupId>io.helidon.health</groupId>
<artifactId>helidon-health-checks</artifactId>
</dependency>Main.java, and the createRouting method:private static Routing createRouting(Config config) {
HealthSupport health = HealthSupport.builder()
.addLiveness(HealthChecks.healthChecks())
.build();
return Routing.builder()
.register(health)
.build();
}- Add built-in health checks (requires the
helidon-health-checksdependency). - Register the created health support with web server routing (adds the
/healthendpoint).
mvn package -DskipTests=true
java -jar target/helidon-quickstart-se.jarcurl http://localhost:8080/health{
"status": "UP",
"checks": [
{
"name": "deadlock",
"status": "UP"
},
{
"name": "diskSpace",
"status": "UP",
"data": {
"free": "319.58 GB",
"freeBytes": 343144304640,
"percentFree": "68.63%",
"total": "465.63 GB",
"totalBytes": 499963174912
}
},
{
"name": "heapMemory",
"status": "UP",
"data": {
"free": "196.84 MB",
"freeBytes": 206404016,
"max": "3.56 GB",
"maxBytes": 3817865216,
"percentFree": "98.66%",
"total": "245.50 MB",
"totalBytes": 257425408
}
}
]
}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.
<dependency>
<groupId>io.helidon.health</groupId>
<artifactId>helidon-health</artifactId>
</dependency>HealthSupport builder in the Main.createRouting method:HealthSupport health = HealthSupport.builder()
.addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
.up()
.withData("time", System.currentTimeMillis())
.build())
.build();- Add a custom liveness health check. This example returns
UPand current time.
curl http://localhost:8080/health{
"status": "UP",
"checks": [
{
"name": "LivenessCheck",
"status": "UP",
"data": {
"time": 1546958376613
}
}
]
}Custom Readiness Health Checks
You can add readiness checks to indicate that the application is ready to be used. In this example, the server will wait five seconds before it becomes ready.
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;- Import AtomicLong.
- Declare the
readyTimevariable. - Sleep five seconds.
- Set the
readyTimeto the time when the server became ready.
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")
.status(readyTime.get() != 0 )
.withData( "time", readyTime.get())
.build())
.build();- Add the readiness check.
curl command with -v within five seconds and you see the application is not ready:curl -v http://localhost:8080/health/ready...
< HTTP/1.1 503 Service Unavailable
...
{
"status": "DOWN",
"checks": [
{
"name": "ReadinessCheck",
"status": "DOWN",
"data": {
"time,": 0
}
}
]
}- The HTTP status is
503since the application is not ready.
curl -v http://localhost:8080/health/ready...
< HTTP/1.1 200 OK
...
{
"status": "UP",
"checks": [
{
"name": "ReadinessCheck",
"status": "UP",
"data": {
"time,": 1566243562097
}
}
]
}- The HTTP status is
200indicating that the application is ready.
Custom Startup Health Checks
You can create custom startup health checks to indicate when the application has fully started and, therefore, when the readiness and liveness checks are meaningful.
This example reuses the readyTime field added above for the custom readiness check and adds a startup check that waits three additional seconds past the "ready" time before declaring the application started.
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")
.status(readyTime.get() != 0 )
.withData("time", readyTime.get())
.build())
.addStartup(() -> HealthCheckResponse.named("StartupCheck")
.status(readyTime.get() != 0
&& Duration.ofMillis(System.currentTimeMillis() - readyTime.get()).getSeconds() >= 3)
.withData("time", readyTime.get())
.build())
.build();- Add the startup check.
curl command with -v within eight seconds and you see the application is not reported as started:curl -v http://localhost:8080/health/started...
< HTTP/1.1 503 Service Unavailable
...
{
"status": "DOWN",
"checks": [
{
"name": "StartupCheck",
"status": "DOWN",
"data": {
"time": 1566243562097
}
}
]
}- The HTTP status is
503since the application is not started.
curl -v http://localhost:8080/health/started...
< HTTP/1.1 200 OK
...
{
"status": "UP",
"checks": [
{
"name": "StartupCheck",
"status": "UP",
"data": {
"time": 1566243562097
}
}
]
}- The HTTP status is
200indicating that the application is started.
When using the health check URLs, you can get the following health check data
liveness only - http://localhost:8080/health/live
readiness only - http://localhost:8080/health/ready
startup only - http://localhost:8080/health/started
curl http://localhost:8080/health{
"status": "UP",
"checks": [
{
"name": "LivenessCheck",
"status": "UP",
"data": {
"time": 1566244094548
}
},
{
"name": "ReadinessCheck",
"status": "UP",
"data": {
"time,": 1566244093012
}
},
{
"name": "StartupCheck",
"status": "UP",
"data": {
"time": 1566244093012
}
}
]
}Combine Built-In and Custom Health Checks
You can combine built-in and custom health checks using the same HealthSupport builder.
Main.createRouting method:HealthSupport health = HealthSupport.builder()
.addLiveness(HealthChecks.healthChecks())
.addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
.up()
.withData("time", System.currentTimeMillis())
.build())
.addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")
.status(readyTime.get() != 0)
.withData("time", readyTime.get())
.build())
.addStartup(() -> HealthCheckResponse.named("StartupCheck")
.status(readyTime.get() != 0
&& Duration.ofMillis(System.currentTimeMillis() - readyTime.get()).getSeconds() >= 3)
.withData("time", readyTime.get())
.build())
.build();- Add the built-in health checks back to
HealthSupportbuilder.
curl http://localhost:8080/health{
"status": "UP",
"checks": [
{
"name": "LivenessCheck",
"status": "UP",
"data": {
"time": 1566245527673
}
},
{
"name": "ReadinessCheck",
"status": "UP",
"data": {
"time,": 1566245527620
},
{
"name": "StartupCheck",
"status": "UP",
"data": {
"time,": 1566245527620
}
},
{
"name": "deadlock",
"status": "UP"
},
{
"name": "diskSpace",
"status": "UP",
"data": {
"free": "326.17 GB",
"freeBytes": 350224424960,
"percentFree": "70.05%",
"total": "465.63 GB",
"totalBytes": 499963174912
}
},
{
"name": "heapMemory",
"status": "UP",
"data": {
"free": "247.76 MB",
"freeBytes": 259791680,
"max": "4.00 GB",
"maxBytes": 4294967296,
"percentFree": "99.80%",
"total": "256.00 MB",
"totalBytes": 268435456
}
}
]
}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, startup, and default health checks.
Main.createRouting method:HealthSupport health = HealthSupport.builder()
.webContext("/probe/live")
.addLiveness(() -> HealthCheckResponse.named("livenessProbe")
.up()
.withData("time", System.currentTimeMillis())
.build())
.build();- Change the liveness URL path using a
WebContext.
/probe/live:curl http://localhost:8080/probe/live{
"status": "UP",
"checks": [
{
"name": "livenessProbe",
"status": "UP",
"data": {
"time": 1546958376613
}
}
]
}Using Liveness, Readiness, and Startup 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, readiness, and startup probes.
HealthSupport builder in the Main.createRouting method to use the built-in liveness checks and custom liveness, readiness, and startup checks:HealthSupport health = HealthSupport.builder()
.addLiveness(HealthChecks.healthChecks())
.addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
.up()
.withData("time", System.currentTimeMillis())
.build())
.addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")
.status(readyTime.get() != 0 )
.withData("time", readyTime.get())
.build())
.addStartup(() -> HealthCheckResponse.named("StartupCheck")
.status(readyTime.get() != 0
&& Duration.ofMillis(System.currentTimeMillis() - readyTime.get()).getSeconds() >= 3)
.withData("time", readyTime.get())
.build())
.build();- Add built-in health checks.
- Add a custom liveness check.
- Add a custom readiness check.
- Add a custom startup check.
curl http://localhost:8080/health/live
curl http://localhost:8080/health/ready
curl http://localhost:8080/health/starteddocker build -t helidon-quickstart-se .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: apps/v1
metadata:
name: helidon-health
spec:
replicas: 1
selector:
matchLabels:
app: helidon-health
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
startupProbe:
httpGet:
path: /health/started
port: 8080
initialDelaySeconds: 8
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
---- A service of type
NodePortthat serves the default routes on port8080. - 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.
- The HTTP endpoint for the startup probe.
- The startup probe configuration.
kubectl apply -f ./health.yamlkubectl get service/helidon-healthNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helidon-health NodePort 10.107.226.62 <none> 8080:30116/TCP 4s - A service of type
NodePortthat serves the default routes on port30116.
curl http://localhost:30116/healthkubectl delete -f ./health.yamlSummary
This guide demonstrated how to use health checks in a Helidon SE application as follows:
Access the default health check
Create and use custom readiness, liveness, and startup checks
Customize the health check root path
Integrate Helidon health check with Kubernetes
Refer to the following reference for additional information: