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