As explained in Deploying your Quarkus applications to OpenShift, there are several approaches for deploying in Red Hat OpenShift Container Platform (RHOCP) 4. Each situation might require different a approach from the ones listed in this article given different (project) requirements.
When doing Quarkus builds for OpenShift, there are several approaches. One of the simplest is the Quarkus Maven plug-in, meaning that the usage of quarkus-openshift
extension considerably facilitates the deployment.
Either via pom.xml
setting the following dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-openshift</artifactId>
</dependency>
Or by adding the extension manually via the command line interface (CLI command) using the Maven Wrapper:
<-- Below has the latest Quarkus version -->
./mvnw com.redhat.quarkus.platform:quarkus-maven-plugin:3.8.4.redhat-00002:add-extension -Dextensions="io.quarkus:quarkus-openshift"
Note that Quarkus 3+ should work with OpenJDK 17. Below is a full example using Quarkus CLI:
$ cd quarkus-example
ls
mvnw mvnw.cmd pom.xml README.md src target <--- this will be created on the build
$ quarkus build -Dquarkus.openshift.deploy=true
...
$ oc get is
NAME IMAGE REPOSITORY UPDATED
openjdk-17 registry.access.redhat.com/ubi8/openjdk-17
quarkus-example image-registry.openshift-image-registry.svc:5000/quarkus-cli/openshift-quickstart
...
$ oc get build
NAME TYPE FROM STATUS STARTED DURATION
quarkus-example-1 Source Binary Complete 10 minutes ago 35s
...
$ oc get bc
NAME TYPE FROM LATEST
quarkus-example Source Binary 1 <------------- binary
$ oc get pod
NAME READY STATUS RESTARTS AGE
quarkus-example-1-8r2kw 1/1 Running 0 4m49s <--- application
quarkus-example-build 0/1 Completed 0 5m27s <--- build
Note that above, -Dquarkus.openshift.deploy=true
is equivalent to -Dquarkus.kubernetes.deployment-target=openshift
plus -Dquarkus.kubernetes.deploy=true
.
Use the verbose details to verify the deployment in RHOCP 4:
[INFO] [io.quarkus.container.image.openshift.deployment.OpenshiftProcessor] Applied: ImageStream openjdk-17
[INFO] [io.quarkus.container.image.openshift.deployment.OpenshiftProcessor] Applied: ImageStream quarkus-example
[INFO] [io.quarkus.container.image.openshift.deployment.OpenshiftProcessor] Applied: BuildConfig quarkus-example
However, the source builds S2I cannot be done when, for instance, the Quarkus application already comes built as a JAR file. There are a few alternatives you can take to deploy an application in RHOCP 4. Three main options are presented below.
- Build from source in the RHOCP pod: In this option, you provide the source and the pod build.
- Inject the JAR: Build local and inject the resulting JAR: Those options you provide the
jar/uber-JAR
and the injection happens in an image.- a) Build local and inject the
quarkus-app
directory. - b) Build local and inject the
uber-JAR
jar.
- a) Build local and inject the
- Inject the JAR: Build local and inject the resulting JAR, but using Dockerfile (or Containerfile)
Additionally, to have a complete and thorough post, I will also cover a Tekton alternative, which is presented in a very summarized approach and can inject the source or the JAR.
Approach 1: Build from the source in the RHOCP pod
The user provides the source for the build in the first option, and the build happens in OCP 4.
The following is an example of creating a BuildConfig
with the ImageStream as output, output-quarkus-sample
:
kind: BuildConfig
apiVersion: build.openshift.io/v1
metadata:
name: quarkus-test-build
namespace: quarkus
spec:
runPolicy: "Serial"
triggers:
- type: "GitHub"
github:
secret: "secret101"
- type: "Generic"
generic:
secret: "secret101"
- type: "ImageChange"
source:
git:
uri: https://github.com/FranciscoMeloJr/quarkus-tests.git
ref: "main"
contextDir: quarkus-tests
strategy:
sourceStrategy:
from:
kind: ImageStreamTag
namespace: openshift
name: 'openjdk-11-runtime:1.16-1.1696518675'
output:
to:
kind: ImageStreamTag
name: output-quarkus-sample:latest <--------------------------------- create this separated
Approach 2: Binary input - Build local and inject the Quarkus application as binary, either as directory or uber-jar
Approach 2a: Build local and inject the Quarkus application as a directory:
In this option, the user provides the Quarkus application in a directory, such as the one below:
$ pwd
/path/quarkus-app
$ls
app lib quarkus quarkus-app-dependencies.txt quarkus-run.jar
Create namespace:
# Create project
$ oc new-project binaryopenjdk-scenario2a
$ oc project binaryopenjdk-scenario2a)
# create no content directory:
$ mkdir /tmp/nocontent
Import image:
$ oc import-image ubi8/openjdk-11:1.17-1.1693366250 --from=registry.access.redhat.com/ubi8/openjdk-11:1.17-1.1693366250 --confirm
Create BuildConfig
:
$ oc new-app binaryopenjdk-scenario2a/openjdk-11:1.17-1.1693366250~/tmp/nocontent --name=example-jdk11-rhel7
...
Java Applications
-----------------
Platform for building and running plain Java applications (fat-jar and flat classpath)
Start to build with JAR in the directory, which is sent as binary to the build.
$ oc start-build example-jdk11-rhel7 --from-dir=./quarkus-app
...
Uploading directory "quarkus-app" as binary input for the build ...
build.build.openshift.io/example-jdk11-rhel7-2 started
The above command specifies the directory: ./quarkus-app
, which will be archived and used as a binary input for the build. This means it will do the build using the directory quarkus-app
as input, after the BuildConfig
is created. The deployed pod will have the complete directory inside /deployments
:
### Result:
$ oc get pod
NAME READY STATUS RESTARTS AGE
example-jdk11-rhel7-55 1/1 Running 0 80m
[container]$ oc rsh example-jdk11-rhel7-55
[container] cd /deployment; ls
/app /data /lib /quarkus quarkus-app-dependencies.txt quarkus-run.jar
Approach 2b: Build local and inject the uber-JAR directly
Build JAR:
$ mvn clean package -Dquarkus.package.type=uber-jar
Create namespace:
$ oc new-project binaryopenjdk-scenario2b
$ oc project binaryopenjdk-scenario2b
# create no content directory:
$ mkdir /tmp/nocontent
Import image:
$ oc import-image ubi8/openjdk-11:1.17-1.1693366250 --from=registry.access.redhat.com/ubi8/openjdk-11:1.17-1.1693366250 --confirm
Create BuildConfig
:
$ oc new-app binaryopenjdk-scenario2b/openjdk-11:1.17-1.1693366250~/tmp/nocontent --name=example-jdk11-rhel7
Start to build with JAR:
$ oc start-build example-jdk11-rhel7 --from-file=./application-runner.jar
Uploading file "application-runner.jar" as binary input for the build ...
Uploading finished
build.build.openshift.io/example-jdk11-rhel7-2 started
The above command specifies the file: application-runner.jar
, which will be used as a binary input for the build—only one file. This means it will do the build using only the jar application-runner.jar
as input, after the BuildConfig
is created.
The deployed pod will contain only the binary jar deployed inside /deployments
:
### Result:
$ oc get pod
NAME READY STATUS RESTARTS AGE
example-jdk11-rhel7-21 1/1 Running 0 80m
[container]$ oc rsh example-jdk11-rhel7-21
[container] cd /deployment; ls
/data application-runner.jar
Approach 3: Build locally and inject the resulting JAR using Dockerfile
spec:
nodeSelector: null
output:
to:
kind: ImageStreamTag
name: 'wget-app:latest'
resources: {}
successfulBuildsHistoryLimit: 5
failedBuildsHistoryLimit: 5
strategy:
type: Docker
dockerStrategy:
from:
kind: ImageStreamTag
namespace: openshift
name: 'openjdk-11:1.17-1.1693366250'
postCommit: {}
source:
type: Dockerfile
dockerfile: >-
FROM registry.access.redhat.com/ubi8/openjdk-11:1.17-1.1693366250
USER root
RUN touch version.txt
RUN echo 'Scenario 1p' > version.txt
RUN mv version.txt $JBOSS_HOME/version.txt
RUN microdnf -y install wget
RUN microdnf -y install unzip
RUN wget https://github.com/path/application/quarkus-app.zip -O quarkus-app.zip
RUN mv quarkus-app.zip /deployments
RUN unzip /deployments/quarkus-app.zip -d /deployments
RUN ls /deployments/
CMD ["java","-jar","/deployments/quarkus-app/quarkus-run.jar"]
runPolicy: Serial
That possibility is listed as an alternative on the S2I OpenShift possibilities as inject the JAR.
Do not try to run the runner JAR as an uber-JAR
, as it won’t work:
$ java -jar quarkus-run.jar
Error: Could not find or load main class io.quarkus.bootstrap.runner.QuarkusEntryPoint
Caused by: java.lang.ClassNotFoundException: io.quarkus.bootstrap.runner.QuarkusEntryPoint
An additional approach: Using Tekton build
For RHOCP 4 builds with Tekton, after installing the OpenShift Pipelines Operator, follow the steps below.
You can build a pipeline by calling a few simple tasks such as below:
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
labels:
app.kubernetes.io/instance: test-and-build-pipeline
pipeline.openshift.io/type: quarkus-example-tekton
name: test-and-build-reproducer
namespace: quarkus-example-tekton
spec:
workspaces:
- name: shared-workspace
- name: maven-settings
params:
- name: image-tag
type: string
default: "latest"
- name: repo-url
description: The source repository's url
type: string
default: "https://github.com/example/quarkus-application.git"
- name: app-dir
type: string
default: "app"
tasks:
- name: task-execute
taskRef:
name: execute-maven-task
kind: Task
runAfter:
- fetch-app-source
params:
- name: GOALS
value:
- verify
- name: CONTEXT_DIR
value: "$(params.app-dir)"
workspaces:
- name: maven-settings
workspace: maven-settings
- name: source
workspace: shared-workspace
- name: build-service
taskRef:
name: execute-maven-task
kind: Task
runAfter:
- test-service
params:
- name: GOALS
value:
- package
- "-Dmaven.test.skip=true"
- name: CONTEXT_DIR
value: "$(params.app-dir)"
workspaces:
- name: source
workspace: shared-workspace
- name: maven-settings
workspace: maven-settings
...
...
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: execute-maven-task
spec:
description: This Task can be used to run a Maven build.
params:
- default: >-
registry.redhat.io/ubi8/openjdk-17@sha256:0d12c4097e098b62f78a7a31c0d711d78e1e5a53f4c007b9a5fc6cc6ab4dc018
description: Maven base image
name: MAVEN_IMAGE
type: string
- default:
- package
description: maven goals to run
name: GOALS
type: array
- default: ''
description: The Maven repository mirror url
name: MAVEN_MIRROR_URL
type: string
steps:
- image: 'registry.access.redhat.com/ubi8/ubi-minimal:8.2'
name: mvn-settings
resources: {}
script: >
#!/usr/bin/env bash
[[ -f $(workspaces.maven-settings.path)/settings.xml ]] && \
exit 0
apiVersion: tekton.dev/v1beta1
...
...
kind: Task
metadata:
name: get-source-with-git
spec:
stepTemplate:
name: "tekton-home-dir"
env:
- name: "HOME"
value: "/tekton/home"
params:
- name: repo-url
description: The source repository's url (http|ssh)
type: string
default: "<changeme>"
- name: image-tag
description: Git commit id to be cloned
type: string
default: ""
- name: app-dir
description: application directory
type: string
default: "app"
- name: GIT_BASE_IMAGE
description: The git base image
type: string
default: registry.redhat.io/openshift-pipelines/pipelines-git-init-rhel8@sha256:89fc179cd82c8ddf010af89213d3f2..
workspaces:
- name: source
description: The git repo will be cloned onto the volume backing this Workspace.
steps:
- name: git-clone
workingDir: /workspace
image: $(params.GIT_BASE_IMAGE)
script: |
#!/usr/bin/env sh
set -eu
if [[ -d $(workspaces.source.path)/$(params.app-dir) ]]; then
rm -rf "$(workspaces.source.path)/$(params.app-dir)"
fi
git config --global --add safe.directory "$(workspaces.source.path)/$(params.app-dir)"
git clone $(params.repo-url) $(workspaces.source.path)/$(params.app-dir)
cd $(workspaces.source.path)/$(params.app-dir)
git reset --hard $(params.image-tag)
git filter-branch --subdirectory-filter quarkus
The generic example above, i.e., this approach, can be generalized for any build in Tekton given that the only part that differs is the use of the Quarkus plug-in in the Maven build in the pom.xml
.
In summary, we have the following options:
Approach | Requirements |
Build from the source in OCP BuildConfig | User needs to have the source |
Build local and inject: jar | User needs to be able to build the application locally |
Build local and inject: uber-jar | User needs to be able to build the application as uber-jar |
Build local and inject jar via Dockerfile build | User needs to be able to build the application locally and make OCP 4 fetch it |
Build in OCP 4 using Tekton | User needs to have the source and knowledge on Tekton for the build |
Additional resources
The examples above are straightforward and do not have a multi-stage build or a pre-build image (for instance, a pre-build image to be consumed by Tekton), and this was done on purpose to simplify the explanation. Naturally, BuildConfig
and Tekton can have complex pipelines including multi-stage builds and on-the-fly addition/injections.
Also, as a disclaimer, the Images' Tags might be outdated depending on the publication of this article.
To learn more, read Deploying your Quarkus applications to OpenShift.
Finally, for monitoring, see about options on quarkus.io such as JFR and collecting heap files, e.g., for native builds: -Dquarkus.native.monitoring=jfr, heapdump
.
For any other specific inquiries and issues, please open a case with Red Hat support. Our global team of experts JVM team and Shift-DevOps can help you with any issues.
Special thanks to Jordan Bell for his input and contributions on Tekton/Shift build issues.