Kubernetes is currently the de-facto standard for deploying applications in the cloud. Every major cloud provider offers a dedicated Kubernetes service (eg. Google Cloud with GKE, AWS with EKS, etc.) to deploy applications in a Kubernetes cluster. Once your stateless Java EE application (this is important, as your application will run with multiple instances) is dockerized you are ready to deploy the application to Kubernetes.
In this blog post, I'll show you how to deploy a sample Java EE 8 and MicroProfile 2.2 application running on Payara 5.192 to a local Kubernetes cluster. You can apply the same for every Kubernetes cluster in the cloud (with small adjustments for the container registry).
Setup Java EE backend
The sample application contains only the Java EE 8 and MicroProfile 2.2 API dependency and built with Maven:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.rieckpil.blog</groupId> <artifactId>java-ee-kubernetes-deployment</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>8.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.microprofile</groupId> <artifactId>microprofile</artifactId> <version>2.2</version> <type>pom</type> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>java-ee-kubernetes-deployment</finalName> </build> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <failOnMissingWebXml>false</failOnMissingWebXml> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> </project> |
The application contains just one JAX-RS resource to return a message injected via the MicroProfile Config API.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Path("sample") public class SampleResource { @Inject @ConfigProperty(name = "message") private String message; @GET public Response message() { return Response.ok(message).build(); } } |
Furthermore, I've added HealthResource
which implements the HealthCheck
interface of the MicroProfile Health 1.0 API. This class is optional, but nice-to-have as Kubernetes will, later on, have to identify if your application is ready for traffic. In this example, the implementation is rather simple as it just returns the UP
status but you could also add further business logic to mark your application as ready. There can also be multiple implementations of the HealthCheck
interface in one application to check for multiple things like e.g. free disk space, database availability, etc. The result of all the health checks is then combined together under /health
.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Health @ApplicationScoped public class HealthResource implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse .name("java-ee") .builder() .up() .build(); } } |
With MicroProfile 3.0 there will also be dedicated annotations for @Readiness
and @Liveness
to differentiate between these two states.
Prepare Kubernetes deployment
First, we need to create a Docker image for our Kubernetes deployment. With Java EE applications this is pretty straightforward, as we just copy the .war
file to the deployment folder of the targeted application server. For this example, I've chosen the Payara server-full 5.192 base image.
1 2 | FROM payara/server-full:5.192 COPY target/java-ee-kubernetes-deployment.war $DEPLOY_DIR |
Next, we create a so-called Kubernetes Deployment. With this Kubernetes object, we define metadata for our application. This includes which image to use, which port to expose, where to find the health endpoint. In addition, we define here how many pods (containers) should run in parallel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | kind: Deployment apiVersion: apps/v1 metadata: name: java-ee-kubernetes spec: replicas: 2 selector: matchLabels: app: java-ee-kubernetes template: metadata: labels: app: java-ee-kubernetes spec: containers: - name: java-ee-kubernetes image: localhost:5000/java-ee-kubernetes imagePullPolicy: Always ports: - containerPort: 8080 readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 45 livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 45 restartPolicy: Always |
In this example, I'm using a local Docker registry and reference therefore to localhost:5000/java-ee-kubernetes
for the application's Docker image. If you plan to deploy your Java EE application to a Kubernetes cluster running in the cloud, you have to push your Docker image to this registry and replace the localhost:5000
e.g. eu.gcr.io/java-ee-kubernetes
(gcr is Google's container registry service).
To give the pod some time for the startup I've added the initialDelaySeconds
attribute to wait 45 seconds until the readiness and liveness probes are requested.
With just the deployment we wouldn't be able to access our application for outside. Therefore we have to specify the access with a so-called Kubernetes Service. The service references our previous deployment and uses the type NodePort. With this configuration, we specify a port on our Kubernetes nodes which forwards to our application's port. For our example we use port 31000 on the Kubernetes node and forward to port 8080 of our Java EE application:
1 2 3 4 5 6 7 8 9 10 11 12 13 | kind: Service apiVersion: v1 metadata: name: java-ee-kubernetes spec: type: NodePort ports: - port: 8080 targetPort: 8080 protocol: TCP nodePort: 31000 selector: app: java-ee-kubernetes |
For a more advanced configuration, have a look at the Kubernetes Ingress Controllers.
Deploy to a local Kubernetes cluster
In this example, I'm deploying the application to a local Kubernetes cluster. The Kubernetes add-on is available in the latest Docker for Mac/Windows version and creates a simple cluster on-demand.
With the following steps you deploy the application to your local Kubernetes cluster:
1 2 3 4 5 6 | docker run -d -p 5000:5000 --restart=always --name registry registry:2 mvn clean package docker build -t java-ee-kubernetes . docker tag java-ee-kubernetes localhost:5000/java-ee-kubernetes docker push localhost:5000/java-ee-kubernetes kubectl apply -f deployment.yml |
After the two pods are up and running, you can access the application at http://localhost:31000/resources/sample and to get a greeting from the Java EE application.
You can find the source code with a step-by-step guide on GitHub.
Have fun deploying your Java EE application to Kubernetes,
Phil