Define custom health checks

While you coded the database configuration in the previous exercise, Quarkus has generated Kubernetes and OpenShift resources in target/kubernetes:

target/kubernetes
|-- kubernetes.json
|-- kubernetes.yml
|-- openshift.json
`-- openshift.yml

Inspect their content and observe that you already have a base for your Kubernetes deployment. But before doing that, let’s implement custom health checks.

Add the health monitoring extension

In the terminal window please execute the following command:

./mvnw quarkus:add-extension -Dextensions="io.quarkus:quarkus-smallrye-health"

If you did not stop DevMode and inspect again target/kubernetes/kubernetes.yml or target/kubernetes/openshift.yml will notice that it had changed. Your Deployment/DeploymentConfig resource will contain a few lines like to:

          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /q/health/live
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 0
            periodSeconds: 30
            successThreshold: 1
            timeoutSeconds: 10
          name: tutorial-app
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /q/health/ready
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 0
            periodSeconds: 30
            successThreshold: 1
            timeoutSeconds: 10
You can access these endpoints also in your local at http://localhost:8080/q/health/ready or http://localhost:8080/q/health/live.

Default health checks

In dev mode, all your heath checks are visible in health UI: http://localhost:8080/q/health-ui/.

Some extensions may provide default health checks, including that the extension will automatically register its health checks. For example, quarkus-agroal (that is used to manage Quarkus datasources) automatically registers a readiness health check that will validate each datasource.

Quarkus has automatic readiness probes added when you use certain extensions:

  • datasource A probe to check database connection status.

  • kafka A probe to check kafka connection status. In this case you need to enable manually by setting quarkus.kafka.health.enabled to true.

  • mongoDB A probe to check MongoDB connection status.

  • neo4j A probe to check Neo4J connection status.

  • artemis A probe to check Artemis JMS connection status.

  • kafka-streams Liveness (for stream state) and Readiness (topics created) probes.

  • vault A probe to check Vault conection status.

  • gRPC A readiness probe for the gRPC services.

  • Cassandra A readiness probe to check Cassandra connection status.

  • Redis A readiness probe to check Redis connection status.

Customize integrations and startup configurations

Let’s assume that at application startup we insert some data in our database, but we should also update the message content from an external service. This type of scenario requires customizing health checks for the readiness probe to take into account the second dependency.

Add the REST Client extension

In the terminal window please execute the following command :

./mvnw quarkus:add-extension -Dextensions="io.quarkus:quarkus-rest-client"

Consuming a REST endpoint

Implement a simple data access object (DTO) to help reading the data:

package com.redhat.developers;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class ExternalGreeting {
    public String hello;
}

And now let’s define an interface and register it as a REST client:

package com.redhat.developers;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;



@RegisterRestClient
@Path("/hellosalut")
public interface HelloService {

    @GET
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    ExternalGreeting getContent(@QueryParam("lang") String lang);
}

And add in src/main/resources/application.properties:

com.redhat.developers.HelloService/mp-rest/url=https://fourtonfish.com

Define actions at startup initialization

Furthermore, we can use the previously defined endpoint to update our data using an intermediary repository:

package com.redhat.developers;

import io.quarkus.hibernate.orm.panache.PanacheRepository;
import io.quarkus.panache.common.Parameters;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;


@ApplicationScoped
public class GreetingRepository implements PanacheRepository<Message> {

    @Transactional
    public int update(String content, String language) {
        return update("content= :content where language= :language ",
                Parameters.with("content", content)
                        .and("language", language));
    }
}

And inject this class in the one used to customize startup initialization of data:

package com.redhat.developers;

import io.quarkus.arc.profile.UnlessBuildProfile;
import io.quarkus.runtime.Startup;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.util.List;

@Startup (1)
@ApplicationScoped
@UnlessBuildProfile("test") (2)
public class MessageInitializer {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageInitializer.class);

    @Inject
    @RestClient
    HelloService helloService; (3)

    @Inject
    GreetingRepository repository; (4)

    @PostConstruct
    public void init() {
        LOGGER.debug("Updating the db from external service");
        List<Message> messages = Message.findAll().list();
        for (Message message : messages) {
            String language = message.getLanguage();
            repository.update(helloService.getContent(language).hello, language);
        }
        LOGGER.debug("End update of the db ");
    }
}
1 This annotation initializes a CDI bean at application startup. This code will be executed after initializing the database from import.sql.
2 Enable for both prod and dev build time profiles.
3 Inject the RestClient service.
4 Inject the service that updates database content and has @Transactional annotation set on the invoked method.
5 Invoke record update.

Customize health endpoints and readiness probe

You can change the root path to the health endpoints by setting the following property in src/main/resources/application.properties:

quarkus.smallrye-health.root-path=/health

The Quarkus Kubernetes/OpenShift extension will take into account your custom probe definitions when generating their YAML. If you reload the context in DevMode (by pressing s), you would notice that your Kubernetes/OpenShift manifests have changed and take into account your new configuration.

As the database readiness is already assessed, we can customize another readiness probe to check the availability of the endpoint https://fourtonfish.com:

package com.redhat.developers;

import io.smallrye.health.checks.UrlHealthCheck;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.HttpMethod;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.Readiness;


@ApplicationScoped
public class CustomHealthCheck {

    @ConfigProperty(name = "com.redhat.developers.HelloService/mp-rest/url")
    String externalURL;

    @Readiness (1)
    HealthCheck checkURL() {
        return new UrlHealthCheck(externalURL+"/hellosalut/?lang=en") (2)
                .name("external-url-check").requestMethod(HttpMethod.GET).statusCode(200);
    }

}
1 Annotate the method with org.eclipse.microprofile.health.Readiness to signal its implementation.
2 UrlHealthCheck checks if host is reachable using a Http URL connection.

Quarkus comes with some HealthCheck implementations for you to check status of different components:

  • SocketHealthCheck: checks if host is reachable using a socket.

  • UrlHealthCheck: checks if host is reachable using a Http URL connection.

  • InetAddressHealthCheck: checks if host is reachable using InetAddress.isReachable method.