Table of Contents
Overview of Spring Cloud Config Server
Spring Cloud Config server is a REST application that is built on top of Spring Boot. The main purpose of a config server is to store the configurations for all services in an application (think microservices). Thus, each service doesn’t need to store its configurations. They just need to point to the config server to get their configurations.
Spring Cloud Config Server can work with various technologies to deliver the configurations in a secure way such as HashiCorp Vault, Git, or file system (less secure, not suitable for most production environments).
Let’s get started with an example.
Code repository
For the impatient, here is the code repository. Feel free to have a look if you want to get your hand dirty first: github repo
Spring Cloud Config Server Quick Start
Let’s say we are building a microservice application for a restaurant chain. One of the services is called cook service. All it does is report the number of cooks available in the whole chain. For simplicity’s sake, we are going to build a Spring Boot application with just one controller like this:
package com.datmt.springcloud.cookservice.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/") public class CookController { @Value("${cook.count}") int cookCount; @GetMapping public String cook() { return "Cook count: " + cookCount; } }
As you can see, this service gets the number of cooks in an application properties variable called cook.count
. Normally, we would set such properties in the service application.properties
file. However, today, we will get it from somewhere else: Spring Cloud config server!
There are many ways to define configurations for services in the config server (which we are going to discover later). In this section, we will read the configuration from the file system.
Read configurations from the file system
Let’s take a look at the configuration of our Spring cloud config server:
server.port=9988 spring.application.name=config-server spring.profiles.active=native spring.cloud.config.server.native.search-locations=classpath:/config
Let’s pay attention to the last 2 lines of this config file. In line 3, we specified the active profile for configuration file scan, which is native. That means the spring cloud configuration server will scan the file system. Line 4 specifies the exact location to search. The classpath
refers to resources
directory in our project:
In the cook-service*.properties
files, there are one setting that has different values for different environment:
In the config folder, there are three files for the cook service. Why are these files named like this? By convention, configurations files for services have this format: servicename-env.yaml
or servicename-env.properties
.
That means the service name here must match the name in the cook service application.properties
The configuration file of the cook service:
server.port=9977 spring.application.name=cook-service spring.profiles.active=dev spring.cloud.config.uri=http://localhost:9988 spring.config.import=optional:configserver:http://localhost:9988
As you can see, on line 2, we named the service as cook-service
. Line 3 specifies the active profile, which is dev. That means the configurations for the cook service will be read from the file config/cook-service-dev.properties
on the config server. In case that file is not available, the content of the file config/cook-service.properties
will be used.
Let’s start the config server and see what we have:
As you can see, the configuration URL for dev and prod profiles includes the default configurations. For a nonexistent profile, the default config will be served.
Now, let’s start the cook service to see if it can get the right configuration. Remember, the active profile is dev so we would expect to see the number 190:
If we change the active profile to prod
server.port=9977 spring.application.name=cook-service spring.profiles.active=prod
and reload the cook service, the output will change:
As you can see, we have successfully gotten the configuration for the cook service from the config server.
Refreshing configuration with Actuator
If there are changes in the config server, how can the cook service get the updated values without restarting?
Spring Actuator is the answer.
Let’s add Spring Actuator as a dependency:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Omit other dependencies --> </dependencies>
In the service application.properties
file, add the following line:
management.endpoints.web.exposure.include=refresh
In the controller, add the annotation @RefreshScrope
to the class:
package com.datmt.springcloud.cookservice.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/") @RefreshScope public class CookController { @Value("${cook.count}") int cookCount; @GetMapping public String cook() { return "Cook count: " + cookCount; } }
By adding these configurations, we can now call the cook service at /actuator/refresh
to request a refresh.
For example, for the current running environment (prod), we change the value 4000 to 5000 and then send the following request:
As you can see, the service responds back with the config that changed.
If we reload the cook service now, the value should be updated.
Configure Spring Cloud Config Server With Git
Let’s find out how you can use git to store your configurations and let the config server pull the data and serve it to other services.
Using Public Git
Let’s say we have a repository on GitHub storing the config files like this:
The content of those files is exactly the same as the ones we have on our local machine. Let’s configure the config server to use git.
The configuration is quite simple, you only need to make these changes to the application.properties
file in the config server:
server.port=9988 spring.application.name=config-server spring.profiles.active=native, git spring.cloud.config.server.native.search-locations=classpath:/config spring.cloud.config.server.git.uri=https://github.com/datmt/quick-samples.git spring.cloud.config.server.git.search-paths=config-server/config
Pay attention to lines number 3, 5, and 6. On line 3, we added git as an active profile without removing the native option.
You may wonder, how does the config server choose which profile to use? The answer is the last one on the list.
On line 5, we point to the Git repository.
On line 6, we tell the config server the path to the config files.
Let’s restart the config server and see if it can read the config:
As you can see, the config server was able to read the configurations from git without problems.
Let’s change a value on git and see if the output is updated. Let’s say we change the cook.count
value from 5000 to 3000:
Just by reloading the browser, we can see the configuration has changed!
Isn’t this awesome? Of course! But who would store sensitive data like configuration in a public git? Let’s add some authentication, shall we?
Adding Git authentication for security
Let’s switch the git repository to a private one. Now, the configuration looks like this:
server.port=9988 spring.application.name=config-server spring.profiles.active=native, git spring.cloud.config.server.native.search-locations=classpath:/config spring.cloud.config.server.git.uri=https://github.com/datmt/Quick-Sample-private.git spring.cloud.config.server.git.search-paths=config-server/config
If we launch the config service now and visit the configuration URL of the cook service, we would see an error page:
This is because the config server is unable to read the files from GitHub since the repo is private.
How can we configure it so the config server can read the data? We are going to use GitHub personal access token!
You can follow the guide here to create one: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token
After creating the token, we will put the details in the config server application.properties
server.port=9988 spring.application.name=config-server spring.profiles.active=native, git spring.cloud.config.server.native.search-locations=classpath:/config spring.cloud.config.server.git.uri=https://github.com/datmt/Quick-Sample-private.git spring.cloud.config.server.git.search-paths=config-server/config spring.cloud.config.server.git.username=my_temp_access_token spring.cloud.config.server.git.password=ghp_D6AlGb0f9CY......D5XG0UqzJB
As you can see on lines 7 and 8, both username and password are needed. The password is the access token, obviously. The username is the name of the token.
Now, if we reload the Spring cloud config server, we can see the settings again:
Enable Configuration Encryption for Security
Up until now, all the configurations are stored in plaintext. This is not the best practice.
Let’s add some security layer by enabling encryption.
Spring cloud config server supports both symmetric and asymmetric encryption. We are going to implement symmetric encryption in this post.
Let’s add encryption settings in the config server application.properties
file:
server.port=9988 spring.application.name=config-server spring.profiles.active=native, git spring.cloud.config.server.native.search-locations=classpath:/config spring.cloud.config.server.git.uri=https://github.com/datmt/quick-samples.git spring.cloud.config.server.git.search-paths=config-server/config encrypt.key=SUPER_SECRET_ENCRYPT_KEY
On line 7, we put the settings for encryption with the encryption key. In production settings, this is a bad practice. You should at least store the encryption key as an environment variable so that it is not exposed in the source code.
With these new settings, we can send a POST request to the config server /encrypt to encrypt data. For example, the following command encrypts the number 500:
curl -X POST -H "Content-Type: text/plain" http://localhost:9988/encrypt -d=5000
Here is the output:
Now let’s put that value to our git:
Conclusion
In this post, we have learned how to use the Spring cloud config server to centralize configurations for all other services. It is a versatile solution that supports multiple mechanisms to store and manage data such as file system, git, HashiCorp Vault, JDBC
I build softwares that solve problems. I also love writing/documenting things I learn/want to learn.