Here you will find everything to build our S2I Maven Builder which is classified as a custom S2I Image Builder (yellow box). In addition we will go through the the process of setting up a proper chained build visualized by the graphic below and deploy the resulting minimal image containing our artefact and runtime in an OpenShift / OKD cluster.
We will show case the benefits of a our S2I Maven Builder. Our builder will be able to build java based git repositories from source with maven if a pom.xml is found. We will base our example on the hello world application provided by wildlfy/quickstart. In addition we will briefly go through the benefits of using so called chained builds.
We have our application source code available in a git repository (wildlfy/quickstart), we would like to containerize the application and run it with Wildfly as our runtime environment in OpenShift or OKD.
Main objectives
- create an image that can run our application
- reduce image size to minimize transfer and deployment time
- application source code should not be part of the final image
- no unnecessary packages are allowed to keep the potential attack surface minimal
- container needs to be capable to run with a random UID (no root in runtime image allowed)
Bonus objectives
- the faster we can build the happier is our user base
- robust builds: if we can't build fast we should at least be able to build
Our restrictions
- we are not allowed to alter any elements in the source code repository
Prerequisits
- access to an OpenShift or OKD cluster
Let us create a new project in OpenShift to store all our resources related to the S2I Maven Builder and the hello world application.
oc new-project s2i-builder-maven \
--display-name="S2I Maven Builder" \
--description="This project contains all resources to build the S2I Maven Builder and use
the builder to compile and run the hello world application. The hello world
application used here is available on github (wildfly/quickstart)."
Let's build the S2I Maven Builder image from its repository and name it s2i-builder-maven
. This will create a BuildConfig and an ImageStream named s2i-builder-maven
and trigger the the first build of our image.
oc new-build https://github.com/gepardec/openshift-builder-maven#1.0.0 --name=s2i-builder-maven
Hint: #1.0.0
at the end of the repository url specifies a tag or branch. Providing a tag or a branch name is optional and if nothing is specified master
will be selected.
Now we have got our first component in place: our S2I Maven Builder s2i-builder-maven
. Next we will use our newly created builder image to build our hello world application artefact from source with the builder's S2I scripts.
Pitfall: Since our hello world application has its pom.xml
in helloworld/pom.xml
we could set contextDir=helloworld
. However, helloworld/pom.xml
refers to a pom.xml
which resides in the root directory of the git repository. Here we got an issue with the current implementation of contextDir. As specified in the OpenShift 4.2 documentation (https://docs.openshift.com/container-platform/4.2/builds/creating-build-inputs.html):
any input content that resides outside of the contextDir will be ingored by the build.
That translates to ../pom.xml
not found for us, since it resides outside of the specified contextDir.
For this reason our custom build image provides an additional environment variable BUILDER_MVN_OPTIONS
that does not have the above mentioned limitation.
In short we will use our s2i-builder-maven
to build wildfly/quickstart tagged 18.0.0.Final and call it binary-artefact
. Our application pom.xml
resides in helloworld/pom.xml
. Therefore we will use the environment variable BUILDER_CONTEXT_DIR
to set our context directory acordingly. In addition we will set BUILDER_MVN_OPTIONS
to use the openshift profile specified in the pom.xml
.
oc new-build s2i-builder-maven~https://github.com/wildfly/quickstart#18.0.0.Final \
--name=binary-artefact \
--env=BUILDER_CONTEXT_DIR=helloworld \
--env=BUILDER_MVN_OPTIONS="-P openshift"
Take a break, without optimization this will take about 10 minutes depending on your internet connection.
Hint: With BUILDER_MVN_OPTIONS
you can provide any maven option that can be provided like this: mvn clean package ${BUILDER_MVN_OPTIONS}
Now we have an image called binary-artefact
that contains our application war in /deployments/helloworld/target/ROOT.war
and our source code. To combine our artefact with a runtime environment we will copy only the artefact from the binary-artefact
image to our new runtime
image that we build via an inline specified dockerfile.
oc new-build --name=runtime --docker-image=jboss/wildfly \
--source-image=binary-artefact \
--source-image-path=/deployments/target/ROOT.war:. \
--dockerfile=$'FROM jboss/wildfly\nCOPY ROOT.war /opt/jboss/wildfly/standalone/deployments/ROOT.war'
In short we have an image with our binary artefact (ROOT.war), an image with wildfly and with the build specified above we define an inline dockerfile that copies the artefact from the source image into our new runtime image.
The heavy lifting is done. We have created our final image called runtime
. Next we want to deploy it as hello-world
.
oc new-app runtime --name=hello-world
In order to access our application from outside the cluster we still need to expose our hello-world
sevice via a route.
oc expose svc/hello-world
You can now access your application through your browser by entering the url that following command provides.
oc describe route/hello-world | grep "Requested Host:"
Hint: all commands executed in Implementation Scenario 1 can be run by executing: usecase.sh
In order to archive our bonus objectives we need to build and deploy our application faster. Maven build typically fetches it dependencies from remote repositories such as maven central. Fetching those dependencies over the internet with every build is not performant enough. We can speed up the process by mirroring the required remote repositories in our infrastructure and use our local repositories instead.
To archive this we will need a Nexus or another product with the capability to proxy the required remote repositories already set up.
To speed up our binary-artefact
build we can set our own maven mirror(s) via a dedicated environment variable.
In short we replace step 3 from Implementation 1 with
oc new-build s2i-builder-maven~https://github.com/wildfly/quickstart#18.0.0.Final \
--name=binary-artefact \
--env=BUILDER_MVN_MIRROR="*|https:/my-maven-mirror/path/to/maven-public/" \
--env=BUILDER_MVN_MIRROR_ALLOW_FALLBACK=true
Hint: you can specify multiple maven mirrors via the BUILDER_MVN_MIRROR
variable. More information on how to do that can be found in the section Available Environment Variables in S2I Maven Builder.
Hint: we have introduced an additional environment variable BUILDER_MVN_MIRROR_ALLOW_FALLBACK
which can be set to true
or false
. If set to false
the build will fail if the specified mirror is unavailable. true
on the other hand will fall back to the repositories specified in the pom.xml and build slowly instead.
BUILDER_MVN_OPTIONS ... can be used to add additional option to the maven execution.
BUILDER_MVN_OPTIONS="-DskipTests"
BUILDER_CONTEXT_DIR ... can be used to define the location of the pom file within the git repository when the entire folder structure of the repository is required. Otherwise use contextDir in your buildconfig. e.g. to use helloworld/pom.xml you can set
BUILDER_CONTEXT_DIR=helloworld
BUILDER_MVN_MIRROR ... can be used to specify maven mirror repositories
a maven repository mirroring all required dependencies can be specified via:
*|http:/my-mirror.com/path/to/repo
multiple mirrors can be specified such that mirror-A is used for central and mirror-B is used for jboss:
central|http:/mirror-A/path/to/repo;jboss|http:/mirror-B/path/to/repo
BUILDER_MVN_MIRROR_ALLOW_FALLBACK ... true
/ false
; default is false
false
... fail if mirror is unavailable
true
... fall back to maven repositories specified in pom.xml if
mirror is unavailable