Spring Boot and Kubernetes Configmaps

Posted on Feb 17, 2022

This document was last revised on 2022-09-24 and updated for the usage with Spring Boot 3.

Check the Repository for an executable example.

Deploying your Spring Boot applications in a Kubernetes environment will bring you many benefits like for example the usage of Configmaps.

What are Configmaps

Configmaps are API objects which store data in key-value pairs. This data can be consumed by your application as environment variables or CLI arguments.

This feature helps you to decouple the environment specific configuration from your application.

Furthermore your application will do an automatic reload of the configuration, so that you don’t need to do a restart of the Pod or rebuild of an image when only one configuration changes.

Kubernetes setup

To allow your application to read resources in your cluster, you’ll need to add a ServiceAccount with a role which is allowed to access the resource configmap.

First you create a ServiceAccount, then a Role and you’ll give your service account the created role via a RoleBinding.

serviceaccount.yml:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: configmap-reader
  namespace: common

role.yml:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: common
  name: role-configmap-reader
rules:
  - apiGroups: [""]
    verbs: ["get", "list", "watch"]
    resources: ["configmaps", "pods", "services", "endpoints", "secrets"]

rolebinding.yml:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: rolebinding-configmap-reader
  namespace: common
subjects:
  - kind: ServiceAccount
    name: configmap-reader
    namespace: common
    apiGroup: ""
roleRef:
  kind: Role
  name: role-configmap-reader
  apiGroup: rbac.authorization.k8s.io

Apply these changes in your Kubernetes cluster. For example with the kubectl apply command.

In this examples I am using the common namespace. Change it accordingly to the one which you want to use. Or create it:

kubectl create namespace my-namespace

Application setup

This example shows you how to prepare your Spring Boot application for the consumption of Configmaps. Spring Boot 2.5.7 is used for this examples. However, for Spring Boot 3.0 is more information added and you can check the repository for a running example.

build.gradle

The first step to prepare your application for Configmaps is to add the needed Spring Cloud dependency to your application.

Spring Cloud offers common cloud patterns and helps you to enable these features fast to your applications.

Check the Spring Cloud documentation to get the current mappings for your used Spring Boot version to the Spring Cloud version.

Here is the table as per 24th of Sep 2022:

Release Train Boot Version
2022.0.0-M3 aka Kilburn 3.0.0-M3
2021.0.x aka Jubilee 2.6.x, 2.7.x (Starting with 2021.0.3)
2020.0.x aka Ilford 2.4.x, 2.5.x (Starting with 2020.0.3)
Hoxton 2.2.x, 2.3.x (Starting with SR5)
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

The following build.gradle uses the Spring Boot version 2.5.7 (1).

As you can see in the table we’ll use Spring Cloud version 2020.0.3 for this by setting an environment variable and adding it to the dependency-management (2, 3).

Furthermore we need the Kubernetes Client Dependency (spring-cloud-starter-kubernetes-client-config) in version 2.0.4 and spring-boot-starter-actuator dependency which enables your application to load config maps (4, 5).

plugins {
	id 'org.springframework.boot' version '2.5.7' // 1
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'de.dkwr'
version = '0.0.1'
sourceCompatibility = '12'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

ext {
	set('springCloudVersion', "2020.0.3") // 2
}

dependencies {
	implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-client-config:2.0.4' // 4
	implementation 'org.springframework.boot:spring-boot-starter-actuator' // 5
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" // 3
	}
}

test {
	useJUnitPlatform()
}

For Spring Boot 3.0 check the following build.gradle which also contains further dependencies.

plugins {
	id 'org.springframework.boot' version '3.0.0-SNAPSHOT'
	id 'io.spring.dependency-management' version '1.0.14.RELEASE'
	id 'java'
}

group = 'de.dkwr'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

ext {
	set('springCloudVersion', "2022.0.0-M3")
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
  // These are dependencies, which are used to have a running application 
	implementation 'org.springframework.boot:spring-boot-starter'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

  // These are the dependencies, you'll need to make it running:
	implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-client-config'
	implementation 'org.springframework.cloud:spring-cloud-config-client'
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" // 3
	}
}

tasks.named('test') {
	useJUnitPlatform()
}

The bootstrap file

A Spring Cloud application needs a bootstrap.yml in the resources directory to make the application run correctly.

To cite the Spring Cloud page:

A Spring Cloud application operates by creating a “bootstrap” context, which is a parent context for the main application. It is responsible for loading configuration properties from the external sources and for decrypting properties in the local external configuration files.

You can define multiple bootstrap files and control which one gets loaded in which environment.

resources/bootstrap.yml

The following bootstrap.yml gets activated on a normal startup (e.g. when you start your application locally):

spring:
  application:
    name: example-application
  cloud:
    kubernetes:
      enabled: false
info:
  app:
    version: "1.0"

For Spring Boot 3.0 use the following one:

spring:
  application:
    name: example-application
  config:
    activate:
      on-profile: default
  cloud:
    config:
      enabled: false
    kubernetes:
      enabled: false
info:
  app:
    version: "1.0"

resources/bootstrap-kubernetes.yml

When you run your application in a Kubernetes environment, the kubernetes profile will be activated automatically. To keep things clean, this configuration will be stored in an own file named bootstrap-kubernetes.yml.

In this case you can activate the reading of the configmaps. This is the same for Spring Boot 3.0.

spring:
  application:
    name: example-application
  config:
    activate:
      on-profile: kubernetes # 1
  cloud:
    kubernetes:
      enabled: true # 2
      reload: 
        enabled: true # 3
        mode: event # 4
      config:
        enabled: true # 5
        name: example-application
        namespace: common
        sources:
          - name: example-application # 6
        enableVersioning: true
      client:
        namespace: common

Here are some more infos on what happens there:

1 - Activates the bootstrap file when the application runs on the profile kubernetes.

2 - Enables the Spring Cloud kubernetes feature.

3 - Enables monitoring of property sources and configuration reload.

4 - Listen on events.

5 - Enable Configmaps as property source.

6 - Name of the ConfigMap to load.

Check this reference for more options on property reload.

And this reference for configmap as property sources.

application.yml files for different profiles

To activate different application.yml files and work with wished properties, you need to create a YAML file for every profile and activate it when the profile is active.

This will overwrite the values from the default application.yml file.

For example for the profile dev you can create a application-dev.yml file:

spring:
  profiles: dev

For the profile prod you can create a application-prod.yml file:

spring:
  profiles: prod

So as a conclusion: You can have one application.yml which acts as your global/central property source.

Per specific environment/deployment you define own application.yml files which will be activated on the specific profile as soon as the application started in that profile. If you don’t need different configurations for different profiles, then just create the default application.yml.

Deployment

Finally you can write your deployment files for the deployment of your application in Kubernetes.

configmap.yml

You need to define a ConfigMap resource and it in your cluster:

apiVersion: v1
kind: ConfigMap
metadata:
  name: example-application
  namespace: common
  labels:
    version: "1.0" # 2
    app: example-application
data: # 1
  application.yml: |-
    server:
      port: 8081    

Beside of the needed metadata for Kubernetes you can find two things here:

1 - The configuration which you define in the data section.

2 - The version of the application for which this configmap applies. This needs to match with the label in the bootstrap.yml (see above). This enables your application to run under different versions. For example when you do bigger database changes/migrations you can let your “old” application run against the known database and start an application as version 2 with an according configmap and run it against the new database.

deployment.yml

The last step is the definition and applying of the Deplyoment resource.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-application
  namespace: common
spec:
  revisionHistoryLimit: 3
  replicas: 1
  selector:
    matchLabels:
      app: example-application
  template:
    metadata:
      labels:
        app: example-application
    spec:
      serviceAccountName: configmap-reader # 1

You can ignore everything except of the (1) in the configmap. Here you define the configmap-reader from the role setup above as the serviceAccount for your application. The other values like for example the definition of the image etc. is up to you.

Now you should be able to build an image and deploy a container in your cluster of this configuration.

How it looks like

When you start your application locally, then everything should work and look as usually:

Terminal screenshot of the application running locally

When you start your application in your Kubernetes cluster, then you should see that the config map has been applied. You can see here, that the port is different than the one from the local execution, as it was changed in the configmap.

Terminal screenshot of the application running in Kubernetes with the configmap applied

Problems

Although Configmaps are a nice feature and working well, the integration with Spring Cloud led me sometimes to problems which were not so easy to understand.

  • Sometimes you need to match the version of the library spring-cloud-starter-kubernetes-client-config with your Spring Boot version. There is no list or documentation about it, but when I worked with Spring Boot 2.1.x versions I had the problem that the configmap didn’t load.
  • When no correct configuration is loaded at startup, you will maybe also need the @Primary annotation in your configuration beans. But this will also be logged accordingly.

I just can suggest to you to try your deployment on a local Kubernetes cluster (like minikube or kind).

Further reading