Spring Cloud Config
Introduction
When working with microservices, managing configuration across multiple services and environments becomes a challenging task. Rather than building configuration management into each service, Spring Cloud Config provides a centralized way to manage external properties for applications across all environments.
Spring Cloud Config offers a 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. The concepts are similar to the Spring Environment
and PropertySource
abstractions, but with special handling for version-controlled configuration and service applications that need to start up with defaults when not connected to the Config Server.
Why Use Spring Cloud Config?
Before diving into the implementation, let's understand why Spring Cloud Config is valuable:
- Centralized management: Store all configurations in one place
- Dynamic updates: Change configurations without restarting applications
- Environment segregation: Maintain separate configurations for development, testing, and production
- Version control: Track configuration changes with Git
- Security: Secure sensitive configuration data
Getting Started with Spring Cloud Config
Let's break down how to implement Spring Cloud Config in two main components:
- Config Server - stores and serves configurations
- Config Client - connects to the server to retrieve configurations
Setting Up Config Server
First, let's create a Spring Cloud Config Server:
Step 1: Create a new Spring Boot project
Create a new Spring Boot application with Spring Cloud Config Server dependency.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
Step 2: Enable Config Server in your main application class
package com.example.configserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
Step 3: Configure the Config Server
In your application.properties
(or application.yml
) file:
server.port=8888
spring.cloud.config.server.git.uri=https://github.com/your-username/config-repo
spring.cloud.config.server.git.default-label=main
Or using YAML:
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/your-username/config-repo
default-label: main
This configures the server to run on port 8888 and fetch configurations from your Git repository.
Setting Up the Config Repository
Now, let's set up a Git repository to store our configuration files:
- Create a new Git repository (e.g., on GitHub)
- Add application configuration files following the naming convention:
{application-name}.properties
{application-name}-{profile}.properties
application.properties (for shared configurations)
For example, if you have a service called "order-service", you might create:
order-service.properties
- default configurationorder-service-dev.properties
- development environment configurationorder-service-prod.properties
- production environment configuration
Here's a simple example of what these files might contain:
order-service.properties:
app.description=Order Processing Service
app.max-orders=500
order-service-dev.properties:
server.port=8081
logging.level.root=DEBUG
order-service-prod.properties:
server.port=8082
logging.level.root=INFO
app.max-orders=10000
Setting Up Config Client
Now, let's create a client application that will retrieve its configuration from our Config Server:
Step 1: Add dependencies to your client application
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Step 2: Configure the client to use the Config Server
Create a bootstrap.properties
(or bootstrap.yml
) file in your resources folder:
spring.application.name=order-service
spring.cloud.config.uri=http://localhost:8888
Or using YAML:
spring:
application:
name: order-service
cloud:
config:
uri: http://localhost:8888
Starting from Spring Cloud 2020.0.0, you need to add the spring-cloud-starter-bootstrap
dependency when using bootstrap.properties, or you can use application.properties with additional configuration.
Step 3: Access the configuration properties in your application
package com.example.orderservice;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class OrderServiceApplication {
@Value("${app.description:Default Order Service}")
private String appDescription;
@Value("${app.max-orders:100}")
private Integer maxOrders;
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
@GetMapping("/service-info")
public String getServiceInfo() {
return "Service: " + appDescription + ", Max Orders: " + maxOrders;
}
}
Dynamic Configuration Refresh
One of the powerful features of Spring Cloud Config is the ability to change configurations on the fly without restarting your applications.
Step 1: Add the Actuator dependency to your client
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Step 2: Enable the refresh endpoint in your client's properties
management.endpoints.web.exposure.include=refresh
Step 3: Add the @RefreshScope annotation to your classes that use configuration
package com.example.orderservice;
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.RestController;
@RestController
@RefreshScope
public class ConfigController {
@Value("${app.description:Default Order Service}")
private String appDescription;
@Value("${app.max-orders:100}")
private Integer maxOrders;
@GetMapping("/service-info")
public String getServiceInfo() {
return "Service: " + appDescription + ", Max Orders: " + maxOrders;
}
}
Step 4: Refresh configurations
When you update your configuration in the Git repository, you can refresh your client application by making a POST request to the refresh endpoint:
POST http://localhost:8081/actuator/refresh
The response will show which properties have been updated:
["app.max-orders"]
Security Considerations
In production environments, you'll want to secure your Config Server. Here's how to add basic security:
Step 1: Add Spring Security dependency to the Config Server
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Step 2: Configure security settings
spring.security.user.name=configuser
spring.security.user.password=configpassword
Step 3: Update client to use authentication
spring.cloud.config.username=configuser
spring.cloud.config.password=configpassword
Real-World Example: Multi-Environment Configuration
Let's build a more comprehensive example that demonstrates how Spring Cloud Config can be used to manage configurations across multiple environments:
Configuration Repository Structure
├── application.properties # Shared properties for all services
├── order-service.properties # Default properties for order service
├── order-service-dev.properties # Dev environment properties
├── order-service-qa.properties # QA environment properties
├── order-service-prod.properties # Production environment properties
├── payment-service.properties # Default properties for payment service
└── ...
Common application.properties
# Common settings for all services
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
spring.jpa.hibernate.ddl-auto=update
management.endpoints.web.exposure.include=health,info,metrics,refresh
app.common.cache-timeout=3600
Order Service Configuration
order-service.properties:
# Default order service settings
app.order.process-timeout=30000
app.order.max-items-per-order=50
server.tomcat.max-threads=200
order-service-dev.properties:
# Dev-specific settings
server.port=8081
logging.level.com.example=DEBUG
spring.datasource.url=jdbc:mysql://localhost:3306/ordersdb_dev
spring.datasource.username=devuser
spring.datasource.password=devpass
app.order.process-timeout=60000
order-service-prod.properties:
# Production-specific settings
server.port=8081
logging.level.com.example=INFO
spring.datasource.url=jdbc:mysql://prod-db:3306/ordersdb
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
server.tomcat.max-threads=800
app.order.max-items-per-order=100
Order Service Client Implementation
package com.example.orderservice.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "app.order")
public class OrderServiceConfig {
private int processTimeout;
private int maxItemsPerOrder;
// Getters and setters
public int getProcessTimeout() {
return processTimeout;
}
public void setProcessTimeout(int processTimeout) {
this.processTimeout = processTimeout;
}
public int getMaxItemsPerOrder() {
return maxItemsPerOrder;
}
public void setMaxItemsPerOrder(int maxItemsPerOrder) {
this.maxItemsPerOrder = maxItemsPerOrder;
}
}
package com.example.orderservice.controller;
import com.example.orderservice.config.OrderServiceConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/orders/config")
public class OrderConfigController {
@Autowired
private OrderServiceConfig orderConfig;
@Value("${app.common.cache-timeout:1800}")
private int cacheTimeout;
@Value("${spring.datasource.url:unknown}")
private String datasourceUrl;
@GetMapping
public Map<String, Object> getConfigInfo() {
Map<String, Object> config = new HashMap<>();
config.put("processTimeout", orderConfig.getProcessTimeout());
config.put("maxItemsPerOrder", orderConfig.getMaxItemsPerOrder());
config.put("cacheTimeout", cacheTimeout);
config.put("datasourceUrl", datasourceUrl);
return config;
}
}
To run the order service in different environments, you'd set the active profile:
# For development
java -jar order-service.jar --spring.profiles.active=dev
# For production
java -jar order-service.jar --spring.profiles.active=prod
Using Spring Cloud Config with Spring Cloud Bus
For large deployments with many client applications, refreshing each client individually becomes impractical. Spring Cloud Bus links your services together with a lightweight message broker and lets you broadcast configuration changes.
Step 1: Add dependencies to your client applications
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
Step 2: Configure RabbitMQ in your client applications
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
Step 3: Enable bus-refresh endpoint
management.endpoints.web.exposure.include=bus-refresh
Step 4: Broadcast configuration changes
When you update your configuration in the Git repository, you can broadcast the changes to all connected clients by making a POST request to any one of the clients:
POST http://localhost:8081/actuator/bus-refresh
This will notify all applications connected to the message broker to refresh their configurations.
Summary
Spring Cloud Config provides a powerful way to manage configurations for microservice applications:
- Config Server centralizes configuration storage and servicing
- Git integration enables version control and history of configurations
- Environment-specific configurations allow for differences between dev, test, and prod
- Dynamic refreshing updates configurations without application restarts
- Spring Cloud Bus enables efficient configuration updates across many services
By using Spring Cloud Config, you can manage application configurations more effectively, especially in complex microservice architectures with multiple environments and services.
Additional Resources
- Spring Cloud Config Official Documentation
- Spring Cloud Config on GitHub
- Spring Cloud Bus Documentation
Exercises
- Set up a Spring Cloud Config Server that retrieves configuration from a local Git repository.
- Create a client application that reads configuration from your Config Server.
- Implement the @RefreshScope annotation and test dynamic configuration updates.
- Set up multiple environment-specific configuration files and test switching between them.
- Advanced: Implement encryption/decryption of sensitive properties in your configuration files.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)