DEV Community

Mạnh Đạt
Mạnh Đạt

Posted on • Originally published at datmt.com

Spring Cloud Config Server Complete Guide

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 overview
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.

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; 

    }

}
Enter fullscreen mode Exit fullscreen mode

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.
Let's take a look at configuration of our Spring cloud config server:
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 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:
Spring cloud configuration server native file system
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
Enter fullscreen mode Exit fullscreen mode

As you can see, on line 2, we named the service as cook-service. Line 3 specify the active profile, which is dev. That means the configurations for 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:
Spring cloud config server default profile
Spring cloud config server dev profile
Spring cloud config server prod profile
Spring cloud config server non-existent profile
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:
Get configuration from config server in other services
If we change the active profile to prod

server.port=9977
spring.application.name=cook-service
spring.profiles.active=prod
Enter fullscreen mode Exit fullscreen mode

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.
In the next section, we will try to reload the configuration when there are updates on the config server without reloading the cook service.

Refreshing configuration with Actuator

If there are changes in the config server, how can you update the cook service so it will get the correct values without restarting?
Spring Actuator is the answer.
In the pom.xml file, add Spring Actuator as a dependency:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

      <!-- Omit other dependencies -->
    </dependencies>
Enter fullscreen mode Exit fullscreen mode

In the service application.properties file, add the following line:

management.endpoints.web.exposure.include=refresh
Enter fullscreen mode Exit fullscreen mode

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;  

    }

}
Enter fullscreen mode Exit fullscreen mode

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:
Sending request to request configuration from config server
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.
Using actuator to refresh config at run time

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
Enter fullscreen mode Exit fullscreen mode

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:
Config server reads configuration from git

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:
Changing configuration values on git
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:
Configure private git access for spring cloud config server

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
Enter fullscreen mode Exit fullscreen mode

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 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, if we want to encrypt the number 5000, the request should be like this:

curl -X POST -H "Content-Type: text/plain" http://localhost:9988/encrypt -d=5000
Enter fullscreen mode Exit fullscreen mode

Here is the output:

Now let's put that value to our git:

Enable encryption on spring cloud configuration server

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

Top comments (0)