Dekorate

"Write once, run everywhere" is a slogan created by Sun Microsystems to illustrate the cross-platform benefits of Java. In the cloud-native world, this slogan is more accurate than ever, with virtualization and containers increasing the distance between code and hardware even further. But what does this shift mean for developers?

Developers need to take care of containerizing their application and also provide a set of manifests for Kubernetes (which now tends to be a synonym of cloud). In this article, we are going to focus on the latter and, more specifically, on how to use Dekorate to create and maintain these manifests with the minimum possible effort.

Dekorate

Dekorate is a set of manifest generators, decorators, and tools that make the creation of Kubernetes manifests as easy as adding a jar into your class path. It's based on Java annotation processors, which means it works with any JVM language that supports annotations, regardless of the build tool you are using (works with Maven, Gradle, Basel, etc).

Dekorate supports vanilla Kubernetes and Red Hat Openshift and also popular extensions and operators (e.g., Prometheus and Jaeger). What's even more exciting is that it provides class support for Spring Boot, which means it can understand things like beans, extensions, and Spring Boot configuration present in your code and aligns the generated manifests accordingly. For example, it can detect the presence of Spring Cloud Kubernetes and configure the required role bindings and service accounts that are required.

Getting started

The easiest way to get started is to add the jar that corresponds to your target platform in the class path. Kubernetes users would need:

<dependency>
  <groupId>io.dekorate</groupId>
  <groupId>kuberentes-annotations</groupId>
  <version>0.7.5</groupId>
</depndency>

Spring Boot users might prefer a "starter" module that encloses all modules related to Spring Boot integration.

<dependency>
  <groupId>io.dekorate</groupId>
  <groupId>kubernetes-spring-starter</groupId>
  <version>0.7.5</groupId>
</depndency>

Openshift users would want to use:

<dependency>
  <groupId>io.dekorate</groupId>
  <groupId>openshift-spring-starter</groupId>
  <version>0.7.5</groupId>
</depndency>

During compilation, Dekorate will be triggered by the compiler and will generate Kubernetes or Openshift (or even both based on which modules are added to classpath) manifests under target/classes/META-INF/dekorate. These manifests can then be directly applied to the cluster in order to deploy the application.

kubectl apply -f target/classes/dekorate/kubernetes.yml

or

oc apply -f target/classes/dekorate/openshift.yml

Dekorating the generated manifests

The generated manifests will contain a basic Deployment resource (or DeploymentConfig in the case of Openshift). The manifest may also contain additional configuration or resources based on what Dekorate could make out of your code. For example, if Dekorate detects an HTTP endpoint, the HTTP port will be added to the container and exposed as a Service.

Further customization/decoration of these manifests can be performed using:

  • Annotations
  • Application configuration
  • Both of the above

Using annotations

The provided annotations allow the user to specify pretty much anything related to the deployment manifest of the application, for example:

  • Annotations and labels
  • Ports
  • Environment variables
  • Volumes & mounts
  • Sidecars
  • More

Here's an example of how to add a label:

import io.dekorate.kubernetes.annotation.KubernetesApplication;

@KubernetesApplication(labels=@Label(key="foo", value="bar"))
public class Main {
   public static void main(String[] args) {
       //your code here.
   }
} 

The analog for Openshift:

import io.dekorate.openshift.annotation.OpenshiftApplication;

@OpenshiftApplication(labels=@Label(key="foo", value="bar"))
public class Main {
   public static void main(String[] args) {
       //your code here.
   }
} 

The beauty of using annotations for decorating the manifests is that the validity of the configuration is validated by the compiler. On top of that modern IDEs provide syntax highlighting and code completion that assists the user in configuring things.

Using annotations, to specify small chunks of configuration can be a preferable experience compared to writing long sheets of JSON or YAML.

Working without a Main class

The code examples above are pretty straight forward for Java applications that feature a Main class (simple Java applications, Spring Boot, etc). What about application frameworks that do not necessarily have a Main class?

Dekorate annotations do not need to be added to the Main class. Any class would do, even an empty class that will serve just for that purpose.

Using application configuration

Developers, often prefer to have the ability to externalize configuration. Others prefer to keep code completely separated from the configuration. To cover those needs, Dekorate makes it possible to use application configuration.

At the moment this feature is only available to Spring Boot. For Spring Boot, the application is configured via application.properties or application.yml. Dekorate will read both of those files and will process all properties prefixed with dekorate.

To add the foo=bar label using application.properties:

dekorate.kubernetes.label[0].key=foo
dekorate.kubernetes.label[0].value=bar

or using application.yaml:

dekorate:
  kubernetes:
    label:
    - key: foo
      value: bar

The full list of supported properties can be found at: https://github.com/snowdrop/dekorate/blob/master/config.md

Using both

If you would like to use annotations and override values through the standard application configuration mechanisms, this is also possible.

Testing the generated manifests

To test the application and the generated manifests, Dekorate provides junit5 extensions, that makes it easy to:

  • Build the application container.
  • Deploy the application to a target cluster.
  • Wait until the application is ready.
  • Inject application metadata inside the test code.
import io.dekorate.deps.kubernetes.client.KubernetesClient;
import io.dekorate.deps.kubernetes.api.model.Pod;
import io.dekorate.testing.annotation.Inject;
import io.dekorate.testing.annotation.Named;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;

@KubernetesIntegrationTest
public class SpringBootOnKubernetesIT {

  @Inject
  private KubernetesClient client;

  @Inject
  Pod pod;

  @Test
  public void testApplication() throws Exception {
    assertNotNull(client);
    // test the application....
  }

Dekorate provides a lot of examples featuring advanced integration tests. For more details, check: https://github.com/snowdrop/dekorate/tree/master/examples.

Building and deploying

Dekorate focuses on manifest generation and maintenance. Building container images and deploying them is not its primary focus. However, for making developers life easier it does provide hooks, for building, pushing to registries and deploying during the compilation phase.

Docker builds

For Docker builds, provided a Dockerfile is in the module root and the docker binary in the PATH, Dekorate can be used with the following flags:

Building container images:

mvn clean package -Ddekorate.build=true

Right after the actual maven build, Dekorate will use the docker binary to perform the docker build.

Pushing container images:

mvn clean package -Ddekorate.push=true

Right after the actual maven build a docker push will be performed.

Deploying the generated manifests:

mvn clean package -Ddekorate.deploy=true

Right after the build, the generated manifests will be deployed to the target cluster. This action assumes that either kubectl or oc are present in the PATH and a valid ~/.kube/config file is present pointing to the target cluster.

S2i builds

For Openshift, dealing with container builds is even simpler as no Dockerfile is required. When the openshfit-annotations or one of the Openshift starters is used with -Ddekorate.build=true, Dekorate will generate a BuildConfig and the required ImageStream resources, for performing an s2i binary build. This action requires the presence of the oc binary in the PATH.

Other features

It would take a lot more than a single blog post to even enumerate all the available features. It worth's mentioning though, that it does provide integration with popular operators (e.g. Prometheus, Jaeger, Component) and extensions and the list is growing fast.

Framework integration

Dekorate does provide integration with quite a few application frameworks like Spring Boot. However, there are frameworks that provide native resource generation for Kubernetes based on Dekorate, such as Quarkus.

Quarkus provides its own extension for Kubernetes. The extension instead of using annotations, is interacting with the rest of the available extensions to retrieve as much information as possible for the application (e.g. server port, health checks and so on). Once, all the required information is collected it uses Dekorate to customize the generated manifests. You can find more information at Quarkus Kubernetes extension.

Tech preview

A trimmed down feature set of Dekorate is also available as a "tech preview" as part of Red Hat OpenShift Application Runtimes (RHOAR).

Conclusion

Dekorate is a fairly new project, that takes care of Kubernetes and Red Hat Openshift manifest management, for all the JVM languages and all build tools. It requires the minimum possible effort to get started and it does provide two styles of configuration: using annotations, and using application configuration. It provides a wide range of integration modules with frameworks, extensions and operators and its growing fast.

Enjoy!

Last updated: June 23, 2023