Last week, I attended ConFoo in my home town of Montreal. There were lots of people in attendance from various programming backgrounds, so plenty to take in. I was particularly blown away when I attended a session by @DavidOstrovsky which covered Elasticsearch and Kibana. Let’s just say my days of creating dashboards are soon coming to an end.
On the second day of the conference, I had a chance to sit with Josh Long and Mark Heckler from Pivotal. Mark opened the day with a fun keynote on cloud connected robots. Later that afternoon, I attended Josh’s inspiring talk about Cloud Native applications using Spring Cloud, in which he squeezed a lengthy demonstration into a tight 45 minute timeline. He even took time for water, so yes, he is human :)
While at ConFoo, I attended a session by James Chambers (a fellow Canadian) about working remotely from home. In that talk, James spoke briefly about the time he takes to invest in himself. He noted that blogging was a discipline he’s developed, as a way to teach himself. This statement struck a cord with me because I realized that’s one of the primary reasons I blog. It forces me to understand something since I’m hopefully going to help someone else learn about it too.
With that said, this blog post is my attempt to demonstrate some of what I learned when I got home and started hacking away with Spring Cloud and everything I had learned in Josh’s talk. I won’t go through everything in this post, but by the end, you’ll see how to setup a Eureka server, a config server, an additional microservice, and propagate configuration changes. I will leave things like load balancing and circuit breakers for a later post.
You can fork my Eureka server repo on GitHub
The first thing I did was start reading the Spring Cloud documentation. It’s rather lengthy, so I’ll try to distill what I learned to get up and running with minimal fuss. The first thing they talk about is config management, where an application can retrieve configuration properties from a configuration repository, or from a configuration server. This is great, but my takeaway was that my app would need explicit knowledge of where to get those configuration properties.
One of the underpinning principles of microservices is service discovery. A way for each microservice to learn what other services are available. Spring provides the Netflix Eureka service registry to do just that. Spring does have other implementations available, but Eureka seems to be the popular one and it’s easy to get up and running.
Without going into too much detail, the Eureka server is simply a Spring Boot application with an extra annotation to enable the Eureka server.
And there’s some configuration required in application.yml.
That’s pretty much all that is needed to get a Eureka server up and running. You’ll notice the register-with-eureka and fetch-registry settings are both set to false. This tells Eureka to work in standalone mode and not try to connect to peer Eureka servers and fail if there are none.
TIP! - My Eureka server at the time I write this uses Spring Cloud Brixton.M5. By default if you don't provide a server.port, it will be listening on port 8080. The problem with this is other services will by default try to register on port 8761, unless you specify the Eureka server path and port in their configuration. So if you want the default behaviour to work, set server.port to 8761.
You can fork my config server repo on GitHub
As you read above, I have foregone the config first approach in favour of the Eureka first approach. Doing things this way, my config server registers itself with Eureka and then other services that register with Eureka will inherit configuration properties into their environment from the registered config server.
Setting up a config server is as easy as the Eureka server was. It’s a Spring Boot application with a couple extra annotations.
@EnableConfigServer annotation enables the config server, while the @EnableDiscoveryClient tells the config server to register itself with a service registry. Eureka in this case.
The config server requires some configuration to get off the ground. The convention is to use port 8888. Eureka will look there by default. As you can see, the git repo backing this config server is expected to be on the local file system, but in production, you probably want to point it to one you’re hosting on the network somewhere.
TIP! - You should provide an application.name in your configuration, otherwise when the config server starts up, it will try to register itself with Eureka as UNKNOWN and it will fail. So I'm using the conventional CONFIGSERVER in this case. Seems odd that this is required, but at the moment it is.
On my laptop, I’ve created a config directory in my home directory that will be used by the config server. I have one configuration file at the moment, application.yml, which only contains a message property.
Let’s add another service
I say “another” service because the config server we wrote just before is in fact just another service that registers itself with Eureka. I wrote a Spring Boot app to explorer Spring Boot Devtools in an earlier post, so I will reuse that app here. You can fork it on Github.
In order to tell this app to register itself with Eureka, I simply need to add an annotation to the Application class. You saw it just before in the config server, it’s
I’ve also added a bootstrap.yml file to the application. This file is used to “bootstrap” the app when it starts. Configuration properties in this file have a lower precedence than those you put in application.yml, so the convention is to use bootstrap.yml for just what is needed to get the app up and registered with Eureka. Other configuration will come from the config server, or from a local application.yml if one exists. For my case, I’ve only included the spring.application.name property which will be the service ID registered in Eureka.
Handling changes to configuration
At this point, we have a Eureka instance running, a config server and an arbitrary service, all wired up through Eureka. Now, what happens if the configuration on the config server changes? In our case, the config server is backed by a local git repository. In order for our microservice to get the updated configuration, the application context needs to refresh. In order for the configuration property changes to propagate, Spring provides the @RefreshScope annotation.
You’ll notice here that
MessageServiceImpl populates a private String with the value from the
message property that you saw earlier in the config server setup. That value will be inherited into the microservice environment. If you look at the source of this microservice though, I also have a message property in a local application.yml. If for whatever reason the config server is unreachable when the app starts, the microservice will gracefully fallback to the local configuration.
@RefreshScope annotation creates a proxy that caches the configuration when the app starts. To propagate a configuration change, we need to repopulate the cache. I’ve added Spring Boot Actuator that exposes a /refresh endpoint to refresh the application context. Simply sending an empty POST request to this endpoint will cause the microservice to refresh it’s context. Any beans annotated with
@RefreshScope will have their caches invalidated and they will then be repopulated with the new configuration. In a real world example where you may have many more services, this method of refreshing the application context is probably not realistic though. You wouldn’t want to do this on all your microservices manually.
TIP! - I have observed that if I shut down my config server and then call the /refresh endpoint on my microservice, the cache is not invalidated on the `@RefreshScope` beans. The value that was cached from the config server remains and it does not fallback to the local application.yml configuration.
The microservice exposes a
/message endpoint. This endpoint merely calls to MessageServiceImpl and returns the configuration property value. So after I’ve refreshed the application context, this endpoint now reflects the new configuration value.
So this blog post doesn’t cover everything in Josh’s talk, and not nearly everything in the Spring Cloud docs. So I’ll plan to write a follow up post covering some of the other goodies.
I’ll close by quoting Mark Heckler at ConFoo…
Spring is like a candy store. You walk in, there's candy everywhere, but then they tell you, we also have all of these rooms, and they're also filled with candy. We're going to open all the doors.