- Helidon MP Tutorial
This tutorial describes how to build a Helidon MicroProfile (MP) application from scratch including JSON REST endpoints, metrics, health check, and configuration.
What You Need
For this 30 minute tutorial, you will need the following:
| A Helidon MP Application | You can use your own application or use the Helidon MP 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. |
| curl | (Optional) for testing |
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 the Maven Project
This tutorial demonstrates how to create the application from scratch, without using the Maven archetypes as a quickstart.
Create a new empty directory for the project (for example, helidon-mp-tutorial). Change into this directory.
Create a new Maven POM file (called pom.xml) and add the following content:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.applications</groupId>
<artifactId>helidon-mp</artifactId>
<version>3.2.16</version>
<relativePath/>
</parent>
<groupId>io.helidon.examples</groupId>
<artifactId>helidon-mp-tutorial</artifactId>
<name>${project.artifactId}</name>
<properties>
<mainClass>io.helidon.examples.Main</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.microprofile.bundles</groupId>
<artifactId>helidon-microprofile</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-libs</id>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jboss.jandex</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<executions>
<execution>
<id>make-index</id>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>The POM file contains the basic project information and configurations needed to get started and does the following:
- Includes the Helidon MP application parent pom. This parent pom contains dependency and plugin management to keep your application’s pom simple and clean.
- Establishes the Maven coordinates for the new project.
- Sets the
mainClasswhich will be used later when building a JAR file. The class will be created later in this tutorial. - Adds a dependency for the MicroProfile bundle which allows the use of MicroProfile features in the application. The helidon-mp parent pom includes dependency management, so you don’t need to include a version number here. You will automatically use the version of Helidon that matches the version of the parent pom ({helidon.version} in this case).
- Adds plugins to be executed during the build. The
maven-dependency-pluginis used to copy the runtime dependencies into your target directory. Thejandex-maven-pluginbuilds an index of your class files for faster loading. The Helidon parent pom handles the details of configuring these plugins. But you can modify the configuration here.
MicroProfile contains features like Metrics, Health Check, Streams Operators, Open Tracing, OpenAPI, REST client, and fault tolerance. You can find detailed information about MicroProfile on the Eclipse MicroProfile site.
With this pom.xml, the application can be built successfully with Maven:
mvn clean packageThis will create a JAR file in the target directory.
The warning message JAR will be empty - no content was marked for inclusion! can be ignored for now because there is no actual content in the application yet.
Start Implementing the MicroProfile Application
The actual application logic can be created now. Create a directory for your source code, and then create directories for the package hierarchy:
mkdir -p src/main/java/io/helidon/examplesThe application will be a simple REST service that will return a greeting to the caller. The first iteration of the application will contain a resource class and a Main class which will be used to start up the Helidon server and the application.
Technically, your own main class is not needed unless you want to control the startup sequence. You can set the mainClass property to io.helidon.microprofile.cdi.Main and it will use Helidon’s default main class.
The GreetResource is defined in the GreetResource.java class as shown below:
package io.helidon.examples;
import jakarta.enterprise.context.RequestScoped;
import jakarta.json.Json;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.Collections;
@Path("/greet")
@RequestScoped
public class GreetResource {
private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
@GET
@Produces(MediaType.APPLICATION_JSON)
public JsonObject getDefaultMessage() {
return JSON.createObjectBuilder()
.add("message", "Hello World")
.build();
}
}- This class is annotated with
Pathwhich sets the path for this resource as/greet. - The
RequestScopedannotation defines that this bean is request scoped. The request scope is active only for the duration of one web service invocation and it is destroyed at the end of that invocation. You can learn more about scopes and contexts, and how they are used from the Specification. - A
public JsonObject getDefaultMessage()method is defined which is annotated withGET, meaning it will accept the HTTP GET method. It is also annotated withProduces(MediaType.APPLICATION_JSON)which declares that this method will return JSON data. - The method body creates a JSON object containing a single object named "message" with the content "Hello World". This method will be expanded and improved later in the tutorial.
So far this is just a JAX-RS application, with no Helidon or MicroProfile specific code in it. There are many JAX-RS tutorials available if you want to learn more about this kind of application.
A main class is also required to start up the server and run the application. If you don’t use Helidon’s built-in main class you can define your own:
package io.helidon.examples;
import io.helidon.microprofile.server.Server;
import java.io.IOException;
public final class Main {
private Main() { }
public static void main(final String[] args) throws IOException {
Server server = startServer();
System.out.println("http://localhost:" + server.port() + "/greet");
}
static Server startServer() {
return Server.create().start();
}
}In this class, a main method is defined which starts the Helidon MP server and prints out a message with the listen address.
- Notice that this class has an empty no-args constructor to make sure this class cannot be instantiated.
- The MicroProfile server is started with the default configuration.
Helidon MP applications also require a beans.xml resource file to tell Helidon to use the annotations discussed above to discover Java beans in the application.
Create a beans.xml in the src/main/resources/META-INF directory with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
version="2.0"
bean-discovery-mode="annotated">
</beans>- The
bean-discovery-modetells Helidon to look for the annotations to discover Java beans in the application.
Build the Application
Helidon MP applications are packaged into a JAR file and the dependencies are copied into a libs directory.
You can now build the application.
mvn packageThis will build the application jar and save all runtime dependencies in the target/libs directory. This means you can easily start the application by running the application jar file:
java -jar target/helidon-mp-tutorial.jarAt this stage, the application is a very simple "Hello World" greeting service. It supports a single GET request for generating a greeting message. The response is encoded using JSON. For example:
curl -X GET http://localhost:7001/greet
{"message":"Hello World!"}In the output you can see the JSON output from the getDefaultMessage() method that was discussed earlier. The server has used a default port 7001. The application can be stopped cleanly by pressing Ctrl+C.
Configuration
Helidon MP applications can use the META-INF/microprofile-config.properties file to specify configuration data. This file (resource) is read by default if it is present on the classpath. Create this file in src/main/resources/META-INF with the following content:
# Microprofile server properties
server.port=8080
server.host=0.0.0.0Rebuild the application and run it again. Notice that it now uses port 8080 as specified in the configuration file.
You can learn more about options for configuring the Helidon Server on the Server Configuration page.
In addition to predefined server properties, application-specific configuration information can be added to this file. Add the app.greeting property to the file as shown below. This property will be used to set the content of greeting message.
# Microprofile server properties
server.port=8080
server.host=0.0.0.0
# Application properties
app.greeting=HelloAdd a new "provider" class to read this property and make it available to the application. The class will be called GreetingProvider.java and have the following content:
package io.helidon.examples;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.concurrent.atomic.AtomicReference;
@ApplicationScoped
public class GreetingProvider {
private final AtomicReference<String> message = new AtomicReference<>();
@Inject
public GreetingProvider(@ConfigProperty(name = "app.greeting") String message) {
this.message.set(message);
}
String getMessage() {
return message.get();
}
void setMessage(String message) {
this.message.set(message);
}
}- This class also has the
ApplicationScopedannotation, so it will persist for the life of the application. - The class contains an
AtomicReferenceto aStringwhere the greeting will be stored. TheAtomicReferenceprovides lock-free thread-safe access to the underlyingString. - The
public GreetingProvider(…)constructor is annotated withInjectwhich tells Helidon to use Contexts and Dependency Injection to provide the needed values. In this case, theString messageis annotated withConfigProperty(name = "app.greeting")so Helidon will inject the property from the configuration file with the keyapp.greeting. This method demonstrates how to read configuration information into the application. A getter and setter are also included in this class.
The GreetResource must be updated to use this value instead of the hard coded response. Make the following updates to that class:
package io.helidon.examples;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.json.Json;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.Collections;
@Path("/greet")
@RequestScoped
public class GreetResource {
private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
private final GreetingProvider greetingProvider;
@Inject
public GreetResource(GreetingProvider greetingConfig) {
this.greetingProvider = greetingConfig;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public JsonObject getDefaultMessage() {
return createResponse("World");
}
private JsonObject createResponse(String who) {
String msg = String.format("%s %s!", greetingProvider.getMessage(), who);
return JSON.createObjectBuilder()
.add("message", msg)
.build();
}
}- This updated class adds a
GreetingProviderand uses constructor injection to get the value from the configuration file. - The logic to create the response message is refactored into a
createResponsemethod and thegetDefaultMessage()method is updated to use this new method. - In
createResponse()the message is obtained from theGreetingProviderwhich in turn got it from the configuration files.
Rebuild and run the application. Notice that it now uses the greeting from the configuration file. Change the configuration file and restart the application, notice that it uses the changed value.
To learn more about Helidon MP configuration please see the Config section of the documentation.
Extending the Application
In this section, the application will be extended to add a PUT resource method which will allow users to update the greeting and a second GET resource method which will accept a parameter.
Here are the two new methods to add to GreetResource.java:
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Response;
// some lines omitted
@Path("/{name}")
@GET
@Produces(MediaType.APPLICATION_JSON)
public JsonObject getMessage(@PathParam("name") String name) {
return createResponse(name);
}
@Path("/greeting")
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response updateGreeting(JsonObject jsonObject) {
if (!jsonObject.containsKey("greeting")) {
JsonObject entity = JSON.createObjectBuilder()
.add("error", "No greeting provided")
.build();
return Response.status(Response.Status.BAD_REQUEST).entity(entity).build();
}
String newGreeting = jsonObject.getString("greeting");
greetingProvider.setMessage(newGreeting);
return Response.status(Response.Status.NO_CONTENT).build();
}- The first of these two methods implements a new HTTP GET service that returns JSON and it has a path parameter. The
Pathannotation defines the next part of the path to be a parameter namedname. In the method arguments thePathParam("name")annotation onString namehas the effect of passing the parameter from the URL into this method asname. - The second method implements a new HTTP PUT service which produces and consumes JSON, note the
ConsumesandPUTannotations. It also defines a path of "/greeting". Notice that the method argument is aJsonObject. Inside the method body there is code to check for the expected JSON, extract the value and update the message in theGreetingProvider.
Rebuild and run the application. Test the new services using curl commands similar to those shown below:
curl -X GET http://localhost:8080/greet
{"message":"Hello World!"}
curl -X GET http://localhost:8080/greet/Joe
{"message":"Hello Joe!"}
curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting
curl -X GET http://localhost:8080/greet/Jose
{"message":"Hola Jose!"}Helidon MP provides many other features which can be added to the application.
Logging
The application logging can be customized. The default logging provider is java.util.logging, however it is possible to use other providers. In this tutorial the default provider is used.
Create a logging.properties file in src/main/resources with the following content:
# Send messages to the console
handlers=io.helidon.common.HelidonConsoleHandler
# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
# Global logging level. Can be overridden by specific loggers
.level=INFO - The Helidon console logging handler is configured. This handler writes to
System.out, does not filter by level and uses a customSimpleFormatterthat supports thread names. - The format string is set using the standard options to include the timestamp, thread name and message.
- The global logging level is set to
INFO.
The Helidon MicroProfile server will detect the new logging.properties file and configure the LogManager for you.
Rebuild and run the application and notice the new logging format takes effect.
// before
Aug 22, 2019 11:10:11 AM io.helidon.webserver.NettyWebServer lambda$start$8
INFO: Channel '@default' started: [id: 0xd0afba31, L:/0:0:0:0:0:0:0:0:8080]
Aug 22, 2019 11:10:11 AM io.helidon.microprofile.server.ServerImpl lambda$start$10
INFO: Server started on http://localhost:8080 (and all other host addresses) in 182 milliseconds.
http://localhost:8080/greet
// after
2019.08.22 11:24:42 INFO io.helidon.webserver.NettyWebServer Thread[main,5,main]: Version: 1.2.0
2019.08.22 11:24:42 INFO io.helidon.webserver.NettyWebServer Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default' started: [id: 0x8f652dfe, L:/0:0:0:0:0:0:0:0:8080]
2019.08.22 11:24:42 INFO io.helidon.microprofile.server.ServerImpl Thread[nioEventLoopGroup-2-1,10,main]: Server started on http://localhost:8080 (and all other host addresses) in 237 milliseconds.
http://localhost:8080/greetMetrics
Helidon provides built-in support for metrics endpoints.
curl -s -X GET http://localhost:8080/metricscurl -H 'Accept: application/json' -X GET http://localhost:8080/metricsIt is possible to disable metrics by adding properties to the microprofile-config.properties file, for example:
metrics.base.classloader.currentLoadedClass.count.enabled=falseCall the metrics endpoint before adding this change to confirm that the metric is included, then add the property to disable the metric, rebuild and restart the application and check again:
# before
curl -s http://localhost:8080/metrics | grep classloader_current
# TYPE base:classloader_current_loaded_class_count counter
# HELP base:classloader_current_loaded_class_count Displays the number of classes that are currently loaded in the Java virtual machine.
base:classloader_current_loaded_class_count 7936
# after
curl -s http://localhost:8080/metrics | grep classloader_current
# (no output)Helidon also support custom metrics. To add a new metric, annotate the JAX-RS resource with one of the metric annotations as shown in the example below:
You can find details of the available annotations in the MicroProfile Metrics Specification.
import org.eclipse.microprofile.metrics.annotation.Timed;
// some lines omitted
@GET
@Produces(MediaType.APPLICATION_JSON)
@Timed
public JsonObject getDefaultMessage() {
return createResponse("World");
}- The
Timedannotation is added to thegetDefaultMessage()method.
Rebuild and run the application. Make some calls to the endpoint (http://localhost:8080/greet) so there will be some data to report. Then obtain the application metrics as follows:
curl -H "Accept: application/json" http://localhost:8080/metrics/application
{
"io.helidon.examples.GreetResource.getDefaultMessage": {
"count": 2,
"meanRate": 0.036565171873527716,
"oneMinRate": 0.015991117074135343,
"fiveMinRate": 0.0033057092356765017,
"fifteenMinRate": 0.0011080303990206543,
"min": 78658,
"max": 1614077,
"mean": 811843.8728029992,
"stddev": 766932.8494434259,
"p50": 78658,
"p75": 1614077,
"p95": 1614077,
"p98": 1614077,
"p99": 1614077,
"p999": 1614077
}
}Learn more about using Helidon and MicroProfile metrics in the Metrics Guide.
Health Check
Helidon provides built-in support for health check endpoints. Obtain the built-in health check using the following URL:
curl -s -X GET http://localhost:8080/health
{
"outcome": "UP",
"status": "UP",
"checks": [
{
"name": "deadlock",
"state": "UP",
"status": "UP"
},
{
"name": "diskSpace",
"state": "UP",
"status": "UP",
"data": {
"free": "381.23 GB",
"freeBytes": 409340088320,
"percentFree": "43.39%",
"total": "878.70 GB",
"totalBytes": 943491723264
}
},
{
"name": "heapMemory",
"state": "UP",
"status": "UP",
"data": {
"free": "324.90 MB",
"freeBytes": 340682920,
"max": "3.46 GB",
"maxBytes": 3715629056,
"percentFree": "97.65%",
"total": "408.00 MB",
"totalBytes": 427819008
}
}
]
}Endpoints for readiness and liveness checks are also provided by default. Obtain the default results using these URLs, which return the same result as the previous example.:
# readiness
curl -i -X GET http://localhost:8080/health/ready
# liveness
curl -i -X GET http://localhost:8080/health/liveHelidon allows the addition of custom health checks to applications. Create a new class GreetHealthcheck.java with the following content:
package io.helidon.examples;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;
@Liveness
@ApplicationScoped
public class GreetHealthcheck implements HealthCheck {
private GreetingProvider provider;
@Inject
public GreetHealthcheck(GreetingProvider provider) {
this.provider = provider;
}
@Override
public HealthCheckResponse call() {
String message = provider.getMessage();
return HealthCheckResponse.named("greeting")
.state("Hello".equals(message))
.withData("greeting", message)
.build();
}
}- This class has the MicroProfile
Livenessannotation which tells Helidon that this class provides a custom health check. You can learn more about the available annotations in the MicroProfile Health Protocol and Wireformat document. - This class also has the
ApplicationScopedannotation, as seen previously. - The
GreetingProvideris injected using Context and Dependency Injection. This example will use the greeting to determine whether the application is healthy, this is a contrived example for demonstration purposes. - Health checks must implement the
HealthCheckfunctional interface, which includes the methodHealthCheckResponse call(). Helidon will invoke thecall()method to verify the healthiness of the application. - In this example, the application is deemed to be healthy if the
GreetingProvider,getMessage()method returns the string"Hello"and unhealthy otherwise.
Rebuild the application, make sure that the mp.conf has the greeting set to something other than "Hello" and then run the application and check the health:
curl -i -X GET http://localhost:8080/health/live
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
Date: Fri, 23 Aug 2019 10:07:23 -0400
transfer-encoding: chunked
connection: keep-alive
{"outcome":"DOWN","status":"DOWN","checks":[{"name":"deadlock","state":"UP","status":"UP"},{"name":"diskSpace","state":"UP","status":"UP","data":{"free":"381.08 GB","freeBytes":409182306304,"percentFree":"43.37%","total":"878.70 GB","totalBytes":943491723264}},{"name":"greeting","state":"DOWN","status":"DOWN","data":{"greeting":"Hey"}},{"name":"heapMemory","state":"UP","status":"UP","data":{"free":"243.81 MB","freeBytes":255651048,"max":"3.46 GB","maxBytes":3715629056,"percentFree":"98.58%","total":"294.00 MB","totalBytes":308281344}}]} - The HTTP return code is now 503 Service Unavailable.
- The status is reported as "DOWN" and the custom check is included in the output.
Now update the greeting to "Hello" using the following request, and then check health again:
# update greeting
curl -i -X PUT -H "Content-Type: application/json" -d '{"greeting": "Hello"}' http://localhost:8080/greet/greeting
HTTP/1.1 204 No Content
Date: Thu, 22 Aug 2019 13:29:57 -0400
connection: keep-alive
# check health
curl -i -X GET http://localhost:8080/health/live
HTTP/1.1 200 OK
Content-Type: application/json
Date: Fri, 23 Aug 2019 10:08:09 -0400
connection: keep-alive
content-length: 536
{"outcome":"UP","status":"UP","checks":[{"name":"deadlock","state":"UP","status":"UP"},{"name":"diskSpace","state":"UP","status":"UP","data":{"free":"381.08 GB","freeBytes":409179811840,"percentFree":"43.37%","total":"878.70 GB","totalBytes":943491723264}},{"name":"greeting","state":"UP","status":"UP","data":{"greeting":"Hello"}},{"name":"heapMemory","state":"UP","status":"UP","data":{"free":"237.25 MB","freeBytes":248769720,"max":"3.46 GB","maxBytes":3715629056,"percentFree":"98.40%","total":"294.00 MB","totalBytes":308281344}}]} - The PUT returns a HTTP 204.
- The health check now returns a HTTP 200.
- The status is now reported as "UP" and the details are provided in the checks.
Learn more about health checks in the Health Check Guide.
Build a Docker Image
To run the application in Docker (or Kubernetes), a Dockerfile is needed to build a Docker image. To build the Docker image, you need to have Docker installed and running on your system.
Add a new Dockerfile in the project root directory with the following content:
FROM maven:3.8.4-openjdk-17-slim as build
WORKDIR /helidon
ADD pom.xml .
RUN mvn package -DskipTests
ADD src src
RUN mvn package -DskipTests
RUN echo "done!"
FROM openjdk:17-jdk-slim
WORKDIR /helidon
COPY --from=build /helidon/target/helidon-mp-tutorial.jar ./
COPY --from=build /helidon/target/libs ./libs
CMD ["java", "-jar", "helidon-mp-tutorial.jar"]
EXPOSE 8080- This Dockerfile uses Docker’s multi-stage build feature. The
FROMkeyword creates the first stage. In this stage, the base container has the build tools needed to build the application. These are not required to run the application, so the second stage uses a smaller container. - Add the
pom.xmland running an "empty" maven build will download all of the dependencies and plugins in this layer. This will make future builds faster because they will use this cached layer rather than downloading everything again. - Add the source code and do the real build.
- Start a second stage using a much smaller runtime image.
- Copy the binary and libraries from the first stage.
- Set the initial command and expose port 8080.
To create the Docker image, use the following command:
docker build -t helidon-mp-tutorial .Make sure the application is shutdown if it was still running locally so that port 8080 will not be in use, then start the application in Docker using the following command:
docker run --rm -p 8080:8080 helidon-mp-tutorial:latestTry the application as before.
curl http://localhost:8080/greet/bob
{"message":"Howdee bob!"}
curl http://localhost:8080/health/ready
{"outcome":"UP","status":"UP","checks":[]}Deploy the application to Kubernetes
If you don’t have access to a Kubernetes cluster, you can install one on your desktop. Then deploy the example:
kubectl cluster-info
kubectl get nodesTo deploy the application to Kubernetes, a Kubernetes YAML file that defines the deployment and associated resources is needed. In this case all that is required is the deployment and a service.
Create a file called app.yaml in the project’s root directory with the following content:
---
kind: Service
apiVersion: v1
metadata:
name: helidon-mp-tutorial
labels:
app: helidon-mp-tutorial
spec:
type: NodePort
selector:
app: helidon-mp-tutorial
ports:
- port: 8080
targetPort: 8080
name: http
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: helidon-mp-tutorial
spec:
replicas: 1
selector:
matchLabels:
app: helidon-mp-tutorial
template:
metadata:
labels:
app: helidon-mp-tutorial
version: v1
spec:
containers:
- name: helidon-mp-tutorial
image: helidon-mp-tutorial
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080- Define a Service to provide access to the application.
- Define a NodePort to expose the application outside the Kubernetes cluster.
- Define a Deployment of the application.
- Define how many replicas of the application to run.
- Define the Docker image to use - this must be the one that was built in the previous step. If the image was built on a different machine to the one where Kubernetes is running, or if Kubernetes is running on multiple machines (worker nodes) then the image must either be manually copied to each node or otherwise pushed to a Docker registry that is accessible to the worker nodes.
This Kubernetes YAML file can be used to deploy the application to Kubernetes:
kubectl create -f app.yaml
kubectl get pods # Wait for quickstart pod to be RUNNINGRemember, if Kubernetes is running on a different machine, or inside a VM (as in Docker for Desktop) then the Docker image must either be manually copied to the Kubernetes worker nodes or pushed to a Docker registry that is accessible to those worker nodes. Update the image entry in the example above to include the Docker registry name. If the registry is private a Docker registry secret will also be required.
The step above created a service that is exposed using any available node port. Kubernetes allocates a free port. Lookup the service to find the port.
kubectl get service helidon-mp-tutorialNote the PORTs. The application can be exercised as before but use the second port number (the NodePort) instead of 8080. For example:
curl -X GET http://localhost:31431/greetIf desired, the Kubernetes YAML file can also be used to remove the application from Kubernetes as follows:
kubectl delete -f app.yamlSummary
This tutorial demonstrated how to build a new Helidon MP application, how to use Helidon and MicroProfile configuration, logging, metrics, and health checks. It also demonstrated how to package the application in a Docker image and run it in Kubernetes.
There were several links to more detailed information included in the tutorial. These links are repeated below and can be explored to learn more details about Helidon application development.