Spring Boot Application on OpenShift with Dekorate



How it Works?

Dekorate is a library that defines a set of annotation processors used for generating and decorating Kubernetes/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 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.

#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 mode with Dekorate, so I avoided it. However, I prefer using annotations on the code, so I prepared the following configuration, which has been succesfully 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 behaviour 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

This very basic Spring Boot web application exposes 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 basing 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. Basing 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 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;



}


Post a Comment

3 Comments

  1. Thanks for sharing such a great information.. It really helpful to me.I always search to read the quality content and finally i found this in you post. keep it up!

    ReplyDelete
  2. Very Good… i really like your blog…

    ReplyDelete