- Spring Cloud Config
- Requirements
- What You Will Learn
- Exercises
- Set up the
app-config
Repo - Set up
config-server
- Set up
greeting-config
- Changing Logging Levels
- Turning on a Feature with
@ConfigurationProperties
- Reinitializing Beans with
@RefreshScope
- Override Configuration Values By Profile
- Deploy the
config-server
andgreeting-config
Apps to PCF - Refreshing Application Configuration at Scale with Cloud Bus
- Set up the
Estimated Time: 60 minutes
- How to set up a git repository to hold configuration data
- How to set up a config server (
config-server
) with a git backend - How to set up a client (
greeting-config
) to pull configuration from theconfig-server
- How to change log levels for a running application (
greeting-config
) - How to use
@ConfigurationProperties
to capture configuration changes (greeting-config
) - How to use
@RefreshScope
to capture configuration changes (greeting-config
) - How to override configuration values by profile (
greeting-config
) - How to use Cloud Bus to notify applications (
greeting-config
) to refresh configuration at scale
To start, we need a repository to hold our configuration.
-
Fork the configuration repo to your account. Browse to: https://github.com/pivotal-enablement/app-config. Then fork the repo.
-
GitHub displays your new fork. Copy the HTTPS clone URL from your fork.
-
Open a new terminal window and clone the fork you just created (you may want to create a common location for your GitHub repos, such as ~/repos):
$ cd [location of your github repos, e.g. ~/repos]
$ git clone <Your fork of the app-config repo - HTTPS clone URL>
$ cd app-config
Notice that this repository is basically empty. This repository will be the source of configuration data.
- Review the following file:
$CLOUD_NATIVE_APP_LABS_HOME/config-server/pom.xml
By addingspring-cloud-config-server
to the classpath, this application is eligible to embed a config-server.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
- Review the following file:
$CLOUD_NATIVE_APP_LABS_HOME/config-server/src/main/java/io/pivotal/ConfigServerApplication.java
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
Note the @EnableConfigServer
annotation. That embeds the config-server.
- Set the GitHub repository for the
config-server
. This will be the source of the configuration data. Edit the$CLOUD_NATIVE_APP_LABS_HOME/config-server/src/main/resources/application.yml
file.
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/d4v3r/app-config.git #<-- CHANGE ME
Make sure to substitute your forked app-config repository. Do not use the literal above.
- Open a terminal window and start the
config-server
.
$ cd $CLOUD_NATIVE_APP_LABS_HOME/config-server
$ mvn clean spring-boot:run
Your config-server will be running locally once you see a "Started ConfigServerApplication..." message. You will not be returned to a command prompt and must leave this window open.
- Let's add some configuration. Edit your fork of the
app-config
repo. Create a file calledhello-world.yml
. Add the content below to the file and push the changes back to GitHub. Be sure to substitute your name for<Your name>
.
name: <Your Name>
- Confirm the
config-server
is up and configured with a backing git repository by calling one of its endpoints. Because the returned payload is JSON, we recommend using something that will pretty-print the document. A good tool for this is the Chrome JSON Formatter plug-in.
Open a browser window and fetch the following url: http://localhost:8888/hello-world/default
What Just Happened?
The config-server
exposes several endpoints to fetch configuration.
In this case, we are manually calling one of those endpoints (/{application}/{profile}[/{label}]
) to fetch configuration. We substituted our example client application hello-world
as the {application}
and the default
profile as the {profile}
. We didn't specify the label to use so master
is assumed. In the returned document, we see the configuration file hello-world.yml
listed as a propertySource
with the associated key/value pair. This is just an example, as you move through the lab you will add configuration for greeting-config
(our client application).
- Review the following file:
$CLOUD_NATIVE_APP_LABS_HOME/greeting-config/pom.xml
By addingspring-cloud-starter-config
to the classpath, this application will consume configuration from the config-server.greeting-config
is a config client.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
- Review the
$CLOUD_NATIVE_APP_LABS_HOME/greeting-config/src/main/resources/bootstrap.yml
spring:
application:
name: greeting-config
spring.application.name
defines the name of the application. This value is used in several places within Spring Cloud: locating configuration files by name, service discovery/registration by name, etc. In this lab, it will be used to locate config files for the greeting-config
application.
Absent from the bootstrap.yml is the spring.cloud.config.uri
, which defines how greeting-config
reaches the config-server
. Since there is no spring.cloud.config.uri
defined in this file, the default value of http://localhost:8888
is used. Notice that this is the same host and port of the config-server
application.
- Open a new terminal window. Start the
greeting-config
application:
$ cd $CLOUD_NATIVE_APP_LABS_HOME/greeting-config
$ mvn clean spring-boot:run
- Confirm the
greeting-config
app is up. Browse to http://localhost:8080. You should see a "Greetings!!!" message.
What Just Happened?
At this point, you connected the greeting-config
application with the config-server
. This can be confirmed by reviewing the logs of the greeting-config
application.
greeting-config
log output:
2015-09-18 13:48:50.147 INFO 15706 --- [lication.main()] b.c.PropertySourceBootstrapConfiguration :
Located property source: CompositePropertySource [name='configService', propertySources=[]]
There is still no configuration in the git repo, but at this point we have everything wired (greeting-config
→ config-server
→ app-config
repo) so we can add configuration parameters/values and see the effects in out client application greeting-config
.
Configuration parameters/values will be added as we move through the lab.
As your first use of the config-server, you will change the logging level of the greeting-config application.
- View the
getGreeting()
method of theGreetingController
class ($CLOUD_NATIVE_APP_LABS_HOME/greeting-config/src/main/java/io/pivotal/greeting/GreetingController.java
).
@RequestMapping("/")
String getGreeting(Model model){
logger.debug("Adding greeting");
model.addAttribute("msg", "Greetings!!!");
if(greetingProperties.isDisplayFortune()){
logger.debug("Adding fortune");
model.addAttribute("fortune", fortuneService.getFortune());
}
//resolves to the greeting.vm velocity template
return "greeting";
}
We want to see these debug messages. By default only log levels of ERROR
, WARN
and INFO
will be logged. You will change the log level to DEBUG
using
configuration. All log output will be directed to System.out
& System.error
by default, so logs will be output to the terminal window(s).
- Edit your fork of the
app-config
repo. Create a file calledgreeting-config.yml
. Add the content below to the file and push the changes back to GitHub.
logging:
level:
io:
pivotal: DEBUG
greeting:
displayFortune: false
quoteServiceURL: http://quote-service-dev.cfapps.io/quote
This file has several configuration parameters that will be used throughout this lab. For this exercise, we have set the log level for classes in the io.pivotal
package to DEBUG
.
-
While watching the
greeting-config
terminal, refresh the http://localhost:8080 url. Notice there are noDEBUG
logs yet. -
Does the
config-server
see the change in your git repo? Let's check what theconfig-server
is serving. Browse to http://localhost:8888/greeting-config/default
The propertySources value has changed! The config-server
has picked up the changes to the git repo. (If you don't see the change,
verify that you have pushed the greeting-config.yml to GitHub.)
- Review the following file:
$CLOUD_NATIVE_APP_LABS_HOME/greeting-config/pom.xml
. For thegreeting-config
application to pick up the configuration changes, it must include theactuator
dependency. Theactuator
adds several additional endpoints to the application for operational visibility and tasks that need to be carried out. In this case, we have added the actuator so that we can use the/refresh
endpoint, which allows us to refresh the application config on demand.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- For the
greeting-config
application to pick up the configuration changes, it must be told to do so. Notifygreeting-config
app to pick up the new config by POSTing to thegreeting-config
/refresh
endpoint. Open a new terminal window and execute the following:
$ curl -X POST http://localhost:8080/refresh
- Refresh the
greeting-config
http://localhost:8080 url while viewing thegreeting-config
terminal. You should see the debug line "Adding greeting"
Congratulations! You have used the config-server to change the logging level of the greeting-config application.
Use of @ConfigurationProperties
is a common way to externalize, group, and validate configuration in Spring applications. @ConfigurationProperties
beans are automatically rebound when application config is refreshed.
- Review
$CLOUD_NATIVE_APP_LABS_HOME/greeting-config/src/main/java/io/pivotal/greeting/GreetingProperties.java
. Use of the@ConfigurationProperties
annotation allows for reading of configuration values. Configuration keys are a combination of theprefix
and the field names. In this case, there is one field (displayFortune
). Thereforegreeting.displayFortune
is used to turn the display of fortunes on/off. Remaining code is typical getter/setters for the fields.
@ConfigurationProperties(prefix="greeting")
public class GreetingProperties {
private boolean displayFortune;
public boolean isDisplayFortune() {
return displayFortune;
}
public void setDisplayFortune(boolean displayFortune) {
this.displayFortune = displayFortune;
}
}
- Review
$CLOUD_NATIVE_APP_LABS_HOME/greeting-config/src/main/java/io/pivotal/greeting/GreetingController.java
. Note how thegreetingProperties.isDisplayFortune()
is used to turn the display of fortunes on/off. There are times when you want to turn features on/off on demand. In this case, we want the fortune feature "on" with our greeting.
@EnableConfigurationProperties(GreetingProperties.class)
public class GreetingController {
Logger logger = LoggerFactory
.getLogger(GreetingController.class);
@Autowired
GreetingProperties greetingProperties;
@Autowired
FortuneService fortuneService;
@RequestMapping("/")
String getGreeting(Model model){
logger.debug("Adding greeting");
model.addAttribute("msg", "Greetings!!!");
if(greetingProperties.isDisplayFortune()){
logger.debug("Adding fortune");
model.addAttribute("fortune", fortuneService.getFortune());
}
//resolves to the greeting.vm velocity template
return "greeting";
}
}
- Edit your fork of the
app-config
repo. Changegreeting.displayFortune
fromfalse
totrue
in thegreeting-config.yml
and push the changes back to GitHub.
logging:
level:
io:
pivotal: DEBUG
greeting:
displayFortune: true
quoteServiceURL: http://quote-service-dev.cfapps.io/quote
- Notify
greeting-config
app to pick up the new config by POSTing to the/refresh
endpoint.
$ curl -X POST http://localhost:8080/refresh
- Then refresh the http://localhost:8080 url and see the fortune included.
Congratulations! You have turned on a feature using the config-server.
Now you will use the config-server to obtain a service URI rather than hardcoding it your application code.
Beans annotated with the @RefreshScope
will be recreated when refreshed so they can pick up new config values.
- Review
$CLOUD_NATIVE_APP_LABS_HOME/greeting-config/src/main/java/io/pivotal/quote/QuoteService.java
.QuoteService.java
uses the@RefreshScope
annotation. Beans with the@RefreshScope
annotation will be recreated when refreshing configuration. The@Value
annotation allows for injecting the value of the quoteServiceURL configuration parameter.
In this case, we are using a third party service to get quotes. We want to keep our environments aligned with the third party. So we are going to override configuration values by profile (next section).
@Service
@RefreshScope
public class QuoteService {
Logger logger = LoggerFactory
.getLogger(QuoteController.class);
@Value("${quoteServiceURL}")
private String quoteServiceURL;
public String getQuoteServiceURI() {
return quoteServiceURL;
}
public Quote getQuote(){
logger.info("quoteServiceURL: {}", quoteServiceURL);
RestTemplate restTemplate = new RestTemplate();
Quote quote = restTemplate.getForObject(
quoteServiceURL, Quote.class);
return quote;
}
}
- Review
$CLOUD_NATIVE_APP_LABS_HOME/greeting-config/src/main/java/io/pivotal/quote/QuoteController.java
.QuoteController
calls theQuoteService
for quotes.
@Controller
public class QuoteController {
Logger logger = LoggerFactory
.getLogger(QuoteController.class);
@Autowired
private QuoteService quoteService;
@RequestMapping("/random-quote")
String getView(Model model) {
model.addAttribute("quote", quoteService.getQuote());
model.addAttribute("uri", quoteService.getQuoteServiceURI());
return "quote";
}
}
- In your browser, hit the http://localhost:8080/random-quote url.
Note where the data is being served from:http://quote-service-dev.cfapps.io/quote
-
Stop the
greeting-config
application using Command-C or CTRL-C in the terminal window. -
Set the active profile to qa for the
greeting-config
application. In the example below, we use an environment variable to set the active profile.
[mac, linux]
$ SPRING_PROFILES_ACTIVE=qa mvn clean spring-boot:run
[windows]
$ set SPRING_PROFILES_ACTIVE=qa
$ mvn clean spring-boot:run
- Make sure the profile is set by browsing to the http://localhost:8080/env endpoint (provided by
actuator
). Under profilesqa
should be listed.
- In your fork of the
app-config
repository, create a new file:greeting-config-qa.yml
. Fill it in with the following content:
quoteServiceURL: http://quote-service-qa.cfapps.io/quote
Make sure to commit and push to GitHub.
- Refresh the application configuration values
$ curl -X POST http://localhost:8080/refresh
-
Refresh the http://localhost:8080/random-quote url. Quotes are now being served from QA.
-
Stop both the
config-server
andgreeting-config
applications.
What Just Happened?
Configuration from greeting-config.yml
was overridden by a configuration file that was more specific (greeting-config-qa.yml
).
- Package and deploy the
config-server
to PCF. The--random-route
flag will generate a random uri for theconfig-server
. Make note of it. You will use it in the next step. Make sure you are targeting your PCF account and execute the following from theconfig-server
directory:
$ mvn clean package
$ cf push config-server -p target/config-server-0.0.1-SNAPSHOT.jar -m 512M --random-route
- Create a user-provided service. This service describes how to connect to your
config-server
application. Make sure to use your config-server uri, not the literal below. For theuri
please specifyhttp://
and no trailing/
at the end. See example below.
$ cf cups config-server -p uri
$ uri> http://config-server-sectarian-flasket.cfapps.io
- Add the following to
$CLOUD_NATIVE_APP_LABS_HOME/greeting-config/src/main/resources/bootstrap.yml
.
spring:
application:
name: greeting-config
cloud: # <-- ADD NEW SECTION
config:
uri: ${vcap.services.config-server.credentials.uri:http://localhost:8888}
When defining the spring.cloud.config.uri
, our app will first look for an environment variable (vcap.services.config-server.credentials.uri
). If it is not present, it will try to connect to the local config-server
.
- Package the
greeting-config
application. Execute the following from thegreeting-config
directory:
$ mvn clean package
- Deploy the
greeting-config
application to PCF, without starting the application:
$ cf push greeting-config -p target/greeting-config-0.0.1-SNAPSHOT.jar -m 512M --random-route --no-start
- Bind the
config-server
service to thegreeting-config
app. This will enable thegreeting-config
app to read configuration values from theconfig-server
.
$ cf bind-service greeting-config config-server
- Start the
greeting-config
app. The proper environment variables will be set. You can safely ignore the TIP: Use 'cf restage' to ensure your env variable changes take effect message from the CLI. Our app doesn't need to be restaged but just started.
$ cf start greeting-config
- Browse to your
greeting-config
application. Are your configuration settings that were set when developing locally mirrored on PCF?
- Is the log level for
io.pivotal
package set toDEBUG
? Yes, this can be confirmed withcf logs
command while refreshing thegreeting-config
/
endpoint (http://<your-random-greeting-config-url/
). - Is
greeting-config
app displaying the fortune? Yes, this can be confirmed by visiting thegreeting-config
/
endpoint. - Is the
greeting-config
app serving quotes fromhttp://quote-service-qa.cfapps.io/quote
? No, this can be confirmed by visiting thegreeting-config
/random-quote
endpoint. Why not? When developing locally we used an environment variable to set the active profile, we need to do the same on PCF.
$ cf set-env greeting-config SPRING_PROFILES_ACTIVE qa
$ cf restart greeting-config
You can safely ignore the TIP: Use 'cf restage' to ensure your env variable changes take effect message from the CLI. Our app doesn't need to be restaged but just re-started.
Then confirm quotes are being served from http://quote-service-qa.cfapps.io/quote
Until now you have been notifying your application to pick up new configuration by POSTing to the /refresh
endpoint.
When running several instances of your application, this poses several problems:
- Refreshing each individual instance is time consuming and too much overhead
- When running on Cloud Foundry you don't have control over which instances you hit when sending the POST request due to load balancing provided by the
router
Cloud Bus addresses the issues listed above by providing a single endpoint to refresh all application instances via a pub/sub notification.
- Create a RabbitMQ service instance, bind it to
greeting-config
$ cf cs p-rabbitmq standard cloud-bus
$ cf bs greeting-config cloud-bus
You can safely ignore the TIP: Use 'cf restage' to ensure your env variable changes take effect message from the CLI. Our app doesn't need to be restaged. We will push it again with new functionality in a moment.
- Include the cloud bus dependency in the
$CLOUD_NATIVE_APP_LABS_HOME/greeting-config/pom.xml
. You will need to paste this in your file.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- Repackage the
greeting-config
application:
$ mvn clean package
- Deploy the application and scale the number of instances.
$ cf push greeting-config -p target/greeting-config-0.0.1-SNAPSHOT.jar -i 3
- Observe the logs that are generated by refreshing the
greeting-config
/
endpoint several times in your browser and tailing the logs.
[mac, linux]
$ cf logs greeting-config | grep GreetingController
[windows]
$ cf logs greeting-config
# then search output for "GreetingController"
All app instances are creating debug statements. Notice the [App/X]
. It denotes which app instance is logging.
2015-09-28T20:53:06.07-0500 [App/2] OUT 2015-09-29 01:53:06.071 DEBUG 34 --- [io-64495-exec-6] io.pivotal.greeting.GreetingController : Adding fortune
2015-09-28T20:53:06.16-0500 [App/1] OUT 2015-09-29 01:53:06.160 DEBUG 33 --- [io-63186-exec-5] io.pivotal.greeting.GreetingController : Adding greeting
2015-09-28T20:53:06.16-0500 [App/1] OUT 2015-09-29 01:53:06.160 DEBUG 33 --- [io-63186-exec-5] io.pivotal.greeting.GreetingController : Adding fortune
2015-09-28T20:53:06.24-0500 [App/1] OUT 2015-09-29 01:53:06.246 DEBUG 33 --- [io-63186-exec-9] io.pivotal.greeting.GreetingController : Adding greeting
2015-09-28T20:53:06.24-0500 [App/1] OUT 2015-09-29 01:53:06.247 DEBUG 33 --- [io-63186-exec-9] io.pivotal.greeting.GreetingController : Adding fortune
2015-09-28T20:53:06.41-0500 [App/0] OUT 2015-09-29 01:53:06.410 DEBUG 33 --- [io-63566-exec-3] io.pivotal.greeting.GreetingController : Adding greeting
- Turn logging down. In your fork of the
app-config
repo edit thegreeting-config.yml
. Set the log level toINFO
. Make sure to push back to Github.
logging:
level:
io:
pivotal: INFO
- Notify applications to pickup the change. Send a POST to the
greeting-config
/bus/refresh
endpoint. Use yourgreeting-config
URL not the literal below.
$ curl -X POST http://greeting-config-hypodermal-subcortex.cfapps.io/bus/refresh
- Refresh the
greeting-config
/
endpoint several times in your browser. No more logs!