Eureka, Zuul, and Cloud Configuration - Local Development

February 28, 2019

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.

Reference

Previous
Eureka, Zuul, and Cloud Configuration - Pivotal Cloud Foundry
Eureka, Zuul, and Cloud Configuration - Pivotal Cloud Foundry

Overview In a previous post I explained how you could create several components to build a Netflix stack fo...

Next
Custom Resource Validation with Admission Webhooks

Introduction One way to extend the Kubernetes platform is by building custom controllers that operate on cu...