Overview
A couple of recent projects I have been on have started our engagement with the Netflix stack described here, and because I wanted to have a way to quickly prototype, I set up this demo. This will be a Spring Boot API that uses Spring Cloud Configuration, Eureka Service Discovery, and a Zuul router. Hopefully, by the end of the demo, you will see how easy it is to create this popular use case. If you want to see the code first, or only care about the code, look here. However, if you want a description of each component, and a look at some code snippets on how the components work, read on.
I went with a single repository for all of the components because simply, it is easier to work with. I would agree that eventually it would be better to move the Spring Cloud Configuration and API to be in their own separate repository and have all of the ‘local only’ components have their own repository. For now, let’s keep it simple.
To look ahead, the Eureka Server/Discovery Service will be the registry for all of the micro-services - Spring Cloud Configuration, Spring Cloud Zuul Router, and Spring Boot API. Once everything is put together, the domain will look like this:
Details
All of the Spring Cloud components will use a build.gradle
file that looks similar. Depending on which component you are in, only the compile time dependencies will differ. As I go through each component I will only specify those dependencies that should change to make it less redundant and to focus on the small amount of configuration changes we will have to make to include the necessary dependencies. Notice that I am using the recently released Finchley.SR1
for the Spring Cloud dependencies and Spring Boot 2.0+. Here is the framework of the .gradle
file you will include in each component:
buildscript {
ext {
springBootVersion = '2.0.2.RELEASE'
springCloudVersion = 'Finchley.SR1'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'io.template'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
dependencies {
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Spring Cloud Netflix
Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers. We call this service, the Eureka Server. Eureka also comes with a Java-based client component, the Eureka Client, which makes interactions with the service much easier. The client also has a built-in load balancer that does basic round-robin load balancing. At Netflix, a much more sophisticated load balancer wraps Eureka to provide weighted load balancing based on several factors like traffic, resource usage, error conditions etc to provide superior resiliency.
– Netflix GitHub
Grab the build.gradle
framework mentioned above, and plug in the following dependency and you are good to go for your Eureka Server. This component will only be used locally because you can leverage the PCF Service - Service Registry when your micro-services are deployed to Pivotal Cloud Foundry or Pivotal Web Services.
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')
This is a standard Spring Boot application with the extra @EnableEurekaServer
annotation that will auto configure a Eureka Server and Client.
package io.template.zuulrekaconfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
~/zuulreka-config/components/eureka/EurekaApplication.java
Since we just want to discover other instances with this component (and not be considered a Eureka Client), I will make it so the application does not try to connect or get the registry from another Eureka Server. I also set the application name and the port as to not conflict with the other components.
spring:
application:
name: eureka
server:
port: 8282
eureka:
client:
register-with-eureka: false
fetch-registry: false
~/zuulreka-config/components/eureka/src/main/resources/application.yml
Spring Cloud Configuration
Spring Cloud Config provides server-side and client-side support for externalized configuration in a distributed system. With the Config Server, you have a central place to manage external properties for applications across all environments.
-Spring Cloud Configuration GitHub
This is another local only component, because you can leverage PCF Service - Config Server when your microservices are deployed to PCF or PWS. Same as the Eureka Server, the build.gradle
for Cloud Configuration Server, a Eureka Client, is just like the template. The difference is to include these dependencies:
compile(
'org.springframework.cloud:spring-cloud-config-server',
'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
The Cloud Configuration Spring Boot application should activate the Configuration Server and Eureka Client configurations by using the @EnableConfigServer
and @EnableEurekaClient
annotations. @EnableConfigServer
will allow remote clients to connect to Configuration Server to use an externalized set of properties. Enabling the Eureka Client tells the Eureka Server that it wants to register and when it registers, other services can use the spring.application.name
defined in the bootstrap.yml
to reference it.
package io.template.zuulrekaconfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableConfigServer
@EnableEurekaClient
@SpringBootApplication
public class CloudConfigApplication {
public static void main(String[] args) {
SpringApplication.run(CloudConfigApplication.class, args);
}
}
~/zuulreka-config/components/cloud-config/src/main/java/io/template/zuulrekaconfig/CloudConfigApplication.java
Because of Spring Boot’s Application Context Hierarchies, when the Eureka Clients start, they need to tell the Eureka Server as early as possible that they need to connect. We can do this with a bootstrap.yml
:
spring:
application:
name: cloud-config
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_URI:http://localhost:8282/eureka}
~/zuulreka-config/components/cloud-config/src/main/resources/bootstrap.yml
The application.yml
declares an explicit port and makes sure the native
profile is set by default. The native
profile will allow the .yml
property files to reside within the cloud configuration component instead of having to use a fake GitHub file.
server:
port: 9999
spring:
profiles:
active: native
~/zuulreka-config/components/cloud-config/src/main/resources/application.yml
Later, I will show the zuul
and netflix-protected
, externalized properties that will reside in this component.
Spring Zuul Router & Filtering
Zuul is an edge service that provides dynamic routing, monitoring, resiliency, security, and more.
Netflix GitHub
This component must be deployed if you were to use PCF or PWS because Zuul is not provided as an add on service in the Pivotal Services Marketplace. Same as usual, use the aforementioned build.gradle
but use these dependencies instead:
compile(
"org.springframework.cloud:spring-cloud-starter-config",
'org.springframework.cloud:spring-cloud-starter-netflix-zuul',
'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
This is another Spring Boot application that has the @EnableEurekaClient
so it can be registered with the Eureka Server. It also has the @EnableZuulProxy
annotation to set up a Zuul server endpoint and installs some reverse proxy filters in it, so it can forward requests to backend servers.
package io.template.zuulrekaconfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
~/zuulreka-config/components/zuul/src/main/java/io/template/zuulrekaconfig/ZuulApplication.java
Next, set up the Spring application context to define the components name, how to connect to the Spring Cloud Configuration and Service Discovery using a bootstrap.yml
file:
server:
port: 8080
spring:
application:
name: zuul
cloud:
config:
uri: http://localhost:9999
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_URI:http://localhost:8282/eureka}
~/zuulreka-config/components/zuul/src/main/resources/bootstrap.yml
By convention, the routes will be configured using the spring.application.name
(with prefixes stripped) so there is no need to map a route the the netflix-protected
component. This is made possible by using a combination of Spring, Eureka, and Ribbon, which you can read about here. If/when the Zuul instance needs a set of properties, they would be added to the Spring Cloud Configuration Server at src/main/resources/zuul.yml
.
Spring Boot Web application
The last component is a simple Spring Boot API. This component will be discoverable by the Eureka Server, use properties from the Spring Cloud Configuration server, and be routed through the Zuul Router. Use the base build.gradle
mentioned at the beginning and use these compile time dependencies:
compile(
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.boot:spring-boot-starter-actuator',
'org.springframework.cloud:spring-cloud-starter-config',
'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
I want it to be registered with the Eureka Server so I will use the @EnableEurekaClient
annotation again.
package io.template.zuulrekaconfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class NetflixProtectedApplication {
public static void main(String[] args) {
SpringApplication.run(NetflixProtectedApplication.class, args);
}
}
~/zuulreka-config/components/netflix-protected/src/main/java/io/template/zuulrekaconfig/NetflixProtectedApplication.java
This controller will get the external.property
from the Spring Cloud Configuration Server and return it when you hit the controller through the Zuul router. This is how we will know that everything is connected the right way. Also note the @RefreshScope
annotation that, with a little bit of extra work, will save us some time by refreshing the properties once the Configuration Server has updated.
package io.template.zuulrekaconfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope
@RestController
public class DemoController {
@Value("${external.property}") String property;
@GetMapping("/hello")
public ResponseEntity<String> hello() {
return ResponseEntity.ok(property);
}
}
~/zuulreka-config/components/netflix-protected/src/main/java/io/template/zuulrekaconfig/DemoController.java
We should use the bootstrap.yml
to define the servlet context path, the application name, the location of the Spring Cloud Configuration and Eureka Servers.
server:
port: 8181
spring:
application:
name: netflix-protected
cloud:
config:
uri: http://localhost:9999
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_URI:http://localhost:8282/eureka}
~/zuulreka-config/components/netflix-protected/src/main/resources/bootstrap.yml
And similar to the zuul
component, the netflix-protected
component will have its properties defined in the cloud-config
component:
external:
property: hello universe
management:
endpoints:
web:
exposure:
include: refresh
~/zuulreka-config/components/cloud-config/src/main/resources/netflix-protected.yml
The management property shown will expose the actuator’s refresh endpoint to tell the API to check for any property updates. I should also point out that the Configuration Server would need to be restarted for this to work and the @RefreshScope
annotation must be applied to the component leveraging the property - not on the Application Class (I wanted that to work too). The final step would be to post to the actuator/refresh
endpoint:
curl -x post localhost:8181/netflix-protected/actuator/refresh
Finale
Now to see it all work, start the Eureka Server, Cloud Configuration Server, Zuul and API Applications. Take a quick breather because in short, the service registry needs about a minute and a half to register - however if you want to know more about this, check out this section in the docs.
Once all the services are started, use an http client to get the message from the netflix-protected
controller, going through the Zuul Router:
curl http://localhost:8080/netflix-protected/hello
Now, to check that the @RefreshScope
annotation is working - change the external.property
for the netflix-protected
application to say hello universe!
, and restart the Configuration Server. Next, POST
to the actuator/refresh
endpoint so the API will refresh its properties. Finally, run the same curl command and get the updated property.
And it is as simple as that. Drop me a line if you have any feedback for me. In a follow up article, I will explain how to have this same setup in a Pivotal Cloud Foundry environment.