Understanding how to deploy a simple RESTful service and front-end on OpenShift using Spring Boot and Apache CXF
This document will guide you through the steps to create a simple "Hello World" JAX-RS based, RESTful web service using Apache CXF and Spring Boot. While learning how to use JAX-RS with Spring Boot is interesting in itself, we will detail the steps and mechanisms involved in deploying the application on an OpenShift cluster in an effort to develop a truly cloud-native application with a workflow focused on making things natural to Java developers.
Conceptually, the application is similar to the one developed in Spring Boot’s guide on building RESTful services. However, where the Spring Boot guide uses Spring-specific annotations to define the endpoint and its method, we will use standard annotations, making it easier to reuse endpoints implementations across servers, should you want to target a different platform than Spring Boot at some point.
The application you will be building exposes a very simple greeting web service accepting GET
HTTP requests to
/api/greeting
, responding with a JSON message in the form {"content":"Hello, World!"}
. The
message can be customized by passing the name
query parameter to the request as in
http://localhost:8080/api/greeting?name=John
which would result in the following response: {"content":"Hello,
John!"}
.
Additionally, a very simple front-end is provided using HTML and jQuery to interact with the greeting endpoint
from a more user-friendly interface than query the service using cURL
.
Note
|
While we will go over the implementation, we will focus mostly on the specifics needed to get your application running on OpenShift as opposed to detailing all the Spring Boot-specific implementation. |
-
Java 8
-
Access to an OpenShift cluster whether locally via minishift or using the different flavors of OpenShift products
The application is composed of a RESTful service. Its code can be found in the src/main/java/io/openshift/booster/service
directory. It is split in two classes: GreetingEndpoint
, which implements the endpoint itself, and Greeting
which is a
simple class representing the payload sent back to users of the endpoint.
Let’s look at the Greeting
class first, which is pretty simple:
public class Greeting {
public static final String FORMAT = "Hello, %s!"; #(1)
private final String content; #(2)
public Greeting() {
this.content = null;
}
public Greeting(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
-
FORMAT
constant that can be used by clients of the class to create greeting messages in the expected format -
content
field which will be used to populate our JSON payload ThisGreeting
class will be automatically marshalled by Jackson using the accessor.
Let’s now look at the GreetingEndpoint
class, short and sweet but packing quite a punch, thanks to annotations:
import javax.ws.rs.DefaultValue; # (1)
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import org.springframework.stereotype.Component;
@Path("/greeting") # (2)
@Component # (3)
public class GreetingEndpoint {
@GET # (4)
@Produces("application/json") # (5)
public Greeting greeting(@QueryParam("name") @DefaultValue("World") String name) { #(6)
final String message = String.format(Greeting.FORMAT, name); #(7)
return new Greeting(message); #(8)
}
}
-
Standard JAX-RS annotation imports
-
Specify that this class is a JAX-RS root resource and that the endpoint will answer requests on
/greeting
-
Mark the endpoint as a Spring component to be managed by Spring Boot. In conjunction with the
cxf.jaxrs.component-scan
property set totrue
inapplication.properties
, this allows CXF to create a JAX-RS endpoint from the auto-discovered JAX-RS root resources. -
Mark the
greeting
method as answering HTTPGET
requests -
Specify that the method returns JSON content (
application/json
content type) -
The
name
method parameter is annotated with@QueryParam("name")
to specify that it is passed as a query parameter in the URL when the service is invoked and that its default value isWorld
if none is provided (thanks to the@DefaultValue("World")
annotation) -
We format the message using the
Greeting.FORMAT
constant… -
and return a
Greeting
instance with the proper message. This object will be automatically serialized to JSON using Jackson as we will see later.
As you can see, there isn’t much to it as far as code goes.
We still need to configure CXF and Spring Boot properly for everything to work well.
On the Spring Boot side, we need an entry point to our service in the form a class annotated with @SpringBootApplication
, also giving us the opportunity to further configure our stack:
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication #(1)
public class BoosterApplication {
public static void main(String[] args) {
SpringApplication.run(BoosterApplication.class, args);
}
@Bean
public JacksonJsonProvider jsonProvider() { #(2)
return new JacksonJsonProvider();
}
}
-
Activates auto-configuration, component scan and marks the class as providing configuration using the
@SpringBootApplication
annotation. This also allows to package the application as a jar that can be run as a typical application. Spring Boot will then start the embedded Tomcat server. -
Specifies that the JSON provider to be used by CXF (which uses Spring as basis of its configuration) should be Jackson.
Note
|
You’ll notice that, contrary to Spring MVC where Jackson only needs to be present on the classpath for it to be used, Apache CXF requires Jackson to be explicitly configured. This could be done via XML but we might as well leverage the @SpringBootApplication configuration capability.
|
Let’s now look at the content of application.properties
which we need to further configure CXF:
cxf.path:/api #(1)
cxf.jaxrs.component-scan:true #(2)
-
Specify that CXF will answer to requests sent to the
/api
context. Our endpoint root resource is annotated with@Path("/greeting")
which means that the full context for our endpoint will be/api/greeting
. -
As mentioned above when we looked at the
GreetingEndpoint
class, we need to set that property totrue
to activate automatic creation of endpoint based on resource detection.
Let’s take a quick look at our frontend. It’s implemented as a static HTML src/resources/static/index.html
file served from the root of the embedded Tomcat server. The basic idea is similar to what is explained in the consuming a RESTful Web Service with jQuery Spring Boot guide so we will only focus on the salient parts for our purpose.
In our case, our service is running on the same server so we don’t need to worry about CORS. Moreover, for the same reason, we don’t need any extra code for Spring Boot to start Tomcat.
The simple UI consists in a form to specify which name to pass to the greeting service and then invoke it:
<form class="form-inline">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" placeholder="World"> #(1)
</div>
<button id="invoke" type="submit" class="btn btn-success">Invoke</button> #(2)
</form>
<p class="lead">Result:</p>
<pre><code id="greeting-result">Invoke the service to see the result.</code></pre> #(3)
-
Text input to enter the name to pass to the greeting service
-
Button to trigger the call to the greeting service
-
Placeholder text that will be replaced by the result of the service call
and the embedded jQuery script:
$(document).ready(function () {
$("#invoke").click(function (e) { #(1)
var n = $("#name").val() || "World"; #(2)
$.getJSON("/api/greeting?name=" + n, function (res) { #(3)
$("#greeting-result").text(JSON.stringify(res)); #(4)
});
e.preventDefault();
});
});
-
Add a
click
event handler to the button with theinvoke
id -
Retrieve the value of the
name
input to pass to the greeting server -
Invoke the RESTful endpoint and retrieve the JSON response
-
Replace the content of the element with the
greeting-result
id with the result of the invocation
You can run the application using ./mvnw spring-boot:run
, using the run
goal of the Maven Spring Boot plugin.
It’s also possible to build the JAR file with ./mvnw clean package
and run it like a traditional Java application:
java -jar target/spring-boot-rest-http-<version>.jar
where <version>
corresponds to the current version of the project.
Once the application is started, you can visit http://localhost:8080/index.html to see the frontend of the application and interact with the greeting service.
Let’s look at the important parts of the Maven project to properly build and run the application locally.
First, we need to tell Maven that we’re using Spring Boot and more specifically that we want to use the Snowdrop supported set of Spring Boot starters. This is accomplished by using 2 properties and importing the Snowdrop Bill Of Materials (BOM) and any dependencies we need for our application:
...
<properties>
<spring-boot-bom.version>1.5.14.Final</spring-boot-bom.version> #(1)
<spring-boot.version>1.5.14.RELEASE</spring-boot.version> #(2)
....
</properties>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>me.snowdrop</groupId>
<artifactId>spring-boot-bom</artifactId>
<version>${spring-boot-bom.version}</version> #(3)
<type>pom</type>
<scope>import</scope>
</dependency>
...
</dependencies>
</dependencyManagement>
<dependencies> #(4)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId> #(5)
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxrs</artifactId> #(6)
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId> #(7)
</dependency>
...
</dependencies>
...
-
Specify the BOM version we want to use. More details on the BOM content and its versioning scheme are available.
-
Associated Spring Boot version
-
The BOM version is imported in the
dependencyManagement
section of the POM file -
Since the BOM defines supported versions, we can then import supported dependencies without having to worry about their respective versions
-
Specify that we want to use Spring Boot with an embedded Tomcat server
-
Needed to be able to use Apache CXF integration with Spring Boot
-
Needed so that Apache CXF can use Jackson as JSON marshaller as seen above when we defined a
jsonProvider
bean provider method in our application entry point
Let’s now look at the build configuration:
...
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering> #(3)
</resource>
</resources>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering> #(4)
</testResource>
</testResources>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version> #(1)
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration/>
<executions>
<execution>
<goals>
<goal>repackage</goal> #(2)
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
-
Add the Spring Boot Maven plugin to the build using the previously defined
spring-boot.version
property. -
Specify that the
repackage
goal of the Spring Boot plugin should be executed during thepackage
phase of the Maven build. This leads to the creation of new jar file repackaged to create a self-contained, executable application. The originally generated jar file is kept but renamed with the.original
suffix appended to its name. -
Activate Maven filtering on files put in
src/main/resources
where Spring Boot configuration files live so that properties in the${property.name}
can be interpolated and replaced during the build -
Also perform Maven filtering on
src/test/resources
test resource files
Now that we’ve seen the gist of the application and how to run it locally, let’s look at what’s needed to deploy it on OpenShift. This is accomplished using the Fabric8 Maven Plugin (aka FMP). FMP brings your Java applications to OpenShift. Tightly integrated with Maven, it leverages the existing build configuration to focus on two tasks: building Docker images and creating OpenShift (or plain Kubernetes) resource descriptors. Since our application is built using Maven, it makes sense to continue to leverage that tool to generate whatever is necessary to deploy and run our application on OpenShift.
Note
|
The following steps assume that you are currently connected to a running OpenShift cluster via oc login . By doing so, FMP will be able to determine that you are targeting an OpenShift deployment automatically and take additional steps to generate OpenShift-specific descriptors (as opposed to generic Kubernetes ones).
|
First, we need to tell Maven that we want to use this plugin. This is accomplished in the parent POM of our booster, which is declared as:
<parent>
<groupId>io.openshift</groupId>
<artifactId>booster-parent</artifactId>
<version>23</version>
</parent>
Note
|
We’re considering removing the need for a parent and including the FMP (Fabric8 Maven Plugin) configuration directly in our boosters. |
Let’s look at the parts that deal with configuring the Fabric8 Maven Plugin:
...
<properties>
...
<fabric8-maven-plugin.version>3.5.40</fabric8-maven-plugin.version> #(1)
...
<fabric8.generator.from>registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift:1.2</fabric8.generator.from> #(2)
<fabric8.openshift.trimImageInContainerSpec>true</fabric8.openshift.trimImageInContainerSpec> #(3)
<fabric8.skip.build.pom>true</fabric8.skip.build.pom> #(4)
</properties>
...
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<version>${fabric8-maven-plugin.version}</version> #(5)
</plugin>
...
</plugins>
</pluginManagement>
...
</build>
...
-
Specify which version of FMP to use in a property, to be used in the plugin definition later on.
-
Specify which Docker base image to use when generating the images for our application. The base image will serve as the foundation on top of which the FMP plugin adds our application to create a container ready to be deployed on a Kubernetes cluster. In this case, the base image is the Red Hat supported OpenJDK 8 image since our application is, at its code, a Java application. The booster’s parent specifies that version
1.2
should be used but this property is overridden in the booster itself to use a more recent version. We will look later at how this is done in our booster (essentially, redefining thefabric8.generator.from
property but with a twist)! -
This configuration property is only needed if running on OpenShift
3.7
version earlier than3.7.2
to work around an issue between an older version of FMP and OpenShift3.7
. This property might be removed in future versions of the boosters. For more details on this issue, see fabric8io/fabric8-maven-plugin#1130 (comment) (and below). -
This configuration property instructs FMP to not build modules with the
pom
type. Without this property, This was required for an older version of FMP and might become needed again. For more details on this issue, see fabric8io/fabric8-maven-plugin#1184 (comment) (and below). -
Specify that we want to use the Fabric8 Maven Plugin at the version that we previously specified.
Note
|
You can see and explore the list of Red Hat supported images that can serve as base images for you applications at: https://access.redhat.com/containers/. |
Now that we’ve seen the configuration of the plugin in the booster parent, let’s look at how it’s used in the booster itself:
...
<properties>
...
<openjdk18-openshift.version>1.3</openjdk18-openshift.version> #(2)
<fabric8.generator.from>
registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift:${openjdk18-openshift.version} #(3)
</fabric8.generator.from>
</properties>
...
<profile>
<id>openshift</id> #(1)
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-maven-plugin</artifactId>
<executions>
<execution>
<id>fmp</id>
<goals>
<goal>resource</goal> #(4)
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
...
-
Since we don’t want the Docker image / OpenShift resource descriptor generation process executed by FMP when we only want to run the application locally, we "hide" the FMP execution behind an
openshift
profile. -
Earlier, we said that we would redefine the version of the base image used to perform our builds. This is how we do it: instead of re-defining the whole
fabric8.generator.from
property, we introduce a newopenjdk18-openshift.version
property, which we set to the desired value and -
… use that property when specifying the
fabric8.generator.from
property, thus allowing us to easily change the image version without touching the base image identifier. -
We bind the plugin
resource
andbuild
goals to Maven’s lifecycle so that they are automatically run when we execute a Maven build. Theresource
goal creates the OpenShift resource descriptors (by default aService
along with an associatedRoute
and aDeploymentConfig
) whilebuild
creates a Docker image using the Source-to-Image (S2I) build process by default using the base image we specified above.
We then need to execute the fabric8:deploy
goal of the Fabric8 Maven plugin to deploy the application to an OpenShift cluster. Since we have bound the resource
and build
goals to Maven’s lifecycle, they will be automatically triggered at appropriate times during the build process. Resource descriptors will therefore be appropriately generated using information from the booster’s POM, a Docker image containing all that’s needed to run a Java application and our application will be built using the S2I process and finally, all these artifacts will be pushed to the OpenShift cluster we’re connected to. This is accomplished by running:
./mvnw clean fabric8:deploy -Popenshift
By default, FMP will operate in auto
mode, meaning that it will attempt to detect which kind of cluster you are targeting (plain Kubernetes or OpenShift). Being logged in an OpenShift cluster (via oc login
) should allow FMP to properly detect that we are focusing on an OpenShift deployment and therefore trigger the creation of OpenShift-specific resource descriptors. fabric8:deploy
will invoke the fabric8:build
and fabric8:resource-apply
goals (this last one being similar to fabric8:resource
but additionally sending the resources to the target cluster to be applied).
In order to understand more deeply what fabric8:deploy
does, let’s execute these steps separately and look at the generated resources.
fabric8:resource
generates a fabric8
directory in target
. This directory is a working directory containing resources that are created from resource fragments. In our case, though, we operate using FMP’s zero-config mode, meaning that we let FMP create automatically the needed resources in an opinionated way. We could override some of these generated values if needed but we’re content with the defaults. In particular, an OpenShift Route is automatically created for our service and it therefore appears in that fabric8
directory.
The more interesting directory when it comes to files generated by fabric8:resource
is the target/classes/META-INF/fabric8
directory. This is where FMP puts the final version of the generated files once they have prepared. Looking at it, we notice it has the following structure:
+ kubernetes
\__ spring-boot-rest-http-deployment.yml
|_ spring-boot-rest-http-svc.yml
- kubernetes.json
- kubernetes.yml
+ openshift
\__ spring-boot-rest-http-deploymentconfig.yml
|_ spring-boot-rest-http-route.yml
|_ spring-boot-rest-http-svc.yml
- openshift.json
- openshift.yml
The first thing that we notice is that descriptors are generated for both plain Kubernetes and OpenShift. For each "flavor", FMP generates top-level resource lists, in both JSON (kubernetes.json
or openshift.json
) and YAML (kubernetes.yml
and openshift.yml
) formats, aggregating the individual resources generated in the associated directories (kubernetes
for plain Kubernetes, openshift
for OpenShift).
Looking at the individual generated resources, we notice that they follow the <artifactId of the project>-<resource type>.yml
convention. We also notice that, while the service descriptors (spring-boot-rest-http-svc.yml
) are identical for both plain Kubernetes and OpenShift flavors, FMP generates a Deployment
descriptor for Kubernetes but a DeploymentConfig
descriptor for OpenShift. OpenShift’s DeploymentConfig
provide additional features on top of Kubernetes' Deployments
, notably lifecycle hooks and custom triggers based on either configuration or image changes. For more details on the differences between both concepts, please take a look at Deployment vs. DeploymentConfig. Additionally, a Route
descriptor is also generated for OpenShift.
It’s worth pausing here a moment and realize that simply thanks to the addition of the FMP plugin to our Maven POM, our application is ready for the cloud. While it is important to understand the concepts at play here, it’s also worth mentioning that simply running ./mvnw fabric8:deploy -Popenshift
while connected to a running OpenShift instance will deploy our app to the cloud and make it available without us having to worry (at least for now) about how it got there or what’s required to do so! In fact, you can access your application by running
oc get route spring-boot-rest-http -o jsonpath='{"http://"}{.spec.host}{"\n"}'
and pasting that URL in your favorite browser.
This works because FMP generated the spring-boot-rest-http
Route
automatically for you as follows:
---
apiVersion: v1
kind: Route #(1)
metadata:
labels:
app: spring-boot-rest-http
provider: fabric8
version: 1.5.14-1-SNAPSHOT
group: io.openshift.booster
name: spring-boot-rest-http #(2)
spec:
port:
targetPort: 8080 #(3)
to:
kind: Service #(4)
name: spring-boot-rest-http #(5)
-
Create a
Route
… -
named
spring-boot-rest-http
… -
targeting the
8080
port of … -
the
Service
… -
named
spring-boot-rest-http
!
Let’s now look at some the spring-boot-rest-http-svc.yml
file, the Service
definition:
---
apiVersion: v1
kind: Service #(1)
metadata:
annotations: #(2)
fabric8.io/git-commit: 1fc0c37f21be5db9a77f93e42382583b32a588a7
fabric8.io/scm-con-url: scm:git:https://github.com/openshiftio/booster-parent.git/spring-boot-rest-http
prometheus.io/port: "9779"
fabric8.io/scm-url: https://github.com/openshiftio/spring-boot-rest-http
fabric8.io/iconUrl: img/icons/spring-boot.svg
fabric8.io/git-branch: guide
prometheus.io/scrape: "true"
fabric8.io/scm-devcon-url: scm:git:git:@github.com:openshiftio/booster-parent.git/spring-boot-rest-http
fabric8.io/scm-tag: booster-parent-23
labels:
expose: "true" #(4)
app: spring-boot-rest-http #(6)
provider: fabric8
version: 1.5.14-1-SNAPSHOT
group: io.openshift.booster
name: spring-boot-rest-http #(3)
spec:
ports:
- name: http #(5)
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: spring-boot-rest-http
provider: fabric8
group: io.openshift.booster
-
Create a
Service
… -
with some annotations, which provide metadata that interested third-parties can leverage…
-
named
spring-boot-rest-http
… -
marked as being exposed, meaning a
Route
should be generated to expose theService
from outside the cluster… -
and exposing the
8080
HTTP port. -
Note also, that we add an
app
label with thespring-boot-rest-http
value, label that is added to all the generated resources to mark them as being part of the same application.
Finally, let’s look at the DeploymentConfig
descriptor, not going into details here, rather just pointing out some interesting parts:
---
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
annotations:
fabric8.io/git-commit: 1fc0c37f21be5db9a77f93e42382583b32a588a7
fabric8.io/metrics-path: dashboard/file/kubernetes-pods.json/?var-project=spring-boot-rest-http&var-version=1.5.14-1-SNAPSHOT
fabric8.io/scm-con-url: scm:git:https://github.com/openshiftio/booster-parent.git/spring-boot-rest-http
fabric8.io/scm-url: https://github.com/openshiftio/spring-boot-rest-http
fabric8.io/iconUrl: img/icons/spring-boot.svg
fabric8.io/git-branch: guide
fabric8.io/scm-devcon-url: scm:git:git:@github.com:openshiftio/booster-parent.git/spring-boot-rest-http
fabric8.io/scm-tag: booster-parent-23
labels:
app: spring-boot-rest-http
provider: fabric8
version: 1.5.14-1-SNAPSHOT
group: io.openshift.booster
name: spring-boot-rest-http
spec:
replicas: 1
revisionHistoryLimit: 2
selector:
app: spring-boot-rest-http
provider: fabric8
group: io.openshift.booster
strategy:
rollingParams:
timeoutSeconds: 3600
type: Rolling
template:
metadata:
annotations:
fabric8.io/git-commit: 1fc0c37f21be5db9a77f93e42382583b32a588a7
fabric8.io/metrics-path: dashboard/file/kubernetes-pods.json/?var-project=spring-boot-rest-http&var-version=1.5.14-1-SNAPSHOT
fabric8.io/scm-con-url: scm:git:https://github.com/openshiftio/booster-parent.git/spring-boot-rest-http
fabric8.io/scm-url: https://github.com/openshiftio/spring-boot-rest-http
fabric8.io/iconUrl: img/icons/spring-boot.svg
fabric8.io/git-branch: guide
fabric8.io/scm-devcon-url: scm:git:git:@github.com:openshiftio/booster-parent.git/spring-boot-rest-http
fabric8.io/scm-tag: booster-parent-23
labels:
app: spring-boot-rest-http
provider: fabric8
version: 1.5.14-1-SNAPSHOT
group: io.openshift.booster
spec:
containers:
- env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: ""
imagePullPolicy: IfNotPresent
livenessProbe: #(5)
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 180
name: spring-boot #(1)
ports:
- containerPort: 8080
name: http #(2)
protocol: TCP
- containerPort: 9779
name: prometheus #(3)
protocol: TCP
- containerPort: 8778
name: jolokia #(4)
protocol: TCP
readinessProbe:
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 10
securityContext:
privileged: false
triggers:
- type: ConfigChange
- imageChangeParams: #(6)
automatic: true
containerNames:
- spring-boot
from:
kind: ImageStreamTag
name: spring-boot-rest-http:latest
type: ImageChange
-
The container name is
spring-boot
. This is due to FMP detecting that we’re deploying a Spring Boot app and therefore triggers the associated generator which actually handles the image creation details automatically for us. You might remember though that we set thefabric8.generator.from
property earlier when configurating FMP. This is where this comes into play: instructing the Spring Boot generator to use the specific base image that will serve as the substrate for our container when the S2I process combines it with our application jar. -
Exposing the
8080
HTTP port but also… -
a port to access Prometheus monitoring information and…
-
a port to access Jolokia
-
FMP also uses enrichers to enrich the generated resources depending on some conditions. For example, one such enricher, the
f8-spring-boot-health-check
enricher is activated when thespring-boot-starter-actuator
is detected as a dependency of a Spring Boot application (which is our case). This enricher then automatically createslivenessProbe
andreadinessProbe
entries to the application’s container. -
Contrary to plain Kubernetes
Deployment
, OpenShift’sDeploymentConfig
can re-deploy containers when related images change. Here, we specify that if the image corresponding to thespring-boot-rest-http:latest
tag in theImageStream
generated byfabric8:build
changes then ourspring-boot
container needs to be redeployed.