Spring Cloud Config with Spring Boot Microservices

6 minute read

Objective

In this article we will see how to configure a secure Spring Cloud Config Server, backed with configuration files from a private GitHub repo. This server will be serving configuration to microservices built with Spring boot 2.x. This system will be extended further in next article where we will also include a spring cloud bus system to inform all the microservices about any config change in runtime and to update their state without downtime.

Setting up a git config repository

First, we will create a new repository in our GitHub account and checkout the branch in local system. Note that we are creating a private repository as shown below.

creating github private repo

Generally, in any production ready project we have different configurations for different environments. Spring boot provides a wonderful way to manage this via “profiles” which we will be making use of in our microservices later. For this article, we will use a microservice which works on three profiles, dev, qa, prod.

After checkout we will create three ‘properties’ files, each representing separate environment mydbapplication-dev.properties, mydbapplication-qa.properties and mydbapplication-prod.properties. When using spring config along with ‘profiles’, you can setup your configuration file path and names in one of the patterns mentioned below. If you have common properties, read the notes section at the bottom of this article.

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

{application} is your client microservice name generally denoted by spring.config.name. In our case it is 'mydbapplication'
{label} is an optional git branch (default master) 
{profile} is the profile name on which our client microservice is running, for which the microservice is going to query the config server.

Each file may contain same property keys but you may set different values as shown below. After creating the files, commit and push it to remote branch.

mydbapplication-dev.properties

myapplication.database.ip = 1.1.1.1
myapplication.database.port = 8080
myapplication.database.user = dev-user
myapplication.database.pass = dev-password

mydbapplication-qa.properties

myapplication.database.ip = 2.2.2.2
myapplication.database.port = 8080
myapplication.database.user = qa-user
myapplication.database.pass = qa-password

mydbapplication-prod.properties

myapplication.database.ip = 3.3.3.3
myapplication.database.port = 8080
myapplication.database.user = prod-user
myapplication.database.pass = prod-password

Now, we will create a ssh key for our github account using which our spring configuration server will connect to this repo to fetch configuration. Instead of using a SSH key, we can also use our password directly but that will mean that we have to save our plain text password somewhere which is not a good practice.To generate a ssh private and public key open terminal and paste the text below, substituting in your GitHub email address.

$ ssh-keygen -t rsa -b 4096 -C "xxxx@xxx.com"
$ ssh-keygen -m PEM -t rsa -b 4096 -C "xxxx@xxx.com” ---MAC VERSION

This creates a new ssh key, using the provided email as a label. When prompted to “Enter a file in which to save the key,” enter any filename,say, github. The file will be generated at the current folder of terminal. At the prompt, type a secure passphrase. Finally, you will have two files a private key (github) and a public key (github.pub). Now we will add the public key to our GitHub Account. Go to your account’s key page, press “New SSH Key” button and copy paste the content of your public key (github.pub) onto the form. Add any title you want, and press “Add SSH Key”.

Creating a Spring config server and connecting it with the git config repository

Fire up your IDE and let’s create a spring config server with maven. Out config server needs to be secure, so we will add a spring security component too. Add the following entries to your pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId><!--Cloud config server dependency-->
    <version>2.1.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId><!--We are creating a secure config server, hence this dependency-->
    <version>2.1.5.RELEASE</version>
</dependency>

Your application should look like this

@EnableConfigServer
@SpringBootApplication
public class ConfigMgtApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigMgtApplication.class, args);
    }
}

Your application.yaml of config server should have the following entries. I preferred a .yaml format for this application over .properties because it’s easier to maintain private key here

server:
  port: 8000
spring:
  application:
    name: config-mgt
  cloud:
    config:
      server:
        git:
          clone-on-start: true
          uri: git@github.com:sumeetmondal/iamsumeet-config-repo
          ignore-local-ssh-settings: true
          #search-paths: 'config-repo/{application} we will look into this later
          private-key: |
                      -----BEGIN RSA PRIVATE KEY-----
                      #Enter the contents of your private key. File name GitHub which we generated previously
                      -----END RSA PRIVATE KEY-----
          #default-label: develop change branch name if required
  security:
    user:
      name: root
      password: s3cr3t

The property clone-on-start=true says that your config server will clone the git repo in local file system before starting the application, we will look into search-paths later. spring.security.user.xx properties are your config server security details, you will need to share this same details with all microservices connecting to this server.

There it is! your config server is ready. Start the application and try to access http://localhost:8000/mydbapplication/dev which should show you your applications dev profile configuration.

Creating microservices which connect to Spring config server

This one is pretty simple, first you will need to add the following dependencies in your pom.xml and configure our properties to use the config server

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId> <!--Required because we are creating a restful microservice-->
    <version>2.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId> <!--Cloud config client dependency-->
    <version>2.1.3.RELEASE</version>
</dependency>
server.port=8001
#We are configuring the application name below, it’s the same one which we used in our property file name
spring.application.name=mydbapplication
spring.cloud.config.uri=http://localhost:8000
#Same credentials which we configured in our config server sec properties
spring.cloud.config.password=s3cr3t
spring.cloud.config.username=root
spring.profiles.active=dev

That’s it, now we can access the properties in your application using @Value. To test it out, let us create a rest controller to see the values and access it from http://localhost:8001/getDBValues

@RestController
public class ApplicationController {
    @GetMapping("getDBValues")
    public Map<String,String> getDBValues(@Value("${myapplication.database.ip}") String ip, 
                                          @Value("${myapplication.database.port}") String port,
                                          @Value("${myapplication.database.user}") String user, 
                                          @Value("${myapplication.database.pass}") String pass){
        return Map.of("databaseIp",ip, "databasePort",port, "databaseUser",user, "databasePass",pass); //Map.of is JDK 9 feature
    }
}

In the next part of this article, we will also include a spring cloud bus system to inform all the microservices about any config change in runtime and to update their state without shutdown.

Notes:

  • If you have common properties across environment, you can use a file named {application}.properties. The property defined in this file will be overridden by any profile specific property file. An environment-specific properties will overriding the non-specific ones (like {application}.properties file).
  • Another important thing to note is, if client’s active profile is not set (spring.profiles.active) the system will work with a profile named ‘default’ and will look for {application}-default.properties, so you can also set all your default or common properties in this file too. To summarize, any property in {application}.properties will be overridden by {application}-default.properties (if this file exist). If {application}-default.properties does not exist client will use values from {application}.properties. If spring.profiles.active is set, the profile specific file overrides any property values set by {application}.properties and {application}-default.properties but inherits non common properties.
  • In our cloud config server, we looked at a property ‘spring.cloud.config.server.git.search-paths’. This is useful in cases where your properties files are not at the root of your repo OR you want the server to navigate at some folder to find the property files, you can also use placeholders like {application} or {profile} or some folder name like “AllApplicationProperties”. In fact, this property is an array, so you can add more folder/paths separated by a comma, you can even use regex syntax.
  • In case you want your client applications to fail startup if they are unable to connect to the config server, set spring.cloud.config.fail-fast=true
  • Look into spring.cloud.config.retry if you want your clients to retry connection in case of failure

Git repo: