Examining simple build and deployment operations on OpenShift 3/4
Note:
I wrote this article originally for OpenShift 3. I'm please to say that, although the architecture of OpenShift 4 is quite different, the command-line interface remains very similar, and the procedures I describe work on OpenShift 4 as well
OpenShift 3 is designed to make simple application deployment more-or-less
automatic -- you can go directly from source code in a repository, to
an application running in a container (pod) with no detailed understanding
of the internal operations involved. However, such an understanding is
necessary to set up more complicated build operations.
This article describes how to deploy a simple Java application, which is
provided as an executable JAR file, by creating a build configuration
and a deployment configuration. All OpenShift steps are carried out on
the command line using the
oc
tool, and we will inspect
the OpenShift artefacts created by the infrastructure at each step.
To follow the steps in this example, you will need a Java 1.8 JDK,
and an installation of Maven (and probably some experience of
setting it up and using it).
I don't assume much knowledge of OpenShift 3, but you need to know
what a pod is, and have some understanding of containers.
Create a simple Apache Camel application from an archetype
For this demonstration I will be using an application based on Fuse
Integration Services (FIS) -- this is a framework for building Apache Camel
applications based on Spring Boot. There is no particularly compelling
reason for this choice, other than that I need to deploy FIS applications
on OpenShift for other purposes. This demonstration would work with
any self-contained Java application that can be run from a JAR
file.
Get the source
A sample FIS Camel application can be built from a Maven archetype,
like this:
$ mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate \
-DarchetypeCatalog=https://maven.repository.redhat.com/ga/io/fabric8/archetypes/\
archetypes-catalog/2.2.195.redhat-000004/archetypes-catalog-2.2.195.redhat-000004-archetype-catalog.xml \
-DarchetypeGroupId=org.jboss.fuse.fis.archetypes \
-DarchetypeArtifactId=spring-boot-camel-xml-archetype \
-DarchetypeVersion=2.2.195.redhat-000004
Fill in the prompted values -- the exact input is unimportant, but
I chose
fis-test
for the artefact name, which also becomes
the name of the directory that Maven creates for the source.
The application includes a lot of infrastructure, including the whole of
the Apache Camel core, but all it does in its stock form is write messages
to standard out.
Build and test the JAR
cd
to the newly-created directory, and then
$ mvn package
This creates a JAR file in the
target
directory.
It should be possible to run the application locally, to see the output
it produces. Of course, in a real development exercise you would probably
want to run unit tests, etc., as well.
$ java -jar target/fis-test-1.0-SNAPSHOT.jar
Create an OpenShift project
Log into OpenShift at the command line (
oc login...
) and create a
new OpenShift project; the project name and description are unimportant
for our present purposes.
$ oc new-project test-project
Create an OpenShift build configuration
In OpenShift, a build configuration (BC) is a specification (in the form
of a Kubernetes object)
that describes how to build a deployable image (usually a Docker or Podman image)
from some kind of source. For our purposes it is important to note that
"source" is interpreted very broadly. Although we are using the
"source-to-image" process here, the source is an executable Java JAR
file. In fact, so far as OpenShift is concerned, "source" is anything
that isn't an image -- it might even be a configuration file.
If we have an image, we can skip the
build step completely, and go directly to creating a deployment
configuration (see below).
Set up a new OpenShift build configuration, using the stock Java builder
image, and indicating that the build will take a binary as input
(a JAR, in this case).
$ oc new-build --binary=true --name=fis-test --image-stream=redhat-openjdk18-openshift:1.2
The name
fis-test
will be used for the created image, and
for a number of other purposes. To see the details of the build configuration:
$ oc describe bc/fis-test
This shows the following information, among other things. Note that the
build will produce an image called fis-test, and that there is no source
for the image yet: we will provide it when invoking the build from
the command line.
Strategy: Source
From Image: ImageStreamTag openshift/redhat-openjdk18-openshift:1.2
Output to: ImageStreamTag fis-test:latest
Binary: provided on build
You can also view, and perhaps edit, the Kubernetes object definition in
YAML format by running
$ oc edit bc/fis-test
Build the application image
Start the build defined by the
fis-test
build configuration,
uploading the JAR built earlier by Maven:
$ oc start-build fis-test --from-file=target/fis-test-1.0-SNAPSHOT.jar --follow
The
--follow
switch here indicates that the command should not
complete until the build does.
OpenShift creates a new pod to do the build, whose name is based on the
build configuration name -- in this case,
fis-test-1-build
.
The pod
takes as its image the builder image specified in the
new-build
command.
To start the build, the file (or directory) specified on the command line
is packed into
a tar file, and then becomes the builder image's standard input. OpenShift
runs the
tar
command in the builder's container, unpacking the
tar file
from stdin into a directory --
/tmp/src
by default.
It then runs the
script
/usr/local/bin/s2i/assemble
which, in this
particular example,
simply copies the JAR files from
/tmp/src
to
/deployments
. The
Java builder image can manage other kinds of Java souce or binary,
and actions will be different in those cases: in some cases, actual
Java compilation may be performed, although this is not necessary in
the present example.
After running
assemble
(and some other scripts that
aren't relevant
in this case) the whole contents of the filesytem of the builder
pod's filesystem
are pushed to the docker registry as a new image. Consequently, we
end up with the a new image called
fis-test
which is
almost exactly the same as
redhat-openjdk18-openshift
, but with our JAR file in
/deployments
. It is the presence of this JAR that will
trigger a JVM execution when the new image is deployed and executed.
The fact that the new image contains everything that was in the builder
image is a point of contention for some OpenShift users. A case can
certainly be made that a production container ought not to
contain build-related
tooling, both for reasons of resource management and for security. If
this is a problem, then a different build mechanism will need to be used,
or a second "chained" build step will be needed to sanitize the generated
image.
We can list the builds in the current project like this, and we should
see one completed build:
$ oc get builds
NAME TYPE FROM STATUS STARTED DURATION
fis-test-1 Source Binary Complete 7 minutes ago 1m5s
We should also see one completed pod, that was used to do the build:
$ oc get pods
NAME READY STATUS RESTARTS AGE
fis-test-1-build 0/1 Completed 0 8m
This pod could now be deleted -- it won't be used for anything else;
presumably it is retained so its logs can
be inspected if the build fails.
Deploy the image into an OpenShift pod
We now have one new image stream in the project's namespace:
$ oc get is
NAME DOCKER REPO TAGS UPDATED
fis-test 172.30.1.1:5000/test-project/fis-test latest 8 minutes ago
The image stream consists of one image -- the one with the default
tag "latest".
We need to tell OpenShift how to deploy and run this image in one or
more pods; this requires the creation of a
deployment configuration.
$ oc new-app fis-test
Note that the name
'fis-test' is the name of the image stream -- there are many other possible
inputs
to
new-app
, including source code repositories.
new-app
can create build configurations as well but, so far as I know, we need
to use
oc new-build
explicitly to provide an executable
to OpenShift.
Looking at the list of deployment configurations:
$ oc get dc
NAME REVISION DESIRED CURRENT TRIGGERED BY
fis-test 1 1 1 config,image(fis-test:latest)
we see that the deployment is (by default) triggered by a configuration
change, or a change to the image ``fis-test:latest``. In this case,
providing the configuration counts as a trigger, and the deployment and
provisioning steps will be triggered without further intervention.
So you should already see one pod running:
$ oc describe dc fis-test|grep Replicas
Replicas: 1 current / 1 desired
Since the build configuration was created using default values, the
desired replica count (number of pods) will be 1.
The single, new pod is created by a transient deployer pod. The name
of this pod, in this example, will be
fis-test-1-deploy
.
Unlike the builder pod, however, this pod is not retained after
completion. You may see it briefly flash into life if you watch the
pod list whilst doing a deployment.
Since the image for the new pod was created by the source-to-image process
using the script /usr/local/s2i/assemble, the entry
point for execution will be
/usr/local/s2i/run
. The
run
script delegates (in this simple case) to
/opt/run-java/run-java.sh
, which detects the JAR file
in
/deployments
, and executes it using
java -jar...
. The command line specifies the use of the
Jolokia Java agent, so that the user can have a graphical view
of the JVM operation.
Further builds
After modifying the application, subsequent OpenShift builds are carried
out by repeating the
oc start-build
command, which will trigger a rebuild and redeployment in
OpenShift. The infrastructure will create new build pods and deployer
pod as necessary, numbering them sequentially.
Note that the default redeployment strategy is 'Rolling',
which will cause the a new pod to be started with the new code,
before the old one is shut down. Depending on the application, this
may be inappropriate, and it might be advisable to change the deployment
strategy to 'Recreate' (e.g., using
oc edit dc/fis-test
).
Closing remarks
In practice, I would expect that substantial OpenShift projects would
be built using some form of pipeline process, perhaps coordinated
by a build manager like Jenkins. The build manager would create new
OpenShift pods to carry out specific build and deployment operations.
Although OpenShift makes it possible to go directly from a source code
repository to a running container, the large number of default settings
involved in this operation make it less suitable for large-scale development
work.
I've used the command-line for everything in this example, not just because
I think it makes the detailed steps clearer, but because I think that
the terminology it uses is more apposite. The OpenShift web console,
although powerful in its own right, uses terms like 'build' and 'deployment'
in non-specific ways. The web console uses the term 'deployment' to
mean both a deployment configuration, and the process of executing a
deployment. If you already understand the OpenShift build and deployment
philosophy that shouldn't be a problem, but the
oc
command
doesn't mix up the different concepts.