Building and deploying a Jakarta EE application on OpenShift

This is the second article about building and deploying a Jakarta EE service in the Cloud. In the first tutorial, we have covered How to build and deploy a Jakarta EE application on Kubernetes

Now we will show how to deploy the same application on OpenShift container application platform.

How to install quickly an OpenShift cluster

The simplest way to install and try OpenShift on your laptop is Red Hat Code Ready Containers (https://developers.redhat.com/products/codeready-containers/overview) which simplifies the setup and testing and emulates the cloud development environment locally with all of the tools needed to develop container-based applications.

A basic tutorial, which covers the Code Ready Containers (CRC) set up, is available here: Getting started with Code Ready Containers

Right now, we will assume that you have installed CRC so you can start it with:

$ crc start
   . . . . .
INFO Starting OpenShift cluster ... [waiting 3m]  
INFO                                              
INFO To access the cluster, first set up your environment by following 'crc oc-env' instructions 
INFO Then you can access it by running 'oc login -u developer -p developer https://api.crc.testing:6443' 
INFO To login as an admin, run 'oc login -u kubeadmin -p kKdPx-pjmWe-b3kuu-jeZm3 https://api.crc.testing:6443' 

We are done with the OpenShift setup.

Coding the Jakarta EE application

We will now deploy the same Jakarta EE application discussed in the tutorial (How to build and deploy a Jakarta EE application on Kubernetes)

The application is a basic REST Service with access to a RDBMs, which is PostgreSQL.

Here is our Model:

@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 as additional layer:

<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

We are done with the Jakarta EE service. Now we will add the last piece of the puzzle: the configuration to connect the two services (WildFly and PostgreSQL)

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
    ├── resources
    │   ├── import.sql
    │   └── META-INF
    │       └── persistence.xml
    └── webapp
        ├── index.jsp
        └── WEB-INF
            └── beans.xml

Additionally, we have included an import.sql file with some initial data for our application:

INSERT INTO customer (id, name, surname) VALUES ( nextval('customerId_seq'), 'John','Doe');
INSERT INTO customer (id, name, surname) VALUES ( nextval('customerId_seq'), 'Fred','Smith');

Deploying the Jakarta EE application on OpenShift

Now it’s time to deploy our artifacts on OpenShift. First of all, let’s create a new project for our application:

oc new-project jakartaee-demo

Next, we will add the PostgreSQL Database using the ‘oc-new app’ command, passing as an argument the environment variables we have in deployment.yaml:

$ oc new-app -e POSTGRESQL_USER=user -e POSTGRESQL_PASSWORD=password -e POSTGRESQL_DATABASE=wildflydb postgresql
--> Found image 40d2ad9 (11 months old) in image stream "openshift/postgresql" under tag "10" for "postgresql"

    PostgreSQL 10 
    ------------- 
    PostgreSQL is an advanced Object-Relational database management system (DBMS). The image contains the client and server programs that you'll need to create, run, maintain and access a PostgreSQL DBMS server.

    Tags: database, postgresql, postgresql10, rh-postgresql10

    * This image will be deployed in deployment config "postgresql"
    * Port 5432/tcp will be load balanced by service "postgresql"
      * Other containers can access this service through the hostname "postgresql"

--> Creating resources ...
    imagestreamtag.image.openshift.io "postgresql:10" created
    deploymentconfig.apps.openshift.io "postgresql" created
    service "postgresql" created
--> Success
    Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
     'oc expose svc/postgresql' 

Please note that it’s also possible to use a more agnostic approach for the creation of the Database by adding the Service, Deployment and ConfigMap YAML files into the jkube/raw folder of the project as shown in the Kubernetes tutorial. On the other hand, OpenShift greatly simplifies the orchestration of containers so I prefer using the ‘oc new-app‘ command which does it all in just one line.

In short time, the PostgreSQL Pod will be available:

$ oc get pods
NAME                         READY   STATUS      RESTARTS   AGE
postgresql-1-deploy          0/1     Completed   0          2m10s
postgresql-1-pjwxb           1/1     Running     0          2m8s

Now let’s deploy our Jakarta EE application too. For this purpose, we will use the JKube Maven plugin, by including the “openshift” profile of your Maven project:

<profile>
    <id>openshift</id>
    <build>
        <plugins>
            <plugin>
                <groupId>org.eclipse.jkube</groupId>
                <artifactId>openshift-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>

To deploy our application, just execute the “deploy” goal against the “openshift” profile:

$ mvn oc:deploy -Popenshift

Then, check that the output of the command is successful:

[INFO] <<< openshift-maven-plugin:1.0.2:deploy (default-cli) < install @ jakartaee-demo <<<
[INFO] 
[INFO] 
[INFO] --- openshift-maven-plugin:1.0.2:deploy (default-cli) @ jakartaee-demo ---
[INFO] oc: Using OpenShift at https://api.crc.testing:6443/ in namespace default with manifest /home/francesco/git/mastertheboss/openshift/jakartaee/target/classes/META-INF/jkube/openshift.yml 
[INFO] oc: OpenShift platform detected
[INFO] oc: Using project: default
[INFO] oc: Creating a Service from openshift.yml namespace default name jakartaee-demo
[INFO] oc: Created Service: target/jkube/applyJson/default/service-jakartaee-demo.json
[INFO] oc: Creating a DeploymentConfig from openshift.yml namespace default name jakartaee-demo
[INFO] oc: Created DeploymentConfig: target/jkube/applyJson/default/deploymentconfig-jakartaee-demo.json
[INFO] oc: Creating Route default:jakartaee-demo host: null
[INFO] oc: HINT: Use the command `oc get pods -w` to watch your pods start up
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:49 min
[INFO] Finished at: 2020-12-29T12:46:09+01:00
[INFO] ------------------------------------------------------------------------

Great, the two Pods should be up and running now:

jakarta ee openshift

Check out the Route to reach the application:

$ oc get routes
NAME             HOST/PORT                                 PATH   SERVICES         PORT   TERMINATION   WILDCARD
jakartaee-demo   jakartaee-demo-default.apps-crc.testing          jakartaee-demo   8080                 None

And finally, let’s test the application:

$ curl jakartaee-demo-default.apps-crc.testing/rest/customers | jq

[
   {
      "id":1,
      "name":"John",
      "surname":"Doe"
   },
   {
      "id":2,
      "name":"Fred",
      "surname":"Smith"
   }
]

That’s it! We have gone through the creation and deployment of a Jakarta EE application on OpenShift container platform.

Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/openshift/ocp-jakartaee