Simplify development on Kubernetes with Dekorate, Skaffold and Spring Boot
Although Kubernetes is a great solution for managing containerized applications, scaling, and automating deployment, a local development on it may be a painful experience. A typical workflow includes several steps like checking the functionality of the code locally, building and tagging a docker image, creating a deployment configuration, and finally deploying everything on Kubernetes. In this article, I’m going to show how to use some tools together to simplify that process.
I have already described how to use such tools as Skaffold and Jib for local Java and Spring Boot development on Kubernetes in one of my previous articles Local Java Development on Kubernetes. I have also already described what is Dekorate framework and how to use it together with Spring Boot for building and deploying applications on OpenShift in the article Deploying Spring Boot application on OpenShift with Dekorate. Using all these tools together may be a great combination! Let’s proceed with the details.
Example
The sample Spring Boot application prepared for usage with Skaffold is available on my GitHub repository https://github.com/piomin/sample-springboot-dekorate-istio.git. Assuming you already have Skaffold, to deploy it on Kubernetes you just need to execute command skaffold dev --port-forward
on the root directory.
Dependencies
We are using version 2.2.7
of Spring Boot with JDK 11. To enable generating manifests during Maven build with Dekorate we need to include library kubernetes-spring-starter
. To use Dekorate annotations in our application we should include kubernetes-annotations
dependency.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.7.RELEASE</version>
</parent>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<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.dekorate</groupId>
<artifactId>kubernetes-spring-starter</artifactId>
<version>0.12.2</version>
</dependency>
<dependency>
<groupId>io.dekorate</groupId>
<artifactId>kubernetes-annotations</artifactId>
<version>0.12.2</version>
</dependency>
</dependencies>
To enable Jib we just need to include its Maven plugin. Of course, we also need to have Spring Boot Maven Plugin responsible for generating uber jar with the application.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>2.3.0</version>
</plugin>
</plugins>
</build>
Integrating Skaffold with Dekorate
Dekorate is generating Kubernetes manifests during the compilation phase of Maven build. The default target directory for those manifests is target/classes/META-INF/dekorate
. I didn’t find any description in Dekorate documentation on how to override that path, but it is not a problem for us. We may as well set that directory in Skaffold configuration.
Dekorate is generating manifests in two variants: YAML and JSON. During tests, I have some problems with the YAML manifest generated by Dekorate, so I decided to switch to JSON. It worked perfectly fine. Here’s our configuration in skaffold.yaml
. As you see I’m enabling integration with Jib and adding a new search path for Kubernetes manifests target/classes/META-INF/dekorate/*.json
in deploy section. I have also left a default path for manifests which is k8s
directory.
apiVersion: skaffold/v2alpha1
kind: Config
build:
artifacts:
- image: minkowp/sample-springboot-dekorate-istio
jib: {}
tagPolicy:
gitCommit: {}
deploy:
kubectl:
manifests:
- target/classes/META-INF/dekorate/*.json
- k8s/*.yaml
Using Dekorate annotations with Spring Boot
As you probably know Spring Boot is an annotation-based framework. So the most suitable way for developer to declare resources on a target platform is of course through annotations. In that case Dekorate is a perfect solution. It has built-in support for Spring Boot – like a detection of health check endpoints configured using Spring Boot Actuator. The rest may be configured using its annotations.
The following code snippet illustrates how to use Dekorate annotations. First you should annotate the main class with @KubernetesApplication
. We have pretty many options here, but I show you the most typical features. We can set the number of pods for a single Deployment
(replicas
). We may also inject environment variable to the container or provide a reference to the property in ConfigMap
. In that case we are using property propertyFromMap
inside sample-configmap
ConfigMap
. We can also expose our application through Service
. The exposed port is 8080
. Each Deployment
may be labelled using many labels declared inside labels
field. It is possible to set some JVM parameters for our application using @JvmOptions
annotation.
@SpringBootApplication
@KubernetesApplication(replicas = 2,
envVars = { @Env(name = "propertyEnv", value = "Hello from env!"),
@Env(name = "propertyFromMap", value = "property1", configmap = "sample-configmap") },
expose = true,
ports = @Port(name = "http", containerPort = 8080),
labels = @Label(key = "version", value = "v1"))
@JvmOptions(server = true, xmx = 256, gc = GarbageCollector.SerialGC)
public class DekorateIstioApplication {
public static void main(String[] args) {
SpringApplication.run(DekorateIstioApplication.class, args);
}
}
A sample ConfigMap
should be placed inside k8s
directory. Assuming that it has not deployed on Kubernetes yet, it is the only one manifest we need to create manually.
apiVersion: v1
kind: ConfigMap
metadata:
name: sample-configmap
data:
property1: I'm ConfigMap property
That process is completely transparent for us, but just for clarification let’s take a look on the manifest generated by Dekorate. As you see instead of 114 lines of YAML code we just had to declare two annotations with some fields.
apiVersion: v1
kind: Service
metadata:
annotations:
app.dekorate.io/commit-id: 25e1ac563793a33d5229ef860a5a1ae169aa62ec
app.dekorate.io/vcs-url: https://github.com/piomin/sample-springboot-dekorate-istio.git
labels:
app.kubernetes.io/name: sample-springboot-dekorate-istio
version: v1
app.kubernetes.io/version: 1.0
name: sample-springboot-dekorate-istio
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app.kubernetes.io/name: sample-springboot-dekorate-istio
version: v1
app.kubernetes.io/version: 1.0
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
app.dekorate.io/commit-id: 25e1ac563793a33d5229ef860a5a1ae169aa62ec
app.dekorate.io/vcs-url: https://github.com/piomin/sample-springboot-dekorate-istio.git
labels:
app.kubernetes.io/name: sample-springboot-dekorate-istio
version: v1
app.kubernetes.io/version: 1.0
name: sample-springboot-dekorate-istio
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: sample-springboot-dekorate-istio
version: v1
app.kubernetes.io/version: 1.0
template:
metadata:
annotations:
app.dekorate.io/commit-id: 25e1ac563793a33d5229ef860a5a1ae169aa62ec
app.dekorate.io/vcs-url: https://github.com/piomin/sample-springboot-dekorate-istio.git
labels:
app.kubernetes.io/name: sample-springboot-dekorate-istio
version: v1
app.kubernetes.io/version: 1.0
spec:
containers:
- env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: propertyEnv
value: Hello from env!
- name: propertyFromMap
valueFrom:
configMapKeyRef:
key: property1
name: sample-configmap
optional: false
- name: JAVA_OPTS
value: -Xmx=256M -XX:+UseSerialGC -server
- name: JAVA_OPTIONS
value: -Xmx=256M -XX:+UseSerialGC -server
image: minkowp/sample-springboot-dekorate-istio:1.0
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /actuator/info
port: 8080
scheme: HTTP
initialDelaySeconds: 0
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 10
name: sample-springboot-dekorate-istio
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /actuator/health
port: 8080
scheme: HTTP
initialDelaySeconds: 0
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 10
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
labels:
app.kubernetes.io/name: sample-springboot-dekorate-istio
version: v1
app.kubernetes.io/version: 1.0
name: sample-springboot-dekorate-istio
spec:
rules:
- host: ""
http:
paths:
- backend:
serviceName: sample-springboot-dekorate-istio
servicePort: 8080
path: /
Here’s the structure of source files in our application.
Deploy Spring Boot on Kubernetes
After executing command skaffold dev --port-forward
Skaffold is continuously listening for the changes in the source code. Thanks to port-forward
option we may easily test it on port 8080. Here’s the status of our Deployment
on Kubernetes.
There is a simple endpoint that test properties injected to the container and from ConfigMap
.
@RestController
@RequestMapping("/sample")
public class SampleController {
@Value("${propertyFromMap}")
String propertyFromMap;
@Value("${propertyEnv}")
String propertyEnv;
@GetMapping("/properties")
public String getProperties() {
return "propertyFromMap=" + propertyFromMap + ", propertyEnv=" + propertyEnv;
}
}
We can easily verify it.
Summary
Spring Boot development on Kubernetes may be a pleasure when using the right tools for it. Dekorate is a very interesting option for developers in connection to annotation-based microservices frameworks. Using it together with Skaffold may additionally speed-up your local development of Spring Boot applications.
1 COMMENT