In this series of tutorials, we will show how to create and deploy a Jakarta EE service in a Cloud environment. Within this first article, we will learn how to deploy a WildFly application on Kubernetes using Minikube and JKube Maven plugin. In the next article, we will target OpenShift as Cloud environment.
Let’s get started. The first step is obviously installing Kubernetes so that we can deploy applications on top of it.
Install a Kubernetes Cluster with Minikube
Kubernetes is an open source system for managing containerized applications across multiple hosts. It provides basic mechanisms for deployment, maintenance, and scaling of applications. The simplest way to get started with Kubernetes is to install Minikube.
Minikube is a lightweight Kubernetes implementation that creates a VM on your local machine and deploys a simple cluster containing a single node and ships with a CLI that provides basic bootstrapping operations for working with your cluster, including start, stop, status, and delete
The procedure for installing Minikube is detailed at: https://minikube.sigs.k8s.io/docs/start/
To install the binary distribution you can just download it and run the “install” command on it:
$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 $ sudo install minikube-linux-amd64 /usr/local/bin/minikube
When done, launch the “minikube start”command which will select the Driver for your environment and download the Virtual Machine required to run Kubernetes components:
$ minikube start 🄠Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
When done, verify that the default and kube-system services are available:
$ minikube service list |-------------|------------|--------------|-----| | NAMESPACE | NAME | TARGET PORT | URL | |-------------|------------|--------------|-----| | default | kubernetes | No node port | | kube-system | kube-dns | No node port | |-------------|------------|--------------|-----|
Great, you Kubernetes cluster is now up and running.
Then, in order to build the Docker image using Minikube’s Docker instance, execute:
$ eval $(minikube docker-env)
The command “minikube docker-env” returns a set of Bash environment variable exports to configure your local environment to re-use the Docker daemon inside the Minikube instance.
If you fail to configure your Minikube environment as above, you will see an error when deploying your resource: “Connection reset by peer”
Configuring the Jakarta EE Service
We will now set up a Jakarta EE REST service which depends on a Database service running as well on Kubernetes.
The REST service follows the standard pattern, that is a Model class named Customer:
@Entity @Table(name = "customer") @NamedQuery(name = "findAllCustomer", query = "SELECT c FROM Customer c") public class Customer implements Serializable { @Id @SequenceGenerator( name = "customerSequence", sequenceName = "customerId_seq", allocationSize = 1, initialValue = 1) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customerSequence") private Long id; @Column private String name; @Column private String surname; // Getter/Setters omitted for brevity }
Then, we include a REST endpoint with a method for adding a new Customer (via HTTP POST) and one for returning the list of Customers (via HTTP GET)
@Path("customers") @Produces(MediaType.APPLICATION_JSON) public class CustomerEndpoint { @Inject CustomerManager manager; @POST public void createCustomer(Customer customer) { manager.createCustomer(customer); } @GET public List<Customer> getAllCustomers() { return manager.getAllCustomers(); } }
The Manager class is the layer responsible for updating the Database:
@ApplicationScoped public class CustomerManager { @PersistenceContext private EntityManager em; @Transactional public void createCustomer(Customer customer) { em.persist(customer); System.out.println("Created Customer "+customer); } public List<Customer> getAllCustomers() { List<Customer> tasks = new ArrayList<>(); try { tasks = em.createNamedQuery("findAllCustomer").getResultList(); } catch (Exception e){ e.printStackTrace(); } return tasks; } }
As we will be using PostgreSQL, our persistence.xml file will contain the following persistence unit definition:
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="primary"> <jta-data-source>java:jboss/datasources/PostgreSQLDS</jta-data-source> <properties> <!-- Properties for Hibernate --> <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="create-drop" /> <property name="hibernate.show_sql" value="true"/> </properties> </persistence-unit> </persistence>
You should have noticed that a Datasource is defined as JTA Datasource. In order to provide a Datasource to our Jakarta EE service, we will provision a WildFly Bootable Jar, which includes the following layers:
<build> <finalName>wildfly-jar-sample</finalName> <plugins> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-jar-maven-plugin</artifactId> <version>${version.wildfly.jar}</version> <configuration> <feature-packs> <feature-pack> <location>[email protected](org.jboss.universe:community-universe)#${version.wildfly}</location> </feature-pack> <feature-pack> <groupId>org.wildfly</groupId> <artifactId>wildfly-datasources-galleon-pack</artifactId> <version>1.1.0.Final</version> </feature-pack> </feature-packs> <layers> <layer>cloud-profile</layer> <layer>postgresql-datasource</layer> </layers> <excluded-layers> <layer>deployment-scanner</layer> </excluded-layers> <cloud/> </configuration> <executions> <execution> <goals> <goal>package</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
As you can see from the above Maven plugin configuration, our WildFly bootable Jar will be provisioned with a:
- cloud-profile: This is an aggregation of some basic layers (bean-validation, cdi, ee-security, jaxrs, jms-activemq, jpa, observability, resource-adapters, web-server) to address cloud use cases
- postgresql-datasource: This layer installs postgresql driver as a module inside a WildFly server. The driver is named postgresql. (For more info, this layer is available at: https://github.com/wildfly-extras/wildfly-datasources-galleon-pack
Configuring PostgreSQL Service on Kubernetes
We will use PostgreSQL as database backend. There are several strategies for deploying PostgreSQL on Kubernetes. We will show here how to use the JKube Maven plugin to address the deployment of all resources required to deploy PostgreSQL. For this purpose, we will add the following files under the src/main/jkube/raw folder of your Maven project:
│ └── raw │ ├── postgres-configmap.yml │ ├── postgres-deployment.yml │ └── postgres-service.yml
The first file, postgres-configmap.yml, contains the Kubernetes ConfigMap with the credentials to access PostgreSQL:
apiVersion: v1 kind: ConfigMap metadata: name: postgres-config labels: app: postgres data: POSTGRESQL_USER: user POSTGRESQL_PASSWORD: password POSTGRESQL_DATABASE: wildflydb
The second one, postgres-deployment.yml, contains information about the Deployment unit, including the base image used for the PostgreSQL service:
apiVersion: apps/v1 kind: Deployment metadata: name: postgres spec: replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: centos/postgresql-96-centos7:latest imagePullPolicy: "IfNotPresent" ports: - containerPort: 5432 envFrom: - configMapRef: name: postgres-config
Finally, the third one (postgres-service.yml), defines the PostgreSQL service:
apiVersion: v1 kind: Service metadata: name: postgres labels: app: postgres spec: type: NodePort ports: - port: 5432 selector: app: postgres
That’s all.
Connecting the Jakarta EE service with PostgreSQL service:
Now we have configured both services. The last thing we need is some glue between the two services. As a matter of fact, the WildFly image when it’s built will search for some environment variables to find out the settings to connect to the PostgreSQL database. So we have to provide this information via Environment variables. That’s a simple task. We will add another YAML file (deployment.yaml) that contains this information and, also, the JVM Settings we need for our Jakarta EE service:
spec: template: spec: containers: - env: - name: POSTGRESQL_USER value: user - name: POSTGRESQL_PASSWORD value: password - name: POSTGRESQL_DATABASE value: wildflydb - name: POSTGRESQL_SERVICE_HOST value: postgres - name: POSTGRESQL_SERVICE_PORT value: 5432 - name: JAVA_OPTIONS value: '-Xms128m -Xmx1024m' - name: GC_MAX_METASPACE_SIZE value: 256 - name: GC_METASPACE_SIZE value: 96
Here is the full project tree:
src └── main ├── java │ └── com │ └── mastertheboss │ ├── model │ │ └── Customer.java │ └── rest │ ├── CustomerEndpoint.java │ ├── CustomerManager.java │ └── JaxrsConfiguration.java ├── jkube │ ├── deployment.yaml │ └── raw │ ├── postgres-configmap.yml │ ├── postgres-deployment.yml │ └── postgres-service.yml ├── resources │ ├── import.sql │ └── META-INF │ └── persistence.xml └── webapp ├── index.jsp └── WEB-INF └── beans.xml
The project includes as well a SQL script to load some Entity objects at start up.
Deploying our services on on Kubernetes
Now that both plugins are in place, we will deploy the application on Kubernetes. To do that, we will include, as Maven profile, the kubernates-maven-plugin which is in charge to create the Kubernetes descriptors, build and deploy the service on Kubernetes:
<profile> <id>kubernetes</id> <build> <plugins> <plugin> <groupId>org.eclipse.jkube</groupId> <artifactId>kubernetes-maven-plugin</artifactId> <version>${jkube.version}</version> <executions> <execution> <goals> <goal>resource</goal> <goal>build</goal> </goals> </execution> </executions> <configuration> <enricher> <config> <jkube-service> <type>NodePort</type> </jkube-service> </config> </enricher> </configuration> </plugin> </plugins> </build> </profile>
From the shell, execute the following steps:
1) Create your Kubernetes resource descriptors.
mvn clean k8s:resource -Pkubernetes
2) Then start docker build by hitting the build goal.
mvn package k8s:build -Pkubernetes
3) Finally, deploy your application on the Kubernetes cluster:
mvn k8s:apply -Pkubernetes
Once deployed, you can see the pods running inside your Kubernetes cluster:
$ kubectl get pods NAME READY STATUS RESTARTS AGE jakartaee-demo-7d98897d57-jhpx5 1/1 Running 0 2m postgresql-bfbb7ff8b-nb9bx 1/1 Running 0 2m
Let’s check the updated Service List:
minikube service list |----------------------|---------------------------|--------------|----------------------------| | NAMESPACE | NAME | TARGET PORT | URL | |----------------------|---------------------------|--------------|----------------------------| | default | jakartaee-demo | http/8080 | http://192.168.39.35:31353 | | default | kubernetes | No node port | | default | postgres | 5432 | http://192.168.39.35:30662 | | kube-system | kube-dns | No node port | | kubernetes-dashboard | dashboard-metrics-scraper | No node port | | kubernetes-dashboard | kubernetes-dashboard | No node port | |----------------------|---------------------------|--------------|----------------------------|
Now we can test the service:
You can now manage your application from Kubernetes using either the ‘kubectl’ command line, or from the Kubernetes Dashboard:
minikube dashboard
Troubleshooting notes
Please notice that there’s a known issue which affects some JDKs: https://bugs.openjdk.java.net/browse/JDK-8236039
When this occurs the client throws an exception:
“javax.net.ssl.SSLHandshakeException: extension (5) should not be presented in certificate_request”
This happens because JDK 11 onwards has support for TLS 1.3 which can cause the above error.
You can work around this issue by setting the property -Djdk.tls.client.protocols=TLSv1.2 to the JVM args to make it use 1.2 instead. As an alternative, update to the latest version od JDK (it’s fully solved in JDK 15).
Enjoy Jakarta EE on Kubernetes!
You can find the source code for this Maven project at: https://github.com/fmarchioni/mastertheboss/tree/master/openshift/kubernetes-jakartaee