Spring Cloud Configuration - Externalize Application Configuration

author-imageBy Dhiraj, 25 February,2018   2K

This tutorial is about spring cloud config.Here, we will take a look into how we can manage to serve and store distributed external configurations properties using spring cloud config across different applications for different environments such as dev, local, prod etc.First we will develop a simple cloud application to externalize application configurations properties using cloud config and then extend the same application to use discovery server to register the application, updating the configuration at runtime and encrypting and decrypting sensitive properties. In a distributive cloud system, we have many smaller systems that combinly makes a larger system and hence we have multiple configurations files. Foe example, if we are using microservices, each microservices will have their own configuration files and managing that configuration within that application becomes cumbersome as there can be multiple instances running and these configuration management becomes deployment-oriented.Even, it becomes challenging not to miss any configuration changes for some of the instances.

For this, spring cloud team provides easy implementation as spring cloud config it provides server and client-side support for externalized configuration in a distributed system. With the Config Server you have a central place to manage external properties for applications across all environments

Spring cloud config is a web application that exposes REST endpoints to access the configuration properties.It supports multiple output formats such as JSON, properties and yaml.The different backened stores it supports are git(default), SVN, filesystem. In our example, we will be using git as a backened store for our configuration properties.

Setting up Git Backened Store

First of all, let us set up our backened store. We will be using github to store our properties and for this purpose I have created a simple github project here to store the configurations.It has basically 3 .properties file. application.properties for storing global properties, spring-cloud-config-client.properties for storing global properties for the application spring-cloud-config-client and similarly we have spring-cloud-config-client-local.properties to store local properties for the application spring-cloud-config-client

spring-cloud-config-client.properties
server.contextPath=spring-cloud-config-client
test.property=property from cloud config
spring-cloud-config-client-local.properties
test.local.property=test local property

The local properties file will have configurations properties to run the spring boot application with local profile and also you can define existing properties of global configurations file if you want to ovrride it in local environment such as DB properties.

Spring Cloud Config Server Implementation

This will be a simple spring boot app. For this implementation, first download a demo spring boot app from start.spring.io with below configs.We will be using the discovery server configuration later in this tutorial.

spring-cloud-config-server

Now import it into the IDE and you can find following maven configurations.

pom.xml
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-config-server</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-eureka-server</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

Let us define our application configurations for this app. To make our example simple,we won't have discovery server related configuration now.Following is the git URL that we discussed in the above section.

application.properties
server.port=8888
spring.cloud.config.server.git.uri=https://github.com/only2dhir/config-repo.git

Following is the implementation of our main spring boot application. On simple annotation - @EnableConfigServer will enable the required configuration for spring cloud config.Note: - Before running this class, you can comment eureka dependency in pom.xml to avoid unnecessary error logs as we have not made any discovery server related configurations now.

package com.devglan.springcloudconfigexample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class SpringCloudConfigExampleApplication {

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

Running above class as a java application will expose following REST endpoints.

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

Here, application is the name of the application. For example if we have our client application name as spring-cloud-config-client then the endpoints URL becomes spring-cloud-config-client-dev.properties where dev is the spring boot active profile.The label here is the git brnach which is am optional parameter.

spring-cloud-config-client

Spring Cloud Config Client Implementation

For the cloud config client we have following dependencies required.we require doscovery client later for service discovery. For now spring-cloud-starter-config is sufficient.

pom.xml
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-config</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-eureka</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

To bootstrap our spring cloud config configuration with the client app, we require following entries in bootstrap.yml. Following configuration will invoke the properties configuration file for app name spring-cloud-config-client and for ctive profile local and our cloud config server is running on http://localhost:8888

spring.application.name=spring-cloud-config-client
spring.profiles.active=local
spring.cloud.config.uri=http://localhost:8888

Now let us define our spring boot application class.

SpringCloudConfigClientApplication.java
package com.devglan.springcloudconfigclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringCloudConfigClientApplication {

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

Now let us define our controller class and use @Value annotation to use external properties using spring cloud config.

DemoController.java
package com.devglan.springcloudconfigclient.controller;

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

@RestController
public class DemoController {

    @Value("${test.property}")
    private String testProperty;

    @Value("${test.local.property}")
    private String localTestProperty;

    @RequestMapping("/")
    public String test() {
        StringBuilder builder = new StringBuilder();
        builder.append("test property - ").append(testProperty).append(" ")
                .append("local property - ").append(localTestProperty);
        return builder.toString();
    }
}

In the properties file we have defined test.property and test.local.property properties which is injected here in the controller.In the properties file we have defined server.contextPath as spring-cloud-config-client and hence our client application will be accessible at http://localhost:8080/spring-cloud-config-client/

spring-cloud-config-output

Integrating Service Discovery with Spring Cloud Config

In my previous article, we create a service discovery app using spring-cloud-netflix-eureka. We will be using the same discovery server which is running on the default port 8761.To integrate with the discovery server, let us first edit our application.properties file of service application to register itself as a service with the discvery server.Following properties will register this application with discovery server as an application name - spring-cloud-config-example

application.properties
spring.application.name=spring-cloud-config-example
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

Annotate the SpringCloudConfigExampleApplication.java with @EnableDiscoveryClient so that this application registers itself with the discovery client.

Also, we need to configure the same in our client application to discover the config server using discovery server.For this annotate the SpringCloudConfigClientApplication.java with @EnableDiscoveryClient and in bootstrap.properties file make below entries for auto discovery of cloud config service.By default, cloud config client looks for application with name configserver with the discovery server for any cloud config server but in our case the applicaton name of cloud config server is spring-cloud-config-example and hence to override it in the client we have used the properties spring.cloud.config.discovery.serviceId

bootstrap.properties
spring.application.name=spring-cloud-config-client
spring.profiles.active=local
#spring.cloud.config.uri=http://localhost:8888
spring.cloud.config.discovery.enabled=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
spring.cloud.config.discovery.serviceId=spring-cloud-config-example

Now start the discovery server, then cloud config server and then the client application and hit http://localhost:8080/spring-cloud-config-client/ and you can expect the same result as above.

So, in brief, first discovery server is started and this exposes an endpoint as http://localhost:8761/eureka to register the service. Now when the cloud config server is started, it registers itself with service id spring-cloud-config-example and exposes endpoints as http://192.168.1.6:8888/. Now when the client is started, first it tries to resolve the config properties. For this it uses the discovery server to discover the config server with service id - spring-cloud-config-example. After this the base url is resolved and then it appends /{application}-{profile}.properties i.e. to this url and fetch the config properties. The final url becomes - http://localhost:8888/spring-cloud-config-client-local.properties

Updating Cloud Configuration at Runtime

This is one of the cool feature of spring cloud config to update the configuration properties at runtime without restarting the application. For example you can change the log levels.To update cloud configuration at runtime, you can change the configuration properties in the git project and push to repository. Then we can either use spring boot actuator /refresh endpoint or /bus/refresh with spring cloud bus or with VCS + /monitor with spring-cloud-config-monitor and spring-cloud-bus. But doing so will not refresh the properties annotated with @Value or @Bean because these properties are initilaized during application start-up. To refresh these properties, spring provides @RefreshScope annotation.We will be implementing this with an example in our next article - Spring cloud config refresh property at runtime

Encrypting and Decrypting Sensitive Configuration

This is yet again another useful feature provided by spring cloud config.The configuration such as database password, username are sensitive configuration and for this encryption and decryption spring provides many features such as encrypted configuration at REST or in-flight.It has also feature for encryption and decryption using symmetric and asymmetric keys. We will be creating a sample application with examples on this topic in our next tutorial.Following is a sample application.properties that has encrypted configurations.Here is the complete configuration for Encrypting and Decrypting Sensitive Configuration

application.properties
spring.datasource.username=root
spring.datasource.password={cipher}ABCFGVH75858GFHDRT

Conclusion

In this tutorial, we learned about the spring cloud config. We created our cloud config server, client and a discovery server to register the service.The source can be downloaded from hereIf you have anything that you want to add or share then please share it below in the comment section

Further Reading on Spring Cloud

1. Spring Netflix Zuul

2. Refresh Property Config Runtime

3. Spring Cloud Netflix Eureka

4. Introduction To Microservices

5. Encrypt Decrypt Cloud Config Properties

If You Appreciate What We Do Here On Devglan, You Should Consider: