Chapter 14. Managing configuration

This chapter covers

  • Running Spring Cloud Config Server
  • Creating Config Server clients
  • Storing sensitive configuration
  • Automatically refreshing configuration

Anyone who has bought a house or a car has probably encountered a thick stack of paper. The contracts you sign when making major purchases tend to thumb their nose at the promise of a paperless society. Whenever I sit across the table from a car dealer or a title agent, I feel as if I should request a stack of bandages before getting started, in preparation for the paper cuts I’ll almost certainly receive before we’re done.

I’ve noticed that although the number of pages I must sign has stayed constant in recent years, I don’t have to fill out as many fields on the forms as I once did. Where forms were once manually filled in, modern forms are more often prepopulated with basic data that was gathered before the forms were printed. This has not only made the process quicker, but has also reduced mistakes resulting from manual duplication of data across multiple forms.

Likewise, many applications have some form of configuration in play. In chapter 5 we talked about ways you can configure Spring Boot applications by setting configuration properties. Often, properties you might set are unique to the application, and it’s easy enough to specify those properties in the application.properties or application.yml file that’s packaged in your application’s deployment.

When an application is architected with microservices, however, configuration properties are often common across multiple services. Just as it once was tedious and error-prone to manually fill in forms with duplicate data, duplicating configuration across multiple application services can be problematic.

In this chapter, we’ll look at Spring Cloud’s Config Server, a service that provides centralized configuration for all services in a given application. With Config Server, you can manage all of an application’s configuration in one place, without duplication.

Before we get started, let’s briefly consider the problems of configuring microservices individually, and how centralized configuration is better.

14.1. Sharing configuration

As you saw in chapter 5, you can configure Spring applications by setting properties in any of several property sources. If a configuration property is likely to change or be unique to the runtime environment, Java system properties or operating system environment variables are a fitting choice. For properties that are unlikely to change and are specific to a given application, placing those property specifications in application.yml or application.properties to be deployed with the packaged application is a fine choice.

These choices are okay for simple applications. But when you’re setting configuration properties in environment variables or Java system properties, you must accept that changing those properties will require the application to be restarted. And if you choose to package the properties inside the deployed JAR or WAR file, you must completely rebuild and redeploy the application should those properties need to change. These same constraints are in play should you need to roll back changes to configuration.

Those limitations may be acceptable in some applications. In others, redeploying and restarting the application just to change a simple property is inconvenient at best and crippling at worst. Moreover, in microservice-architected applications, property management is spread across multiple codebases and deployment instances, making it unreasonable to apply the same change in every single instance of multiple services in a running application.

Some properties are sensitive, such as database passwords and other types of secrets. Although those values can be encrypted when written to an individual application’s properties, the application must include the ability to decrypt those properties before they can be used. Even then, some properties may need to be kept from even the application developers, making it highly undesirable to set them in environment variables or manage them with the same source code control system as the rest of the application code.

In contrast, consider how those scenarios play out when configuration management is centralized:

  • Configuration is no longer packaged and deployed with the application code, making it possible to change or roll back configuration without rebuilding or redeploying the application. Configuration can be changed on the fly without even restarting the application.
  • Microservices that share common configuration needn’t manage their own copy of the property settings and can share the same properties. If changes to the properties are required, those changes can be made once, in a single place, and applied to all microservices.
  • Sensitive configuration details can be encrypted and maintained separate from the application code. The unencrypted values can be made available to the application on demand, rather than requiring the application to carry code that decrypts the information.

Spring Cloud Config Server provides centralized configuration with a server that all microservices within an application can rely on for their configuration. Because it’s centralized, it’s a one-stop shop for configuration that’s common across all services, but it’s also able to serve configuration that’s specific to a given service.

The first step in using Config Server is to create and run the server.

14.2. Running Config Server

Spring Cloud Config Server provides a centralized source for configuration data. Much like Eureka, Config Server can be considered just another microservice whose role in the greater application is to serve configuration data for other services in the same application.

Config Server exposes a REST API through which clients (which are other services) can consume configuration properties. The configuration that’s served through the Config Server is housed external to the Config Server, typically in a source code control system such as Git. Figure 14.1 illustrates how this works.

Figure 14.1. Spring Cloud Config Server serves configuration properties from a backing Git repository or Vault secret store to other services.

Take note of the fact that the box in figure 14.1 has the Git logo, but not the GitHub logo. That’s significant—you can use any implementation of Git to store your configuration, including but not limited to GitHub. GitLab, Microsoft’s Team Foundation Server, or Gogs are all valid choices as a backend for Config Server.

Note

Although it makes little difference which Git server you use with Config Server, I’m using Gogs (http://gogs.io), a lightweight, easy-to-set-up Git server. More specifically, I’m running Gogs on my development machine using the instructions for running Gogs in Docker at https://github.com/gogits/gogs/tree/master/docker.

By storing the configuration in a source code control system such as Git, the configuration can be versioned, branched, labeled, reverted, and blamed, just like application source code. But by keeping the configuration separate from the applications that consume it, it can evolve and be versioned independently of those applications.

You probably also noticed that HashiCorp Vault is included in figure 14.1. Vault is especially useful when the configuration properties you wish to serve are to be kept completely secret and locked away until they’re needed. We’ll talk more about using Vault with Config Server in section 14.5.

14.2.1. Enabling Config Server

As another microservice within a greater application system, Config Server is developed and deployed as a distinct application. Therefore, you’ll need to create a brand new project for Config Server. The easiest way to do this is with the Spring Initializr or one of its clients (such as the New Spring Starter Project wizard in Spring Tool Suite).

Configuration: the overloaded term

When talking about Spring Cloud Config Server, the term “configuration” gets thrown about a lot, and it isn’t always referring to the same thing. There are configuration properties that you’ll write to configure the Config Server itself. There are also configuration properties that the Config Server will serve to your applications. And the Config Server itself has the word “Config” in its name, adding slightly to the confusion.

I’ll do my best to make it clear which configuration I’m referring to whenever I use the word “configuration,” and I’ll always refer to the Config Server with the shortened word “Config.”

I’m inclined to name the new project “config-server”, but you’re welcome to name it however you wish. The most important thing to do is to specify the Config Server dependency by checking the Config Server check box. This will result in the following dependency being added to the produced project’s pom.xml file:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>

The Config Server version is ultimately determined by the Spring Cloud release train that’s chosen. It’s the version of the Spring Cloud release train that must be configured in the pom.xml file. At the time I’m writing this, the latest Spring Cloud release train version is Finchley.SR1. As a result, you’ll also find the following property and <dependencyManagement> block in the pom.xml file:

<properties>
  ...
  <spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>

...

<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>

Although the Config Server dependency adds Spring Cloud to the project’s classpath, there’s no autoconfiguration to enable it, so you’ll need to annotate a configuration class with @EnableConfigServer. This annotation, as its name implies, enables a Config Server when the application is running. I usually just drop @EnableConfigServer on the main class like so:

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

There’s only one more thing that must be done before you can fire up the application and see the Config Server at work: you must tell it where the configuration properties that it’s to serve can be found. To start, you’ll use configuration that’s served from a Git repository, so you’ll need to set the spring.cloud.config.server.git.uri property with the URL of the configuration repository:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/tacocloud/tacocloud-config

You’ll see how to populate the Git repository with properties in section 14.2.2.

First, though, there’s one other property you may wish to set for local development purposes. When testing your services locally, you’ll end up having several services all running and listening on different ports on localhost. As a typical Spring Boot web application, the Config Server will listen on port 8080 by default. To avoid port collisions, you’ll want to specify a unique port number by setting server.port:

server:
  port: 8888

Here you’ve set server.port to 8888 because, as you’ll see in section 14.3, that’s the default port that the configuration clients will attempt to retrieve configuration from. You’re welcome to set it to any value you wish, but you’ll need to be sure to configure the configuration client services to match.

It’s important to realize that the configuration you’ve written thus far in this section is configuration for the Config Server itself. It’s not the same configuration that will be served by the Config Server. Config Server will serve configuration that it pulls from Git or Vault.

At this point, if you start the application, you’ll have a Config Server listening for requests on port 8888, but it will be serving absolutely no configuration properties. You don’t have any Config Server clients yet, but you can pretend to be one by using the curl command-line client (or an equivalent HTTP client of your choosing):

$ curl localhost:8888/application/default
{
  "name": "application",
  "profiles": [
    "default"
  ],
  "label": null,
  "version": "ca791b15df07ce41d30c24937eece4ec4b208f4d",
  "state": null,
  "propertySources": []
}

The request made here is an HTTP GET request for the path /application/default on the Config Server. This path is made up of two or three parts, as illustrated in figure 14.2.

Figure 14.2. Config Server exposes a REST API through which configuration properties can be consumed.

The first part of the path, “application”, is the name of the application making the request. You’ll see later in section 14.4.1 how Config Server can use this part of the request path to serve application-specific configuration. For now you don’t have any application-specific configuration, so any value will do.

The second part in the path is the name of the Spring profile that’s active in the application making the request. In section 14.4.2 we’ll look at how Config Server can use this profile in the request path to serve configuration that’s specific to an active profile. You don’t yet have any profile-specific configuration, so any profile name will work for now.

The third part of the path, which is optional, specifies the label or branch in the backend Git repository from which to pull configuration. If not specified, this defaults to the “master” branch.

The response gives us some basic information about what the Config Server is serving, including the version and label of the Git commit that it’s serving configuration from. What are clearly missing, however, are any actual configuration properties. Normally you’d see them in the propertySources property, but it’s empty in this response. That’s because you still need to populate the Git repository with properties for the Config Server to serve. Let’s take care of that now.

14.2.2. Populating the configuration repository

There are several ways to set up properties for the Config Server to serve. The most basic, straightforward option is to commit an application.properties or application.yml file to the root path of the Git repository.

Let’s say you’ve pushed a file named application.yml to the Git repository configured in the previous section. This configuration file isn’t the same as the one you configured in the previous section; it’s the configuration that will be served by the Config Server. Suppose that in that application.yml file you’ve configured the following properties:

server:
  port: 0

eureka:
 client:
   service-url:
     defaultZone: http://eureka1:8761/eureka/

Although there isn’t much in this application.yml, what it does configure is rather significant. It tells every service in the application to choose a randomly available port and where it can register with Eureka. That means that when you adapt the services into Config Server clients in section 14.3, you’ll be able to remove explicit Eureka configuration from the services.

Acting as a client of the Config Server, you can use curl at the command line to see this new configuration data served from the Config Server:

$ curl localhost:8888/someapp/someconfig
{
  "name": "someapp",
  "profiles": [
    "someconfig"
  ],
  "label": null,
  "version": "95df0cbc3bca106199bd804b27a1de7c3ef5c35e",
  "state": null,
  "propertySources": [
    {
      "name": "http://localhost:10080/habuma/tacocloud-
     config/application.yml",
      "source": {
        "server.port": 0,
        "eureka.client.service-url.defaultZone":
     "http://eureka1:8761/eureka/"
      }
    }
  ]
}

Unlike your earlier request to the Config Server, this response has stuff in the propertySources array. Specifically, it contains a property source whose name property references the Git repository and whose source contains the properties you’ve pushed into the Git repository.

Serving configuration from Git subpaths

If it suits your organizational style, you may choose to store configuration in the Git repository in a subpath instead of at the root. For example, suppose you want to put the configuration in a subdirectory named “config” relative to the root of the Git repository. If so, setting spring.cloud.config.server.git.search-paths as follows will tell the Config Server to serve configuration from /config instead of from the root:

  spring:
    cloud:
      config:
        server:
          git:
            uri: http://localhost:10080/tacocloud/tacocloud-config
            search-paths: config

Notice that the spring.cloud.config.server.git.search-paths property is plural. That means you can have Config Server serve from multiple paths by listing them, separated by commas:

  spring:
    cloud:
      config:
        server:
          git:
            uri: http://localhost:10080/tacocloud/tacocloud-config
            search-paths: config,moreConfig

This sets up Config Server to serve configuration from both the /config and /moreConfig paths in the Git repository.

You may also use wildcards when specifying search paths:

  spring:
    cloud:
      config:
        server:
          git:
            uri: http://localhost:10080/tacocloud/tacocloud-config
            search-paths: config,more*

Here, Config Server will serve configuration from /config as well as any subdirectory whose name begins with “more.”

Serving configuration from a branch or label

By default, Config Server serves configuration from the master branch in Git. From the client, a specific branch or label can be specified as a third member of the request path to Config Server, as you saw in figure 14.2. But you might find it useful to have Config Server default to a specific label or branch in Git instead of the master branch. The spring.cloud.config.server.git.default-label property overrides the default label or branch.

For instance, consider the following configuration that sets up Config Server to serve configuration from a branch (or label) named “sidework”:

spring:
  cloud:
    config:
      server:
        git:
          uri: http://localhost:10080/tacocloud/tacocloud-config
          default-label: sidework

As configured here, configuration will be served from the “sidework” branch unless otherwise specified by the Config Server client requesting configuration.

Authenticating with the Git backend

It’s quite likely that the backend Git repository your Config Server retrieves configuration from will be secured behind a username and password. In that case, you’ll definitely need to provide the Config Server with the credentials for your Git repository.

The spring.cloud.config.server.username and spring.cloud.config.server.password properties set the username and password for the backend repository. The following Config Server configuration shows how you might set these properties:

spring:
  cloud:
    config:
      server:
        git:
          uri: http://localhost:10080/tacocloud/tacocloud-config
          username: tacocloud
          password: s3cr3tP455w0rd

This sets the username and password to tacocloud and s3cr3tP455w0rd, respectively.

Using curl to pretend to be a Config Server client helps to give you some idea of how the Config Server works. And there’s a lot more that Config Server can do. But the microservices you write won’t be using curl to fetch configuration data. So before we look at any more ways that Config Server can be used to serve configuration data, let’s shift our attention to the microservices and see how you can enable them as Config Server clients.

14.3. Consuming shared configuration

In addition to offering a centralized configuration server, Spring Cloud Config Server also provides a client library that, when included in a Spring Boot application’s build, enables that application as a client of the Config Server.

The easiest way to turn any Spring Boot application into a Config Server client is to add the following dependency to the project’s Maven build:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

This same dependency is also available in the Spring Initializr as the check box labeled Config Client.

When the application is run, autoconfiguration will kick in to automatically register a property source that draws its properties from a Config Server. By default, it assumes that the Config Server is running on localhost and listening on port 8888. But if that’s not the case, you can configure the location of the Config Server by setting the spring.cloud.config.uri property:

spring:
  cloud:
    config:
      uri: http://config.tacocloud.com:8888

Just to be clear, this property must be set local to the application that’s to become a client of the Config Server, such as in the application.yml or application.properties file that’s packaged and deployed with each microservice.

Now that you have a centralized configuration server, almost all configuration will be served from there, and each microservice won’t need to carry much of its own configuration. Typically, you’ll only need to set spring.cloud.config.uri to specify the location of the configuration server and spring.application.name to identify the application to the configuration server.

Which comes first: the Config Server or the Service Registry?

You’re setting up your microservices to learn about the Eureka service registry from the Config Server. This is a common approach to avoid propagating service registry details across every single microservice in an application.

Alternatively, it’s possible to have the Config Server register itself with Eureka and have each microservice discover the Config Server as it would any other service. If you prefer this model, you’ll need to configure the Config Server as a discovery client and set the spring.cloud.config.discovery.enabled property to true. As a result, the Config Server will register itself in Eureka with the name “configserver.”

The downside of this approach is that each service will need to make two calls at startup: one to Eureka to discover the Config Server, followed by one to Config Server to fetch configuration data.

When the application starts up, the property source provided by the Config Server client will make a request to the Config Server. Whatever properties it receives will be made available in the application’s environment. What’s more, those properties will be effectively cached; they’ll be available even if the Config Server goes down. (We’ll look at a few ways to refresh properties when they change in section 14.6.)

So far you’ve kept the configuration served by Config Server fairly simple, targeting all applications and any profile. But sometimes you’ll need to enable configuration that’s unique to a particular application or that should only be available when an application is running with a specific active profile. Let’s take another look at Config Server and see a few more ways to use it, including serving application- and profile-specific properties.

14.4. Serving application- and profile-specific properties

As you’ll recall, when a Config Server client starts up, it makes a request to the Config Server with a request path that contains both the application name as well as the name of an active profile. When serving configuration data, Config Server will consider these values and return application-specific and profile-specific configuration to the client.

From a client perspective, consuming application-specific and profile-specific configuration properties isn’t much different than if you weren’t using Config Server. An application’s name is specified by setting the spring.application.name property (the same one used to identify the application to Eureka). And the active profile(s) can be specified by setting the spring.profiles.active property (often as an environment variable named SPRING_PROFILES_ACTIVE).

Similarly, there’s not much that needs to be done in the Config Server itself to serve properties that target a specific application or profile. What does matter, however, is how those properties are stored in the backing Git repository.

14.4.1. Serving application-specific properties

As we’ve discussed, one of the benefits of using Config Server is that you’re able to share common configuration properties across all of the microservices in an application. That notwithstanding, there are often properties that are unique to one service and that need not (or should not) be shared across all services.

Along with shared configuration, Config Server is able to manage configuration properties that are targeted to a specific application. The trick is to name the configuration file the same as the application’s spring.application.name property.

In the previous chapter, you used spring.application.name to give your microservices names that would be registered to Eureka. That same property is also used by a configuration client to identify itself to the Config Server, so that the Config Server can serve configuration specific to that application.

For example, in the Taco Cloud application where you’ve broken your application down into a handful of microservices named ingredient-service, order-service, taco-service, and user-service, you would have specified those names in each of the service applications’ spring.application.name properties. Then you could create individual configuration YAML files in the Config Server’s Git backend with filenames such as ingredient-service.yml, order-service.yml, taco-service.yml, and user-service.yml. The screenshot in figure 14.3 shows the files in the configuration repository as displayed in the Gogs web application.

Figure 14.3. Application-specific configuration files have names based on each application’s spring.application.name property.

No matter what an application is named, all applications will receive configuration properties from the application.yml file. But each service application’s spring.application.name property will be sent in the request to Config Server (in the first part of the request path), and if there’s a matching configuration file, those properties will also be returned. In the event of duplicate property definitions between the common properties in application.yml and those in an application-specific configuration file, the application-specific properties will take precedence.

It’s worth noting that although figure 14.3 shows YAML configuration files, the same behavior holds true if properties files are checked into the Git repository.

14.4.2. Serving properties from profiles

You saw in chapter 5 how to take advantage of Spring profiles when writing configuration properties so that certain properties will only be applicable when a given profile is active. Spring Cloud Config Server supports profile-specific properties in exactly the same way that you’d use them in an individual Spring Boot application. This includes

  • Providing profile-specific .properties or YAML files, such as configuration files named application-production.yml
  • Including multiple profile configuration groups within a single YAML file, separated with --- and spring.profiles

For example, consider the Eureka configuration that you’re now sharing through the Config Server to all of your application’s microservices. As it stands, it only references a single Eureka development instance. That’s perfect for development environments. But if your services are running in production, you may want them to be configured with references to multiple Eureka nodes.

What’s more, although you’ve set the server.port property to 0 in your development configuration, once the services go into production, they may each run in individual containers that map port 8080 to an external port, thus requiring that the applications all listen on port 8080.

With profiles, you can declare multiple configurations. In addition to the default application.yml file that you pushed into the Config Server’s Git backend, you can push another YAML file named application-production.yml that looks like this:

server:
  port: 8080

eureka:
 client:
   service-url:
     defaultZone: http://eureka1:8761/eureka/,http://eureka2:8761/eureka/

When the application fetches configuration from the Config Server, it will identify which profile is active (in the second part of the request path). If the active profile is production, both sets of properties—application.yml and application-production.yml—will be returned, with those properties in application-production.yml taking precedence over the default properties in application.yml. Figure 14.4 shows what this might look like in the backend Git repository.

Figure 14.4. Profile-specific configuration files can be named with a suffix equal to the name of the active profile.

You can also specify properties that are specific to both a profile and an application using the same naming convention. That is, name the configuration file with the application name, hyphen, profile name.

For example, suppose you need to set properties for the application named ingredient-service that should only be applicable if the production profile is active. In that case, a configuration file named ingredient-service-production.yml could contain those application-specific and profile-specific properties, as illustrated in figure 14.5.

Figure 14.5. Configuration files can be both application-specific and profile-specific.

You can also use property files instead of YAML files in the backend Git repository using this same naming convention for profile-specific properties. But with YAML files, you can also include profile-specific properties in the same file as default profile properties by using a triple-hyphen separator and spring.profiles, as you learned in chapter 5.

14.5. Keeping configuration properties secret

Most configuration served by Config Server may not be all that secret. But you might need Config Server to serve properties containing sensitive information such as passwords or security tokens that are best kept secret in the backend repository.

Config Server offers two options for working with secret configuration properties:

  • Writing encrypted values in configuration files stored in Git
  • Using HashiCorp’s Vault as a backend store for Config Server in addition to (or in place of) Git

Let’s take a look at how each of these options can be used with Config Server to keep configuration properties secret. We’ll start with writing encrypted properties to the Git backend.

14.5.1. Encrypting properties in Git

In addition to serving unencrypted values, Config Server can also serve encrypted values written in configuration files stored in Git. The key to working with encrypted data stored in Git is literally a key—an encryption key.

To enable encrypted properties, you need to configure the Config Server with an encryption key that it will use to decrypt values before serving them to its client applications. Config Server supports both symmetric and asymmetric keys. To set a symmetric key, set the encrypt.key property in the Config Server’s own configuration to some value that will act as the encryption and decryption key:

encrypt:
  key: s3cr3t

It’s important that this property be set in bootstrap configuration (for example, bootstrap.properties or bootstrap.yml) so that it’s loaded and available before autoconfiguration enables the Config Server.

For a bit tighter security, you can opt to configure Config Server with an asymmetric RSA key pair or a reference to a keystore. To create such a key, you can use the keytool command-line tool:

keytool -genkeypair -alias tacokey -keyalg RSA 
-dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" 
-keypass s3cr3t -keystore keystore.jks -storepass l3tm31n

The resulting keystore will be written to a file named keystore.jks. You can keep the keystore file on the filesystem or place it in the application itself. In either event, you’ll need to configure the location and credentials for the keystore in the Config Server’s bootstrap.yml file.

Note

In order to use encryption in Config Server, you must have installed the Java Cryptography Extensions Unlimited Strength policy files. See Oracle’s Java SE page for details: http://www.oracle.com/technetwork/java/javase/downloads/index.html.

For example, suppose you choose to package the keystore in the application itself, at the root of the classpath. Then you can configure the Config Server to use that keystore with the following properties:

encrypt:
  key-store:
    alias: tacokey
    location: classpath:/keystore.jks
    password: l3tm31n
    secret: s3cr3t

With a key or a keystore in place, you now must encrypt some data. Config Server exposes an /encrypt endpoint to help. All you must do is submit a POST request to the /encrypt endpoint with some data to be encrypted. For example, suppose you’d like to encrypt a password to the MongoDB database. Using curl, you can encrypt the password like this:

$ curl localhost:8888/encrypt -d "s3cr3tP455w0rd"
93912a660a7f3c04e811b5df9a3cf6e1f63850cdcd4aa092cf5a3f7e1662fab7

After submitting the POST request, you’ll receive an encrypted value as the response. All that’s left is to copy that value and paste it into the configuration that’s hosted in the Git repository.

To set the MongoDB password, add the spring.data.mongodb.password property to the application.yml file stored in the Git repository:

spring:
  data:
    mongodb:
      password: '{cipher}93912a660a7f3c04e811b5df9a3cf6e1f63850...'

Notice that the value given to spring.data.mongodb.password is wrapped in single quotes (') and is prefixed with {cipher}. This is a clue to Config Server that the value is an encrypted value and not a plain, unencrypted value.

After committing and pushing the changes in application.yml file to the Git repository, Config Server is ready to serve encrypted properties. To see it in action, use curl to pretend to be a Config Server client:

$ curl localhost:8888/application/default | jq
{
  "name": "app",
  "profiles": [
    "prof"
  ],
  "label": null,
  "version": "464adfd43485182e4e0af08c2aaaa64d2f78c4cf",
  "state": null,
  "propertySources": [
    {
      "name": "http://localhost:10080/tacocloud/tacocloud-config/application.yml",
      "source": {
        "spring.data.mongodb.password": "s3cr3tP455w0rd"
      }
    }
  ]
}

As you can see, the value served for spring.data.mongodb.password is served in a decrypted form. By default, any encrypted values served by Config Server are only encrypted while at rest in the backend Git repository; they’ll be decrypted before being served. This means the client application that consumes the configuration doesn’t require any special code or configuration to receive properties that are encrypted in Git.

If you’d prefer that Config Server serve encrypted properties in their still-encrypted form, you can set the spring.cloud.config.server.encrypt.enabled property to false:

spring:
  cloud:
    config:
      server:
        git:
          uri: http://localhost:10080/tacocloud/tacocloud-config
        encrypt:
          enabled: false

This results in Config Server serving all property values, including encrypted property values, exactly as they’re set in the Git repository. Pretending once more to be a client, the curl command reveals the effect of disabling decryption:

$ curl localhost:8888/application/default | jq
{
  ...
  "propertySources": [
    {
      "name": "http://localhost:10080/tacocloud/tacocloud-config/application.yml",
      "source": {
        "spring.data.mongodb.password": "{cipher}AQA4JeVhf2cRXW..."
      }
    }
  ]
}

Of course, if the client is receiving encrypted property values, the client is now responsible for decrypting them on its own.

Although it’s possible to store encrypted secrets in Git to be served by Config Server, we’ve seen that encryption is not native to Git. It requires effort on your part to encrypt any data written to the backing Git repository. Moreover, unless you push the burden of decryption to the Config Server client applications, the secret information is served decrypted through the Config Server API for anyone who may ask. Let’s take a look at another Config Server backend option that only serves secrets to those who are authorized to see them.

14.5.2. Storing secrets in Vault

HashiCorp Vault is a secret-management tool. This means that in contrast to Git, Vault’s core feature is handling secret information natively. For sensitive configuration data, this makes Vault a much more attractive option as a backend to Config Server.

To get started with Vault, download and install the vault command-line tool by following the installation instructions on the Vault website: https://www.vaultproject.io/intro/getting-started/install.html. In this section, you’ll use the vault command both for managing secrets as well as starting a Vault server.

Starting a Vault server

Before you can write secrets and serve them with Config Server, you’ll need to start a Vault server. For your purposes, the easiest way to do so is to start the server in development mode with the following commands:

$ vault server -dev -dev-root-token-id=roottoken
$ export VAULT_ADDR='http://127.0.0.1:8200'
$ vault status

The first command starts a Vault server in development mode with a root token whose ID is roottoken. Development mode, as its name suggests, is a simpler, yet not entirely secure, runtime of Vault. It shouldn’t be used in a production setting, but it’s quite convenient when working with Vault during your development workflow.

Note

The Vault server is a feature-filled and robust secret-management server. There’s nowhere near enough space in this chapter to talk about running the Vault server beyond its simple use in development mode. I strongly recommend you get to know Vault in greater detail by reading the Vault documentation at https://www.vaultproject.io/docs/index.html before attempting to use Vault in a production setting.

All access to a Vault server requires that a token be presented to the server. The root token is an administrative token that, among other things, allows you to create more tokens. It can also be used to read and write secrets. If you don’t specify a root token when starting the server in development mode, one will be generated for you and written to the logs at startup time. For ease of use, I recommend setting the root token to an easy-to-remember value, such as roottoken.

Once the development-mode server is started, it will be listening on port 8200 on the local machine. So that the vault command line knows where the Vault server is at, it’s important to set the VAULT_ADDR environment variable, as in the second command in the previous code snippet.

Finally, the vault status command verifies that the previous two commands worked as expected. You should receive a list of about a half-dozen properties describing the configuration of the Vault server, including whether the Vault is sealed or not. (It shouldn’t be sealed in development mode.)

If you’re working with Vault 0.10.0 or later, there are a couple of other commands you’ll need to perform to get Vault ready for working with Config Server. Some changes to how Vault works result in one of the standard secret backends being incompatible with Config Server. The following two commands recreate the backend whose name is secret to be compatible with Config Server:

$ vault secrets disable secret
$ vault secrets enable -path=secret kv

These steps aren’t required if you’re working with an older version of Vault.

Writing secrets to Vault

The vault command makes it easy to write secrets into Vault. For example, suppose you want to store the password to MongoDB—the spring.data.mongodb.password property—in Vault instead of in Git. Using the vault command, you can do this:

$ vault write secret/application spring.data.mongodb.password=s3cr3t

Figure 14.6 breaks down the vault write command, explaining what role each part of it plays in writing the secret to Vault.

Figure 14.6. Writing a secret to Vault with the vault command

The most significant pieces to pay attention to for now are the secret path, key, and value. The secret path, much like a filesystem path, allows you to group related secrets in a given path and other secrets in different paths. The secret/ prefix to the path identifies the Vault backend—in this case a key-value backend named “secret.”

The secret key and value are the actual secrets that you’re writing to Vault. When writing secrets that will be served by Config Server, it’s important to use secret keys that are equal to the configuration properties they’ll be used for.

You can verify that the secret was written with the vault read command:

$ vault read secret/application
Key                             Value
---                             -----
refresh_interval                768h
spring.data.mongodb.password    s3cr3t

When writing secrets to a given path, be aware that every write to a given path will overwrite any secrets previously written to that path. For example, suppose you also wanted to write the MongoDB username to Vault at the same path as in the previous example. You couldn’t simply write the spring.data.mongodb.username secret by itself—doing so would result in the spring.data.mongodb.password secret being lost. Instead, you must write them both at the same time:

% vault write secret/application 
              spring.data.mongodb.password=s3cr3t 
              spring.data.mongodb.username=tacocloud

Now that you’ve written a few secrets to Vault, let’s see how you can enable Vault as a backend source of properties for Config Server.

Enabling a Vault backend in Config Server

To add Vault as a backend for the Config Server, the least you’ll need to do is add vault as an active profile. In the Config Server’s application.yml file, that will look like this:

spring:
  profiles:
    active:
    - vault
    - git

As shown here, both the vault and git profiles are active, allowing Config Server to serve configuration from both Vault and Git. Generally, you’d only write sensitive configuration properties to Vault and continue to use a Git backend for those properties that don’t require secrecy. But if you wish to write all configuration to Vault or have no need for a Git backend, you can set spring.profiles.active to vault and have no Git backend at all.

By default, Config Server will assume that Vault is running on localhost, listening on port 8200. But you can change that in the Config Server’s configuration like this:

spring:
  cloud:
    config:
      server:
        git:
          uri: http://localhost:10080/tacocloud/tacocloud-config
          order: 2
        vault:
          host: vault.tacocloud.com
          port: 8200
          scheme: https
          order: 1

The spring.cloud.config.server.vault.* properties let you override the default assumptions about Vault made by Config Server. Here you’re telling Config Server that Vault’s API can be accessed at https://vault.tacocloud.com:8200.

Notice that you left the Git configuration in place, assuming that Vault and Git will split responsibility for providing configuration. The order property specifies that secrets provided by Vault will take precedence over any properties provided by Git.

Once Config Server is configured to use the Vault backend, you can try it out by using curl to pretend to be a client:

[habuma:habuma]% curl localhost:8888/application/default | jq
{
  "timestamp": "2018-04-29T23:33:22.275+0000",
  "status": 400,
  "error": "Bad Request",
  "message": "Missing required header: X-Config-Token",
  "path": "/application/default"
}

Oh no! It looks like something went wrong! In fact, this error is an indication that Config Server is serving secrets from Vault, but that the request hasn’t included the Vault token.

It’s important that all requests to Vault include an X-Vault-Token header in the request. Rather than configure that token in the Config Server itself, each Config Server client will need to include the token in an X-Config-Token header in all requests to the Config Server. Config Server will transfer the token it receives in the X-Config-Token header to the X-Vault-Token header in requests it sends to Vault.

As you can see, for lack of a token in the request, Config Server will refuse to serve any properties, even those from Git, because Vault demands a token before divulging any of its secrets. This is an interesting side effect of using Vault alongside Git—even Git properties are indirectly hidden by the Config Server unless a valid token is provided.

Try it again, this time adding an X-Config-Token header to the request:

$ curl localhost:8888/application/default
        -H"X-Config-Token: roottoken" | jq

The X-Config-Token header in the request should yield better results, including the secrets you’ve written to Vault. The token given here is the root token you specified when starting the Vault server in development mode, although it could be any token created within the Vault server that’s valid, non-expired, and that has been granted access to the Vault secret backend.

Setting the Vault token in Config Server clients

Obviously, you won’t be able to use curl in each of your microservices to specify the token when consuming properties from the Config Server. Instead, you’ll need to add a little bit of configuration to each of your service application’s local configuration:

spring:
  cloud:
    config:
      token: roottoken

The spring.cloud.config.token property tells the Config Server client to include the given token value in all requests it makes to the Config Server. This property must be set in the application’s local configuration (not stored in Config Server’s Git or Vault backend) so that Config Server can pass it along to Vault and be able to serve properties.

Writing application- and profile-specific secrets

When served by Config Server, secrets written to the application path will be served to all applications, regardless of their name. If you need to write secrets that are specific to a given application, replace the application portion of the path with the application name. For example, the following vault write command will write a secret specific to an application whose name (as identified by its spring.application.name property) is ingredient-service:

$ vault write secret/ingredient-service 
              spring.data.mongodb.password=s3cr3t

Similarly, if you don’t specify a profile, secrets written to Vault will be served as part of the default profile. That is, clients will receive those secrets regardless of what their active profile may be. You may, however, write secrets to a specific profile like this:

% vault write secret/application,production 
              spring.data.mongodb.password=s3cr3t 
              spring.data.mongodb.username=tacocloud

This writes the secrets such that they will only be served to applications whose active profile is production.

14.6. Refreshing configuration properties on the fly

As I’m writing this chapter, I’m on a plane that was pulled back to the gate for a maintenance issue. It was nothing serious, and if you’re reading this, you know that the mechanics did their job satisfactorily. Even so, the interesting thing about maintenance on an airplane is that it requires the plane to be on the ground. There’s not much that can be done while in flight.

In contrast, in the Star Wars movies, if Luke Skywalker’s or Poe Dameron’s X-Wing Fighter needed maintenance, the onboard mech droid could be deployed to address the issue, even while the X-Wing was in battle.

Traditionally, application maintenance, including configuration changes, has required that an application be redeployed or at least restarted. The application would need to be brought back to the gate, so to speak, for lack of a mech droid to adjust even the smallest configuration property. But that’s unacceptable for cloud-native applications. We’d like to be able to change configuration properties on the fly, without even bringing the application down.

Fortunately, Spring Cloud Config Server supports the ability to refresh configuration properties of running applications with zero downtime. Once the changes have been pushed to the backing Git repository or Vault secret store, each microservice in the application can immediately be refreshed with the new configuration in one of two ways:

  • Manual The Config Server client enables a special Actuator endpoint at /actuator/refresh. An HTTP POST request to that endpoint on each service will force the config client to retrieve the latest configuration from its backends.
  • Automatic A commit hook in the Git repository can trigger a refresh on all services that are clients of the Config Server. This involves another Spring Cloud project called Spring Cloud Bus for communicating between the Config Server and its clients.

Each option has its pros and cons. Manual refresh gives more precise control over when services are updated with fresh configuration, but it requires an individual HTTP request to be issued to each instance of each microservice. Automatic refresh applies updated configuration instantly to all microservices in an application, but it’s ultimately triggered from a commit to the configuration repository, which may be a bit too scary for some projects.

Let’s take a look at each option. Then I’ll let you decide which you’d prefer to apply in your projects.

14.6.1. Manually refreshing configuration properties

In chapter 16, we’re going to look at the Spring Boot Actuator, one of the foundational elements of Spring Boot that enables runtime insight and some limited manipulation of runtime state, such as logging levels. But for now, we’ll look at a specific Actuator feature that’s only enabled in applications that are configured as Spring Cloud Config Server clients.

Whenever you enable an application to be a client of the Config Server, the autoconfiguration in play also configures a special Actuator endpoint for refreshing configuration properties. To make use of this endpoint, you’ll need to include the Actuator starter dependency along with the Config Client dependency in your project’s build:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

As you may have guessed, this dependency is also available from the Spring Initializr by checking the Actuator check box.

With the Actuator in play in the running config client application, you can refresh the configuration properties from the backend repositories any time you wish by submitting an HTTP POST request to /actuator/refresh.

To see this in action, let’s suppose you have a @ConfigurationProperties-annotated class named GreetingProps:

@ConfigurationProperties(prefix="greeting")
@Component
public class GreetingProps {
  private String message;

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }
}

Additionally, you also have a controller class that’s injected with GreetingProps and that echoes the value of the message property when handling a GET request:

@RestController
public class GreetingController {

  private final GreetingProps props;

  public GreetingController(GreetingProps props) {
    this.props = props;
  }

  @GetMapping("/hello")
  public String message() {
    return props.getMessage();
  }

}

Meanwhile, in your configuration Git repository, you have an application.yml file that has the following property set:

greeting:
  message: Hello World!

With both the Config Server and this simple hello-world config client running, an HTTP GET request to /hello will yield the following response:

$ curl localhost:8080/hello
Hello World!

Now, without restarting either the Config Server or the hello-world application, change the application.yml file and push it into the backend Git repository such that the greeting.message property looks like this:

greeting:
  message: Hiya folks!

If you make the same GET request to the hello-world application, you’ll still get the same “Hello World!” response, even though the configuration has changed in Git. But you can force a refresh by POSTing to the refresh endpoint:

$ curl localhost:53419/actuator/refresh -X POST
["config.client.version","greeting.message"]

Notice that the response includes a JSON array of property names that have changed. Included in that array is your greeting.message property. It also includes a change to the config.client.version property, which contains the hash value of the Git commit that the current configuration comes from. Because the configuration is now based on a new Git commit, this property will change every time there’s any change in the backend configuration repository.

The response from the POST request tells you that greeting.message changed. But the real proof is when you issue a GET request to the /hello path again:

$ curl localhost:8080/hello
Hiya folks!

Without restarting the application or even restarting the Config Server, the application is now serving a brand new value for the greeting.message property!

The /actuator/refresh endpoint is great when you want full control over when an update to configuration properties takes place. But if your application is made up of several microservices (and perhaps several instances of each of those services), it can be tedious to propagate configuration to all of them. Let’s take a look at how you can have configuration changes applied automatically, all at once.

14.6.2. Automatically refreshing configuration properties

As an alternative to manually refreshing properties on all Config Server clients in an application, Config Server can automatically notify all clients of changes to configuration by way of another Spring Cloud project called Spring Cloud Bus. Figure 14.7 illustrates how it works.

Figure 14.7. Config Server, along with Spring Cloud Bus, can broadcast changes to applications so that their properties can be refreshed automatically when there are changes.

The property refresh process shown in figure 14.7 can be summarized like this:

  • A webhook is created on the configuration Git repository to notify Config Server of any changes (such as any pushes) to the Git repository. Webhooks are supported by many Git implementations, including GitHub, GitLab, Bitbucket, and Gogs.
  • Config Server reacts to webhook POST requests by broadcasting a message regarding the change by way of a message broker, such as RabbitMQ or Kafka.
  • Individual Config Server client applications subscribed to the notifications react to the notification messages by refreshing their environments with new property values from the Config Server.

The effect is that all participating Config Server client applications will always have the latest configuration property values from Config Server almost immediately following those changes being pushed to the backend Git repository.

There are several moving parts in play when using automatic property refresh with Config Server. Let’s review the changes that you’re about to make to get a high-level understanding of what needs to be done:

  • You’ll need a message broker available to handle the messaging between Config Server and its clients. You may choose either RabbitMQ or Kafka.
  • A webhook will need to be created in the backend Git repository to notify Config Server of any changes.
  • Config Server will need to be enabled with the Config Server monitor dependency (which provides the endpoint that will handle webhook requests from the Git repository) and either the RabbitMQ or Kafka Spring Cloud Stream dependency (for publishing property change messages to the broker).
  • Unless the message broker is running locally with the default settings, you’ll need to configure the details for connecting to the broker in both the Config Server and in all of its clients.
  • Each Config Server client application will need the Spring Cloud Bus dependency.

I’m going to assume that the prerequisite message broker (RabbitMQ or Kafka ... your choice) is already running and ready to channel property change messages. You’ll start by applying changes to the Config Server to handle webhook update requests.

Creating a webhook

Many Git server implementations support the creation of webhooks to notify applications of changes, including pushes, to a Git repository. The specifics of setting up webhooks will vary from implementation to implementation, making it difficult to describe them all here. I will, however, show you how to set up a webhook for a Gogs repository.

I choose Gogs because it’s easy to run locally and to have a webhook to POST to your locally running application (something that’s difficult with GitHub). Also, because the process for setting up a webhook with Gogs is almost identical to that for GitHub, describing the Gogs process will indirectly give you the steps needed to set up a webhook for GitHub.

First, visit your configuration repository in the web browser and click the Settings link, as shown in figure 14.8. (The location of the Settings link is slightly different in GitHub, but it has a similar appearance.)

Figure 14.8. Click the Settings button in Gogs or GitHub to get started creating a webhook.

This will take you to the repository’s settings page, which includes a menu of settings categories on the left. Choose the Webhooks item from the menu. This will display a page similar to what’s shown in figure 14.9.

Figure 14.9. The Add Webhook button under the Webhooks menu opens the form for creating a webhook.

From the webhooks settings page, click the Add Webhook button. In Gogs, this will produce a dropdown list of options for different types of webhooks. Select the Gogs option, as shown in figure 14.9. You’ll then be presented with a form to create a new webhook, as shown in figure 14.10.[1]

1

GitHub doesn’t have a dropdown list of webhook options. Instead, you’ll be taken directly to the webhook creation form after clicking Add Webhook.

Figure 14.10. To create a webhook, specify the Config Server’s /monitor URL and JSON payload.

The Add Webhook form has several fields, but the two most significant are Payload URL and Content Type. Soon you’ll be outfitting Config Server to handle webhook POST requests. When you do, Config Server will be able to handle webhook requests at a path of /monitor. Therefore, the Payload URL field should be set with a URL that references the /monitor endpoint on your Config Server. Because I’m running Gogs in a Docker container, the URL I’ve given in figure 14.10 is http://host.docker.internal:8888/monitor, which has a hostname of host.docker.internal. This hostname enables the Gog server to see past the boundaries of its container to the Config Server running on the host machine.[2]

2

In a Docker container, “localhost” means the container itself, not the Docker host.

I’ve also set the Content Type field to application/json. This is important because the Config Server’s /monitor endpoint doesn’t support application/x-www-form-urlencoded, the other option for content type.

If set, the Secret field will include a header in the webhook POST request named X-Gogs-Signature (or X-Hub-Signature in the case of GitHub) that contains an HMAC-SHA256 digest (or HMAC-SHA1 for GitHub) with the given secret. At this time, Config Server’s /monitor endpoint doesn’t recognize the signature header, so you can leave this field blank.

Finally, you only care about push requests to the configuration repository, and you certainly wish for the webhook to be active, so you make sure that the Just the Push Event radio button and the Active check box are selected. Click the Add Webhook button at the end of the form, and the webhook will be created and will start sending POST requests to the Config Server for every push made to the repository.

Now you must enable the /monitor endpoint in Config Server to handle those requests.

Handling webhook updates in Config Server

Enabling the /monitor endpoint in Config Server is a simple matter of adding the spring-cloud-config-monitor dependency to the Config Server’s build. In a Maven pom.xml file, the following dependency will do the trick:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-monitor</artifactId>
</dependency>

With that dependency in place, autoconfiguration will kick in to enable the /monitor endpoint. But it’s no good unless Config Server also has a means of broadcasting the change notifications. For that, you’ll need to add another dependency for Spring Cloud Stream.

Spring Cloud Stream is another one of the Spring Cloud projects; it enables the creation of services that communicate by way of some underlying binding mechanism, either RabbitMQ or Kafka. The services are written in such a way that they don’t know much about how they’re being used, and they accept data from the stream for processing, return data to the stream for handling by downstream services, or both.

The /monitor endpoint uses Spring Cloud Stream to publish notification messages to participating Config Server clients. To avoid being hardcoded to any particular messaging implementation, the monitor acts as a Spring Cloud Stream source, publishing messages into the stream and letting the underlying binding mechanism deal with the specifics of sending the messages.

If you’re using RabbitMQ, you’ll need to include the Spring Cloud Stream RabbitMQ binding dependency in the Config Server’s build:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

On the other hand, if Kafka’s more your style, you’ll need the following Spring Cloud Stream Kafka dependency instead:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>

With the dependencies in place, the Config Server is almost ready to participate in automatic property refresh. In fact, if your RabbitMQ or Kafka brokers are running locally and with default settings, the Config Server is good to go. But if your message brokers are running somewhere other than localhost, on some nondefault port, or if you’ve changed the credentials to access the broker, you’ll need to set a few properties in the Config Server’s own configuration.

For a RabbitMQ binding, the following entries in application.yml can be used to override the default values:

spring:
  rabbitmq:
    host: rabbit.tacocloud.com
    port: 5672
    username: tacocloud
    password: s3cr3t

You only need to set the properties that are different for your RabbitMQ broker, even though you’ve set them all here.

If you’re using Kafka, a similar set of properties is available:

spring:
  kafka:
    bootstrap-servers:
    - kafka.tacocloud.com:9092
    - kafka.tacocloud.com:9093
    - kafka.tacocloud.com:9094

You may recognize these properties from chapter 8, where we looked at messaging with Kafka. In fact, configuring RabbitMQ and Kafka backends for automatic refresh are much the same as for any other use of those brokers in Spring.

Creating a Gogs notification extractor

Each Git implementation has its own take on what the webhook POST request should look like. This makes it important for the /monitor endpoint to be able to understand different data formats when handling webhook POST requests. Under the covers of the /monitor endpoint is a set of components that examine the POST request, try to determine what kind of Git server the request came from, and map the request data to a common notification type that’s sent to each client.

Out of the box, Config Server comes with support for several popular Git implementations, such as GitHub, GitLab, and Bitbucket. If you’re using one of those Git implementations, nothing special is required. But as I write this, Gogs is not yet officially supported.[3] Therefore, you’ll need to include a Gogs-specific notification extractor in your project if you’re using Gogs as your Git implementation.

3

I submitted a pull request to the Config Server project to add Gogs support. Once it’s merged, this section of the book will no longer be relevant. See https://github.com/spring-cloud/spring-cloud-config/pull/1003 for the status of this pull request.

The next listing shows the notification extractor implementation that I used in Taco Cloud for Gogs integration.

Listing 14.1. A Gogs notification extractor implementation
package tacos.gogs;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.cloud.config.monitor.PropertyPathNotification;
import
     org.springframework.cloud.config.monitor.PropertyPathNotificationExtractor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;

@Component
@Order(Ordered.LOWEST_PRECEDENCE - 300)
public class GogsPropertyPathNotificationExtractor
    implements PropertyPathNotificationExtractor {

  @Override
  public PropertyPathNotification extract(
      MultiValueMap<String, String> headers,
      Map<String, Object> request) {
    if ("push".equals(headers.getFirst("X-Gogs-Event"))) {
      if (request.get("commits") instanceof Collection) {
        Set<String> paths = new HashSet<>();
        @SuppressWarnings("unchecked")
        Collection<Map<String, Object>> commits =
            (Collection<Map<String, Object>>) request
            .get("commits");
        for (Map<String, Object> commit : commits) {
          addAllPaths(paths, commit, "added");
          addAllPaths(paths, commit, "removed");
          addAllPaths(paths, commit, "modified");
        }
        if (!paths.isEmpty()) {
          return new PropertyPathNotification(
              paths.toArray(new String[0]));
        }
      }
    }
    return null;
  }

  private void addAllPaths(Set<String> paths,
                           Map<String, Object> commit,
                           String name) {
    @SuppressWarnings("unchecked")
    Collection<String> files =
            (Collection<String>) commit.get(name);
    if (files != null) {
      paths.addAll(files);
    }
  }
}

The details of how GogsPropertyPathNotificationExtractor works is mostly irrelevant to our discussion and will become even less relevant once Spring Cloud Config Server includes Gogs support out of the box. Therefore, I won’t dwell on it much and only include it here as an artifact of interest if you’re using Gogs.

Enabling auto-refresh in Config Server clients

Enabling automatic property refresh in Config Server clients is even easier than in Config Server itself. Only a single dependency is required:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

This adds the AMQP (for example, RabbitMQ) Spring Cloud Bus starter to the build. If you’re using Kafka, you should use the following dependency instead:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>

With the appropriate Spring Cloud Bus starter in place, autoconfiguration will kick in as the application starts up and will automatically bind itself to a RabbitMQ broker or Kafka cluster running locally. If your RabbitMQ or Kafka is running elsewhere, you’ll need to configure its details in each client application just as you did for the Config Server itself.

Now that both the Config Server and its clients are configured for automatic refresh, fire everything up and give it a spin by making a change (any change you want) to application.yml. When you push to the Git repository, you’ll immediately see the change take effect in the client applications.

Summary

  • Spring Cloud Config Server offers a centralized source of configuration data to all microservices that make up a larger microservice-architected application.
  • The properties served by Config Server are maintained in a backend Git or Vault repository.
  • In addition to global properties, which are exposed to all Config Server clients, Config Server can also serve profile-specific and application-specific properties.
  • Sensitive properties can be kept secret by encrypting them in a backend Git repository or by storing them as secrets in a Vault backend.
  • Config Server clients can be refreshed with new properties either manually via an Actuator endpoint or automatically with Spring Cloud Bus and Git webhooks.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset