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 like Skaffold and Jib for local Java 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 to 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 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 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 dokumentation 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 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

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 is 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.

development-on-kubernetes-dekorate-skaffold-springboot-sourcecode

Deploy

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.

development-on-kubernetes-dekorate-skaffold-springboot-deployment

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.

development-on-kubernetes-dekorate-skaffold-springboot-curl

Summary

Development on Kubernetes may be a pleasure, when using a 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.

One thought on “Simplify development on Kubernetes with Dekorate, Skaffold and Spring Boot

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.