Service discovery with Spring Boot Eureka

Service discovery with Spring Boot Eureka

In this post, you’ll learn how to set up Spring Boot 2 applications to build a Eureka cluster for Service Registration, and how to make use of Service Discovery via clients that can get each other’s information through the Eureka Registry.

Introduction

I wrote this guide because I couldn’t find too much information about how to set this entire configuration up while writing my book. I found some quick guides and valuable information in the official Spring Cloud configuration, but there were still some topics that made me struggle.

So I built an example application, available on GitHub, and accompanied it with this post to explain the code with more details. These are the topics that I’ll cover:

  • Build a Eureka Server with Peer Awareness (a Eureka Cluster) using Spring Boot 2 and Spring Cloud Netflix.
  • Connect from a Spring Boot 2 application to the Eureka Service Registry, which means how to put Service Discovery into practice.
  • Make Spring Cloud Netflix Eureka work with Docker

Quickly jump to the diagram below if you’re curious about how the system looks like.

Setting up a Service Registry with Spring Cloud Eureka

All the code in this post is available on GitHub: Spring Boot Eureka. If you find it useful, please give it a star!

Quick-start

Creating a plain simple Eureka Server is easy with Spring Boot. You can go to start.spring.io and pick Spring Boot 2, the Eureka Server as a dependency and choose Maven if you want to follow my steps. This version uses Spring Boot 2.0.0 and Spring Cloud Finchley.

Open the project in your favorite editor. Then, you just need to add an annotation to your main class, like this:

package com.thepracticaldeveloper.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

  public static void main(String[] args) {
    SpringApplication.run(EurekaServerApplication.class, args);
  }
}

If you don’t add any configuration, you’ll get the following by default:

  • The default Spring Boot server.port property is 8080, so you'll get the Eureka Service Registry available in that port. The dashboard will be accessible at http://localhost:8080.
  • By default, the Eureka Service Registry comes with a client behavior too. That means that it will try to register itself in a registry which, by default, is configured to be at http://localhost:8761/eureka. To fix this problem, you can override the property carrying that value (http://localhost:8761/eureka) or set your server port to 8761. However, it's not useful to have the server registering on its own instance (since we're in standalone mode) so we can just disable it.

Basic configuration

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
server:
  port: 8761

I recommend you to change the port to 8761, as above. It’s set as default to that value in many configuration classes in Eureka so better use it to avoid extra configuration.

If you run this app with that first version of the configuration, the service registry will start in standalone mode (a single instance). Use your IDE or the command line to execute mvn spring-boot:run and you’ll get a very sad and lonely service registry. You can navigate with your browser to http://localhost:8761 to check the Eureka Dashboard (we’ll have a look at it later).

This dashboard lists the DS replicas (registry peers) and the registered clients (none, thus far). You can ignore the fact that the own server is listed as an unavailable replica (also part of some default configuration).

Let’s quickly move on, this is just plain simple configuration. Let’s do something more interesting.

Eureka Registry Cluster via enabling Peer Awareness

Resilience with Eureka

What if we want to use the Peer Awareness feature that comes with the Eureka Service Registry? Basically, we’d like to have two registry instances that can share with each other the contents of the registry (the registered clients) so we implement resiliency. To prove that this works, later we’ll register a client in each registry and check if those clients can find each other contacting the instance that they know. See the figure for a better understanding of the scenario.

Spring Boot Eureka

Eureka’s Peer Awareness: Concepts

For now, we aim to have two Eureka Service Registry instances that can share the registry information: a Eureka Cluster (a.k.a. the Replica mode in Eureka). To achieve this, we need to take into account some very important concepts about how the Eureka Replica mode works:

  1. The Replica mode will NOT work if you use the same hostname in both instances. That means that you need to give your host two different aliases if you want to test this within the same host.
  2. The Replica mode will NOT work if you use different application names in both instances. It makes sense since the application itself is the same, it's just that we're replicating it.
  3. The magic behind the Replica mode is as simple as configuring each instance to register in another one. You can extend this to as many instances as you like, as long as you keep connecting all the edges (A registers in B, B registers in C, C registers in A). In our case, it's just crossing both of them.
Get the book Practical Software Architecture

Having that in mind, we can use two different Spring Boot profiles for a better-organized configuration.

tpd:
  peer1Port: 8761
  peer2Port: 8762

management:
  endpoint:
    health:
      enabled: true
      show-details: always
    shutdown:
      enabled: true
  endpoints:
    web:
      base-path: /
      # By default, only 'health' and 'info' are accessible via web
      exposure:
        include: '*'

---
spring:
  profiles: peer1
eureka:
  instance:
    # See blog post for details, modify /etc/hosts
    hostname: eureka-peer1
    # Either this one or the spring boot name must be the same
    # (it works without setting it too, using the alias UNKNOWN)
    appname: eureka-cluster
  client:
    serviceUrl:
      defaultZone: http://eureka-peer2:${tpd.peer2Port}/eureka
    register-with-eureka: true
    fetch-registry: true
server:
  port: ${tpd.peer1Port}

---
spring:
  profiles: peer2
eureka:
  instance:
    hostname: eureka-peer2
    appname: eureka-cluster
  client:
    serviceUrl:
      defaultZone: http://eureka-peer1:${tpd.peer1Port}/eureka
    register-with-eureka: true
    fetch-registry: true
server:
  port: ${tpd.peer2Port}

Eureka’s Replica Mode configuration explained

Let’s review this configuration:

  1. There is a common part applicable to both profiles - the part above the first ---. This is optional, but I prefer to declare two properties to quickly find the exposed ports (tpd.peerNPort). They are used in the profiles. Also, I've configured Spring Boot Actuator to expose the /health and /info endpoints from the root context. This is a good idea in general but, besides that, the Eureka Dashboard creates links to each registered service pointing to these endpoints so you need to configure this if you want them to work (although is a mere aesthetic detail). If you want to use these endpoints, don't forget to add the spring-boot-starter-actuator dependency to the pom.xml file.
  2. Note that in this file it's also configured the /shutdown endpoint and exposed it via web. This is for educational purposes, don't do this in production :). You can use it later to kill one of the server replicas and see how the peer still has the full registry contents.
  3. The first profile, peer1, exposes the server on the port 8761, uses the host eureka-peer1 and the common application name: eureka-cluster. You can use the property spring.application.name instead, that will also work.
  4. The second profile, peer2, uses the port 8762, a hostname eureka-peer2 and the common eureka-cluster application alias.
  5. Pay attention to the eureka.client.serviceUrl property. We create a defaultZone key in each instance pointing to the other peer (peer2 registers on peer1, and vice-versa).
  6. If you wonder where the /eureka context is coming from I tell you that this is hard-coded in a hidden corner of the Eureka implementation. The fact is that you need it since the REST API of the registry is located there and otherwise they won't see each other.

Preparing your system to test it

Before you execute this application you need an extra step though. As pointed out before, the Peer Awareness feature won’t work if you use the same hostname. We set them to eureka-peer1 and eureka-peer2 in our configuration, but we need to redirect those aliases to our real local host for that to work on our machine.

  • In windows, you need to modify your hosts file and add a couple of lines like these (note that you usually need administrator privileges and save the file without adding an extension, you can find more info here):

      127.0.0.1 eureka-peer1
      127.0.0.1 eureka-peer2
    
  • In Linux or Mac, you need to add the same lines, but this time in your /etc/hosts file (more info on the same link).</li>

Run Eureka Servers in Replica Mode

Now you can run two times this application, again from your preferred IDE or from the command line, using for each execution a different profile:

$ mvn spring-boot:run -Dspring-boot.run.profiles=peer1
$ mvn spring-boot:run -Dspring-boot.run.profiles=peer2

Once they are up and running, navigate with your browser to http://localhost:8761 and http://localhost:8762 respectively. You’ll see the Dashboard showing this time both replicas and where to find them, and also listing them as available.

Eureka Peer Awareness

If you’re getting your Eureka instance as an ‘unavailable replica’, it might be due to one of these reasons:

  • You didn't use the same application name in both instances (pay attention to the EUREKA-CLUSTER in the screenshot, what do you see?).
  • You missed the context /eureka in your defaultZone URLs.
  • Did you use the same hostname for both instances (e.g. localhost)? Remember that they must be different.
  • The hostnames can't be mapped by your computer (did you include them in your hosts file?).

Now that we have our Eureka Cluster, let’s use it to register some dummy clients and see how Service Discovery with Eureka and Peer replication works.

Registering Eureka Clients

Create a Eureka Client

To see Eureka in action and demonstrate that everything is working as expected, we’ll create two Spring Boot applications, each one registering in only one of the instances of the Eureka cluster. We want to prove that the cluster will replicate the registry entries so, in our example, these micro-apps will be able to see each other by using their application alias.

All the code in this post is available on GitHub: Spring Boot Eureka. If you find it useful, please give it a star!

If you want to start from scratch, go to start.spring.io and choose Spring Boot 2, Maven and Eureka Discovery and Web as dependencies. Otherwise, just clone the repository available on GitHub.

Sample service using Eureka Client

We create two different controllers for two different profiles in our client application: spanish and english. The idea is that we have two apps communicating with each other:

  • When the HolaController processes a request to http://localhost:8084/hola, it will try to reach http://tpd-en/hello-server to get an English greeting and concatenate it with its own.
  • The HelloController will do the other way around. It's accessible for us at http://localhost:8085/hello, and will build a greeting concatenating the result from the Spanish instance at http://tpd-es/hola-server.

We use Service Discovery here for the clients to be able to resolve those aliases: tpd-es (localhost:8084) and tpd-en (localhost:8085). This is the implementation of the Spanish controller:

package com.thepracticaldeveloper.eurekaclient.controller;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Profile("spanish")
public class HolaController {

  private final RestTemplate restTemplate;
  private final EurekaClient discoveryClient;
  private final String englishVirtualAlias;

  public HolaController(final RestTemplate restTemplate,
                        final EurekaClient discoveryClient,
                        @Value("${tpd.appconfig.english-alias}") final String englishVirtualAlias) {
    this.restTemplate = restTemplate;
    this.discoveryClient = discoveryClient;
    this.englishVirtualAlias = englishVirtualAlias;
  }

  @GetMapping("/hola-server")
  public String holaServer() {
    return "Hola desde el cliente en Español!";
  }

  @GetMapping("/hola")
  public String hola() {
    final InstanceInfo instance = discoveryClient.getNextServerFromEureka(englishVirtualAlias, false);
    final String theOtherResponse = restTemplate.getForObject(instance.getHomePageUrl() + "/hello-server", String.class);
    return holaServer() + " My English peer says: " + theOtherResponse;
  }
}

As you can imagine, the implementation of the HelloController class (click to open it on GitHub) is similar to this one. We can inject the EurekaClient bean since it’s loaded in the context just by having the Eureka client dependency in our classpath. Then, we use it to resolve the name of the fixed virtual alias (tpd-en) to the real location of the instance. The logic behind that call will contact the Service Registry (if it hasn’t been previously fetched) and ask for the resolution of the virtual name.

Eureka Client Configuration using Peer Awareness

This client instance is connected to just one of the instances of the Service Registry but, given that we set up the Peer Awareness, that registry instance will contain the mapping of both clients. Let’s have a look at the configuration of the client:

eureka:
  instance:
    lease-renewal-interval-in-seconds: 10

tpd:
  appconfig:
    spanish-alias: tpd-es
    english-alias: tpd-en
    spanish-port: 8084
    english-port: 8085
    peer1-address: eureka-peer1:8761
    peer2-address: eureka-peer2:8762

---
spring:
  profiles: spanish
  application:
    name: ${tpd.appconfig.spanish-alias}
server:
  port: ${tpd.appconfig.spanish-port}
# to make it more interesting, let's have each client registering at a different instance
eureka:
  client:
    service-url:
      defaultZone: http://${tpd.appconfig.peer1-address}/eureka

---
spring:
  profiles: english
  application:
    name: ${tpd.appconfig.english-alias}
server:
  port: ${tpd.appconfig.english-port}
eureka:
  client:
    service-url:
      defaultZone: http://${tpd.appconfig.peer2-address}/eureka
Get the book Practical Software Architecture
  • The common configuration sets the property eureka.instance.lease-renewal-interval-in-seconds to 10 seconds, replacing the default value of 30 seconds. This is completely optional, I normally change it to have clients renewing their lease with the server in a faster way, which is more useful when you're testing the configuration values.
  • Hanging from tpd.config I've extracted some property values like each instance's port, the server URLs and the virtual aliases to use. These are used in the profiles below.
  • As detailed above, the spanish profile sets the application name to the virtual alias (so it will be registered like that in the Eureka Registry). Note that the service-url property is set to the server's peer1 instance, whereas the english client instance (identified by the second profile) connects to the server's peer2 instance.

Eureka Server Cluster (Replica) in Action

Run the Eureka Servers and Clients

Time to play with the applications! You can use your IDE to run the four applications but I’ll give you also the instructions to do that from the command line.

Within the eureka-server-example folder, run these commands in two different terminals:

eureka-server-example$ mvn spring-boot:run -Dspring-boot.run.profiles=peer1
eureka-server-example$ mvn spring-boot:run -Dspring-boot.run.profiles=peer2

And now, the client instances, which are also two different commands to run the same application:

eureka-client$ mvn spring-boot:run -Dspring-boot.run.profiles=spanish
eureka-client$ mvn spring-boot:run -Dspring-boot.run.profiles=english

In the background, these four Spring Boot application instances will start communicating with each other thanks to Eureka and our fine-tuned configuration:

  • The server instances will register on each other and then synchronize their registries.
  • The client instances will register each one on one registry instance, causing them to synchronize their registries again.

Eureka Dashboard showing Server replica and Clients

Navigate with your browser to one of the Registry instances to very that everything went as expected. You should see something like this:

Eureka Cluster with Spring Boot

Testing the basics

Finally, if you do a GET request to our exposed endpoints for each client instance (also with your browser, or you can go for curl or http if you know how), you’ll see how our Eureka plumbing works perfectly. For example, if you request http://localhost:8084/hola:

Hola desde el cliente en Español! My English peer says: Hello from the English client!

Getting a 500 error instead? You impatient! Give Eureka some extra time to register the clients and propagate the registry contents.

Peer awareness in Eureka Clients

Bear in mind that, in order to demonstrate that Peer Awareness works, we didn’t use the optimal configuration here. To really achieve resilience, each client should know how to fetch both registries, so our final setup for a production-ready system should specify both instances in the clients (like shown below). However, if we would have done from the beginning, the registry replication wouldn’t have been so obvious to us.

eureka:
  client:
    service-url:
      defaultZone: http://${tpd.appconfig.peer1-address}/eureka,http://${tpd.appconfig.peer2-address}/eureka

Great! You just learned how to set up Service Discovery with Resiliency using Eureka and Spring Boot. Of course, there is much more to learn in terms of Microservices Architecture with Spring Boot and Spring Cloud, like how to use Load Balancing or a Circuit Breaker in this scenario. Also, an API Gateway will be useful if we want to have the same entry point from our browser to access /hello and /hola. If you want to learn all these topics from a practical perspective, have a look at my book Learn Microservices with Spring Boot, I’m sure you’ll enjoy the step-by-step approach used there.

My book can help you to grasp these patterns - Microservices, Service Discovery, Load Balancing, API Gateway, Circuit Breaker, End-to-End tests, etc.

Liked this guide? Then you can star the code on GitHub or leave a comment here, thanks!

Bonus: Docker configuration

If you want to run this example architecture, you can also do it using Docker. First, you need to generate the jar files by executing mvn clean package in both client and server folders. After that, go to the docker folder in the repository and execute:

docker-compose up

The images will be built and then executed as containers. Note that you don’t need to assign host aliases in this case since that’s now managed by Docker. Enjoy it!

Moisés Macero's Picture

About Moisés Macero

Software Developer, Architect, and Author.
Do you need help?

Amsterdam, The Netherlands https://thepracticaldeveloper.com