In this new entry of Refactorizando, we will see how to externalize Spring Boot config to configmap with Kubernetes. Similar to Spring Cloud, we can externalize our configuration in a Config Server with different profiles, for example, for different environments. By using Kubernetes ConfigMap, we can achieve the same result.
In this entry, we will explain step by step, using an example, how to connect our Spring Boot application with a ConfigMap in Kubernetes, allowing us to externalize our configuration and change it directly in Kubernetes. Additionally, if we add a Bus to make our configuration changes instantly effective, it would be perfect.
If you want to see the complete example, you can find it on our GitHub.
What is a Kubernetes ConfigMap?
A Kubernetes ConfigMap can be defined as a key-value dictionary used to store non-critical values in terms of security. Usually, it is used to store information related to pod configuration or values that you do not want to have in your application, so that this information is not confidential.
Configuring ConfigMap with Spring Boot
The first thing we are going to do is to add the necessary Maven dependencies:
Maven dependencies with Spring Boot and ConfigMap
The following dependency will add the necessary dependencies to connect our application.
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-kubernetes-config</artifactId> </dependency>
Creating a ConfigMap to use it with Spring Boot
To include an application.yml in a ConfigMap, you must create the ConfigMap using the Kubernetes syntax (Kind: ConfigMap) and include the application.yml or application.properties inside the data section. The following shows how to do it:
kind: ConfigMap apiVersion: v1 metadata: name: order-service data: application.yml: |- server: port: 8080 spring: application: name: order-service cloud: kubernetes: reload: enabled: true secrets: name: db-secret data: mongodb: host: mongodb-service port: 27017 database: admin username: ${MONGO_USERNAME} password: ${MONGO_PASSWORD} management: endpoint: health: enabled: true info: enabled: true restart: enabled: true
In case you want different profiles, profiles will be established in the default way of Spring in the files, for example:
kind: ConfigMap apiVersion: v1 metadata: name: order-service data: application.yml: |- greeting: message: Say something farewell: message: I Say Goodbye --- spring: profiles: development greeting: message: Say something farewell: message: I say hi
One alternative is to use different applications for each profile, such as application-dev.yml or application-pro.yml.
Configuración de los ficheros de Configuración de Spring
In order to make use of externalized configuration using a ConfigMap, we must configure it through spring.cloud.kubernetes.config.sources. This configuration should be included in our bootstrap.properties or bootstrap.yml file. In this file, we must provide the name of the ConfigMap and the namespace.
If the ConfigMap name and namespace are not provided, the application will take the default namespace in which it is deployed, and the ConfigMap will be named after the application.
Other configuration properties in our bootstrap.yml file will include:
spring.cloud.kubernetes.config.enabled | Boolean | Activates or deactivates the configuration with ConfigMap, by default it’s TRUE. |
spring.cloud.kubernetes.config.name | String | The name of the ConfigMap, by default, is Spring.application.name. |
spring.cloud.kubernetes.config.namespace | String | Sets the namespace name, by default where it is deployed. |
Next, we are going to write our bootstrap.yml:
spring: application: name: order-service cloud: kubernetes: config: enabled: true
In our previous file, we activated the ConfigMap configuration, but it is not necessary as it is activated by default when the Maven dependency is added. We did not specify the name of the ConfigMap or the namespace, so it will take the name of “order-service” and the namespace will be the default where it is deployed.
Using ConfigMap properties in Spring Boot
After adding the necessary configuration, the ConfigMap will work like an application.properties or application.yml in our configuration. To see this, we simply add a @Value in our controller:
private final OrderRepository orderRepository; @Value("${spring.data.mongodb.username}") private String username; @Value("${spring.data.mongodb.password}") private String password; @GetMapping("/orders") public ResponseEntity<List<Order>> getOrders() { return new ResponseEntity<>(orderRepository.findAll(), HttpStatus.OK); } @PostMapping("/orders") public ResponseEntity<Order> saveOrder(@RequestBody Order order) { return new ResponseEntity<>(orderRepository.save(order), HttpStatus.CREATED); } @GetMapping("/") public ResponseEntity<String> info() throws UnknownHostException { String serviceInfo = "Host: " + InetAddress.getLocalHost().getHostName() + "<br/>" + "IP: " + InetAddress.getLocalHost().getHostAddress() + "<br/>" + "Type: " + "Order Service" + "<br/>" + "Connecting to mongodb with : username: " + username + " password: " + password; return new ResponseEntity<>(serviceInfo, HttpStatus.OK); } }
The @Value we have indicated in the controller will work identically to when the application.yml is in the project.
Reloading properties with Spring Cloud in Kubernetes
One of the functionalities provided by Spring Cloud is the ability to change the properties in our configuration files (ConfigMap or secrets) and apply them to our application instantly. To do this, we will add the following configuration to our Bootstrap.yml:
spring: application: name: order-service cloud: kubernetes: config: enabled: true reload: enabled: true monitoring-config-maps: true strategy: refresh mode: event
Los campos que se van encargar de dotar a nuestra aplicación estos cambios en caliente son:
spring.cloud.kubernetes.reload.enabled | true/false | You can enable or disable property reload. |
spring.cloud.kubernetes.reload.monitoring-config-maps | true/false | it is activated if changes to the configmap are monitored |
spring.cloud.kubernetes.reload.strategy | refresh/restart_context | Refresh strategy only affects the bean annotated with @RefreshScope. Restart_context reloads the application. |
spring.cloud.kubernetes.reload.mode | event/poll | In Event, a modification event is generated with each change. With Poll, it checks for changes at certain intervals. |
It is important to note that if we activate the refresh strategy, we must add the @RefreshScope annotation, for example:
@Configuration(proxyBeanMethods = false) @ConfigurationProperties(prefix = "messages") @RefreshScope @Getter @Setter public class MeesageProperties { private String message; }
In the above class, it is indicated that every time the configmap has a change in “message”, that value will be automatically captured by the application without any downtime or the need for Kubernetes to perform a rollout of our service.
Example of externalizing Spring Boot configuration in Kubernetes with ConfigMap
Next, we will develop an example in which we will deploy a MongoDB database whose keys will be in a Kubernetes secret. Our Spring Boot application will connect to this MongoDB using a ConfigMap that we will deploy in our Kubernetes Cluster. You can follow the example on our GitHub.
To begin, we will deploy our database and secrets:
apiVersion: v1 kind: Service metadata: labels: app: mongo name: mongodb-service spec: type: NodePort ports: - name: "http" port: 27017 protocol: TCP targetPort: 27017 selector: service: mongo --- apiVersion: apps/v1 kind: Deployment metadata: name: mongo spec: replicas: 1 selector: matchLabels: service: mongo template: metadata: labels: service: mongo name: mongodb-service spec: containers: - args: - mongod image: mongo:latest name: mongo env: - name: MONGO_INITDB_ROOT_USERNAME valueFrom: secretKeyRef: name: db-secret key: username - name: MONGO_INITDB_ROOT_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password
apiVersion: v1 kind: Secret metadata: name: db-secret data: username: dXNlcg== password: cDQ1NXcwcmQ=
To deploy both files in Kubernetes, we will do:
Kubectl apply -f mongo-deployment.yaml kubectl apply -f secrets.yaml
Next, we will deploy our ConfigMap.yaml file, as we discussed and explained earlier, this file will have the configuration of our application.yml:
kind: ConfigMap apiVersion: v1 metadata: name: order-service data: application.yml: |- server: port: 8080 spring: application: name: order-service cloud: kubernetes: reload: enabled: true secrets: name: db-secret data: mongodb: host: mongodb-service port: 27017 database: admin username: dXNlcg== password: cDQ1NXcwcmQ= management: endpoint: health: enabled: true info: enabled: true restart: enabled: true
As before, we will do:
Kubectl apply -f configmap.yaml
Finally, we will configure our bootstrap.yml. Activating Kubernetes is not necessary since having the Maven dependency adds the ConfigMap configuration by default.
spring: application: name: order-service cloud: kubernetes: config: enabled: true
We cannot forget to add our service account for communication between the service and our database.
And we add our service account for communication with the database.
kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: default name: namespace-reader rules: - apiGroups: ["", "extensions", "apps"] resources: ["configmaps", "pods", "services", "endpoints", "secrets"] verbs: ["get", "list", "watch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: namespace-reader-binding namespace: default subjects: - kind: ServiceAccount name: default apiGroup: "" roleRef: kind: Role name: namespace-reader apiGroup: ""
The next step would only be to create the Spring Boot application normally. And deploy it on Kubernetes. To do this, let’s first create our Docker image:
Create Docker image: docker build -t order-service .
Deploy the image on Kubernetes, for which we will create a deployment and a service:
apiVersion: v1 kind: Service metadata: name: order-service spec: selector: app: order-service ports: - protocol: TCP port: 8080 nodePort: 30081 type: NodePort externalIPs: - 1.2.4.120 --- apiVersion: apps/v1 kind: Deployment metadata: name: order-service spec: selector: matchLabels: app: order-service replicas: 2 template: metadata: labels: app: order-service spec: containers: - name: order-service image: order-service:latest imagePullPolicy: Never ports: - containerPort: 8080 env: - name: MONGO_USERNAME valueFrom: secretKeyRef: name: db-secret key: username - name: MONGO_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password
And in the same way as before with kubectl apply -f deployment.yml, we would have our service up and running.
Remember to execute all Kubernetes files with the command kubectl apply -f <filename>
To test it, you can run the following command:
minikube service order-service
Conclusión
Spring Cloud offers us the ability to work with externalized configuration. In this post, we have seen how to externalize Spring Boot config to configmap with Kubernetes.
If you liked it, don’t hesitate to leave a comment, and if you have any questions, feel free to ask.
If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!