Spring Cloud Config Server

Spring Cloud Config Server | Config Server:- Externalize all common properties(key = value) from every microservice.

  • In our application, there can be multiple Microservices. Example: Search, Payment, Cart, Feedback, etc.
  • In every Microservice we provide key = value using properties.
  • In those key = values few are common. Examples: database connection, email, eureka location, logging, pool, etc.
  • Those common key = values we should not repeat in every Microservices, instead provide them one time using Config Server so that we can manage them easily.

This Config Server are two types:-

  1. External Config server (read from Git Account) (default; mostly used)
  2. Native Config server (read from Local Drive System example: D:/drive) (It is mainly used for testing purposes).

For this:-

  1. We should create one Mediator Server project Config Server (spring-cloud-config-server).
  2. The recommended Port number for the Config Server is 8888. We can even change it.
  3. Config Server also contains one properties file that holds the location of the External/Native Config file.
spring.cloud.config.server.git.uri=https://gitlab.com/knowprogram/my-config-test.git
# If it is private account
spring.cloud.config.server.git.username=knowprogram
spring.cloud.config.server.git.password=enter_password
  1. At every microservice add one dependency ‘Config Client‘. [no coding is required, only dependency]
  2. When we start our microservice, the first component executed is ‘Config Client’. That gets common key=value from Config Server.

When we start our microservice:-

  1. Call Config Client First
  2. It goes to the Config Server
  3. Read Location from ConfigServer#properties
  4. Go to External/Native Location
  5. Read all common keys into ConfigServer
  6. Provide it to Config Client
  7. Merge with microservice properties
  8. Execute other lines of Code/Services/ etc.

Q) How many config servers can be created for one Application?
One Config Server.

Q) Which dependency should we add at microservice to link with the Config server?
Config Client.

Q) How many types of Config Servers exist? 2 types:-
a. External Config (git) [Default]
b. Native Config (local)

Q) What types of keys are placed in Config Server?

  • Database connection
  • Email Keys
  • Eureka Keys
  • Actuators
  • Security Roles
  • Token userId
  • topicNames for MQs
  • Connection Pool Details
  • Admin server keys
  • etc

Spring Cloud Config Server Example

Let us develop these applications:-

  1. Eureka Server
  2. Config Server
  3. Microservice

1. Eureka Server

  1. Create a new spring starter project SpringCloudEurekaServer with dependencies:- Eureka Server (spring-cloud-starter-netflix-eureka-server).
  2. In the starter class add the annotation:- @EnableEurekaServer
  3. In the application.properties file:-
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

2. Config server setup with Gitlab

  1. Go to Gitlab and Click on the New Project Button
  2. Choose Blank Project
  3. Enter a project name: my-config-test
  4. Use Private (if you don’t want to share it with others) else use public.
  5. Click on the Create button
  6. Click on Add File
  7. Enter file name: application.properties. We can also use the YML file.
  8. Enter key=value
my.app.title=knowprogram
  1. Commit Button > Commit Message > Click on Commit
  2. Project > Your Projects
  3. Click on your Project name
  4. Click on clone.
  5. Copy the HTTPS option. Example:- https://gitlab.com/knowprogram/my-config-test.git

3. Config Server

  1. Create a new spring starter project SpringCloudCSConfigServer with dependencies:- Config Server (spring-cloud-config-server).
  2. In the starter class add the annotation:- @EnableConfigServer
  3. In the application.properties file:-
server.port=8888
spring.cloud.config.server.git.uri=https://gitlab.com/knowprogram/my-config-test.git

# If it is private account
spring.cloud.config.server.git.username=username
spring.cloud.config.server.git.password=enter_password
  1. Run the application and Verify whether it can read the properties file or not:- http://localhost:8888/SpringCloudCSConfigServer/default [GET]. It will give the following output:-
{
  "name": "SpringCloudCSConfigServer",
  "profiles": [
    "default"
  ],
  "label": null,
  "version": "99ff711492a299e866156e5607de58407f4c5306",
  "state": null,
  "propertySources": [
    {
      "name": "https://gitlab.com/knowprogram/my-config-test.git/application.properties",
      "source": {
        "my.app.title": "xyz"
      }
    }
  ]
}

4. Microservice

  1. Create a new spring starter project SpringCloudCSEmployeeService with dependencies:- Spring Web, Eureka Discovery Client, Config Client (spring-cloud-starter-config).
  2. In the starter class add the annotation:- @EnableDiscoveryClient
  3. In application.properties:-
server.port=8686
spring.application.name=EMPLOYEE-SERVICE
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
spring.config.import=optional:configserver:http://localhost:8888

Don’t forget to add a spring.config.import=configserver: property to your configuration. If configuration is not required add spring.config.import=optional:configserver: instead. To disable this check, set spring.cloud.config.enabled=false or spring.cloud.config.import-check.enabled=false.

  1. In RestController:-
package com.knowprogram.demo.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/emp")
public class EmployeeRestController {

    @Value("${my.app.title}")
    private String title;

    @GetMapping("/data")
    public String showData() {
        return "Hello FROM Microservice: " + title;
    }

}

Execution Order:-

  1. Run Eureka Server
  2. Run Config Server
  3. Run the Microservice app. Open http://192.168.31.151:8686/emp/data

Let us see:-

  1. Config Client workflow + different location
  2. Refresh Scope
  3. Native Config

Config Client Workflow with Different Locations

  • When we create any microservice we need to add Config Client dependency: spring-cloud-starter-config.
  • We don’t write any code for Config Client in our microservice, because Config Client code already exists in the parent project.
  • Config Server URL exists in Config Client Code in the parent project as url = http://localhost:8888 (it is not in our project).

Q) Where is the code for Config Client?
It is in the parent project (Spring Cloud) that we just add dependency/jar.

Q) Where is the location (URL) of the Config Server (not Git)?
At the parent (Spring Cloud) properties file it has the location of Config Server Application as:-
spring.cloud.config.uri = http://localhost:8888. It means the default IP of ConfigServer is localhost and the default port recommended is 8888.

Q) Is our application.properties loaded first or Parent Project Config Client code is executed first?
First Parent project config client code is executed. At last, our application.properties is loaded.

Q) How can we override the above Config Server default URL?
We can override the above config server default value using spring.config.import property. Let us demonstrate it through an example. Instead of using the default port 8888, let us use 8008.

In Config Server Project (SpringCloudCSConfigServer) change port number:-

server.port=8008

In Microservice (SpringCloudCSEmployeeService) modify spring.config.import value:-

spring.config.import=optional:configserver:http://localhost:8008

Refresh Scope

Problem statement:- Microservices with Eureka and Config Server are started. Even Microservices can read/print keys from the Config server, if we modify key=value in the External Config Server(in git) value is not reflected in our microservices.

Solutions:-

  1. Restart all Microservices and Config Server apps.
  2. Use @RefreshScope to avoid the re-start concept.

We can either restart all the microservices and config server applications or we can use @RefreshScope. Let us see @RefreshScope in detail.

  1. In microservices App (SpringCloudCSEmployeeService) pom.xml, add dependency Spring Boot Actuator (spring-boot-starter-actuator).
  2. Add the below property to the application.properties:-
management.endpoints.web.exposure.include=*
  1. At RestController add: @RefreshScope
  2. Start All Apps
  3. Update config in Gitlab, verify in server config:- http://localhost:8008/SpringCloudCSConfigServer/default

The changes won’t be reflected in the microservices itself. This is where the actuator comes to help.

  1. Make a POST request using microservices IP/PORT as http://MS_IP:PORT/actuator/refresh. It does not have anybody.
curl -X POST http://localhost:8686/actuator/refresh

Now the updated config will be available to the microservices.

Q) What @RefreshScope is doing here?
It calls Config Client code again which gets the latest keys from Config Server and merges back to our application that affects our microservices.

Native Config Server

  • Here, Native indicates the local machine.
  • In the case of Dev Environment, it is recommended to use the native config server.
  • Until the Application goes to Production/UAT, we may not get the actual GIT link.
  • In that case, we can use the Native Config Server.
  • It is never used in Production/UAT Environments.
  1. In SpringCloudCSConfigServer => application.properties file:-
server.port=8888
# This is for external config server
#spring.cloud.config.server.git.uri=

# This is for native config server
# spring.cloud.config.server.native.search-locations=file:D:/abcd
spring.cloud.config.server.native.search-locations=classpath:mydata
spring.profiles.active=native
  1. Create one folder “mydata” under the src/main/resources folder.
  2. Create an application.properties file under the mydata folder.
[project-name]
    |--src/main/resources
        |- mydata
            |- application.propreties [native config file]
  1. In this application.properties, add your all microservice’s common keys.
my.app.title=FROM-LOCAL-CONFIG

Restart the config server (SpringCloudCSConfigServer) and Employee microservices (SpringCloudCSEmployeeService).

How @RefreshScope works?

@RefreshScope:- This process is used to avoid restarting of microservice and Config Server. Every key in Spring Application (Even Spring Boot and microservice) is stored inside Environment(I)[org.springframework.core.env]. The implementation class is ApplicationEnvironment(C) which stores data in key=value of .properties, .yml, OS properties, JVM Properties, etc by using the method getProperty(key): value.

To test this create a new Spring starter project.

package com.knowprogram.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class TestData implements CommandLineRunner {

    @Autowired
    private Environment environment;

    @Override
    public void run(String... args) throws Exception {
        System.out.println(environment.getClass());
        System.out.println(environment.getProperty("my.app.title"));
    }
}

In application.properties file:-

my.app.title=SAMPLE

Automating Refresh

Work Flow:- If we make an HTTP call ‘POST’ with URL /actuator/refresh, then RefreshScope(C) is triggered for the ApplicationContext refresh event ‘ContextRefreshedEvent’ there it gets the latest Environment from Parent call and merged with our Environment memory.

@RefreshScope: It gets modified keys from the config server to the microservice only if an HTTP Post Request is made from the microservice.

We had used the POSTMAN Tool initially to call the API and get the modified keys. Now let us see how to automate HTTP calls by writing code using ‘RestTemplate# post’ to make calls and scheduling.

Scheduling:- Execute request call POST call in a loop like getting the latest data every night at midnight: cron = 0 0 0 * * *

This scheduling can be done either in a microservice or in a different project.

At starter class: @EnableScheduling

package com.knowprogram.demo;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class RefreshMemoryProps {

    @Scheduled(cron = "0 24 10 * * *")
    public void run() throws Exception {
        RestTemplate rt = new RestTemplate();
        HttpHeaders header = new HttpHeaders();
        header.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<String>("{}", header);
        rt.exchange("http://192.168.0.7:8686/actuator/refresh", 
                             HttpMethod.POST, entity, String.class);
        System.out.println("DONE");
    }
}

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published. Required fields are marked *