A previous article described the specifications in the Eclipse MicroProfile 1.2 release and the benefits for Java-based cloud-native applications. This article shows how software developers writing Java-based microservices can leverage those specifications to take advantage of the management capabilities provided by Red Hat OpenShift.
The MicroProfile 1.2 release provides specifications to solve the common challenges that Java developers are facing when building and deploying microservices on container platforms:
- The Config specification provides a uniform way to configure Java applications regardless of the deployment environment (local JVM, testing environment for continuous integration, container platform for production deployment, etc.).
- The Health Check specification provides a single HTTP endpoint that can be queried to determine the overall status of the application (that can be composed internally of many different and unrelated Health Checks).
The purpose of this article is to provide an overview of the MicroProfile Config and Health Check specifications. It will also illustrate how a Java application can use the APIs to integrate with the management capabilities provided by container/cloud platforms, such as Red Hat OpenShift or Kubernetes.
Eclipse MicroProfile
As stated on its website, the mission of the Eclipse MicroProfile is to define:
An open forum to optimize Enterprise Java for a microservices architecture by innovating across multiple implementations and collaborating on common areas of interest with a goal of standardization.
The Eclipse MicroProfile project provides an umbrella of Java specifications to develop and deploy microservice-based Java applications. Some of these specifications are coming from Java EE (like JAX-RS or JSON-P), but Eclipse MicroProfile also specifies new ones that fill gaps not covered by existing Java specifications or APIs.
In this article, we will focus on the two specifications from the MicroProfile 1.2 release that relates to application configuration and healthiness:
- MicroProfile Config
- MicroProfile Health Check
MicroProfile Config Specification
The MicroProfile Config specification is the first specification from the Eclipse MicroProfile project. It provides a common way to retrieve configuration coming from a variety of sources (properties file, system properties, environment variables, database, etc.). The API is very simple and consists mainly of 2 objects:
- a
Config
interface can be used to retrieve (possibly optional) values identified by a name from many config sources. - a
@ConfigProperty
annotation to directly inject a configuration value using CDI.
A sample code to use for the Config API looks like this:
@Inject
@ConfigProperty(name = "num_size", defaultValue = "3")
private int numSize;
@Inject
@ConfigProperty(name = "num_max", defaultValue = "100")
private int numMax;
The @ConfigProperty
annotation accepts a defaultValue
parameter that will be used to set the field if the property is not present in the configuration.
Alternatively, you can use an injected Config
object to get a property value:
@Inject
Config config;
...
Optional<URL> url = config.getOptionalValue("my_url", URL.class);
...
The Config
object provides two methods to retrieve properties:
getValue(String, Class)
getOptionalValue(String, Class)
getValue()
will throw an exception if the property is not present in the configuration. This method must be used only for mandatory configuration.
getOptionalValue()
returns a java.util.Optional
object that is empty if the property is not present in the configuration. This method is used for optional configuration.
Both methods will also throw exceptions if the property value (retrieved as a String
from the configuration) cannot be converted to the expected Java type passed as the second argument.
ConfigSource
The API also provides a way to add different config sources from where the properties are fetched.
By default, they can come from:
- JVM System properties (backed by
System.getProperties()
) - OS environment (backed by
System.getenv()
) - Properties file (stored in the application archive in
META-INF/microprofile-config.properties
)
Implementations of the Config API can provide additional config sources (for example by reading properties from a relational database or a key-value store).
Applications that use the Config API can also provide custom config source by implementing the org.eclipse.microprofile.config.spi.ConfigSource
and registers it in a file /META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
with the fully qualified class name of the custom implementation.
Converters
The MicroProfile Config specification will automatically try to convert the property value (which is retrieved as a String
) to the specified type (like URL
in the code example above) based on well-known conversion methods (for example, a constructor with a String
parameter but also static methods named parse(CharSequence)
or valueOf(String)
).
If the application uses a Java type that does not conform to these naming conventions, the applications can provide additional Converters to handle any custom-made conversion. The application must provide an implementation of org.eclipse.microprofile.config.spi.Converter
and registers it in a file /META-INF/services/org.eclipse.microprofile.config.spi.Converter
with the fully qualified class name of the custom implementation.
Most Java types that map to configuration values (String
, primitive types such as long
or boolean
, but also classes like java.time
or URL
) are supported by the implicit Converters required by the MicroProfile Config specification.
MicroProfile Health Check Specification
The Eclipse MicroProfile Health Check specification defines a single container runtime mechanism for validating the availability and status of a MicroProfile application. This is primarily intended as a mechanism for use in containerized environments such as OpenShift or Kubernetes.
The specification defines a single /health
HTTP endpoint that can be queried by the container platform to determine the status of the application running inside the container.
The specification also defines a Java API to create application specific health checks:
@FunctionalInterface
public interface HealthCheck {
HealthCheckResponse call();
}
When the container queries the /health
endpoint, the MicroProfile Health Check implementation calls all registered HealthCheck
instances to determine the overall status of the application.
The HealthCheckResponse
returned by the HealthCheck
instance contains a name, an outcome, and optional data. For example:
public class CheckDiskspace implements HealthCheck {
@Override
public HealthCheckResponse call() {
return HealthCheckResponse.named("diskspace")
.withData("free", "780mb")
.up()
.buid();
}
}
When the application is started, the MicroProfile HealthCheck implementations load the HealthCheck
(that is annotated with the @Health
annotation) using CDI.
The container can then call the /health
endpoint to determine the overall status of the application.
$ curl -v http://localhost:8080/health
...
< HTTP/1.1 200 OK
...
{"checks": [
{"name":"diskspace","state":"UP", "data":{"free": "780mb"}}],
"outcome": "UP"
}
If the overall outcome of the Health Check is UP
, the HTTP returns a 200 OK
response.
If one of more of the HealthChecks would return DOWN
, the overall outcome of the Health Check would be DOWN
and the HTTP request would return a 503 Service Unavailable
response instead.
Build and deploy a MicroProfile Application on OpenShift
To illustrate the use of MicroProfile API in a Java application, we will use a very simple Java application running with WildFly Swarm. We will describe it and test it locally first. Then we will deploy it on OpenShift and illustrates how the MicroProfile Config and HealthCheck can leverage the OpenShift container platform.
Description of the Application
This application is a simple Web Service with a unique HTTP endpoint that returns a list of random integers.
The source code for the application is hosted at https://github.com/jmesnil/microprofile-openshift-example/.
When a user sends a request to http://localhost:8080/, the application responds with a list of random integers in a JSON array:
$ curl http://localhost:8080/
[630030638,1689535734,1995796600]
Application Configuration
The application uses MicroProfile Config to configure its behavior. It defines two configuration properties:
num_size
- the number of generated random integers (3
by default)num_max
- the maximum value of integer (100
by default)
These two properties are configured using the Eclipse MicroProfile Config API in the NumbersGenerator
class:
@Inject
@ConfigProperty(name = "num_size", defaultValue = "3")
private int numSize;
@Inject
@ConfigProperty(name = "num_max", defaultValue = "100")
private int numMax;
In the absence of a more specific configuration, the application will return a list of 3 integers between 0 and 100.
Application Health check
Our demo application is quite simple and does not really require you to define Health Checks. However we will add one just to illustrate how they can fit in more realistic applications.
We will add a single Health Check to determine the healthiness of the NumbersGenerator
. The Health Check will check the configuration of the NumbersGenerator
and will return DOWN
if the configuration is not correct (in a real example it would be better to prevent the application to start if it is misconfigured).
@ApplicationScoped
@Health
public static class ConfigHealthCheck implements HealthCheck {
private static final String PROBE_NAME = "numbers.config";
@Inject
NumbersGenerator generator;
@Override
public HealthCheckResponse call() {
if (generator.numSize < 0 ||
generator.numMax <= MIN_VALUE) {
return HealthCheckResponse.named(PROBE_NAME)
.withData("num_size", generator.numSize)
.withData("num_max", generator.numMax)
.down()
.build();
} else {
return HealthCheckResponse.named(PROBE_NAME)
.up()
.build();
}
}
}
If the NumbersGenerator
is correctly configured, the Health Checks will return UP
. If it is misconfigured, the Health Check will return DOWN
as well as the configuration values that are causing the issue.
Build the application and test it locally
The application is built using Maven:
$ mvn clean package
The application is packaged as a Java UberJar that can be run locally:
$ java -jar target/numbers-swarm.jar
...
2018-01-15 11:58:50,658 INFO [org.jboss.as.server] (main) WFLYSRV0010: Deployed "numbers.war" (runtime-name : "numbers.war")
2018-01-15 11:58:50,677 INFO [org.wildfly.swarm] (main) WFSWARM99999: WildFly Swarm is Ready
We can go repeatedly to http://localhost:8080/ to generate lists of random integers:
$ curl http://localhost:8080/
[99,38,43]
[75,43,75]
[9,55,69]
We can now change the configuration of the application by using environment variables to specify different values for the num_size
and num_max
properties.
$ num_size=5 num_max=10 java -jar target/numbers-swarm.jar
The application will now return a list of 5 random integers between 0 and 10:
[2,2,5,9,7]
[6,4,0,0,2]
[1,9,4,0,3]
[1,9,5,3,6]
We could provide the same configuration using System properties (using -D
on the command line):
$ java -jar target/numbers-swarm.jar -Dnum_size=5 -Dnum_max=10
The MicroProfile Config specifies that each config source has an ordinal value that determines which config source will provide the actual property value to the application. If a property is available in multiple config sources, the one with the highest ordinal wins.
The MicroProfile Config specifies that properties coming from the environment variables have an ordinal of 300 while properties coming from the JVM System properties have an ordinal of 400.
That means that if the num_size
property is defined both in the environment and the JVM system properties, the value used by the application comes from the JVM System properties:
$ num_size=5 java -jar target/numbers-swarm.jar -Dnum_size=1
In that example above, the application will use 1 for the value of its num_size
configuration property:
$ curl http://localhost:8080/
[96]
We can also test the Health Check locally:
$ curl -v http://localhost:8080/
< HTTP/1.1 200 OK
...
{"checks": [
{"name":"numbers.config","state":"UP"}],
"outcome": "UP"
}
If we are starting the application with an incorrect configuration (e.g. using a num_max
of -1), the Health Check will return DOWN
:
$ java -jar target/numbers-swarm.jar -Dnum_max=-1
...
$ curl -v http://locahost:8080/health
< HTTP/1.1 503 Service Unavailable
...
{"checks": [
{"name":"numbers.config","state":"DOWN","data": {"num_size":3,"num_max":-1}}],
"outcome": "DOWN"
}
Deploy on OpenShift
We have tested the application locally. Now let's deploy it on OpenShift.
The application uses the fabric8 Maven plugin to deploy on OpenShift. It has a few prerequisites:
- Sign up to https://manage.openshift.com
- Ensure that the
oc
command line is configured to point to our OpenShift account (once you are logged in OpenShift Web Console, you can use the"Copy Login Command"
and paste it into your terminal)
$ oc login https:/xxxxx.openshift.com --token=xxxxx
$ oc status
Once you are logged in from the command line, you can deploy the application by running the Maven command:
$ mvn -Popenshift clean fabric8:deploy
Maven uses the openshift
profile to deploy the application on OpenShift under the name microprofile-openshift-example
.
The Java application is configured to automatically expose a route to its 8080 port. To find the name of the exposed route to interact with the application, we can look at the application configuration with the oc
command:
$ oc get routes/microprofile-openshift-example
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
microprofile-openshift-example microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com microprofile-openshift-example 8080
If you deploy the application with your own OpenShift account, the URL will be different from mine, which is: http://microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com.
At this point, the application is accessible and returns a list of random integers:
$ curl http://microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com
[94,48,3]
OpenShift services for MicroProfile application
OpenShift Environment Configuration
We will now modify the OpenShift environment so that it provides the configuration value for the application.
Using the oc
tool, we can list the environment variables for our microprofile-openshift-example application:
$ oc env dc/microprofile-openshift-example --list
# deploymentconfigs microprofile-openshift-example, container wildfly-swarm
# KUBERNETES_NAMESPACE from field path metadata.namespace
We can now specify that we want to return a list of 5 random integers between 0 and 10:
$ oc env dc/microprofile-openshift-example num_size=5 num_max=10
OpenShift detects that the environment has changed and will roll out the application to deploy a version with the updated environments.
Once the deployment has been rolled out, we can check that the list of random integers returned by our application now contains 5 random integers between 0 and 10.
Check here: http://microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com
$ curl http://microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com
[9,2,9,5,1]
OpenShift Liveness and Readiness Probes
The readiness probe is automatically configured in OpenShift to use the /health
endpoint of our application. This configuration is extracted from the configuration of the fabric8 Maven plugin is configured in the application’s pom.xml:
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
...
<configuration>
...
<enricher>
<config>
<wildfly-swarm-health-check>
<path>/health</path>
</wildfly-swarm-health-check>
</config>
</enricher>
</configuration>
</plugin>
In the OpenShift Web Console, the deployment page for the application lists the probes:
Readiness Probe: GET /health on port 8080 (HTTP) 10s delay, 1s timeout
Liveness Probe: GET /health on port 8080 (HTTP) 180s delay, 1s timeout
The oc
tool can be used to check that they are properly configured too:
$ oc describe dc/microprofile-openshift-example
Name: microprofile-openshift-example
...
Containers:
wildfly-swarm:
Liveness: http-get http://:8080/health delay=180s timeout=1s period=10s #success=1 #failure=3
Readiness: http-get http://:8080/health delay=10s timeout=1s period=10s #success=1 #failure=3
Summary
The MicroProfile 1.2 release and its specifications help developing and deploying MicroServices-based Java applications.
In this article, we have used the MicroProfile Config specification to provide a uniform configuration to the Java application. We have then shown how to provide different values to the configuration using System properties or environment variables.
We have also used the MicroProfile Health Check specification to define health probes and provide a HTTP entry point that container platforms such as OpenShift can use to query the healthiness of the Java application.
Finally, we have deployed the Java application on OpenShift and demonstrated how to set environment variables to configure the application. We have also configured OpenShift liveness and readiness probes to use the MicroProfile Health Check HTTP entry point to check the application healthiness.
Contribute and Collaborate
The Microprofile is an open and vibrant community hosted under the Eclipse Foundation. This, together with the decision to move future Java EE specification work under the Eclipse foundation, provides an excellent opportunity to provide feedback, contribute, and collaborate.
Eclipse Microprofile projects
Microprofile 1.2 Specifications
- BOM and Maven Coordinates
- https://github.com/eclipse/microprofile-config/
- https://github.com/eclipse/microprofile-health/
Discussion Groups and Mailing lists
Last updated: January 12, 2024