"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).
- The tech preview release includes the modules that are listed at: https://github.com/snowdrop/dekorate.
- The tech preview release artifacts can be found at: https://maven.repository.redhat.com/ga/io/dekorate.
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