In this lab, we explore yet another Spring Cloud project: Spring Cloud Config.

This project was developed entirely by the Pivotal Spring team. The focus here is on configuration, and the main idea is to provide configuration information to applications as a service.

Imagine a REST API with endpoints that can be used to retrieve a json document which represents the configuration for a specific application (e.g. greeting, fortune) in a specific environment (test, qa, staging, prod).

Spring Cloud’s Config Server has a similar feel to many other Spring Cloud projects: it’s based on Spring Boot as a foundation, with associated starter dependencies for the server and client, annotations to enable a Spring Boot application as a config server, and more.

Let’s introduce a Spring Cloud config server to our system under test to gain insight into this product and what it has to offer.

1. Review The Current Configuration

At the moment each service’s configuration lies in its application.yml, a resource file bundled with each project, and into the build artifact itself. Spring also supports overriding of configuration via Java system properties, and via environment variables, which is convenient.

Let’s review the configuration properties currently in place. Here’s the fortune service’s configuration:

fortune-service/src/main/resources/application.yml
spring:
  application:
    name: fortune

management:
  security:
    enabled: false

server:
  port: 8081

We mainly disable security for the management endpoints, for convenience. Otherwise, there’s really not much configuration in place for this service.

And here’s the configuration for greeting:

greeting-app/src/main/resources/application.yml
spring:
  application:
    name: greeting

management:
  security:
    enabled: false

greeting:
  displayFortune: true

fortune:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

This is a little more interesting. We have a sort of feature toggle: whether or not to display fortunes, and we also configure the load balancing algorithm used to fetch fortunes.

With Spring Cloud Config, we externalize these configurations to a version control system: a git repository. The property spring.application.name becomes the handle by which applications identify themselves to the config server. Each application’s configuration is placed in either a .properties or .yml file whose name is the spring.application.name (aka service id).

Spring Cloud provides a means of reducing duplication in configuration by allowing us to place common configuration in a file named application.yml.

2. Create a local git repository

Begin by creating a local git repository:

  1. Select a working directory, and in it, create a subdirectory named config-repo:

    $ mkdir config-repo
    $ cd config-repo
  2. Initialize this directory as a git repository:

    $ git init
  3. In this directory, create a file named application.yml that will contain the following general configuration that will apply to all applications:

    config-repo/application.yml
    management:
      security:
        enabled: false (1)
    
    logging:
      level:
        io:
          pivotal:
            training: INFO (2)
    1 Disable security for management endpoints by default
    2 Set the default logging level to INFO for all applications

    Notice that, for the fortune service, we require no additional configuration.

  4. Create a file named greeting.yml (the name has to match the spring.application.name for the greeting application), and port over its configuration, as follows:

    greeting:
      displayFortune: true
    
    fortune:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
  5. Add and commit both files to the git repository:

    $ git add .
    $ git commit -m "initial configuration"

Make sure to note the full path of the directory config-repo. We’ll need it shortly when we configure our config server.

3. Create a Config Server

Visit the Spring initializr at http://start.spring.io/ and create a new gradle project with the config server as a dependency, as shown in this screenshot:

config server initializr

As usual, click Generate Project, unzip the downloaded zip file alongside the other projects (greeting-app, fortune-service, eureka-server), and import the project into your IDE.

  1. Study the project’s build file. You’ll see the spring-cloud-config-server dependency.

  2. Enable the configuration server by annotating the application class with @EnableConfigServer:

    config-server/src/main/java/io/pivotal/training/springcloud/configserver/ConfigServerApplication.java
    @@ -2,8 +2,10 @@ package io.pivotal.training.springcloud.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) {
  3. Configure the application to run on an available port. Let’s use port number 8090. Also, configure the config server to fetch configuration information from the local git repository we configured earlier:

    server.port=8090
    spring.cloud.config.server.git.uri=file://${user.home}/work/config-repo
    On Windows, the git uri needs to be specified with three foreslashes instead of two, as in file:///${user.home}/work/config-repo. Also, be sure to use the path to your config-repo folder, the one shown above is just an example.

Ok, let’s test this config server. Start up the application:

$ gradle bootRun

Visit the following URLs:

In the screenshot below, we see how the configuration for the greeting application contains both the greeting-specific configuration properties and the general configuration (logging level, management security).

greeting configuration

In a similar manner, we can define additional configurations for the same applications for different environments, using Spring Profiles. For example, any configuration for the greeting application that is unique to the "qa" profile can be written to the file greeting-qa.yml and that information will override any values specified in the default profile. The config server always returns a union of all matching files. For example, for the application named "greeting" and the profile named "qa", the url becomes http://localhost:8090/greeting/qa, and if any of these files exist, their configuration would be returned as a single json response:

  1. application.yml (or .properties)

  2. application-qa.yml

  3. greeting.yml

  4. greeting-qa.yml

The more specific files always supersede or override the more general ones.

4. Register the Config Server with Eureka

Before turning our attention to the clients, let’s address the question: how should clients locate the config server?

We could configure clients with the config server url using the configuration property spring.cloud.config.uri. But wait, we have a service registry, why not have the config server register with eureka, so that clients can just discover its url? Let’s take that latter route.

As you already know, to make any Spring Boot application a eureka client, do the following:

  1. Add the spring-cloud-starter-eureka dependency to the build file, and..

  2. Enable the application as a discovery client, as shown in this diff:

    config-server/build.gradle
    @@ -28,6 +28,7 @@ ext {
    
     dependencies {
       compile 'org.springframework.cloud:spring-cloud-config-server'
    +  compile 'org.springframework.cloud:spring-cloud-starter-eureka'
    
       testCompile 'org.springframework.boot:spring-boot-starter-test'
     }
    config-server/src/main/java/io/pivotal/training/springcloud/configserver/ConfigServerApplication.java
    @@ -2,10 +2,12 @@ package io.pivotal.training.springcloud.configserver;
    
     import org.springframework.boot.SpringApplication;
     import org.springframework.boot.autoconfigure.SpringBootApplication;
    +import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
     import org.springframework.cloud.config.server.EnableConfigServer;
    
     @SpringBootApplication
     @EnableConfigServer
    +@EnableDiscoveryClient
     public class ConfigServerApplication {
    
       public static void main(String[] args) {
  3. Also, the application registering with eureka must identify itself. So, set the spring.application.name to configserver. By default, config clients that are configured to lookup config servers with eureka are going to be looking for the service id configserver:

    config-server/src/main/resources/application.properties
    @@ -1,2 +1,3 @@
    +spring.application.name=configserver
     server.port=8090
     spring.cloud.config.server.git.uri=file://${user.home}/work/config-repo

Verify that a config server will now register with eureka as it initializes:

  1. Start the eureka-server

  2. Visit the eureka dashboard at http://localhost:8761/

  3. Start the config-server

  4. Refresh the dashboard, do you see a new serviceId named "CONFIGSERVER" show up in the registry?

  5. Do the configuration endpoints (e.g. http://localhost:8090/greeting/default) still resolve?

5. Retrofit the clients

  1. Begin by removing the configuration properties from the projects themselves. From now on, these will be fetched from the config repository, via the config server:

    fortune-service/src/main/resources/application.yml
    @@ -2,9 +2,5 @@ spring:
       application:
         name: fortune
    
    -management:
    -  security:
    -    enabled: false
    -
     server:
       port: 8081
    greeting-app/src/main/resources/application.yml
    @@ -2,13 +2,3 @@ spring:
       application:
         name: greeting
    
    -management:
    -  security:
    -    enabled: false
    -
    -greeting:
    -  displayFortune: true
    -
    -fortune:
    -  ribbon:
    -    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

    The remaining configuration (application name, server port) must be defined in a file named bootstrap.yml. The spring application name must be available to the application at a very early stage, before the application.yml file is loaded.

  2. Rename both application.yml files to bootstrap.yml

  3. Add the Spring Cloud Config client dependency to both the fortune and greeting projects:

    fortune-service/build.gradle
    @@ -49,6 +49,7 @@ dependencies {
       compile 'org.springframework.boot:spring-boot-starter-actuator'
    
       compile 'org.springframework.cloud:spring-cloud-starter-eureka'
    +  compile 'org.springframework.cloud:spring-cloud-starter-config'
    
       runtime 'org.springframework.boot:spring-boot-devtools'
    greeting-app/build.gradle
    @@ -33,6 +33,7 @@ dependencies {
       compile 'org.springframework.cloud:spring-cloud-starter-hystrix'
       compile 'org.springframework.cloud:spring-cloud-starter-eureka'
       compile 'org.springframework.cloud:spring-cloud-starter-feign'
    +  compile 'org.springframework.cloud:spring-cloud-starter-config'
    
       runtime 'org.springframework.boot:spring-boot-devtools'
  4. Finally, instruct each application to locate the config server via service discovery:

    fortune-service/src/main/resources/bootstrap.yml
    @@ -1,6 +1,10 @@
     spring:
       application:
         name: fortune
    +  cloud:
    +    config:
    +      discovery:
    +        enabled: true
    
     server:
       port: 8081
    greeting-app/src/main/resources/bootstrap.yml
    @@ -1,4 +1,7 @@
     spring:
       application:
         name: greeting
    +  cloud:
    +    config:
    +      discovery:
    +        enabled: true

Test the solution.

  1. Be sure to have the eureka and config servers running, and the config server registered with eureka.

  2. Now start the fortune and greeting applications

  3. Do they fetch their configuration information from the config server? The application’s startup log should have a line that reads something similar to this:

    c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at: http://localhost:8090/
  4. Can you access their management endpoints, for example http://localhost:8080/env? If not, this is a sure sign that the application was unable to retrieve its configuration from the config server. Review your configuration, and the appplication’s logs for clues as to what might have gone wrong.

  5. Do they register with eureka? The eureka dashboard should show entries for both FORTUNE and GREETING.

  6. Does the greeting application function as expected? Does it display fortunes?

Notice how the end result of this refactoring is that we now have a single place to keep configuration information for all of our services, and for multiple environments.

The config server enables a number of capabilities. Here are some use cases:

  1. feature toggles: in our case, we should be able to toggle displayFortune without having to restart services

  2. change log levels: to diagnose the behavior of a running service

  3. environments and profiles: we can manage the configuration of a set of services across multiple environments

  4. encrypting configuration properties

Let’s test the first use case: feature toggles.

6. Toggle displayFortune

  1. Visit the greeting.yml configuration file in the config-repo repository, and modify it as follows:

    config-repo/greeting.yml
    @@ -1,5 +1,5 @@
     greeting:
    -  displayFortune: true
    +  displayFortune: false
    
     fortune:
       ribbon:
  2. Save the file and commit the change to git:

    $ git add greeting.yml
    $ git commit -m "turning off displayFortune"
  3. Manually verify that the config server is aware of this change: visit http://localhost:8090/greeting/default. The value for the property greeting.displayFortune should now be false.

  4. Refresh the greeting application page. Fortunes should still be displaying. By design, applications will not update their configuration from the server until they’re instructed to do so.

  5. Using some http client tool such as curl, httpie, or postman, submit an http POST to the greeting application, at the endpoint '/refresh'. Here’s an example, using httpie:

    $ http post localhost:8080/refresh

    Here is the response:

    HTTP/1.1 200
    Content-Type: application/json;charset=UTF-8
    Date: Wed, 06 Sep 2017 21:06:20 GMT
    Transfer-Encoding: chunked
    X-Application-Context: greeting
    
    [
        "config.client.version",
        "greeting.displayFortune"
    ]

Note how the response displays the specific configuration properties that have changed. Now refresh the greeting application page and notice that fortunes are no longer displayed.

7. Congratulations

You’ve just externalized your system’s configuration with Spring Cloud Config Server. In the next lab, we explore deploying our solution to Pivotal Cloud Foundry, with the aid of a product named Spring Cloud Services.