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 a 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 one of the 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 Kubernetes ConfigMap 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 allows 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 ConfigMap 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 based on its metadata.name
. I redefined this mechanism to base on the label app
. Let’s take a look at 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 based 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, the 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 of Kubernetes ConfigMap versioning
Our sample Spring Boot application is very simple. It just exposes a single HTTP endpoint that displays the 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 the currently described versioning mechanism. First let’s run an instance of Kubernetes locally using Kind (Kubernetes IN Docker). Here’s the command that creates the 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.
Now, let’s create two test ConfigMaps
for our application with kubectl apply
command. Let’s take a look at the result.
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 the 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 the version of deployment to the previous one (1
) by using command kubectl rollout undo
.
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