0%
Service Discovery avec Eureka

Service Discovery

Service Discovery avec Eureka

10-15 min

Service Discovery avec Eureka

La découverte de services est un composant essentiel dans une architecture microservices. Netflix Eureka permet de gérer l’enregistrement et la découverte des services de manière efficace.

1. Configuration du Serveur Eureka

1.1 Création du Projet

curl https://start.spring.io/starter.tgz \
    -d dependencies=cloud-eureka-server \
    -d groupId=com.example \
    -d artifactId=eureka-server \
    -d name=eureka-server \
    -d description=Eureka%20Server%20for%20Microservices \
    -d packageName=com.example.eureka \
    -d packaging=jar \
    -d javaVersion=17 \
    | tar -xzvf -

1.2 Configuration du Serveur

# application.yml
server:
  port: 8761

spring:
  application:
    name: eureka-server

eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

1.3 Configuration de la Haute Disponibilité

# application-peer1.yml
spring:
  application:
    name: eureka-server
  profiles: peer1

server:
  port: 8761

eureka:
  instance:
    hostname: peer1
  client:
    service-url:
      defaultZone: http://peer2:8762/eureka/

---
# application-peer2.yml
spring:
  application:
    name: eureka-server
  profiles: peer2

server:
  port: 8762

eureka:
  instance:
    hostname: peer2
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka/

1.4 Configuration du Monitoring

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
    metrics:
      enabled: true
    prometheus:
      enabled: true

eureka:
  server:
    wait-time-in-ms-when-sync-empty: 0
    enable-self-preservation: true
    eviction-interval-timer-in-ms: 4000

2. Configuration des Clients

2.1 Ajout des Dépendances

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2.2 Configuration des Clients

# application.yml
spring:
  application:
    name: product-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    fetch-registry: true
    register-with-eureka: true
    registry-fetch-interval-seconds: 5
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.hostname}:${spring.application.name}:${server.port}
    lease-renewal-interval-in-seconds: 10
    lease-expiration-duration-in-seconds: 30

2.3 Configuration Multi-environnements

# application-dev.yml
spring:
  profiles: dev
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    metadata-map:
      environment: development

---
# application-prod.yml
spring:
  profiles: prod
eureka:
  client:
    service-url:
      defaultZone: http://eureka-server:8761/eureka/
  instance:
    metadata-map:
      environment: production
    prefer-ip-address: true

3. Utilisation de la Découverte de Services

3.1 Injection du Client Eureka

@Service
public class ProductService {
    private final EurekaClient eurekaClient;
    private final RestTemplate restTemplate;

    @Autowired
    public ProductService(EurekaClient eurekaClient, RestTemplate restTemplate) {
        this.eurekaClient = eurekaClient;
        this.restTemplate = restTemplate;
    }

    public Product getProduct(Long id) {
        InstanceInfo instance = eurekaClient.getNextServerFromEureka("order-service", false);
        String baseUrl = instance.getHomePageUrl();
        return restTemplate.getForObject(baseUrl + "/api/orders/" + id, Product.class);
    }
}

3.2 Configuration du RestTemplate avec Load Balancing

@Configuration
public class RestTemplateConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

3.3 Utilisation avec Feign Client

@FeignClient(name = "order-service", configuration = FeignConfig.class)
public interface OrderClient {
    @GetMapping("/api/orders/{id}")
    Order getOrder(@PathVariable("id") Long id);
}

@Configuration
public class FeignConfig {
    @Bean
    public Retryer retryer() {
        return new Retryer.Default(100, 1000, 3);
    }
}

4. Gestion des Erreurs et Health Checks

4.1 Configuration des Health Checks

# application.yml
eureka:
  client:
    healthcheck:
      enabled: true
  instance:
    health-check-url-path: /actuator/health
    health-check-interval: 10s
    health-check-timeout: 5s

4.2 Gestion des Erreurs

@ControllerAdvice
public class EurekaErrorHandler {
    @ExceptionHandler(EurekaClientException.class)
    public ResponseEntity<ErrorResponse> handleEurekaClientException(EurekaClientException ex) {
        ErrorResponse error = new ErrorResponse(
            "SERVICE_DISCOVERY_ERROR",
            "Erreur lors de la découverte du service",
            ex.getMessage()
        );
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(error);
    }
}

5. Monitoring et Métriques

5.1 Configuration des Métriques

@Component
public class EurekaMetrics {
    private final MeterRegistry registry;
    private final EurekaClient eurekaClient;

    @Autowired
    public EurekaMetrics(MeterRegistry registry, EurekaClient eurekaClient) {
        this.registry = registry;
        this.eurekaClient = eurekaClient;
        initializeMetrics();
    }

    private void initializeMetrics() {
        Gauge.builder("eureka.registered.services", eurekaClient, this::getRegisteredServicesCount)
            .description("Nombre de services enregistrés")
            .register(registry);

        Gauge.builder("eureka.available.services", eurekaClient, this::getAvailableServicesCount)
            .description("Nombre de services disponibles")
            .register(registry);
    }

    private double getRegisteredServicesCount(EurekaClient client) {
        return client.getApplications().getRegisteredApplications().size();
    }

    private double getAvailableServicesCount(EurekaClient client) {
        return client.getApplications().getRegisteredApplications().stream()
            .filter(app -> app.getInstances().stream().anyMatch(InstanceInfo::isUp))
            .count();
    }
}

5.2 Configuration des Alertes

# prometheus.yml
groups:
  - name: eureka
    rules:
      - alert: ServiceDown
        expr: eureka_available_services < 2
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Service indisponible"
          description: "Le service {{ $labels.service }} est indisponible depuis 5 minutes"

6. Tests

6.1 Test d’Intégration

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class EurekaIntegrationTest {
    @Autowired
    private EurekaClient eurekaClient;

    @Test
    void whenServiceStarts_thenItRegistersWithEureka() {
        Application application = eurekaClient.getApplication("product-service");
        assertNotNull(application);
        assertTrue(application.getInstances().size() > 0);
        assertTrue(application.getInstances().get(0).getStatus() == InstanceInfo.InstanceStatus.UP);
    }
}

6.2 Test de Résilience

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class EurekaResilienceTest {
    @Autowired
    private EurekaClient eurekaClient;

    @Test
    void whenEurekaServerDown_thenClientHandlesGracefully() {
        // Simuler une panne du serveur Eureka
        eurekaClient.shutdown();
        
        // Vérifier que le client gère gracieusement la situation
        assertThrows(EurekaClientException.class, () -> {
            eurekaClient.getApplication("product-service");
        });
    }
}

Conclusion

La découverte de services avec Eureka offre plusieurs avantages :

  • Gestion dynamique des services
  • Haute disponibilité avec la configuration peer-to-peer
  • Monitoring et métriques intégrés
  • Configuration flexible pour différents environnements
  • Intégration native avec Spring Cloud

Dans le prochain tutoriel, nous verrons comment centraliser la configuration de nos microservices avec Spring Cloud Config.

Commentaires

Les commentaires sont alimentés par GitHub Discussions

Connectez-vous avec GitHub pour participer à la discussion

Lien copié !