Ok

En poursuivant votre navigation sur ce site, vous acceptez l'utilisation de cookies. Ces derniers assurent le bon fonctionnement de nos services. En savoir plus.

Basic of Microservices - Part 3: The Netflix Eureka Server

In the first part of this articles series (http://nicolasduminil.blogspirit.com/archive/2018/05/25/basics-of-microservices-with-spring-boot-3106415.html) we presented a complex architecture consisting in two microservices, one running an ActiveMQ broker, another one running a Spring Boot based REST API. This architecture has been refined in the second part (http://nicolasduminil.blogspirit.com/archive/2018/05/28/basics-of-microservices-part-2-the-spring-cloud-configuratio-3106503.html) in order to add to it the Spring Cloud Configuration Server. This is the 3rd part in which we demonstrate the use of the Netflix Eureka Server.

Our sample microservice presented in the first and second part was a single-instance one, deployed in a docker container, having a known IP address and TCP port number. This is not typical for a microservice-based architecture where a microservice might have several instances, each running in its own docker container and is served by its own servlet engine. In these cases a consumer of these microservices cannot call them through a well defined IP address and TCP port number, not only because these information aren't known to the consumer, but also that using a dedicated HTTP connection will result in calling a pinned instance of the microservices, which is not what one expects when dealing with a cloud-based cluster of microservices. What one expects in this situation is that he consumer simply mentions the naml of the service and the infrastructure load-balances and chooses the one in the cluster which is the most suitable at that moment to serve the requests. Entry Netflix Eureka Server.

The Netflix Eureka Server Spring is a discovery service which maintains a set of references to microservices instances. At their start time, the microservices are instructed to register with the Eureka server, which will cache the IP address and TCP port of their HTTP listener.  Then, consumers of these microservices won't directly call them, but will rather call the Eureka server to ask for the their endpoints URLs and call further these URLs. This way the microservices consumers are abstracted away from the microservices physical location via the Eureka discovery service. Hence, new microservices instances may be added or removed without affecting their consumers.

Okay, so let's look at some code. In order to exercise the provided samples, you need to clone the GIT repository, as follows:

mkdir ms-core-config-discovery
cd ms-core-config-discovery
git clone https://github.com/nicolasduminil/micro-services.git
git checkout discovery

The GIT repository above is the one hosting the project. Our samples also use the Spring Cloud configuration service so you also need to clone the GIT repository hosting the configuration: 

mkdir spring-config
cd spring-config
git clone https://github.com/nicolasduminil/spring-config.git
git checkout discovery

The core microservice is the same as the one presented previously in the first and second part. The only new part is the ms-discovery maven module. Netflix Eureka is completely integrated wirh Spring Boot and, in order to use this integration, we need the following dependency:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
Here is the code of the discovery service:

package fr.simplex_software.micro_services.discovery;

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

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication

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

That’s all we need in order to define a discovery service. This code will run an embedded tomcat container having deployed in it a microservice named DiscoveryServerApplication. This microservice is our discovery service, the one that our core microservice will use in order to register with and the one that the consumers will call in order to get the target endpoints. Here is how: 

server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
waitTimeInMsWhenSyncEmpty: 5
serviceUrl:
defaultZone: http://localhost:8761

The code above is the file application.yml found in ms-discovery/src/main/resources. It states the following:

  • The TCP port on which the discovery service will be listening is 8761
  • Our microservice has neither to register with the Eureka service, nor to fetch the registered microservices from it as it is itself the Eureka service. This is mentioned by the registryWithEureka and fetchRegistry properties above.
  • Our discovery service will wait for 5 ms before starting. This is set by the waitTimeInMsWhenSyncEmpty property. This is required in our case as starting an Eureka service without any peer will by default wait for 5 minutes for sync puposes, i.e. to give a chance to all the existent microservices to register. This is of course not what we want here.
  • Last but not least, we define the discovery service URL as being http://localhost:8761.

This is all we need here in order to start an Eureka service. Of course, our core microservice stays exactly the same as previous. We only have added a new endpoint which allows to display the registered services. Here is the code:

@Autowired
private DiscoveryClient discoveryClient;

..........

@RequestMapping(value = "/services", method = RequestMethod.GET)
public List<String> getRegisteredServices()
{
  logger.debug("*** HmlRestController.getRegisteredService()");
  List<String> services = new ArrayList<String>();
  discoveryClient.getServices().forEach(serviceName ->
  {
   discoveryClient.getInstances(serviceName).forEach(instance ->
   {
    services.add(String.format("%s:%s", serviceName, instance.getUri()));
   });
  });
  return services;
}

This endpoint, when called, interrogates the microservices registered with the Eureka service and returns their list. In this respect, we use the Spring discovery client.

Our core microservices uses Spring Cloud configuration service in order to define its required properties. If you look into the hml-core.yml file in the spring-config project, you'll se the following:

logging:
  level:
    ROOT: ERROR
    org.springframework.web: ERROR
    org.springframework.web.client: DEBUG
    fr.simplex_software.micro_services.core: DEBUG
jms:
  destination:
    name: hml.global.topic
  broker:
    url: tcp://active-mq:61616
spring:
  activemq:
    packages:
      trust-all: "true"
eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://ms-discovery:8761/eureka/

This file is almost the same as in the previous two parts, we just added the Eureka configuratio to it. Here we set the property preferIpAddress to true such that to use IP adresses rather the DNS names. We also require our microservice to register with Eureka service as soon as it starts and to fetch the existent registered services. Last but not least, we define the Eureka service URL as being http://ms-discovery:8761/eureka/ where ms-discovery is the name of the docker container running the discovery Eureka service.

In order to build and run the project you need to perform the following operations: 

mvn –DskipTests clean install
docker-compose –f docker/common/docker-compose.yml up
 

Here we’re using the docker-compose command to orchestrate the docker containers.  Once the build has finished, we’ll find ourself with the following five docker containers running:

  • A container named active-mq running the ActiveMQ broker, like previously
  • A container named ms-core running a first instance of our core microservice.
  • A container named ms-core2 running a second instance of our core microservice.
  • A container named ms-config running the Spring Cloud config service.
  • A container named ms-discovery running the Netflix Eureka discovery service.

The five containers start in order, first active-mq, then ms-config, then ms-discovery and, finally, ms-core and ms-core2. Now that all the containers are running, you may browse to http://<ms-discovery>:8761 to see the Eureka home page showing all the registered microservices. Here ms-discovery is the IP address of the docker container named ms-discovry. You can find it using the following command:

    docker inspect ms-discovery

You may now run the test, as you did previously, such that to check that everything behave as expected. Please notice that the test has been modified in order to take advantage of the discovery service. Instead of calling explicit instances of the core microservices mentioning their IP addresses and TCP ports, the test is now using the Netflix Ribon API which is also fully integrated wirh Spring Boot. Here is the new test:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = HmlApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HmlRestControllerTest
{
  public static final Logger logger
    LoggerFactory.getLogger(HmlRestControllerTest.class);


  @Autowired
  @LoadBalanced
  private RestTemplate restTemplate;


  @Test
  public void test1() throws MalformedURLException, JsonProcessingException

  {
    assertNotNull(restTemplate);
    ResponseEntity<List<String>> services = restTemplate.exchange("http://hml-core/api
      /services"
, HttpMethod.GET,null, new ParameterizedTypeReference<List<String>>() 
      {});

    services.getBody().forEach(service ->
    {
      logger.debug ("*** Registered service: {}", service);
    });
    SubscriberInfo si = new SubscriberInfo("subscriptionName",
      new JmsTopicSubscriberInfo("selector", "clientId", "http://api/test/"));

    HttpEntity<SubscriberInfo> request = new HttpEntity<SubscriberInfo>(new
     
SubscriberInfo("subscriptionName",

      new JmsTopicSubscriberInfo("selector", "clientId", "http://hml-core/api
        /test/"
)));

    ResponseEntity resp = restTemplate.postForEntity("http://hml-core/api/subscribe/",
      request, Void.class);

    assertEquals(resp.getStatusCode(), HttpStatus.ACCEPTED);
    HttpEntity<HmlEvent> request2 = new HttpEntity<>(new HmlEvent("subscriptionName",
      "messageId", "payload"));

    HmlEvent hmle = restTemplate.postForObject("http://hml-core/api/publish/"
      request2, HmlEvent.class);

    assertThat(hmle, notNullValue());
    assertEquals(hmle.getMessageId(), "messageId");
    assertEquals(hmle.getPayload(), "payload");
  }
}

As you may observe, we replaced the endpoints URLs such that to make appear the target microservice name instead of the pair hotsIP:tcpPort which was used in the previous versions of the same test. The Netflix Ribbon API used here under the hood will provide the right mapping afer having interacted with the Eureka service. There are two instances of the core microservice and runnin this test will invoke the first one, named ms-core. Now, you can stop the ms-core instance by stopping the docker container with the same name. Running the test again will invoke the remaining instance.

Congratulations, just by running this simple docker-compose command, you got a very complex environment, running five docker containers, each one hosting a full Linux OS. Enjoy !

Les commentaires sont fermés.