Kubernetes ConfigMap Versioning for Spring Boot Apps


Kubernetes doesn’t provide built-in support for ConfigMap or Secret versioning. Sometimes such a feature may be useful, when we are deciding to rollback current version of our application. In Kubernetes we are able to rollback just a version of Deployment without any additional configuration properties stored in ConfigMap or Secret.Although Kubernetes does not support versioning, we may achieve it there using some third-party solutions. One of them is Spinnaker. Spinnaker is an open source, multi-cloud continuous delivery platform for releasing software changes with high velocity and confidence created by Netflix. Configuration versioning is just a one of many features offered by that tool. For more details you may refer to its documentation site, because today I would not discuss it.
In this article I’m going to describe my custom versioning mechanism created for Spring Boot applications. I have mentioned Spinnaker, since my concept is very close to the concept used there. We are just creating dedicated ConfigMap per each new version of Deployment. My solution is based on the project Spring Cloud Kubernetes Config. It makes Kubernetes ConfigMap instances available during application bootstrapping, and allow them to be used as property sources by our Spring Boot application. For more details you may refer to my previous article about Spring Cloud Kubernetes Microservices with Spring Cloud Kubernetes.

Source Code

By default, Spring Cloud Kubernetes also does not support ConfigMap or Secret versioning. That feature is available in my forked version of this library. For accessing it go to my GitHub repository: https://github.com/piomin/spring-cloud-kubernetes.

Concept

The concept around Kubernetes configuration versioning is pretty easy. Especially with Spring Cloud Kubernetes. We are creating ConfigMap that needs to be labelled with app and version. The label app contains the name of Spring Boot application, that is configured by property spring.application.name. The label version indicates a number of application version, which is fetched by the library from property info.app.version. Both these properties need to be available inside application.yml orbootstrap.yml. Here’s sample version 1.0 of ConfigMap for application api-test.

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-test-1
  labels:
    version: "1.0"
    app: api-test
data:
  application.yaml: |-
    property1: v1.0

We may maintain many versions of ConfigMap used by a given application just by changing name, labels and content. Here’s version 1.1 of ConfigMap for the same api-test application.

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-test-2
  labels:
    version: "1.1"
    app: api-test
data:
  application.yaml: |-
    property1: v1.1

By default, Spring Cloud Kubernetes Config is able to inject ConfigMap to the application basing on its metadata.name. I redefined this mechanism to base on label app. Let’s take a look on some details.

Implementation

Now the question is – how to use that feature in our application. Of course, you need to include the required library to your Maven dependencies. Remember to use my forked version Spring Cloud Kubernetes Config instead of the official one.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
</dependency>

Then the bootstrap.yaml file in your application needs to contains at least the following properties to enable ConfigMap versioning mechanism.

spring:
  application:
    name: api-test
  cloud:
    kubernetes:
      config:
        enableVersioning: true
info:
  app:
    version: 1.0

Let’s take a look on the key fragment of versioning mechanism implementation inside Spring Cloud Kubernetes Config. If it is not enabled with property spring.cloud.kubernetes.config.enableVersioning it works basing on metadata.name – the same as before changes. We are also checking if property info.app.version is set inside the application settings. If not versioning mechanism is being disabled. Otherwise we are checking all ConfigMaps in the selected namespace, and filtering them by app and version labels.

String version = environment.getProperty("info.app.version");
LOG.info("Get Config: versioning->" + enableVersioning + ", name->" + name + ", version->" + version);
Map result = new HashMap();
ConfigMap map = null;
if (!enableVersioning || version == null) {
	map = StringUtils.isEmpty(namespace) ? client.configMaps().withName(name).get()
			: client.configMaps().inNamespace(namespace).withName(name).get();
} else {
	Optional optMap = StringUtils.isEmpty(namespace)
		? client.configMaps().list().getItems().stream()
			.filter(configMap -> configMap.getMetadata().getLabels()
				.containsKey("app")
					&& configMap.getMetadata().getLabels().get("app").equals(name)
					&& configMap.getMetadata().getLabels().containsKey("version")
					&& configMap.getMetadata().getLabels().get("version").equals(version))
				.findFirst()
		: client.configMaps().inNamespace(namespace).list().getItems().stream()
			.filter(configMap -> configMap.getMetadata().getLabels()
				.containsKey("app")
					&& configMap.getMetadata().getLabels().get("app").equals(name)
					&& configMap.getMetadata().getLabels().containsKey("version")
					&& configMap.getMetadata().getLabels().get("version").equals(version))
				.findFirst();
	if (optMap.isPresent()) {
		map = optMap.get();
	}
}

Demo

Our sample Spring Boot application is very simple. It just exposes a single HTTP endpoint that display value of property injected from ConfigMap as shown below.

@RestController
public class ApiController {

	@Value("${property1}")
	private String property1;

	@GetMapping("/property")
	public String getProperty1() {
		return property1;
	}
	
}

Of course it is using currently described versioning mechanism. First let’s run instance of Kubernetes locally using Kind (Kubernetes IN Docker). Here’s the command that creates local cluster.

$ kind create cluster --config=cluster.yaml

To make it available under virtual address 192.168.99.100 we need to provide the following configuration file cluster.yaml.

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  apiServerAddress: 192.168.99.100
  apiServerPort: 6443

After running the following command kind is ready to use.

kubernetes-configmap-versioning-spring-boot-kind

Now, let’s create two test ConfigMaps for our application with kubectl apply command. Let’s take a look on the result.

kubernetes-configmap-versioning-spring-boot-configmap

Then we should deploy our sample app. The only difference between two subsequent deployments is in info.app.version property, that is 1.0 for first deployment, and 1.1 for the second deployment. Here’s the Deployment manifest. As you see below it does not inject any ConfigMap or Secret using Kubernetes mechanisms.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-test
spec:
  selector:
    matchLabels:
      app: api-test
  template:
    metadata:
      labels:
        app: api-test
    spec:
      containers:
      - name: api-test
        image: piomin/api-test
        ports:
        - containerPort: 8080

In the newest version of our sample application I just changed the value of property info.app.version from 1.0 to 1.1.

spring:
  application:
    name: api-test
  cloud:
    kubernetes:
      config:
        enableVersioning: true
info:
  app:
    version: 1.1

Let’s call our test endpoint. It returns value of property1 taken from ConfigMap labelled with 1.1.

$ curl http://localhost:8080/property
v1.1

Here’s the history of deployments for api-test application. We may rollback version of deployment to the previous one (1) by using command kubectl rollout undo.

kubernetes-configmap-versioning-spring-boot-rollback

Now we may call our test endpoint one more time. As you can see below it returns v1.0, which is set as a property1 value in ConfigMap labelled with version 1.0.

$ curl http://localhost:8080/property
v1.0

Conclusion

To use described versioning mechanism on Kubernetes you just need to create ConfigMap labelled with properly, include forked Spring Cloud Kubernetes Config from my repository and add property info.app.version to the Spring Boot application properties.

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.