Red Hat Universal Base Images (UBI) contain the full Red Hat build of OpenJDK. Universal Base Images is available to anyone under the terms of the UBI end user license agreement and is fully supported for Red Hat customers. These "builder" images are designed to be suitable for building and running a wide range of Java-based applications, particularly when used in a Red Hat OpenShift environment. They contain the full Java Development Kit (JDK) including the development tools, Java compiler, Maven, and related build tooling.
The OpenShift source-to-image (S2I) process makes it straightforward to build and update your application's source code within an OpenShift cluster, and your deployments will be updated whenever the underlying image or your application sources are updated. With this workflow, the application is layered on top of the builder image, so the deployment contains the full JDK and Maven tooling. Some developers want their deployments to be based on a slimmer base image; perhaps without Maven, or without the full JDK tooling, or both.
To address this need, Red Hat has released new OpenJDK runtime container images, which do not contain the full JDK or other build tooling. This article explains how to use these new images within an OpenShift environment to automatically deploy your application using the S2I workflow. We will use a Quarkus quickstart as the application source.
S2I and Quarkus quickstart with the builder images
To get started with the Quarkus quickstart, make sure you are logged into an OpenShift instance and run the following commands:
# Build the image on OpenShift
$ oc new-app --context-dir=getting-started --name=quarkus-quickstart \
'registry.access.redhat.com/ubi8/openjdk-11~https://github.com/quarkusio/quarkus-quickstarts.git#1.13.3.Final'
# Watch the build, to see when it completes
$ oc logs -f bc/quarkus-quickstart
# Create a route
$ oc expose svc/quarkus-quickstart
# Get the route URI
$ export URI="http://$(oc get route | grep quarkus-quickstart | awk '{print $2}')"
# Test the app!
$ curl $URI/hello/greeting/quarkus
The first command, oc new-app
, creates all the OpenShift components needed to build and deploy the quickstart application, namely a BuildConfig
, ImageStreams
for both the ubi8/openjdk-11
base image and the resulting application image, and a DeploymentConfig
and a Service
. The command also instantiates a build and a deployment. The oc expose
command creates a Route
to the deployment, allowing us to test the running web application.
The great thing about this setup is that a new build can be triggered whenever the application sources are updated in their Git repository, or whenever the underlying builder image is updated.
A runtime image
Now for the new bit: We are going to define a new ImageStream
for a lean runtime image. As part of this image, we will copy the application artifacts out of the application ImageStream
that was created in the previous step. We also need to define the other OpenShift pieces to get the application up and running, as was done automatically for the initial quickstart: A DeploymentConfig
, Service
, and Route
. Figure 1 shows our process.
First, we need the lean base image. We're going to use the Red Hat UBI8 minimal base image. We need to import that as an ImageStream
into OpenShift. That's as easy as the following command:
$ oc create -n openshift -f https://raw.githubusercontent.com/jboss-container-images/openjdk/release/templates/runtime-image-streams.json
Now we define the OpenShift components for our lean application, which we will create one at a time. First, we create the ImageStream
into which we'll publish the lean runtime application images:
$ oc create imagestream quarkus-quickstart-runtime
Now we need a BuildConfig
to build the image. This is more complicated, so we will define it in a YAML file rather than on the command line. For this and all YAML snippets that follow in this article, save the snippet to a file and load it into OpenShift with:
$ oc create -f snippet.yaml
The BuildConfig follows:
apiVersion: v1
kind: BuildConfig
metadata:
name: quarkus-quickstart-runtime
spec:
output:
to:
kind: ImageStreamTag
name: quarkus-quickstart-runtime:latest
source:
images:
- from:
kind: ImageStreamTag
name: quarkus-quickstart:latest
paths:
- sourcePath: /deployments
destinationDir: ./deployments
dockerfile: |-
FROM -
COPY deployments /
CMD java -jar /deployments/quarkus-run.jar
strategy:
type: Docker
dockerStrategy:
from:
kind: ImageStreamTag
name: ubi8-openjdk-11-runtime:latest
triggers:
- type: ConfigChange
- type: ImageChange
imageChange:
automatic: true
from:
kind: ImageStreamTag
name: quarkus-quickstart:latest
- type: ImageChange
imageChange:
automatic: true
from:
kind: ImageStreamTag
name: ubi8-openjdk-11-runtime:latest
Note: The Dockerfile for the image is inline in the YAML. We use a placeholder FROM
image of "-"; the dockerStrategy
section of the YAML tells OpenShift to override the FROM
line with a value that represents the latest image in the ubi8-openjdk-11-runtime
image stream.
We copy the /deployments
directory from the Quarkus quickstart image into our new lean image. We know that the entirety of the Quarkus quickstart application is present in this directory, so this is all we need to copy. Further steps might be needed depending on the specific application. Finally, we set the lean container’s CMD
to invoke java
directly, passing the application's JAR on the command line.
Any changes to this configuration, the underlying quarkus-quickstart
image, or the base ubi8-openjdk-11-runtime
image will trigger a rebuild of this image.
Save the snippet to a temporary file and load it into OpenShift as described earlier. OpenShift should now schedule and start a build of the image. As before, you can watch the logs to see its progress:
$ oc logs -f bc/quarkus-quickstart-runtime
Image sizes
Once the build has completed, we can inspect the result and check its size. First, get the size of the normal quickstart ImageStream
, layered on top of the builder image:
$ oc get istag quarkus-quickstart:latest -o json | jq .image.dockerImageMetadata.Size
308976772
=> 295 MiB
For comparison, get the new runtime image:
$ oc get istag quarkus-quickstart-runtime:latest -o json | jq .image.dockerImageMetadata.Size
135747392
=> 129 MiB
The new runtime image is less than half the size of the full UBI8 OpenJDK builder image, which contains the full OpenJDK and Maven.
Finishing touches
Next we need to define the DeploymentConfig
, Service
, and Route
. There's not much to say about these, so I’ve run them together into a single YAML snippet. You can save it and load them in one go via oc create -f
, as we did earlier:
apiVersion: v1
kind: DeploymentConfig
metadata:
name: quarkus-quickstart-runtime
spec:
replicas: 1
selector:
app: quarkus-quickstart-runtime
template:
metadata:
labels:
app: quarkus-quickstart-runtime
spec:
containers:
- image: ' '
name: quarkus-quickstart-runtime
ports:
- containerPort: 8080
protocol: TCP
triggers:
- type: ConfigChange
- imageChangeParams:
automatic: true
containerNames:
- quarkus-quickstart-runtime
from:
kind: ImageStreamTag
name: quarkus-quickstart-runtime:latest
type: ImageChange
---
apiVersion: v1
kind: Service
metadata:
name: quarkus-quickstart-runtime
spec:
selector:
app: quarkus-quickstart-runtime
ports:
- port: 8080
protocol: TCP
targetPort: 8080
---
apiVersion: v1
kind: Route
metadata:
name: quarkus-quickstart-runtime
spec:
to:
kind: Service
name: quarkus-quickstart-runtime
Once these are loaded, use the same technique to obtain the route and fetch a "Hello world" URI to make sure all is well:
$ export URI="http://$(oc get route | awk '/quarkus-quickstart-runtime/ {print $2}')"
curl $URI/hello/greeting/quarkus
Wrap-up
The Universal Base Images OpenJDK builder image were designed to meet the needs of a wide variety of customers wishing to build and deploy their software on OpenShift. However, the image's flexibility comes at the cost of image size. Some developers wish to have smaller images or images without the build tooling. Red Hat has introduced the new UBI OpenJDK runtime images to address this need. The method described in this article allows you to customize and build a do-it-yourself (DIY) application image within OpenShift based on the new runtime images. This method also ensures that the image is rebuilt when the constituent parts change.
Future work
The main driver for the new runtime images was to ensure they consisted of as little as possible. The existing builder images contain a number of features that are not present in the runtime images, including (but not limited to) a featureful run script; the Prometheus agent for metric collection; OpenShift readiness probes; default JVM and garbage collection parameters; and a wealth of environment variables for tuning the image. If you want these features, the full builder images might be for you. If, instead, you want the smallest possible runtime images, with the fewest moving parts, and don't mind a bit of DIY, the runtime images might be a better fit.
At Red Hat, we're continually evolving our containers to take advantage of the latest techniques and technologies. We know that image sizes and deployment surface area are important issues for many developers and we are continually working to reduce the size of both the OpenJDK builder and runtime images with each release. We've got some promising developments in the pipeline. Watch this space!
Last updated: April 25, 2022