Deploying Spring Boot Application on OpenShift with Dekorate

Deploying Spring Boot Application on OpenShift with Dekorate

More advanced deployments to Kubernetes or OpenShift are a bit troublesome for developers. In comparison to Kubernetes OpenShift provides S2I (Source-2-Image) mechanism, which may help reduce the time required for preparation of application deployment descriptors. Although S2I is quite useful for developers, it solves only simple use cases and does not provide a unified approach to building deployment configuration from a source code. Dekorate (https://dekorate.io), the recently created open-source project, tries to solve that problem. This project seems to be very interesting. It appears to be confirmed by RedHat, which has already announced a decision on including Dekorate to Red Hat OpenShift Application Runtimes as a “Tech Preview”.

You can read more about it in this article: https://developers.redhat.com/blog/2019/08/15/how-to-use-dekorate-to-create-kubernetes-manifests.

How does it work?

Dekorate is a library that defines a set of annotation processors used for generating and decorating Kubernetes or OpenShift manifests. In fact, you just need to annotate your application main class properly and Dekorate will take care of everything else. To use this library you only have to include it in your Maven pom.xml as shown below.

<dependency>
   <groupId>io.dekorate</groupId>
   <artifactId>openshift-spring-starter</artifactId>
   <version>0.8.2</version>
</dependency>

The starter contains not only annotations and annotation processors that may be used on your application, but also support for generation during Maven build, which is executed during the compile phase. To enable Decorate during build you need to set property dekorate.build to true. You can also enable deployment by setting property dekorate.deploy to true as shown below.

$ mvn clean install -Ddekorate.build=true -Ddekorate.deploy=true

Dekorate supports OpenShift S2I. It generates ImageStream for builder and target application, and also BuildConfig resource. If you enable deploy mode it also generates deployment config with required resources. Here’s the screen with logs from Maven build executed on my local machine.

spring-boot-decorate-openshift-1

In this case Dekorate is generating OpenShift manifest files and saves them inside directory target/classes/META-INF/dekorate/, and then performing deployment on my instance of Minishift available under virtual address 192.168.99.100.
Unfortunately, we have to modify generated BuildConfig for our convenience. So instead of source type Binary we will just declare Git source repository address.

apiVersion: "build.openshift.io/v1"
kind: "BuildConfig"
metadata:
  labels:
    app: "sample-app"
    version: "1.1.0"
    group: "minkowp"
  name: "sample-app"
spec:
  output:
    to:
      kind: "ImageStreamTag"
      name: "sample-app:1.1.0"
  source:
    git:
      uri: 'https://github.com/piomin/sample-app.git'
    type: Git
  strategy:
    sourceStrategy:
      from:
        kind: "ImageStreamTag"
        name: "s2i-java:2.3"

In order to apply the changes execute the following commands:

$ oc delete bc sample-app
$ oc apply -f build-config-dekorate.yaml

Customization

If you have Spring Boot applications it is possible to completely bypass annotations by utilizing already-existing, framework-specific metadata. To customize the generated manifests you can add dekorate properties to your application.yml or application.properties descriptors. I have some problems running these modes with Dekorate, so I avoided it. However, I prefer using annotations on the code, so I prepared the following configuration, which has been successfully generated:

@SpringBootApplication
@OpenshiftApplication(replicas = 2, expose = true, envVars = {
        @Env(name="sample-app-config", configmap = "sample-app-config")
})
@JvmOptions(xms = 128, xmx = 256, heapDumpOnOutOfMemoryError = true)
@EnableSwagger2
public class SampleApp {

    public static void main(String[] args) {
        SpringApplication.run(SampleApp.class, args);
    }
   
    // ... REST OF THE CODE
}

In the fragment of code visible above we have declared some useful settings for the application. First, it should be run in two pods (replicas=2). It also should be exposed outside a cluster using OpenShift route (expose=true). The application uses Kubernetes ConfigMap as a source of dynamically managed configuration settings. By annotating the main class with @JvmOptions we may customize the behavior of JVM running on the container, for example by setting maximum heap memory consumption. Here’s the definition of ConfigMap created for the test purpose:

apiVersion: v1
kind: ConfigMap
metadata:
  name: sample-app-config
  namespace: myproject
data:
  showAddress: 'true'
  showContactInfo: 'true'
  showSocial: 'false'

Sample Application

The sample application code snippet is available on GitHub under repository https://github.com/piomin/sample-app.git. This very basic Spring Boot web application exposes a simple REST API with some monitoring endpoints included with Spring Boot Actuator and API documentation generated using Swagger.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>2.9.2</version>
</dependency>
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.9.2</version>
</dependency>

Dekorate is able to automatically detect existence of Spring Boot Actuator dependency and based on it generate OpenShift readiness and liveness healthchecks definitions as shown below. By default it sets timeout on 10 seconds, and period on 30 seconds. We can override a default behaviour using fields liveness and readiness of @OpenshiftApplication.

dekorate-3

The controller class provides implementation for some basic CRUD REST operations. It also injects some environment variables taken from ConfigMap sample-app-config. Based on their values it decides whether to show or not to show additional person parameters like address, contact information or social links. Here’s an implementation of PersonController:

@RestController
@RequestMapping("/persons")
public class PersonsController {

    private static final Logger LOGGER = LoggerFactory.getLogger(PersonsController.class);

    @Autowired
    PersonRepository repository;

    @Value(value = "${showAddress:false}")
    boolean showAddress;
    @Value(value = "${showContactInfo:false}")
    boolean showContactInfo;
    @Value(value = "${showSocial:false}")
    boolean showSocial;

    @PostMapping
    public Person add(@RequestBody Person person) {
        LOGGER.info("Person add: {}", person);
        return repository.add(person);
    }

    @GetMapping("/{id}")
    public Person findById(@PathVariable("id") Integer id) {
        LOGGER.info("Person find: id={}", id);
        return hidePersonParams(repository.findById(id));
    }

    @GetMapping
    public List<Person> findAll() {
        LOGGER.info("Person find");
        return repository.findAll().stream().map(this::hidePersonParams).collect(Collectors.toList());
    }

    private Person hidePersonParams(Person person) {
        if (!showAddress)
            person.setAddress(null);
        if (!showContactInfo)
            person.setContact(null);
        if (!showSocial)
            person.setSocial(null);
        return person;
    }
}

Here’s an implementation of a model class. I used the Lombok library for getters, setters, and constructor generation.

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {

    private Integer id;
    private String name;
    private int age;
    private Gender gender;
    private Address address;
    private Contact contact;
    private Social social;

}

Here’s a definition of ConfigMap sample-app-config on my instance of Minishift.

spring-boot-decorate-openshift-4

Deployment

The current version of Dekorate uses fabric/s2i-java in version 2.3 as a builder image. It creates ImageStream without any tags, so we have the Docker image from docker.io manually by executing the following command:


$ oc import-image fabric/s2i-java:2.3

This commands forces to download some additional versions of a builder image including the newest version for JDK 11. The library still uses the older version of s2i-java image although we have declared JDK 11 as a default version in pom.xml. In this example we can see that Dekorate has still some things to improve 🙂

dekorate-5

Assuming we have already performed all the required steps described in the previous sections of this article we may start OpenShift build by running the following command:

$ oc start-build sample-app

The build should be finished successfully as shown below.

dekorate-6

After successful build the new image is pushed to OpenShift registry. It triggers the start of a new deployment of our application. As you see on the picture below our application is started in two instances, and the route is created automatically.

dekorate-7

We can also take a look at environment settings. JVM options have been set, and ConfigMap has been assigned to the deployment config.

spring-boot-decorate-openshift-8

Finally we may test the sample application. The Swagger API documentation is available under address http://sample-app-myproject.192.168.99.100.nip.io/swagger-ui.html.

spring-boot-decorate-openshift-9

Conclusion

I think it is a great idea to use Java annotations for customizing application deployment on Kubernetes/OpenShift. Using Dekorate with frameworks based on annotation processing like Spring Boot greatly simplifies the deployment process for developers. Although the library is in the early stage of development and still has some things to improve I definitely recommend using it. In this article, I’m trying to give you some tips for a quick start. Enjoy 🙂

2 COMMENTS

comments user
Ioannis Canellos

Great article!

I really love the idea that builder image selection should be aligned with JDK version defined in the build tool.
In fact, I just created an issue about it: https://github.com/dekorateio/dekorate/issues/363

I have a tiny correction, if I may.

The following phrase is not 100% accurate:

“The starter contains not only annotations and annotation processors that may be used on your application, but also Maven plugin, which is executed during compile phase”

Dekorate doesn’t use or define any maven plugin as it aspires to be build tool independent.
So the dekorate starters are there so that user can add a single dependency and transitively get things like:

– openshift support
– spring boot suuport
– options support

    comments user
    Piotr Mińkowski

    Hi. Thanks for your suggestion. I changed this sentence in my article following your suggestions. I think I could have misspoken.

Leave a Reply