Cloud Config Server; Push-Based or Pull-Based?

Mahdi Mallaki
10 min readFeb 17, 2024

Using the Spring Cloud Config Server as a central config server and employing push-based and pull-based approaches for getting configs

What’s Spring Cloud Config Server?

Spring Cloud Config Server provides centralized external configuration management backed by a variety of data repositories such as Git, SVN, filesystems, and Vault. It is part of the larger Spring Cloud framework, which aims to simplify some of the common patterns encountered in developing distributed systems, such as configuration management, service discovery, circuit breakers, and more.

Why Spring Cloud Config Server?

Spring Cloud Config Server offers several features that are useful for resolving issues and enhancing best practices for microservice architecture:

  • Decoupling Configuration from Deployment
  • Dynamic Configuration Updates
  • Enhanced Security
  • Centralized Configuration Management

1- Decoupling Configuration from Deployment

As depicted in the following diagram, Spring Cloud can decouple the deployment of an application from configuration changes. You may use a CI/CD pipeline to deploy your application with your configuration into Kubernetes, and whenever you need to change your configuration, you run the pipeline again. Spring Cloud Config introduces a new way of thinking about configurations by decoupling them from the deployment process.

2- Dynamic Configuration Updates (Hot Reloading)

Spring Cloud Config supports Hot Reloading, which means changing the application configuration on the fly without needing to restart it. It can be useful for applications that have a high rate of configuration changes and need to reload the configuration without any interruption. Although you can achieve zero downtime using Rolling Update mechanisms in Kubernetes, even gracefully shutting down applications can cause connection interruptions.

3- Enhanced Security

Spring Cloud Config can enhance security by providing Vault as a backend storage. Your microservices can access Vault secrets using the Config Server REST API, eliminating the need to store your configuration in Kubernetes ConfigMaps or Secrets (which are not encrypted) or on disk space.

4- Centralized Config server

As you can see in the following diagram, the Spring Cloud Config Server can create an abstraction layer over your configuration providers such as Git Repository, Vault, Kubernetes ConfigMap, etc. Your microservices won’t care about where your configuration is saved; they’ll simply retrieve it from a single REST API.

Spring Cloud Architecture

in the following section I’ll describe the different ways of implementing Spring Cloud config.

1- Instant Reloading (Push-based)

As you can see in the following diagram, the Catalog Service (which is a sample microservice) receives its configuration at startup time from the Config Server. However, if the configuration changes on one of the configuration providers (like Vault or Git Repository), it will not be notified. In this model, you need to have a Queue (like Kafka or RabbitMQ) to notify the application about a change that occurred in the providers. Spring Cloud Config uses Spring Cloud Bus, which integrates well with Kafka and RabbitMQ, eliminating the need to write any code to manage the notification.

spring cloud config push-based model by using Kafka/RabbitMQ queue

2- Scheduled Reloading (Pull-Based)

Another way to configure the Spring Config Server is by using scheduling mechanisms to pull the configuration changes from the Config Server. As you can see in the following diagram, the Catalog sends a request every 1 minute to pull its configuration from the Config Server. You may be concerned about misconfiguration between different replicas of the Catalog Service. It’s possible to configure it to pull the configuration at specific cron schedules like 0 * * * *, which means every minute at 0 seconds. In this case, your different instances will not have different configurations because they’re pulling the configuration at the same time.

spring cloud config pull-based model by cron scheduling

3- Push-based vs Pull-based model

In the following table you can see that I compared the Push-based model with Pull-based one:

+-------------------------------------------------------------+
| Push-based | Pull-based |
|------------------------------|------------------------------|
| Instant updating | Updating by schedule |
| Complicated Architecture | Simple Architecture |
+-------------------------------------------------------------+

In Action

In this section, I’ll describe the ways to implement the config server and integrate microservices to be able to read their configuration from the config server.

1- Configuring the Config Server

First, let’s create the Config Server and configure it to connect to different sources like Git repository, Vault, Kubernetes ConfigMap, and Kubernetes Secrets. I created the POC using Spring Boot 3.2, but it may also work on Spring Boot 2. You can find the whole source code of the Config Server in the following Git repository:

Here, I’ll describe some important concepts on how to configure and connect the Config Server to different sources.

Configuring the build.gradle File:

You need to add some dependencies to your build.gradle file to be able to use Spring Cloud Config libraries, like this:

plugins {
id 'java'
id 'org.springframework.boot' version '3.2.1'
id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
}

repositories {
mavenCentral()
}

ext {
set('springCloudVersion', "2023.0.0")
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-config-server'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-bus-kafka'
//implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp'
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-client'
implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-client-config'
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}

If you need to connect your Config Server to Kafka, you’ll need to use the org.springframework.cloud:spring-cloud-starter-bus-kafka dependency. If you need to connect it to ActiveMQ, you need to use the org.springframework.cloud:spring-cloud-starter-bus-amqp library. In this document, I'll use the Kafka dependency.

Enable Config Server

You’ll need to enable the Config Server on your Spring Boot using the @EnableConfigServer annotation.

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {

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

}

application.properties File

You need to configure the application.properties like this to be able to connect to different sources:

server.port=8888

spring.profiles.active=git,vault,kubernetes

# configuration for reading configs from Git server
spring.cloud.config.server.git.uri=ssh://git@github.com:mlkmhd/spring-cloud-sample-config.git
spring.cloud.config.server.git.defaultLabel=master
spring.cloud.config.server.git.skipSslValidation=true

# Git Refresh Rate
# You can control how often the config server will fetch updated configuration data from your Git backend by using spring.cloud.config.server.git.refreshRate.
# The value of this property is specified in seconds.
# By default the value is 0, meaning the config server will fetch updated configuration from the Git repo every time it is requested.
spring.cloud.config.server.git.refreshRate=60

# configuration for connecting to vault server and loading secrets from it
spring.cloud.config.server.vault.host=vault
spring.cloud.config.server.vault.port=8200
spring.cloud.config.server.vault.scheme=http
spring.cloud.config.server.vault.backend=secret
spring.cloud.config.server.vault.default-key=application
spring.cloud.config.server.vault.profile-separator=,
spring.cloud.config.server.vault.authentication=TOKEN
spring.cloud.config.server.vault.token=root
spring.cloud.config.server.vault.kvVersion=2
spring.cloud.vault.config.lifecycle.enabled=true
spring.cloud.vault.config.lifecycle.min-renewal=10s
spring.cloud.vault.config.lifecycle.expiry-threshold=1m

# enable spring cloud bus to be able to connect to Kafka broker
spring.cloud.bus.enabled=true
spring.kafka.bootstrap-servers=kafka:9092
spring.kafka.properties.security.protocol=SASL_PLAINTEXT
spring.kafka.properties.sasl.mechanism=PLAIN
spring.kafka.properties.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="user1" password="xxxxxxxx";

# configuration for reading configs from Kubernetes ConfigMap and Secret
spring.config.activate.on-cloud-platform=kubernetes
spring.cloud.kubernetes.secrets.enableApi=true
spring.config.import=optional:kubernetes:
spring.cloud.kubernetes.config.enableApi=true
spring.cloud.kubernetes.reload.enabled=true

# endpoint exposure configuration like /actuator/busrefresh
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

Let me describe the above configuration one by one:

  • spring.profiles.active=git,vault,kubernetes : This means to activate different Spring Cloud configs to connect to Git, Vault, and Kubernetes as backend configuration storage.
  • spring.cloud.config.server.git.uri=xxxxxx : For connecting your Config Server to a Git repository, you'll need to use the SSH URL to connect to it. The Spring Cloud Config will use your ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub files for authentication to the Git server. So, you'll need to add your SSH key files to the Git repository to be able to clone it without showing an authentication prompt. You need to store your files inside the Git repository like catalog-service-dev.properties , which is in {application_name}-{profile}.properties format.
  • spring.cloud.config.server.vault.host=vault : This configuration indicates the address of your Vault server. You'll need to define a secret inside the Vault server in the format catalog-service,dev , which is in {application_name},{profile} format.
  • spring.kafka.bootstrap-servers=kafka:9092 : This is the configuration for connecting to the Kafka broker.
  • spring.config.activate.on-cloud-platform=kubernetes : This configuration is for reading configs from Kubernetes ConfigMap and Secret. You almost don't need to create any ServiceAccount, and the default ServiceAccount is enough for reading ConfigMaps and Secrets in the deployment namespace.
  • management.endpoints.web.exposure.include=* : You'll need to expose some endpoints like /actuator/busrefresh using this configuration.

2- Configuring Catalog Service (Push-Based Sample)

In this section, I’ll describe the important details of the Catalog Service, which is the name of a sample microservice that uses the config server for retrieving its configuration.

Configuring the build.gradle

You need to add some dependencies to this microservice to be able to use Spring Cloud libraries:

plugins {
id 'java'
id 'org.springframework.boot' version '3.2.1'
id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
}

repositories {
mavenCentral()
}

ext {
set('springCloudVersion', "2023.0.0")
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-bus-kafka'
//implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp'
}

Injecting configuration from Config Server

Injecting configuration from the Config Server is simple; you just need to use the @Value annotation for this purpose, as shown in the following sample code:

@RestController
@RefreshScope
@RequestMapping("/config")
public class MyController {

@Value("${db.password}")
private String dbpassword;

@Value("${db.name}")
private String dbname;

@Value("${my.test.config}")
private String myTestConfig;

@GetMapping
public String getConfigValue() {
return "database password is: "+ dbpassword + ", and dbname is: "+ dbname + " and my test config is: "+ myTestConfig;
}
}

If you want to reload your configuration, you need to use the @RefreshScope annotation to refresh the values of configs when the configuration changes on the Config Server.

Configuring the application.properties

To connect the application to the Spring Cloud service, you need to configure it like this:

spring.application.name=catalog-service
spring.profiles.active=dev

# config reads from http://CONFIG_SERVER:PORT/{spring.application.name}/{spring.profiles.active}/{master or main}
spring.cloud.config.uri=http://config-server:8888
spring.cloud.config.name=catalog-service
spring.cloud.config.fail-fast=true
spring.cloud.config.max-attempts=10
spring.cloud.config.max-interval=1500
spring.cloud.config.multiplier=1.2
spring.cloud.config.initial-interval=1100
spring.cloud.config.allowOverride=true
spring.cloud.config.overrideNone=true

management.endpoints.web.exposure.include=*

server.port=8081

# configuration of spring cloud bus for receiving the configuration changes notification
spring.cloud.bus.enabled=true
spring.kafka.bootstrap-servers=kafka:9092
spring.kafka.properties.security.protocol=SASL_PLAINTEXT
spring.kafka.properties.sasl.mechanism=PLAIN
spring.kafka.properties.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="user1" password="xxxxxxxx";

Let me describe the above configuration one by one:

  • spring.cloud.config.uri=http://config-server:8888: This is the address of the config server. If the config server is not available at startup time, the application will not start because the spring.cloud.config.fail-fast config is set to true .
  • spring.application.name and spring.profiles.active : This configuration is important because Spring uses this name to fetch the configuration from the config server. For example, using the above configuration, Spring will fetch the configuration from the http://config-server:8888/catalog-service/dev URL.
  • spring.cloud.bus.enabled=true : This is the same configuration as the Spring Cloud Config Server, which enables the Spring Cloud Bus and configures the Kafka server.

3- Configuring Order Service (Pull-Based Sample)

In this section, I’ll describe the Order Service , which is another microservice that uses pull-based configuration update mechanisms.

Configuring the build.gradle

Just like the previous example, you need to add some dependencies to the build.gradle to be able to use Spring Cloud Config, like this:

plugins {
id 'java'
id 'org.springframework.boot' version '3.2.1'
id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
}

repositories {
mavenCentral()
}

ext {
set('springCloudVersion', "2023.0.0")
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-bus-kafka'
//implementation 'org.springframework.cloud:spring-cloud-starter-bus-amqp'
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}

tasks.named('bootBuildImage') {
builder = 'paketobuildpacks/builder-jammy-base:latest'
}

Injecting configuration from Config Server

Just like the previous example, Injecting configuration from config server is so simple and you just need to use @Value annotation for this purpuse like the following sample code:

@RefreshScope
@RestController
@RequestMapping("/config")
public class MyController {

@Value("${db.password}")
private String dbpassword;

@Value("${db.name}")
private String dbname;

@GetMapping
public String getConfigValue() {
return "database password is: "+ dbpassword + ", and dbname is: "+ dbname;
}
}

Configuration Update Scheduler

To pull the latest configuration files from the Config Server, you need to define a scheduler to run periodically and pull the latest configuration like this:

@Component
@Configuration
@EnableScheduling
public class ConfigRefreshScheduler {

private static final Log LOG = LogFactory.getLog(ConfigRefreshScheduler.class);

@Autowired
private ContextRefresher contextRefresher;

@Scheduled(cron = "0 * * * * *") // every minute at 0's second
public void refreshConfig() {
LOG.info("refreshing the context ...");
contextRefresher.refresh();
}
}

As you can see in the above file, there’s @Scheduled(cron = "0 * * * * *") , which schedules the refreshConfig() function to run every minute at the 0th second. The 0 * * * * * format is in the cron format, and you can learn more about it on the "https://crontab.guru" website. By using cron, all your instances will be updated at a specific time, ensuring there will be no misconfiguration between different instances as they all update simultaneously.

application.properties File

Just like the previous example, this application.properties is used for configuring the application. The only difference between this one and the previous one is that it's not using Spring Cloud Bus for updating its configuration and pulling the configuration periodically. As you can see in the following section, spring.cloud.bus.enabled is set to false , and there's no Kafka configuration.

server.port=8082

spring.application.name=order-service
spring.profiles.active=dev

# config reads from http://CONFIG_SERVER:PORT/{spring.application.name}/{spring.profiles.active}/{master or main}
spring.cloud.config.uri=http://config-server:8888
spring.cloud.config.name=order-service
spring.cloud.config.fail-fast=true
spring.cloud.config.max-attempts=10
spring.cloud.config.max-interval=1500
spring.cloud.config.multiplier=1.2
spring.cloud.config.initial-interval=1100
spring.cloud.config.allowOverride=true
spring.cloud.config.overrideNone=true

management.endpoints.web.exposure.include=*

spring.cloud.bus.enabled=false

Conclusion

In conclusion, the use of Spring Cloud Config Server offers a robust solution for centralized external configuration management, facilitating configuration retrieval from various data repositories such as Git, SVN, filesystems, and Vault. Employing push-based and pull-based approaches provides flexibility in obtaining configurations, catering to different architectural needs.

Ultimately, the choice between push-based and pull-based approaches depends on specific project requirements and architectural considerations, each offering its own set of advantages in managing configurations effectively.

I have another article that examines Spring Cloud Config from a different perspective:
https://medium.com/itnext/spring-cloud-config-hot-reloading-c61f6ea82e6c

Support My Blog ☕️

If you enjoy my technical blog posts and find them valuable, please consider buying me a coffee at here. Your support goes a long way in helping me produce more quality articles and content. If you have any feedback or suggestions for improving my code, please leave a comment on this post or send me a message on my LinkedIn.

--

--