Spring Cloud Consul

  • Spring Cloud Consul integrates Consul with Spring Boot applications, facilitating service discovery, configuration management, and distributed system coordination.
  • Leverages the open-source Consul platform for managing complex microservice architectures, enhancing service resilience and ease of scaling.
graph TD A[Spring Cloud Consul] --> B[Service Discovery] A --> C[Configuration Management] A --> D[Health Checking] A --> E[Load Balancing] B --> F[Service Registration] B --> G[Service Discovery API] C --> H[Centralized Config Storage] C --> I[Dynamic Config Updates] D --> J[Health Check Monitors] D --> K[Service Status Tracking] E --> L[Traffic Distribution] E --> M[Failover Handling] style A fill:#2b6cb0,stroke:#ffffff,stroke-width:2px style B fill:#4caf50,stroke:#ffffff,stroke-width:1px style C fill:#ff9800,stroke:#ffffff,stroke-width:1px style D fill:#2196f3,stroke:#ffffff,stroke-width:1px style E fill:#9c27b0,stroke:#ffffff,stroke-width:1px

Install and Start Consul

  • Download Consul: https://developer.hashicorp.com/consul/install
  • Unzip the downloaded file.
  • add the location to your PATH for Windows).
  • consul agent -dev
  • it will run on localhost:8500.
  • Access the Consul UI: http://localhost:8500 to access the Consul UI.

Tp1: Service Discovery

1. Client-A Application

Step 2.1: Create a Spring Boot Project for Client-A
Dependencies:
  • Spring Web
  • Spring Cloud Starter Consul Discovery
  • Spring Boot DevTools (optional for easier development)
pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
</dependencies>

Step 2.2: Configure application.yml for Client-A

Create a file named application.yml in the src/main/resources:

spring:
  application:
    name: client-a
  cloud:
    consul:
      host: localhost
      port: 8500
	  

Step 2.3: Create a REST Controller for Client-A

Create a REST controller that exposes an endpoint:

package com.example.clienta;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ClientAController {
    
    @GetMapping("/greet")
    public String greet() {
        return "Hello from Client A!";
    }
}

Step 2.4: Create the Main Application Class


package com.example.clienta;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Run Client A

2. Client-B Application Setup

Create a Spring Boot Project for Client-B

Similar to Client-A, create another Spring Boot project for Client-B with the same dependencies.

pom.xml


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
</dependencies>

application.yml for Client-B

Create a file named application.yml in the src/main/resources directory with the following content:

spring:
  application:
    name: client-b
  cloud:
    consul:
      host: localhost
      port: 8500
	  

Create a REST Controller for Client-B

Create a REST controller that calls Client-A:

package com.example.clientb;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class ClientBController {

    @Autowired
    private DiscoveryClient discoveryClient;

    private final RestTemplate restTemplate = new RestTemplate();

    @GetMapping("/call-a")
    public String callClientA() {
        List<ServiceInstance> instances = discoveryClient.getInstances("client-a");
        if (instances != null && !instances.isEmpty()) {
            String clientAUrl = instances.get(0).getUri().toString() + "/greet";
            return restTemplate.getForObject(clientAUrl, String.class);
        }
        return "Client A not available";
    }
}
 
  • DiscoveryClient: Used for service discovery. It provides information about registered services (such as their URLs) and allows applications to locate other services by name.
  • RestTemplate: Used for making HTTP requests. After obtaining the service URL from DiscoveryClient, RestTemplate uses this URL to make RESTful calls to the discovered service.
  • Client-B uses DiscoveryClient to retrieve the URL of Client-A from the Consul Server.
  • DiscoveryClientcontacts Consul Server to fetch the URL of Client-A.
  • RestTemplate then uses the retrieved URL to make HTTP requests to Client-A.
graph TD ClientB["Client-B Application"] -->|Uses| DiscoveryClient["DiscoveryClient"] DiscoveryClient -->|Finds service instance URL for 'client-a'| ClientAURL["client-a URL"] ClientB -->|Uses| RestTemplate["RestTemplate"] RestTemplate -->|Makes HTTP request to| ClientAURL ClientB --> ConsulServer["Consul Server"] DiscoveryClient -->|Fetches service information from| ConsulServer ClientAURL -->|Registered with| ConsulServer

Create the Main Application Class for Client-B


package com.example.clientb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Run

  • Run Consul
  • Run Client-A:
  • Run Client-B:
Go to http://localhost:8500 to see both Client-A and Client-B registered.
Test the API: Use a tool like Postman or cURL to test the endpoints.

Call Client-A: http://localhost:8080/greet
Call Client-B :that call A :http://localhost:8081/call-a
You should see:

The response from Client-A when hitting the /greet endpoint.
The response from Client-B calling Client-A through service discovery.

Configuration Management

  1. Consul: Acts as a centralized configuration storage where configurations (key-value pairs) for applications are stored.

  2. Spring Boot Application: The application loads configurations from Consul when it starts and injects them into the @ConfigurationProperties class (e.g., AppConfig).

  3. @RefreshScope: This annotation allows specific beans, such as AppConfig, to be refreshed with updated values when a configuration refresh is triggered.

  4. Actuator /refresh Endpoint: When a POST request is sent to /actuator/refresh, Spring Cloud reloads the configurations from Consul without restarting the application.

  5. User/Admin: Can update configuration values directly in Consul and then trigger the /refresh endpoint, making the updated configurations immediately available in the application.

graph TD Consul["Consul Server (Central Config Storage)"] -->|Stores config values| KeyValueStore["Key-Value Store"] ClientApp["Spring Boot Application"] -->|Loads config on startup| ConfigProperties["@ConfigurationProperties (AppConfig)"] ClientApp --> ActuatorRefresh["/actuator/refresh Endpoint"] KeyValueStore -->|Provides config values| ClientApp ConfigProperties -->|Holds config values| RefreshScope["@RefreshScope"] User["User/Admin"] -->|Updates config| KeyValueStore User -->|Triggers refresh| ActuatorRefresh ActuatorRefresh -->|Refreshes updated config| ConfigProperties

Create Configuration

Create Configuration in Consul Key-Value Store
  • Open the Consul UI at http://localhost:8500.
  • Navigate to Key/Value.
  • Create a key-value pair :config/application-name/data
  • config/client-a/messge/
  • use cmd :consul kv put config/client-a/message "client a message"

Create the Spring Boot Application

including the following dependencies:
  • Spring Web (to create REST endpoints)
  • Spring Cloud Starter Consul Config (for Consul integration)
  • Spring Boot Actuator (for monitoring and refreshing)

pom.xml

:

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-consul-discovery</artifactId>
		</dependency>
		<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
		  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

Configure application.yml for Consul

spring:
  application:
    name: clienta

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        enabled: true
      config:
        enabled: true
        prefix: config

  config:
    import: "consul:"

management:
  endpoints:
    web:
      exposure:
        include: health,info,refresh

server:
  port: 8080


Use Consul Configuration in the Application


package com.example.clienta;

import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@RefreshScope
@Component

public class AppConfig {
	@Value("${message}")
	private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
To make the configuration update dynamically, we’ll use Spring Cloud’s @RefreshScope annotation and Spring Boot Actuator’s /refresh endpoint.

Create a REST Controller to Display the Configuration


package com.example.clienta;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConfigController {

    private final AppConfig appConfig;

    public ConfigController(AppConfig appConfig) {
        this.appConfig = appConfig;
    }

    @GetMapping("/message")
    public String getMessage() {
        return appConfig.getMessage();
    }
}

Run

Enable Actuator’s Refresh Endpoint In application.yml, enable the /actuator/refresh endpoint by adding the following configuration:

management:
  endpoints:
    web:
      exposure:
        include: refresh

Run

http://localhost:8080/message
You should see the value of message as set in Consul (Hello from Consul Config!).
Update the Configuration in Consul: Go to http://localhost:8500. Update the message key under config/client-a/data to a new value, like "Hello, updated from Consul!".
Refresh the Configuration: you can
  • 1.Restart the application
  • 2.send a POST request to the /actuator/refresh endpoint:
     POST http://localhost:8080/actuator/refresh
    http://localhost:8080/message

Load balancing

  • Load balancing is a technique for distributing network traffic across a server farm.
  • It optimizes network performance, reliability, and capacity.
  • Reduces latency by evenly distributing demand among multiple servers and resources.
  • Uses an appliance (physical or virtual) to identify the best server for client requests in real-time.
  • Prevents overwhelming a single server during heavy network traffic.
  • Provides failover; if one server fails, workloads are redirected to a backup server.
graph TD A[Client Request] --> B[Load Balancer] B --> C{Available Servers} C -->|Server A| D[Forward to Server A] C -->|Server B| E[Forward to Server B] C -->|Server C| F[Forward to Server C] D --> G[Process Request] E --> G F --> G G --> H[Return Response] subgraph Failover Handling H -->|Failure Detected| I[Load Balancer] I --> C C -->|Backup Server| J[Forward to Backup Server] J --> K[Process on Backup Server] K --> L[Return Response to Client] end
  1. Client Request:

    • The process starts with a client making a request.
  2. Load Balancer:

    • The load balancer receives the client request and determines which server is available to handle the request.
  3. Available Servers:

    • The load balancer evaluates the pool of available servers (e.g., Server A, Server B, Server C) and chooses one to forward the request.
  4. Forwarding Requests:

    • The load balancer forwards the request to the selected server, which processes the request and returns a response.
  5. Response:

    • The server processes the request and sends the response back to the client.
  6. Failover Handling:

    • If the load balancer detects a failure while processing the request (for example, if the selected server becomes unavailable):
      • The load balancer redirects the request to a backup server.
      • The backup server processes the request and returns the response to the client.
Tutoriel Microservices : HashiCorp Consul avec Spring Boot Windows

HashiCorp Consul avec Spring Boot

Guide Complet et Détaillé - Explications Approfondies

Installation via developer.hashicorp.com/consul/install - Windows OS

Introduction à Consul et Spring Boot

Qu'est-ce que HashiCorp Consul ?

HashiCorp Consul est une solution de service mesh et de découverte de services complète qui permet aux services de découvrir, configurer et sécuriser les communications entre eux. Consul offre une plateforme unifiée pour connecter et sécuriser les services dans n'importe quel environnement.

Fonctionnalités Principales de Consul

  • Service Discovery : Découverte automatique des services et de leurs emplacements
  • Health Checking : Surveillance continue de l'état de santé des services
  • Key/Value Store : Stockage distribué de configuration et de métadonnées
  • Multi Datacenter : Support natif pour les environnements multi-datacenter
  • Service Mesh : Connectivité sécurisée entre les services avec observabilité
  • Connect : Communication sécurisée entre services avec mTLS

Architecture de Consul

Architecture typique avec Consul :

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Service A     │    │   Service B     │    │   Service C     │
│ (Consul Client) │    │ (Consul Client) │    │ (Consul Client) │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         └───────────────────────┼───────────────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │   Consul Server         │
                    │   (Agent Serveur)       │
                    │                         │
                    │  ┌─────────────────┐    │
                    │  │ Service Registry│    │
                    │  │ Health Checks   │    │
                    │  │ KV Store        │    │
                    │  └─────────────────┘    │
                    └─────────────────────────┘
                    

Spring Boot et Consul

Intégration Spring Boot

Spring Cloud Consul fournit une intégration transparente entre Spring Boot et Consul :

Avantages de l'Intégration

  • Auto-configuration : Configuration automatique des clients Consul
  • Service Discovery : Découverte automatique des services avec @EnableDiscoveryClient
  • Configuration distribuée : Récupération de configuration depuis Consul KV
  • Load balancing : Load balancing client-side intégré
  • Health checks : Enregistrement automatique des health checks

Cycle de Vie de l'Intégration

  1. Démarrage : L'application Spring Boot démarre
  2. Configuration : Auto-configuration de Consul Client
  3. Enregistrement : Enregistrement du service dans Consul
  4. Découverte : Découverte d'autres services via DiscoveryClient
  5. Configuration : Récupération de la configuration depuis KV Store
  6. Monitoring : Envoi de métriques et health checks

Installation Consul sur Windows

Méthode Officielle via developer.hashicorp.com

1

Accédez à la page officielle d'installation :

https://developer.hashicorp.com/consul/install

Options d'Installation Windows

  • Package ZIP : Méthode recommandée pour Windows
  • Chocolatey : Package manager Windows
  • Windows Subsystem for Linux (WSL) : Pour environnement Linux-like
  • Docker : Pour environnement conteneurisé
2

Téléchargement via navigateur :

Étapes de Téléchargement

  1. Allez sur https://developer.hashicorp.com/consul/install
  2. Sélectionnez Windows comme système d'exploitation
  3. Choisissez l'architecture AMD64 (64-bit)
  4. Cliquez sur Download pour télécharger le fichier ZIP
  5. Le fichier téléchargé sera nommé quelque chose comme : consul_1.16.1_windows_amd64.zip
3

Installation manuelle :

# 1. Créez un répertoire pour Consul
mkdir C:\consul
cd C:\consul

# 2. Extrayez le fichier ZIP téléchargé
# Utilisez l'explorateur Windows ou PowerShell :
Expand-Archive -Path "chemin\vers\consul_*.zip" -DestinationPath "C:\consul"

# 3. Vérifiez l'extraction
dir C:\consul
# Vous devriez voir consul.exe
4

Ajout au PATH système :

# Méthode PowerShell (en tant qu'administrateur)
$env:PATH += ";C:\consul"
[Environment]::SetEnvironmentVariable("PATH", $env:PATH, [EnvironmentVariableTarget]::Machine)

# Ou via l'interface graphique Windows :
# 1. Clic droit sur "Ce PC" → Propriétés
# 2. Paramètres système avancés
# 3. Variables d'environnement
# 4. Sélectionnez "Path" → Modifier
# 5. Ajoutez "C:\consul"
# 6. Redémarrez PowerShell/CMD
5

Vérification de l'installation :

# Ouvrez une nouvelle invite de commande
consul --version

# Réponse attendue :
# Consul v1.16.1
# Revision 12345678

# Vérification des commandes disponibles
consul --help

Validation

Si la commande consul --version retourne le numéro de version, l'installation est réussie !

Installation via Chocolatey

Installation avec Chocolatey

Chocolatey est un package manager pour Windows qui simplifie l'installation de logiciels.

1

Installation de Chocolatey (si non installé) :

# Ouvrez PowerShell en tant qu'administrateur
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

# Vérifiez l'installation
choco --version
2

Installation de Consul via Chocolatey :

# Installation de Consul
choco install consul

# Mise à jour de Consul
choco upgrade consul

# Désinstallation de Consul
choco uninstall consul

Avantages de Chocolatey

  • Mise à jour automatique : Facile de garder Consul à jour
  • Gestion des dépendances : Gère automatiquement les dépendances
  • Installation silencieuse : Peut être automatisée dans des scripts
  • Centralisation : Gère plusieurs logiciels avec une seule interface

Installation via Docker (Windows)

Installation avec Docker Desktop

Docker Desktop pour Windows permet d'exécuter des conteneurs Linux et Windows.

1

Installation de Docker Desktop :

# Téléchargez Docker Desktop depuis :
# https://www.docker.com/products/docker-desktop

# Installez Docker Desktop en suivant l'assistant d'installation

# Vérifiez l'installation
docker --version
docker-compose --version
2

Exécution de Consul dans Docker :

# Démarrage de Consul en mode développement
docker run -d --name=consul-dev -p 8500:8500 -p 8600:8600/udp hashicorp/consul:latest agent -dev -client=0.0.0.0

# Vérification
docker ps
# Vous devriez voir le conteneur consul-dev en cours d'exécution

# Accès à l'interface web
# http://localhost:8500

# Arrêt du conteneur
docker stop consul-dev
docker rm consul-dev
3

Configuration persistante avec Docker :

# Création de volumes pour persistance
docker volume create consul-data

# Démarrage avec volume persistant
docker run -d --name=consul-server \
  -p 8500:8500 -p 8600:8600/udp \
  -v consul-data:/consul/data \
  -v C:\consul\config:/consul/config \
  hashicorp/consul:latest agent \
  -server -bootstrap-expect=1 -ui -config-dir=/consul/config

# Configuration locale
# C:\consul\config\server.json
{
  "server": true,
  "node_name": "consul-server-windows",
  "datacenter": "dc1",
  "data_dir": "/consul/data",
  "bind_addr": "0.0.0.0",
  "client_addr": "0.0.0.0",
  "bootstrap_expect": 1,
  "ui_config": {
    "enabled": true
  }
}

Configuration Spring Boot

Création du Projet Spring Boot

1

Création via Spring Initializr :

# Accédez à https://start.spring.io/
# Configuration recommandée :
Project: Maven Project
Language: Java
Spring Boot: 2.7.x ou 3.x
Packaging: Jar
Java: 11 ou 17

# Dépendances à inclure :
- Spring Web
- Spring Boot Actuator
- Spring Cloud Consul Discovery
- Spring Cloud Consul Config
- Spring Cloud LoadBalancer (optionnel)
2

Ajout des dépendances Maven :

<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>user-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>user-service</name>
    <description>User Service with Consul</description>
    
    <properties>
        <java.version>11</java.version>
        <spring-cloud.version>2021.0.5</spring-cloud.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring Boot Actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- Spring Cloud Consul Discovery -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        
        <!-- Spring Cloud Consul Config -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-config</artifactId>
        </dependency>
        
        <!-- Spring Cloud LoadBalancer -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        
        <!-- Testing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
3

Configuration de la classe principale :

package com.example.userservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

Explication des Annotations

  • @SpringBootApplication :

    Combinaison de @Configuration, @EnableAutoConfiguration et @ComponentScan.

  • @EnableDiscoveryClient :

    Active la découverte de services via Consul Discovery.

Configuration Bootstrap

1

Configuration bootstrap.yml :

# src/main/resources/bootstrap.yml
spring:
  application:
    name: user-service
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        instance-id: ${spring.application.name}:${server.port}:${random.value}
        service-name: ${spring.application.name}
        prefer-ip-address: true
        ip-address: ${spring.cloud.client.ip-address}
        port: ${server.port}
        health-check-path: /actuator/health
        health-check-interval: 15s
        tags: 
          - ${spring.profiles.active:default}
          - version=1.0.0
        metadata:
          version: 1.0.0
          environment: ${spring.profiles.active:default}
      config:
        enabled: true
        format: YAML
        prefix: config
        default-context: application
        profile-separator: ','
        data-key: data

management:
  endpoints:
    web:
      exposure:
        include: health,info,refresh
  endpoint:
    health:
      show-details: always
2

Configuration application.yml :

# src/main/resources/application.yml
server:
  port: 8080

spring:
  application:
    name: user-service
  cloud:
    consul:
      discovery:
        enabled: true
        register: true
        deregister: true
        heartbeat:
          enabled: true
      config:
        enabled: true
        watch:
          enabled: true
          delay: 1000

management:
  endpoint:
    health:
      show-details: always

logging:
  level:
    org.springframework.cloud.consul: DEBUG
    com.ecwid.consul: DEBUG

Explication des Propriétés

  • spring.application.name :

    Nom unique du service dans Consul.

  • spring.cloud.consul.host/port :

    Coordonnées du serveur Consul.

  • prefer-ip-address=true :

    Utilise l'IP plutôt que le nom d'hôte pour l'enregistrement.

  • health-check-path :

    Endpoint pour la vérification de santé.

  • health-check-interval :

    Fréquence des vérifications de santé.

Premier Démarrage

1

Démarrage du serveur Consul :

# Démarrage en mode développement
consul agent -dev

# Ou démarrage avec configuration
consul agent -config-dir=C:\consul\config
2

Construction et démarrage du service :

# Construction du projet
mvn clean package

# Démarrage du service
java -jar target/user-service-0.0.1-SNAPSHOT.jar

# Ou démarrage via Maven
mvn spring-boot:run
3

Vérification de l'enregistrement :

# Vérification via API
curl http://localhost:8500/v1/agent/services

# Vérification via interface web
# http://localhost:8500/ui/dc1/services

# Vérification des membres
curl http://localhost:8500/v1/agent/members

Validation

Le service devrait apparaître dans l'interface web de Consul avec le statut "passing".

Service Discovery avec Consul

Configuration de Base du Discovery Client

1

Configuration dans application.yml :

# application.yml
spring:
  cloud:
    consul:
      discovery:
        enabled: true
        register: true
        deregister: true
        service-name: ${spring.application.name}
        instance-id: ${spring.application.name}:${server.port}:${random.value}
        prefer-ip-address: true
        ip-address: ${spring.cloud.client.ip-address}
        port: ${server.port}
        health-check-path: /actuator/health
        health-check-interval: 15s
        heartbeat:
          enabled: true
        tags:
          - ${spring.profiles.active:default}
          - version=1.0.0
        metadata:
          version: 1.0.0
          environment: ${spring.profiles.active:default}
          region: us-east-1

# Actuator endpoints
management:
  endpoints:
    web:
      exposure:
        include: health,info,refresh
  endpoint:
    health:
      show-details: always

Explication des Propriétés Discovery

  • enabled=true :

    Active la découverte de services.

  • register=true :

    Enregistre le service dans Consul au démarrage.

  • deregister=true :

    Désenregistre le service lors de l'arrêt.

  • service-name :

    Nom du service dans Consul.

  • instance-id :

    ID unique de l'instance (utile pour plusieurs instances).

  • heartbeat.enabled=true :

    Active le heartbeat pour maintenir l'état du service.

2

Utilisation du DiscoveryClient :

@Service
public class ServiceDiscoveryService {
    
    @Autowired
    private DiscoveryClient discoveryClient;
    
    public List<String> getAllServices() {
        return discoveryClient.getServices();
    }
    
    public List<ServiceInstance> getServiceInstances(String serviceName) {
        return discoveryClient.getInstances(serviceName);
    }
    
    public ServiceInstance getRandomServiceInstance(String serviceName) {
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
        if (!instances.isEmpty()) {
            Random random = new Random();
            return instances.get(random.nextInt(instances.size()));
        }
        return null;
    }
}

Appel de Services Distants

1

Appel via RestTemplate avec LoadBalancer :

@Service
public class RemoteServiceClient {
    
    @Autowired
    @LoadBalanced
    private RestTemplate restTemplate;
    
    @Bean
    @LoadBalanced
    public RestTemplate loadBalancedRestTemplate() {
        return new RestTemplate();
    }
    
    public String callRemoteService(String serviceName, String endpoint) {
        String url = "http://" + serviceName + endpoint;
        return restTemplate.getForObject(url, String.class);
    }
    
    public <T> T postToRemoteService(String serviceName, String endpoint, Object requestBody, Class<T> responseType) {
        String url = "http://" + serviceName + endpoint;
        return restTemplate.postForObject(url, requestBody, responseType);
    }
}
2

Appel via WebClient avec LoadBalancer :

@Service
public class ReactiveServiceClient {
    
    @Autowired
    @LoadBalanced
    private WebClient.Builder webClientBuilder;
    
    public Mono<String> callRemoteService(String serviceName, String endpoint) {
        return webClientBuilder.build()
            .get()
            .uri("http://" + serviceName + endpoint)
            .retrieve()
            .bodyToMono(String.class);
    }
    
    public <T> Mono<T> postToRemoteService(String serviceName, String endpoint, Object requestBody, Class<T> responseType) {
        return webClientBuilder.build()
            .post()
            .uri("http://" + serviceName + endpoint)
            .bodyValue(requestBody)
            .retrieve()
            .bodyToMono(responseType);
    }
}
3

Contrôleur d'appel de service :

@RestController
@RequestMapping("/api")
public class ServiceCallController {
    
    @Autowired
    private RemoteServiceClient remoteServiceClient;
    
    @Autowired
    private ReactiveServiceClient reactiveServiceClient;
    
    @GetMapping("/call-service/{serviceName}/{endpoint}")
    public ResponseEntity<String> callService(
            @PathVariable String serviceName,
            @PathVariable String endpoint) {
        
        try {
            String result = remoteServiceClient.callRemoteService(serviceName, "/" + endpoint);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Error calling service: " + e.getMessage());
        }
    }
    
    @GetMapping("/call-service-reactive/{serviceName}/{endpoint}")
    public Mono<ResponseEntity<String>> callServiceReactive(
            @PathVariable String serviceName,
            @PathVariable String endpoint) {
        
        return reactiveServiceClient.callRemoteService(serviceName, "/" + endpoint)
            .map(ResponseEntity::ok)
            .onErrorReturn(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Error calling service"));
    }
}

Configuration Avancée du Discovery

1

Configuration avec tags et métadonnées :

# application.yml
spring:
  cloud:
    consul:
      discovery:
        tags:
          - environment=${spring.profiles.active:default}
          - version=${app.version:1.0.0}
          - region=${app.region:us-east-1}
          - zone=${app.zone:a}
        metadata:
          version: ${app.version:1.0.0}
          environment: ${spring.profiles.active:default}
          region: ${app.region:us-east-1}
          zone: ${app.zone:a}
          team: ${app.team:backend}
        query-passing: true
        acl-token: ${consul.acl.token:}
        prefer-agent-address: false
2

Filtrage par tags :

@Service
public class TaggedServiceDiscovery {
    
    @Autowired
    private DiscoveryClient discoveryClient;
    
    public List<ServiceInstance> getProductionServices(String serviceName) {
        return discoveryClient.getInstances(serviceName)
            .stream()
            .filter(instance -> instance.getMetadata().containsKey("environment") &&
                               "prod".equals(instance.getMetadata().get("environment")))
            .collect(Collectors.toList());
    }
    
    public List<ServiceInstance> getServicesByVersion(String serviceName, String version) {
        return discoveryClient.getInstances(serviceName)
            .stream()
            .filter(instance -> instance.getMetadata().containsKey("version") &&
                               version.equals(instance.getMetadata().get("version")))
            .collect(Collectors.toList());
    }
    
    public ServiceInstance getNearestService(String serviceName) {
        return discoveryClient.getInstances(serviceName)
            .stream()
            .filter(instance -> instance.getMetadata().containsKey("region") &&
                               "us-east-1".equals(instance.getMetadata().get("region")))
            .findFirst()
            .orElse(null);
    }
}
3

Configuration de l'ACL :

# application.yml
spring:
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        acl-token: ${CONSUL_ACL_TOKEN:}
        scheme: http
      config:
        acl-token: ${CONSUL_ACL_TOKEN:}
        scheme: http

# Ou via variables d'environnement
# SET CONSUL_ACL_TOKEN=your-consul-acl-token
# SET SPRING_CLOUD_CONSUL_DISCOVERY_ACL_TOKEN=your-consul-acl-token

Gestion de Configuration avec Consul

Configuration de Base du Config Client

1

Configuration dans bootstrap.yml :

# bootstrap.yml
spring:
  application:
    name: user-service
  cloud:
    consul:
      host: localhost
      port: 8500
      config:
        enabled: true
        format: YAML
        prefix: config
        default-context: application
        profile-separator: ','
        data-key: data
        watch:
          enabled: true
          delay: 1000
        acl-token: ${CONSUL_ACL_TOKEN:}
      discovery:
        enabled: true
        register: true
        service-name: ${spring.application.name}

management:
  endpoints:
    web:
      exposure:
        include: health,info,refresh
  endpoint:
    health:
      show-details: always

Explication des Propriétés Config

  • enabled=true :

    Active la récupération de configuration depuis Consul.

  • format=YAML :

    Format de configuration (YAML, PROPERTIES, FILES).

  • prefix=config :

    Préfixe des clés de configuration dans Consul KV.

  • watch.enabled=true :

    Active la surveillance des changements de configuration.

  • delay=1000 :

    Délai entre les vérifications de changements (ms).

2

Stockage de configuration dans Consul KV :

# Configuration globale
curl -X PUT -d 'server:
  port: 8080
spring:
  application:
    name: user-service
logging:
  level:
    root: INFO' \
  http://localhost:8500/v1/kv/config/application/data

# Configuration spécifique à l'application
curl -X PUT -d 'app:
  name: User Service
  version: 1.0.0
database:
  url: jdbc:mysql://localhost:3306/userdb
  username: user
  password: password123
  pool-size: 10' \
  http://localhost:8500/v1/kv/config/user-service/data

# Configuration par profil
curl -X PUT -d 'app:
  name: User Service Dev
  version: 1.0.0-SNAPSHOT
database:
  url: jdbc:h2:mem:devdb
  username: sa
  password: 
  pool-size: 5' \
  http://localhost:8500/v1/kv/config/user-service,dev/data

Utilisation de la Configuration

1

Injection de valeurs de configuration :

@Component
public class AppConfig {
    
    @Value("${app.name:Default App}")
    private String appName;
    
    @Value("${app.version:1.0.0}")
    private String appVersion;
    
    @Value("${database.url:jdbc:h2:mem:default}")
    private String databaseUrl;
    
    @Value("${database.pool-size:10}")
    private int databasePoolSize;
    
    // Getters
    public String getAppName() { return appName; }
    public String getAppVersion() { return appVersion; }
    public String getDatabaseUrl() { return databaseUrl; }
    public int getDatabasePoolSize() { return databasePoolSize; }
}

@RestController
public class ConfigController {
    
    @Autowired
    private AppConfig appConfig;
    
    @Autowired
    private Environment environment;
    
    @GetMapping("/config")
    public Map<String, Object> getConfig() {
        Map<String, Object> config = new HashMap<>();
        config.put("appName", appConfig.getAppName());
        config.put("appVersion", appConfig.getAppVersion());
        config.put("databaseUrl", appConfig.getDatabaseUrl());
        config.put("databasePoolSize", appConfig.getDatabasePoolSize());
        config.put("activeProfiles", environment.getActiveProfiles());
        return config;
    }
}
2

Configuration avec @RefreshScope :

@Service
@RefreshScope
public class RefreshableConfigService {
    
    @Value("${app.refreshable-property:defaultValue}")
    private String refreshableProperty;
    
    @Value("${app.timeout:5000}")
    private int timeout;
    
    public String getRefreshableProperty() {
        return refreshableProperty;
    }
    
    public int getTimeout() {
        return timeout;
    }
}

@RestController
public class RefreshConfigController {
    
    @Autowired
    private RefreshableConfigService refreshableConfigService;
    
    @PostMapping("/refresh")
    public ResponseEntity<String> refreshConfig() {
        // Cette méthode déclenche l'actualisation des beans @RefreshScope
        return ResponseEntity.ok("Configuration refreshed at: " + new Date());
    }
    
    @GetMapping("/refreshable-config")
    public Map<String, Object> getRefreshableConfig() {
        Map<String, Object> config = new HashMap<>();
        config.put("refreshableProperty", refreshableConfigService.getRefreshableProperty());
        config.put("timeout", refreshableConfigService.getTimeout());
        return config;
    }
}
3

Configuration de base de données :

@Configuration
public class DatabaseConfig {
    
    @Value("${database.url}")
    private String databaseUrl;
    
    @Value("${database.username}")
    private String username;
    
    @Value("${database.password}")
    private String password;
    
    @Value("${database.pool-size:10}")
    private int poolSize;
    
    @Bean
    @Primary
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(databaseUrl);
        config.setUsername(username);
        config.setPassword(password);
        config.setMaximumPoolSize(poolSize);
        return new HikariDataSource(config);
    }
}

// Utilisation dans un repository
@Repository
public class UserRepository {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public List<User> findAll() {
        return jdbcTemplate.query("SELECT * FROM users", 
            (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")));
    }
}

Configuration Avancée

1

Configuration hiérarchique :

# Structure du KV store
config/
├── application/
│   ├── data (configuration globale)
│   └── dev/
│       └── data (configuration dev globale)
├── user-service/
│   ├── data (configuration user-service)
│   └── dev/
│       └── data (configuration user-service dev)
└── order-service/
    ├── data (configuration order-service)
    └── prod/
        └── data (configuration order-service prod)

# Hiérarchie de priorité (du plus spécifique au plus général) :
# 1. config/{application}/{profile}/data
# 2. config/{application}/data
# 3. config/application/{profile}/data
# 4. config/application/data
2

Configuration avec différents formats :

# Format YAML
curl -X PUT -d 'app:
  features:
    - user-management
    - authentication
  settings:
    timeout: 5000
    retries: 3' \
  http://localhost:8500/v1/kv/config/user-service/data

# Format PROPERTIES
curl -X PUT -d 'app.features[0]=user-management
app.features[1]=authentication
app.settings.timeout=5000
app.settings.retries=3' \
  http://localhost:8500/v1/kv/config/user-service/data

# Format JSON
curl -X PUT -d '{
  "app": {
    "features": ["user-management", "authentication"],
    "settings": {
      "timeout": 5000,
      "retries": 3
    }
  }
}' \
  http://localhost:8500/v1/kv/config/user-service/data
3

Surveillance des changements :

@Component
public class ConfigChangeEventListener {
    
    private static final Logger logger = LoggerFactory.getLogger(ConfigChangeEventListener.class);
    
    @EventListener
    public void handleRefreshEvent(RefreshRemoteApplicationEvent event) {
        logger.info("Configuration refresh event received: {}", event);
        // Logique de traitement après actualisation
    }
    
    @EventListener
    public void handleEnvironmentChangeEvent(EnvironmentChangeEvent event) {
        logger.info("Environment changed keys: {}", event.getKeys());
        // Réagir aux changements spécifiques
        event.getKeys().forEach(key -> {
            logger.info("Changed property: {}", key);
        });
    }
}

@RestController
public class ConfigWatchController {
    
    @Autowired
    private ConfigurableEnvironment environment;
    
    @GetMapping("/config-values")
    public Map<String, Object> getCurrentConfigValues() {
        Map<String, Object> config = new HashMap<>();
        
        // Récupérer toutes les propriétés
        for (Iterator<PropertySource<?>> it = environment.getPropertySources().iterator(); it.hasNext();) {
            PropertySource<?> propertySource = it.next();
            if (propertySource instanceof EnumerablePropertySource) {
                EnumerablePropertySource<?> enumerablePropertySource = (EnumerablePropertySource<?>) propertySource;
                for (String propertyName : enumerablePropertySource.getPropertyNames()) {
                    config.put(propertyName, environment.getProperty(propertyName));
                }
            }
        }
        
        return config;
    }
}

Health Checks

Configuration de Base des Health Checks

1

Configuration dans application.yml :

# application.yml
spring:
  cloud:
    consul:
      discovery:
        health-check-path: /actuator/health
        health-check-interval: 15s
        health-check-timeout: 3s
        health-check-critical-timeout: 1m
        heartbeat:
          enabled: true
          ttl-value: 30
          ttl-unit: s

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      show-details: always
      probes:
        enabled: true
  health:
    defaults:
      enabled: true

Explication des Propriétés Health

  • health-check-path :

    Endpoint pour la vérification de santé.

  • health-check-interval :

    Intervalle entre les vérifications (15 secondes).

  • health-check-timeout :

    Timeout pour chaque vérification (3 secondes).

  • health-check-critical-timeout :

    Temps avant de marquer le service comme critique (1 minute).

  • heartbeat.enabled=true :

    Active le heartbeat pour maintenir l'état.

2

Health checks personnalisés :

@Component
public class CustomHealthIndicator implements HealthIndicator {
    
    private static final Logger logger = LoggerFactory.getLogger(CustomHealthIndicator.class);
    
    @Override
    public Health health() {
        try {
            // Vérification personnalisée (ex: base de données, cache, etc.)
            boolean isDatabaseUp = checkDatabaseConnection();
            boolean isCacheUp = checkCacheConnection();
            
            if (isDatabaseUp && isCacheUp) {
                return Health.up()
                    .withDetail("database", "connected")
                    .withDetail("cache", "connected")
                    .withDetail("timestamp", System.currentTimeMillis())
                    .build();
            } else {
                return Health.down()
                    .withDetail("database", isDatabaseUp ? "connected" : "disconnected")
                    .withDetail("cache", isCacheUp ? "connected" : "disconnected")
                    .withDetail("timestamp", System.currentTimeMillis())
                    .build();
            }
        } catch (Exception e) {
            logger.error("Health check failed", e);
            return Health.down()
                .withDetail("error", e.getMessage())
                .withException(e)
                .build();
        }
    }
    
    private boolean checkDatabaseConnection() {
        // Implémentation de vérification de base de données
        try {
            // Exemple avec JDBC
            // Connection connection = dataSource.getConnection();
            // return connection.isValid(1);
            return true; // Simplifié pour l'exemple
        } catch (Exception e) {
            return false;
        }
    }
    
    private boolean checkCacheConnection() {
        // Implémentation de vérification de cache
        try {
            // Exemple avec Redis
            // redisTemplate.getConnectionFactory().getConnection().ping();
            return true; // Simplifié pour l'exemple
        } catch (Exception e) {
            return false;
        }
    }
}

Health Checks Avancés

1

Health checks réactifs :

@Component
public class ReactiveHealthIndicator implements ReactiveHealthIndicator {
    
    @Autowired
    private WebClient.Builder webClientBuilder;
    
    @Override
    public Mono<Health> health() {
        return webClientBuilder.build()
            .get()
            .uri("http://external-service/health")
            .retrieve()
            .bodyToMono(String.class)
            .map(response -> Health.up().withDetail("external-service", "reachable").build())
            .onErrorResume(throwable -> 
                Mono.just(Health.down().withDetail("external-service", "unreachable").build()));
    }
}

@Component
public class CompositeHealthIndicator implements HealthIndicator {
    
    @Autowired
    private List<HealthIndicator> healthIndicators;
    
    @Override
    public Health health() {
        Health.Builder builder = Health.unknown();
        Map<String, Health> details = new HashMap<>();
        
        boolean allUp = true;
        boolean anyDown = false;
        
        for (HealthIndicator indicator : healthIndicators) {
            try {
                Health health = indicator.health();
                String name = indicator.getClass().getSimpleName().replace("HealthIndicator", "");
                details.put(name, health);
                
                if (health.getStatus() == Status.DOWN) {
                    anyDown = true;
                    allUp = false;
                } else if (health.getStatus() != Status.UP) {
                    allUp = false;
                }
            } catch (Exception e) {
                details.put(indicator.getClass().getSimpleName(), Health.down(e).build());
                anyDown = true;
                allUp = false;
            }
        }
        
        if (anyDown) {
            builder.down();
        } else if (allUp) {
            builder.up();
        } else {
            builder.unknown();
        }
        
        return builder.withDetails(details).build();
    }
}
2

Configuration des health checks dans Consul :

# Enregistrement de service avec health check HTTP
{
  "service": {
    "name": "user-service",
    "id": "user-service-1",
    "address": "192.168.1.100",
    "port": 8080,
    "checks": [
      {
        "id": "user-service-http",
        "name": "User Service HTTP Check",
        "http": "http://192.168.1.100:8080/actuator/health",
        "method": "GET",
        "interval": "10s",
        "timeout": "1s",
        "status": "passing"
      },
      {
        "id": "user-service-tcp",
        "name": "User Service TCP Check",
        "tcp": "192.168.1.100:8080",
        "interval": "30s",
        "timeout": "1s",
        "status": "critical"
      }
    ]
  }
}

# Enregistrement via API
curl -X PUT -d @service-registration.json http://localhost:8500/v1/agent/service/register
3

Monitoring des états de santé :

@RestController
public class HealthMonitoringController {
    
    @Autowired
    private DiscoveryClient discoveryClient;
    
    @GetMapping("/health-summary")
    public Map<String, Object> getHealthSummary() {
        Map<String, Object> summary = new HashMap<>();
        
        // Services passing
        List<ServiceInstance> passingServices = new ArrayList<>();
        List<ServiceInstance> warningServices = new ArrayList<>();
        List<ServiceInstance> criticalServices = new ArrayList<>();
        
        List<String> serviceNames = discoveryClient.getServices();
        for (String serviceName : serviceNames) {
            List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
            for (ServiceInstance instance : instances) {
                // Vérifier l'état via Consul API
                String healthStatus = getConsulHealthStatus(serviceName, instance);
                switch (healthStatus.toLowerCase()) {
                    case "passing":
                        passingServices.add(instance);
                        break;
                    case "warning":
                        warningServices.add(instance);
                        break;
                    case "critical":
                        criticalServices.add(instance);
                        break;
                }
            }
        }
        
        summary.put("passing", passingServices.size());
        summary.put("warning", warningServices.size());
        summary.put("critical", criticalServices.size());
        summary.put("total", serviceNames.size());
        
        return summary;
    }
    
    private String getConsulHealthStatus(String serviceName, ServiceInstance instance) {
        // Implémentation de vérification via Consul HTTP API
        try {
            String url = "http://localhost:8500/v1/health/checks/" + serviceName;
            // Appel HTTP pour récupérer l'état de santé
            return "passing"; // Simplifié pour l'exemple
        } catch (Exception e) {
            return "critical";
        }
    }
    
    @GetMapping("/health-details/{serviceName}")
    public List<Map<String, Object>> getHealthDetails(@PathVariable String serviceName) {
        List<Map<String, Object>> details = new ArrayList<>();
        
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
        for (ServiceInstance instance : instances) {
            Map<String, Object> instanceHealth = new HashMap<>();
            instanceHealth.put("instanceId", instance.getInstanceId());
            instanceHealth.put("host", instance.getHost());
            instanceHealth.put("port", instance.getPort());
            instanceHealth.put("status", getInstanceHealthStatus(instance));
            instanceHealth.put("metadata", instance.getMetadata());
            details.add(instanceHealth);
        }
        
        return details;
    }
    
    private String getInstanceHealthStatus(ServiceInstance instance) {
        // Implémentation de vérification individuelle
        return "passing"; // Simplifié pour l'exemple
    }
}

Notifications et Alertes

1

Système de notification :

@Component
public class HealthNotificationService {
    
    private static final Logger logger = LoggerFactory.getLogger(HealthNotificationService.class);
    
    @EventListener
    public void handleInstanceRegisteredEvent(InstanceRegisteredEvent event) {
        logger.info("New instance registered: {}", event.getInstance());
        // Envoyer notification d'enregistrement
        sendNotification("Instance Registered", 
            "New instance registered: " + event.getInstance().getServiceId());
    }
    
    @EventListener
    public void handleInstanceDeregisteredEvent(InstanceDeregisteredEvent event) {
        logger.info("Instance deregistered: {}", event.getInstance());
        // Envoyer notification de désenregistrement
        sendNotification("Instance Deregistered", 
            "Instance deregistered: " + event.getInstance().getServiceId());
    }
    
    @EventListener
    public void handleHealthChangeEvent(HealthChangeEvent event) {
        logger.info("Health status changed: {} -> {}", 
            event.getOldStatus(), event.getNewStatus());
        
        if (event.getNewStatus() == Status.DOWN || event.getNewStatus() == Status.OUT_OF_SERVICE) {
            sendAlert("Service Health Alert", 
                "Service " + event.getServiceId() + " is now " + event.getNewStatus());
        }
    }
    
    private void sendNotification(String subject, String message) {
        // Implémentation d'envoi de notification
        // Email, Slack, SMS, etc.
        logger.info("Sending notification - Subject: {}, Message: {}", subject, message);
    }
    
    private void sendAlert(String subject, String message) {
        // Implémentation d'envoi d'alerte
        logger.error("ALERT: {} - {}", subject, message);
        // Intégration avec système d'alerte (PagerDuty, OpsGenie, etc.)
    }
}

@Component
public class PeriodicHealthChecker {
    
    @Autowired
    private DiscoveryClient discoveryClient;
    
    @Scheduled(fixedRate = 30000) // Toutes les 30 secondes
    public void checkServiceHealth() {
        List<String> services = discoveryClient.getServices();
        for (String serviceName : services) {
            List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
            for (ServiceInstance instance : instances) {
                checkInstanceHealth(serviceName, instance);
            }
        }
    }
    
    private void checkInstanceHealth(String serviceName, ServiceInstance instance) {
        try {
            String healthUrl = "http://" + instance.getHost() + ":" + instance.getPort() + "/actuator/health";
            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<String> response = restTemplate.getForEntity(healthUrl, String.class);
            
            if (!response.getStatusCode().is2xxSuccessful()) {
                logger.warn("Health check failed for service {} instance {}", serviceName, instance.getInstanceId());
                // Envoyer alerte
            }
        } catch (Exception e) {
            logger.error("Health check exception for service {} instance {}", serviceName, instance.getInstanceId(), e);
            // Envoyer alerte critique
        }
    }
}
2

Intégration avec systèmes externes :

@Service
public class ExternalNotificationService {
    
    @Value("${notification.slack.webhook.url:}")
    private String slackWebhookUrl;
    
    @Value("${notification.email.recipients:admin@example.com}")
    private String emailRecipients;
    
    public void sendSlackNotification(String message) {
        if (StringUtils.hasText(slackWebhookUrl)) {
            try {
                RestTemplate restTemplate = new RestTemplate();
                Map<String, Object> payload = new HashMap<>();
                payload.put("text", message);
                
                restTemplate.postForObject(slackWebhookUrl, payload, String.class);
            } catch (Exception e) {
                logger.error("Failed to send Slack notification", e);
            }
        }
    }
    
    public void sendEmailNotification(String subject, String message) {
        try {
            // Implémentation d'envoi d'email
            JavaMailSender mailSender = // ... configuration
            SimpleMailMessage email = new SimpleMailMessage();
            email.setTo(emailRecipients.split(","));
            email.setSubject(subject);
            email.setText(message);
            // mailSender.send(email);
        } catch (Exception e) {
            logger.error("Failed to send email notification", e);
        }
    }
}

@RestController
public class NotificationController {
    
    @Autowired
    private ExternalNotificationService notificationService;
    
    @PostMapping("/send-notification")
    public ResponseEntity<String> sendNotification(@RequestBody Map<String, String> request) {
        String type = request.get("type"); // "slack" ou "email"
        String message = request.get("message");
        String subject = request.get("subject");
        
        switch (type) {
            case "slack":
                notificationService.sendSlackNotification(message);
                break;
            case "email":
                notificationService.sendEmailNotification(subject, message);
                break;
            default:
                return ResponseEntity.badRequest().body("Unsupported notification type");
        }
        
        return ResponseEntity.ok("Notification sent successfully");
    }
}

Load Balancing

Configuration de Base du Load Balancer

1

Configuration dans application.yml :

# application.yml
spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true
      cache:
        enabled: true
        ttl: 30s
      ribbon:
        enabled: false  # Désactiver Ribbon, utiliser Spring Cloud LoadBalancer
    consul:
      discovery:
        # Configuration pour le load balancing
        prefer-ip-address: true
        query-passing: true

# Configuration des retries
resilience4j:
  retry:
    configs:
      default:
        maxAttempts: 3
        waitDuration: 1000
        retryExceptions:
          - java.net.ConnectException
          - java.net.SocketTimeoutException
    instances:
      user-service:
        baseConfig: default
2

Configuration de RestTemplate avec LoadBalancer :

@Configuration
public class LoadBalancerConfig {
    
    @Bean
    @LoadBalanced
    public RestTemplate loadBalancedRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        
        // Configuration des timeouts
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setConnectTimeout(5000);
        factory.setReadTimeout(10000);
        restTemplate.setRequestFactory(factory);
        
        return restTemplate;
    }
    
    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}

@Service
public class LoadBalancedServiceClient {
    
    @Autowired
    @LoadBalanced
    private RestTemplate restTemplate;
    
    @Autowired
    @LoadBalanced
    private WebClient.Builder webClientBuilder;
    
    public String callService(String serviceName, String endpoint) {
        String url = "http://" + serviceName + endpoint;
        return restTemplate.getForObject(url, String.class);
    }
    
    public <T> T callService(String serviceName, String endpoint, Class<T> responseType) {
        String url = "http://" + serviceName + endpoint;
        return restTemplate.getForObject(url, responseType);
    }
    
    public <T> Mono<T> callServiceReactive(String serviceName, String endpoint, Class<T> responseType) {
        return webClientBuilder.build()
            .get()
            .uri("http://" + serviceName + endpoint)
            .retrieve()
            .bodyToMono(responseType);
    }
}

Explication des Propriétés LoadBalancer

  • retry.enabled=true :

    Active le mécanisme de retry automatique.

  • cache.enabled=true :

    Active le cache pour améliorer les performances.

  • cache.ttl=30s :

    Durée de vie du cache (30 secondes).

  • ribbon.enabled=false :

    Désactive Ribbon pour utiliser Spring Cloud LoadBalancer.

Stratégies de Load Balancing

1

Configuration du Round Robin (par défaut) :

@Configuration
public class LoadBalancerConfiguration {
    
    @Bean
    ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RoundRobinLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name);
    }
}

// Utilisation dans un service
@Service
public class RoundRobinServiceClient {
    
    @Autowired
    @LoadBalanced
    private RestTemplate restTemplate;
    
    public String callService(String serviceName) {
        // Le load balancing Round Robin est appliqué automatiquement
        return restTemplate.getForObject("http://" + serviceName + "/api/data", String.class);
    }
}
2

Configuration du Random Load Balancer :

@Configuration
public class RandomLoadBalancerConfiguration {
    
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name);
    }
}

// Utilisation avec annotation
@LoadBalancerClient(name = "user-service", configuration = RandomLoadBalancerConfiguration.class)
public class UserServiceClient {
    
    @Autowired
    @LoadBalanced
    private RestTemplate restTemplate;
    
    public User getUser(Long userId) {
        return restTemplate.getForObject("http://user-service/users/{id}", User.class, userId);
    }
}
3

Load Balancing avec poids :

@Configuration
public class WeightedLoadBalancerConfiguration {
    
    @Bean
    ReactorLoadBalancer<ServiceInstance> weightedLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new WeightedResponseTimeLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name);
    }
}

@Service
public class WeightedServiceClient {
    
    @Autowired
    @LoadBalanced
    private WebClient.Builder webClientBuilder;
    
    public Mono<String> callServiceWithWeightedLB(String serviceName, String endpoint) {
        return webClientBuilder.build()
            .get()
            .uri("http://" + serviceName + endpoint)
            .retrieve()
            .bodyToMono(String.class)
            .timeout(Duration.ofSeconds(5))
            .retryWhen(Retry.backoff(3, Duration.ofMillis(100)));
    }
}

// Configuration des poids via metadata dans Consul
// Service registration avec metadata
{
  "service": {
    "name": "user-service",
    "id": "user-service-1",
    "address": "192.168.1.100",
    "port": 8080,
    "meta": {
      "weight": "80",  // Poids plus élevé = plus de trafic
      "version": "1.0.0",
      "region": "us-east-1"
    }
  }
}

Retry et Résilience

1

Configuration des retries :

# application.yml
resilience4j:
  retry:
    configs:
      default:
        maxAttempts: 3
        waitDuration: 1000
        enableExponentialBackoff: true
        exponentialBackoffMultiplier: 2
        retryExceptions:
          - org.springframework.web.client.ResourceAccessException
          - java.net.ConnectException
          - java.net.SocketTimeoutException
          - java.util.concurrent.TimeoutException
        ignoreExceptions:
          - org.springframework.web.client.HttpClientErrorException
    instances:
      user-service:
        baseConfig: default
      order-service:
        baseConfig: default
        maxAttempts: 5
        waitDuration: 2000

spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true
        retryable-status-codes: 503,504,408

# Configuration des timeouts
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            connectTimeout: 5000
            readTimeout: 10000
2

Retry avec Resilience4j :

@Service
public class ResilientServiceClient {
    
    private final Retry retry;
    private final RestTemplate restTemplate;
    
    public ResilientServiceClient(RetryRegistry retryRegistry) {
        this.retry = retryRegistry.retry("user-service");
        this.restTemplate = new RestTemplate();
    }
    
    @Retry(name = "user-service", fallbackMethod = "fallback")
    public String callService(String serviceName, String endpoint) {
        String url = "http://" + serviceName + endpoint;
        return restTemplate.getForObject(url, String.class);
    }
    
    public String fallback(String serviceName, String endpoint, Exception ex) {
        logger.warn("Fallback executed for service: " + serviceName, ex);
        return "Service temporarily unavailable";
    }
    
    public String callServiceWithManualRetry(String serviceName, String endpoint) {
        Supplier<String> retryableSupplier = Retry.decorateSupplier(retry, () -> {
            String url = "http://" + serviceName + endpoint;
            return restTemplate.getForObject(url, String.class);
        });
        
        return Try.ofSupplier(retryableSupplier)
            .recover(throwable -> "Service temporarily unavailable: " + throwable.getMessage());
    }
}

@RestController
public class ResilientController {
    
    @Autowired
    private ResilientServiceClient resilientServiceClient;
    
    @GetMapping("/resilient-call/{serviceName}/{endpoint}")
    public ResponseEntity<String> resilientCall(
            @PathVariable String serviceName,
            @PathVariable String endpoint) {
        
        try {
            String result = resilientServiceClient.callService(serviceName, "/" + endpoint);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                .body("Service call failed: " + e.getMessage());
        }
    }
}
3

Configuration avancée des timeouts :

@Configuration
public class HttpClientConfig {
    
    @Bean
    public CloseableHttpClient httpClient() {
        RequestConfig config = RequestConfig.custom()
            .setConnectTimeout(5000)    // 5 secondes pour la connexion
            .setSocketTimeout(10000)    // 10 secondes pour la lecture
            .setConnectionRequestTimeout(2000)  // 2 secondes pour obtenir une connexion
            .build();
        
        return HttpClients.custom()
            .setDefaultRequestConfig(config)
            .setMaxConnTotal(100)
            .setMaxConnPerRoute(20)
            .build();
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate loadBalancedRestTemplate() {
        HttpComponentsClientHttpRequestFactory factory = 
            new HttpComponentsClientHttpRequestFactory(httpClient());
        RestTemplate restTemplate = new RestTemplate(factory);
        return restTemplate;
    }
}

@Service
public class TimeoutAwareServiceClient {
    
    @Autowired
    @LoadBalanced
    private RestTemplate restTemplate;
    
    public String callServiceWithTimeout(String serviceName, String endpoint, int timeoutMs) {
        // Configuration dynamique du timeout
        HttpComponentsClientHttpRequestFactory factory = 
            (HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory();
        factory.setReadTimeout(timeoutMs);
        
        try {
            return restTemplate.getForObject("http://" + serviceName + endpoint, String.class);
        } finally {
            // Restaurer le timeout par défaut
            factory.setReadTimeout(10000);
        }
    }
}

Load Balancing avec OpenFeign

1

Configuration OpenFeign :

<!-- Ajout de la dépendance OpenFeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

# application.yml
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 10000
        loggerLevel: basic
        requestInterceptors:
          - com.example.interceptor.AuthInterceptor
  httpclient:
    enabled: true
    max-connections: 200
    max-connections-per-route: 50
  compression:
    request:
      enabled: true
    response:
      enabled: true
  hystrix:
    enabled: false  # Utiliser Resilience4j à la place

spring:
  cloud:
    openfeign:
      client-config:
        user-service:
          connectTimeout: 5000
          readTimeout: 10000
2

Interface Feign avec LoadBalancer :

@FeignClient(
    name = "user-service",
    configuration = UserServiceFeignConfig.class,
    fallback = UserServiceFallback.class
)
public interface UserServiceClient {
    
    @GetMapping("/users/{id}")
    User getUser(@PathVariable("id") Long id);
    
    @GetMapping("/users")
    List<User> getAllUsers();
    
    @PostMapping("/users")
    User createUser(@RequestBody User user);
    
    @PutMapping("/users/{id}")
    User updateUser(@PathVariable("id") Long id, @RequestBody User user);
    
    @DeleteMapping("/users/{id}")
    void deleteUser(@PathVariable("id") Long id);
}

@Configuration
@EnableFeignClients
public class FeignConfig {
    // Configuration globale de Feign
}

@Component
public class UserServiceFallback implements UserServiceClient {
    
    private static final Logger logger = LoggerFactory.getLogger(UserServiceFallback.class);
    
    @Override
    public User getUser(Long id) {
        logger.warn("Fallback for getUser: " + id);
        return new User(id, "Fallback User", "fallback@example.com");
    }
    
    @Override
    public List<User> getAllUsers() {
        logger.warn("Fallback for getAllUsers");
        return Collections.emptyList();
    }
    
    @Override
    public User createUser(User user) {
        logger.warn("Fallback for createUser");
        throw new RuntimeException("User service temporarily unavailable");
    }
    
    @Override
    public User updateUser(Long id, User user) {
        logger.warn("Fallback for updateUser: " + id);
        throw new RuntimeException("User service temporarily unavailable");
    }
    
    @Override
    public void deleteUser(Long id) {
        logger.warn("Fallback for deleteUser: " + id);
        throw new RuntimeException("User service temporarily unavailable");
    }
}
3

Utilisation du client Feign :

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserServiceClient userServiceClient;
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        try {
            User user = userServiceClient.getUser(id);
            return ResponseEntity.ok(user);
        } catch (Exception e) {
            logger.error("Error getting user: " + id, e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();
        }
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        try {
            User createdUser = userServiceClient.createUser(user);
            return ResponseEntity.ok(createdUser);
        } catch (Exception e) {
            logger.error("Error creating user", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();
        }
    }
    
    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        try {
            List<User> users = userServiceClient.getAllUsers();
            return ResponseEntity.ok(users);
        } catch (Exception e) {
            logger.error("Error getting all users", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();
        }
    }
}

@Service
public class UserServiceConsumer {
    
    @Autowired
    private UserServiceClient userServiceClient;
    
    public User processUser(Long userId) {
        // Le load balancing est appliqué automatiquement
        User user = userServiceClient.getUser(userId);
        
        // Traitement métier
        user.setLastAccessed(new Date());
        userServiceClient.updateUser(userId, user);
        
        return user;
    }
}

Sécurité et ACL

Configuration de Base des ACL

1

Activation des ACL dans Consul :

# consul.hcl (serveur)
acl {
  enabled = true
  default_policy = "deny"
  down_policy = "extend-cache"
  enable_token_persistence = true
}

# Bootstrap initial
bootstrap_expect = 1
server = true

# Pour les clients
acl {
  enabled = true
  default_policy = "deny"
  down_policy = "fail"
}

# Configuration du token ACL
acl_tokens {
  master = "your-master-token"
  agent = "your-agent-token"
  default = "your-default-token"
}
2

Bootstrap des ACL :

# Démarrage du serveur avec bootstrap
consul agent -server -bootstrap-expect=1 -data-dir=C:\consul\data -config-dir=C:\consul\config

# Génération du token bootstrap
consul acl bootstrap

# Réponse :
AccessorID:       b7a3f4c2-1d8e-4f2a-9b3c-1a2d3e4f5g6h
SecretID:         12345678-1234-1234-1234-123456789012
Description:      Bootstrap Token (Global Management)
Local:            false
Create Time:      2023-01-01 12:00:00 +0000 UTC
Policies:
   00000000-0000-0000-0000-000000000001 - global-management

# Configuration du token
SET CONSUL_HTTP_TOKEN=12345678-1234-1234-1234-123456789012
3

Création des politiques :

# Création d'une politique pour les services
consul acl policy create -name "service-policy" -description "Service read/write policy" -rules @service-policy.hcl

# service-policy.hcl
service_prefix "web" {
  policy = "write"
}

service_prefix "api" {
  policy = "read"
}

node_prefix "" {
  policy = "read"
}

# Création d'une politique pour le KV store
consul acl policy create -name "kv-policy" -description "KV store policy" -rules @kv-policy.hcl

# kv-policy.hcl
key_prefix "config/app/" {
  policy = "write"
}

key_prefix "config/shared/" {
  policy = "read"
}

# Création d'une politique pour les agents
consul acl policy create -name "agent-policy" -description "Agent policy" -rules @agent-policy.hcl

# agent-policy.hcl
agent_prefix "" {
  policy = "read"
}

node_prefix "" {
  policy = "read"
}

Gestion des Tokens

1

Création de tokens :

# Création d'un token pour un service web
consul acl token create -description "Web service token" -policy-name "service-policy" -policy-name "kv-policy"

# Réponse :
AccessorID:       c8b4g5d3-2e9f-5g3b-0c4d-2b3e5f6g7h8i
SecretID:         98765432-9876-9876-9876-987654321098
Description:      Web service token
Local:            false
Create Time:      2023-01-01 12:05:00 +0000 UTC
Policies:
   87654321-8765-8765-8765-876543210987 - service-policy
   12345678-1234-1234-1234-123456789012 - kv-policy

# Création d'un token local (pour un agent spécifique)
consul acl token create -description "Node agent token" -policy-name "agent-policy" -local

# Création d'un token avec expiration
consul acl token create -description "Temporary token" -policy-name "service-policy" -expiration-ttl=1h
2

Configuration des agents avec tokens :

# Configuration du serveur
# C:\consul\config\server-acl.json
{
  "acl": {
    "tokens": {
      "agent": "98765432-9876-9876-9876-987654321098",
      "default": "98765432-9876-9876-9876-987654321098"
    }
  }
}

# Configuration du client
# C:\consul\config\client-acl.json
{
  "acl": {
    "tokens": {
      "default": "c8b4g5d3-2e9f-5g3b-0c4d-2b3e5f6g7h8i"
    }
  }
}

# Ou via variables d'environnement
SET CONSUL_HTTP_TOKEN=c8b4g5d3-2e9f-5g3b-0c4d-2b3e5f6g7h8i
3

Gestion des tokens :

# Liste des tokens
consul acl token list

# Récupération d'un token spécifique
consul acl token read -id c8b4g5d3-2e9f-5g3b-0c4d-2b3e5f6g7h8i

# Mise à jour d'un token
consul acl token update -id c8b4g5d3-2e9f-5g3b-0c4d-2b3e5f6g7h8i -description "Updated web service token" -policy-name "service-policy"

# Suppression d'un token
consul acl token delete -id c8b4g5d3-2e9f-5g3b-0c4d-2b3e5f6g7h8i

# Clone d'un token
consul acl token clone -id c8b4g5d3-2e9f-5g3b-0c4d-2b3e5f6g7h8i -description "Cloned token"

Sécurité Avancée

1

Configuration TLS :

# Génération des certificats
# openssl req -newkey rsa:2048 -nodes -keyout consul-agent-key.pem -x509 -days 365 -out consul-agent.pem

# Configuration TLS
# C:\consul\config\tls.json
{
  "ca_file": "C:/consul/certs/consul-agent-ca.pem",
  "cert_file": "C:/consul/certs/consul-agent.pem",
  "key_file": "C:/consul/certs/consul-agent-key.pem",
  "verify_incoming": true,
  "verify_outgoing": true,
  "verify_server_hostname": true,
  "auto_encrypt": {
    "allow_tls": true
  }
}

# Activation HTTPS
ports {
  https = 8501
  http = -1  # Désactiver HTTP
}

# Configuration dans application.yml
spring:
  cloud:
    consul:
      scheme: https
      host: localhost
      port: 8501
      tls:
        key-store: classpath:keystore.jks
        key-store-password: changeit
        key-password: changeit
2

Configuration mTLS :

# Configuration mTLS pour Connect
connect {
  enabled = true
  ca_provider = "consul"
  ca_config {
    cluster_id = "12345678-1234-1234-1234-123456789012"
  }
}

# Configuration des intentions
# consul config write intention.hcl

# intention.hcl
Kind = "service-intentions"
Name = "web"

Sources = [
  {
    Name   = "api"
    Action = "allow"
  },
  {
    Name   = "*"
    Action = "deny"
  }
]

# Configuration dans Spring Boot
spring:
  cloud:
    consul:
      connect:
        enabled: true
        certificate:
          path: C:/consul/certs/
          key-store: client.jks
          key-store-password: password
          key-password: password
3

Audit et logging :

# Configuration du logging
# C:\consul\config\logging.json
{
  "log_level": "INFO",
  "log_file": "C:/consul/logs/consul.log",
  "log_rotate_bytes": 52428800,
  "log_rotate_duration": "24h",
  "log_rotate_max_files": 10,
  "enable_syslog": true,
  "syslog_facility": "LOCAL0"
}

# Configuration de l'audit
{
  "audit": {
    "enabled": true,
    "sink": {
      "type": "file",
      "path": "C:/consul/logs/audit.log",
      "mode": "755"
    }
  }
}

# Surveillance des accès
consul acl token list -format=json | jq '.[] | {Description, CreateTime, Policies}'

# Configuration de sécurité dans Spring Boot
spring:
  cloud:
    consul:
      discovery:
        acl-token: ${CONSUL_ACL_TOKEN:}
        scheme: https
      config:
        acl-token: ${CONSUL_ACL_TOKEN:}
        scheme: https

# Variables d'environnement
SET CONSUL_ACL_TOKEN=your-secure-token
SET SPRING_CLOUD_CONSUL_DISCOVERY_SCHEME=https
SET SPRING_CLOUD_CONSUL_CONFIG_SCHEME=https

Monitoring et Métriques

Configuration des Métriques

1

Activation des métriques :

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    metrics:
      enabled: true
    prometheus:
      enabled: true
  metrics:
    enable:
      consul: true
    distribution:
      percentiles-histogram:
        consul: true
      percentiles:
        consul: 0.5,0.9,0.95,0.99
      sla:
        consul: 10ms,50ms,100ms,500ms

spring:
  cloud:
    consul:
      discovery:
        metrics:
          enabled: true
      config:
        metrics:
          enabled: true

# Configuration des métriques spécifiques Consul
management:
  metrics:
    enable:
      spring.cloud.consul: true
    distribution:
      percentiles-histogram:
        spring.cloud.consul.requests: true
      percentiles:
        spring.cloud.consul.requests: 0.5,0.9,0.95,0.99
      sla:
        spring.cloud.consul.requests: 10ms,50ms,100ms,500ms
2

Métriques spécifiques Consul :

# Métriques disponibles
spring.cloud.consul.requests
spring.cloud.consul.discovery.requests
spring.cloud.consul.config.requests
spring.cloud.consul.catalog.requests
spring.cloud.consul.session.requests

# Exemple de configuration
management:
  metrics:
    enable:
      spring.cloud.consul: true
    distribution:
      percentiles-histogram:
        spring.cloud.consul.requests: true

Intégration avec Prometheus et Grafana

1

Configuration Prometheus :

# prometheus.yml
scrape_configs:
  - job_name: 'consul-server'
    static_configs:
      - targets: ['localhost:8500']
    metrics_path: '/v1/metrics'
    params:
      format: ['prometheus']
    scrape_interval: 15s

  - job_name: 'spring-boot-apps'
    static_configs:
      - targets: ['localhost:8080']  # User service
      - targets: ['localhost:8081']  # Order service
    metrics_path: '/actuator/prometheus'
    scrape_interval: 15s

  - job_name: 'consul-clients'
    consul_sd_configs:
      - server: 'localhost:8500'
        services: []
    metrics_path: '/actuator/prometheus'
    scrape_interval: 15s
2

Dashboard Grafana :

# Panel 1: Taux de requêtes
rate(spring_cloud_consul_requests_seconds_count[5m])

# Panel 2: Latence 95th percentile
histogram_quantile(0.95, sum(rate(spring_cloud_consul_requests_seconds_bucket[5m])) by (le))

# Panel 3: Taux d'erreurs
rate(spring_cloud_consul_requests_seconds_count{status="5xx"}[5m]) / 
rate(spring_cloud_consul_requests_seconds_count[5m]) * 100

# Panel 4: Utilisation mémoire
jvm_memory_used_bytes{area="heap"}

# Panel 5: Nombre de services enregistrés
count(consul_catalog_service_node{service="user-service"})
3

Alertes Prometheus :

# Alerte pour latence élevée
groups:
  - name: consul-alerts
    rules:
      - alert: HighConsulLatency
        expr: histogram_quantile(0.95, sum(rate(spring_cloud_consul_requests_seconds_bucket[5m])) by (le)) > 1
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "High Consul latency"
          description: "Consul latency above 1 second"

      - alert: ConsulServiceDown
        expr: up{job="consul-server"} == 0
        for: 30s
        labels:
          severity: critical
        annotations:
          summary: "Consul server is down"
          description: "Consul server is not responding"

Événements et Logging

1

Configuration du logging :

# application.yml
logging:
  level:
    org.springframework.cloud.consul: DEBUG
    com.ecwid.consul: DEBUG
    org.springframework.cloud.loadbalancer: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/consul-client.log

# Configuration avancée du logging
logging:
  pattern:
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/consul-client.log
    max-size: 10MB
    max-history: 30
  logback:
    rollingpolicy:
      max-file-size: 10MB
      total-size-cap: 1GB
      max-history: 30
2

Écoute des événements personnalisés :

@Component
public class ConsulEventListener {
    
    private static final Logger logger = LoggerFactory.getLogger(ConsulEventListener.class);
    
    @EventListener
    public void onHeartbeatEvent(HeartbeatEvent event) {
        logger.debug("Consul heartbeat event: {}", event.getValue());
    }
    
    @EventListener
    public void onInstanceRegisteredEvent(InstanceRegisteredEvent event) {
        logger.info("Service instance registered: {}", event.getInstance().getServiceId());
    }
    
    @EventListener
    public void onInstanceDeregisteredEvent(InstanceDeregisteredEvent event) {
        logger.info("Service instance deregistered: {}", event.getInstance().getServiceId());
    }
    
    @EventListener
    public void onRefreshEvent(RefreshRemoteApplicationEvent event) {
        logger.info("Configuration refresh event: {}", event.getDestinationService());
    }
    
    @EventListener
    public void onEnvironmentChangeEvent(EnvironmentChangeEvent event) {
        logger.info("Environment changed: {}", event.getKeys());
    }
}

Patterns d'Architecture

Pattern de Service Mesh

Service Mesh avec Consul Connect

Connect fournit une solution de service mesh intégrée avec mTLS, autorisations et observabilité.

1

Configuration du service mesh :

# service-defaults.hcl
Kind = "service-defaults"
Name = "web"

Protocol = "http"

# Appliquer la configuration
consul config write service-defaults.hcl

# service-intentions.hcl
Kind = "service-intentions"
Name = "web"

Sources = [
  {
    Name   = "api"
    Action = "allow"
  },
  {
    Name   = "frontend"
    Action = "allow"
  },
  {
    Name   = "*"
    Action = "deny"
  }
]

# Appliquer les intentions
consul config write service-intentions.hcl
2

Configuration des proxies sidecar :

# web-service.hcl
service {
  name = "web"
  id = "web-1"
  port = 8080
  
  connect {
    sidecar_service {
      proxy {
        upstreams = [
          {
            destination_name = "api"
            local_bind_port = 9102
          }
        ]
        
        config {
          protocol = "http"
          envoy_access_log_path = "C:/consul/logs/envoy-access-web.log"
          envoy_detailed_metrics_enabled = true
        }
      }
    }
  }
  
  check {
    name = "Web HTTP Check"
    http = "http://localhost:8080/health"
    interval = "10s"
  }
}
3

Communication sécurisée :

# Dans l'application web
# Appel à l'API via le proxy sidecar
curl http://localhost:9102/api/users

# Le trafic est automatiquement :
# 1. Chiffré avec mTLS
# 2. Authentifié avec les intentions
# 3. Observé avec les métriques
# 4. Routé vers le service api

# Configuration du service api
service {
  name = "api"
  port = 8081
  
  connect {
    sidecar_service {}
  }
  
  check {
    name = "API HTTP Check"
    http = "http://localhost:8081/health"
    interval = "10s"
  }
}

Pattern de Configuration Distribuée

Gestion de Configuration avec KV Store

Utilisation du KV store pour la configuration distribuée et l'actualisation dynamique.

1

Structure de configuration :

# Structure du KV store
config/
├── application/
│   ├── database/
│   │   ├── host = "localhost"
│   │   ├── port = "5432"
│   │   └── pool-size = "10"
│   ├── cache/
│   │   ├── redis-host = "localhost"
│   │   ├── redis-port = "6379"
│   │   └── ttl = "300"
│   └── logging/
│       ├── level = "INFO"
│       └── file = "C:/logs/app.log"
├── services/
│   ├── web/
│   │   ├── timeout = "5000"
│   │   └── retries = "3"
│   └── api/
│       ├── timeout = "10000"
│       └── retries = "5"
└── environments/
    ├── dev/
    │   └── database/
    │       └── host = "dev-db.local"
    └── prod/
        └── database/
            └── host = "prod-db.company.com"
2

Application avec configuration dynamique :

# Application Spring Boot avec Consul Config
# bootstrap.yml
spring:
  application:
    name: web-service
  cloud:
    consul:
      host: localhost
      port: 8500
      config:
        enabled: true
        format: YAML
        prefix: config
        default-context: application
        profile-separator: ','
        data-key: data
      discovery:
        enabled: true

# Service de configuration
@Service
@RefreshScope
public class DynamicConfigurationService {
    
    @Value("${config.database.host:localhost}")
    private String databaseHost;
    
    @Value("${config.database.port:5432}")
    private String databasePort;
    
    @Value("${config.cache.ttl:300}")
    private int cacheTtl;
    
    public DatabaseConfig getDatabaseConfig() {
        return new DatabaseConfig(databaseHost, databasePort);
    }
    
    public int getCacheTtl() {
        return cacheTtl;
    }
}

# Contrôleur de refresh
@RestController
public class ConfigRefreshController {
    
    @Autowired
    private RefreshScope refreshScope;
    
    @PostMapping("/refresh-config")
    public ResponseEntity<String> refreshConfiguration() {
        refreshScope.refreshAll();
        return ResponseEntity.ok("Configuration refreshed");
    }
}
3

Scripts de gestion de configuration :

# config-manager.ps1
param(
    [string]$ConsulHost = "localhost:8500",
    [string]$ConfigPath = "C:\consul\config"
)

# Fonction pour mettre à jour la configuration
function Update-Config {
    param([string]$Key, [string]$Value)
    
    $uri = "http://$ConsulHost/v1/kv/config/$Key"
    Invoke-RestMethod -Uri $uri -Method Put -Body $Value
    Write-Host "Updated $Key = $Value"
}

# Fonction pour lire la configuration
function Get-Config {
    param([string]$Key)
    
    $uri = "http://$ConsulHost/v1/kv/config/$Key?raw"
    $value = Invoke-RestMethod -Uri $uri -Method Get
    Write-Host "$Key = $value"
    return $value
}

# Fonction pour déployer la configuration
function Deploy-Config {
    param([string]$Environment, [string]$Service)
    
    $configFile = Join-Path $ConfigPath "$Environment\$Service.yaml"
    if (Test-Path $configFile) {
        $config = Get-Content $configFile | ConvertFrom-Yaml
        foreach ($item in $config.GetEnumerator()) {
            $key = "config/$Service/$($item.Key)"
            $value = $item.Value
            Update-Config -Key $key -Value $value
        }
    }
}

# Exemple d'utilisation
switch ($args[0]) {
    "update" { Update-Config -Key $args[1] -Value $args[2] }
    "get" { Get-Config -Key $args[1] }
    "deploy" { Deploy-Config -Environment $args[1] -Service $args[2] }
    default { Write-Host "Usage: config-manager.ps1 {update|get|deploy} [args...]" }
}

Pattern de Discovery et Load Balancing

Load Balancing Intelligent avec Tags et Meta

Utilisation des tags et métadonnées pour le routage intelligent et le load balancing.

1

Configuration des services avec tags :

# web-service-primary.hcl
service {
  name = "web"
  id = "web-primary-1"
  tags = ["primary", "v1", "production"]
  meta = {
    version = "1.2.3"
    environment = "production"
    zone = "us-east-1a"
    weight = "80"
  }
  port = 8080
  address = "192.168.1.100"
  
  check {
    name = "Web Primary Health"
    http = "http://192.168.1.100:8080/health"
    interval = "10s"
  }
}

# web-service-secondary.hcl
service {
  name = "web"
  id = "web-secondary-1"
  tags = ["secondary", "v1", "production"]
  meta = {
    version = "1.2.3"
    environment = "production"
    zone = "us-east-1b"
    weight = "20"
  }
  port = 8080
  address = "192.168.1.101"
  
  check {
    name = "Web Secondary Health"
    http = "http://192.168.1.101:8080/health"
    interval = "10s"
  }
}
2

Client avec load balancing intelligent :

@Service
public class IntelligentServiceClient {
    
    @Autowired
    private DiscoveryClient discoveryClient;
    
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    
    public List<ServiceInstance> getAvailableServices() {
        return discoveryClient.getInstances("web");
    }
    
    public String callPrimaryService() {
        // Trouver les services avec le tag "primary"
        List<ServiceInstance> primaryInstances = discoveryClient.getInstances("web")
            .stream()
            .filter(instance -> instance.getMetadata().containsKey("tags") && 
                              instance.getMetadata().get("tags").contains("primary"))
            .filter(instance -> "passing".equals(getServiceHealth(instance)))
            .collect(Collectors.toList());
        
        if (!primaryInstances.isEmpty()) {
            ServiceInstance selectedInstance = weightedSelection(primaryInstances);
            return callService(selectedInstance);
        }
        
        throw new RuntimeException("No primary service instances available");
    }
    
    private ServiceInstance weightedSelection(List<ServiceInstance> instances) {
        // Implémentation du load balancing pondéré
        int totalWeight = instances.stream()
            .mapToInt(instance -> Integer.parseInt(
                instance.getMetadata().getOrDefault("weight", "1")))
            .sum();
        
        int randomWeight = new Random().nextInt(totalWeight);
        int currentWeight = 0;
        
        for (ServiceInstance instance : instances) {
            currentWeight += Integer.parseInt(
                instance.getMetadata().getOrDefault("weight", "1"));
            if (randomWeight < currentWeight) {
                return instance;
            }
        }
        
        return instances.get(0);
    }
    
    private String callService(ServiceInstance instance) {
        String url = "http://" + instance.getHost() + ":" + instance.getPort();
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.getForObject(url, String.class);
    }
    
    private String getServiceHealth(ServiceInstance instance) {
        // Implémentation de vérification de santé
        try {
            String healthUrl = "http://" + instance.getHost() + ":" + instance.getPort() + "/health";
            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<String> response = restTemplate.getForEntity(healthUrl, String.class);
            return response.getStatusCode().is2xxSuccessful() ? "passing" : "critical";
        } catch (Exception e) {
            return "critical";
        }
    }
}
3

Load balancing avec Consul Template :

# nginx-load-balancer.ctmpl
upstream web_backend {
  # Services primaires (80% du trafic)
  {{range service "web" "passing" | filter "Tag" "primary"}}
  server {{.Address}}:{{.Port}} weight=4 max_fails=3 fail_timeout=30s;
  {{end}}
  
  # Services secondaires (20% du trafic)
  {{range service "web" "passing" | filter "Tag" "secondary"}}
  server {{.Address}}:{{.Port}} weight=1 max_fails=3 fail_timeout=30s;
  {{end}}
  
  # Configuration avancée
  least_conn;
  keepalive 32;
}

server {
  listen 80;
  server_name web.example.com;
  
  location / {
    proxy_pass http://web_backend;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    
    # Health checks
    {{range service "web" "passing"}}
    # Service {{.Service}} on {{.Node}}:{{.Address}}:{{.Port}} - Status: {{.Checks.AggregatedStatus}}
    {{end}}
  }
  
  # Monitoring endpoint
  location /health {
    access_log off;
    return 200 "healthy\n";
    add_header Content-Type text/plain;
  }
}

Bonnes Pratiques Consul et Spring Boot

Configuration et Déploiement

Principes Fondamentaux

  • Configuration externalisée : Utilisez des fichiers de configuration ou Config Server
  • Environnements séparés : Différentes configurations pour dev, test, prod
  • Versioning : Versionnez vos configurations avec le code
  • Documentation : Documentez clairement les stratégies de résilience

Configuration Recommandée

# Production
spring:
  cloud:
    consul:
      discovery:
        health-check-interval: 15s
        health-check-timeout: 3s
        health-check-critical-timeout: 1m
      config:
        watch:
          enabled: true
          delay: 1000
    loadbalancer:
      retry:
        enabled: true
      cache:
        enabled: true
        ttl: 30s
                    

Performance et Optimisation

Optimisations Clés

  • Configuration appropriée : Ajustez les seuils selon vos besoins spécifiques
  • Timeouts raisonnables : Ni trop courts (erreurs prématurées) ni trop longs (ressources bloquées)
  • Retry stratégique : Retry seulement sur les erreurs récupérables
  • Monitoring actif : Surveillez les métriques pour ajuster les configurations

Paramètres de Performance Optimisés

Paramètre Valeur Recommandée Raison
health-check-interval 15s Équilibre entre réactivité et charge
health-check-timeout 3s Détection rapide des pannes réseau
retry.max-attempts 3 tentatives Évite la surcharge sans abandon prématuré
cache.ttl 30s Performance optimale avec fraîcheur des données

Sécurité et Résilience

Considérations de Sécurité

  • Validation des entrées : Validez toujours les données avant de les traiter
  • Chiffrement des communications : Utilisez HTTPS entre les services
  • Rate limiting : Protégez contre les abus et DDoS
  • Monitoring des accès : Surveillez les patterns d'accès inhabituels

Pièges Courants à Éviter

  • Timeouts trop courts : Causent des erreurs prématurées
  • Trop de retries : Amplifient les problèmes de performance
  • Pas de health checking : Envoient des requêtes à des services défaillants
  • Configuration statique : Difficile d'adapter aux conditions changeantes
  • Pas de monitoring : Difficile de diagnostiquer les problèmes

Tests et Validation

Stratégies de Test

  • Tests unitaires : Testez les configurations de Consul
  • Tests d'intégration : Vérifiez l'interaction avec Consul
  • Tests de charge : Simulez des scénarios de haute charge
  • Tests de chaos : Simulez des pannes de services
  • Tests de performance : Mesurez l'impact sur la latence
// Exemple de test d'intégration
@SpringBootTest
class ConsulIntegrationTest {
    
    @Autowired
    private DiscoveryClient discoveryClient;
    
    @Autowired
    private ConfigurableApplicationContext context;
    
    @Test
    void testServiceRegistration() {
        // Attendre que le service soit enregistré
        await().atMost(30, SECONDS).untilAsserted(() -> {
            List<String> services = discoveryClient.getServices();
            assertThat(services).contains("user-service");
        });
    }
    
    @Test
    void testConfigurationLoading() {
        // Vérifier que la configuration est chargée
        String appName = context.getEnvironment().getProperty("app.name");
        assertThat(appName).isNotNull();
        assertThat(appName).isEqualTo("User Service");
    }
    
    @Test
    void testHealthCheck() {
        // Vérifier que le health check fonctionne
        HealthEndpoint healthEndpoint = context.getBean(HealthEndpoint.class);
        Health health = healthEndpoint.health();
        assertThat(health.getStatus()).isEqualTo(Status.UP);
    }
}

Résumé des Bonnes Pratiques

En suivant ces bonnes pratiques, vous obtiendrez :

  • Haute disponibilité : Résilience face aux pannes
  • Bonne performance : Distribution optimale de la charge
  • Facilité de maintenance : Configuration claire et documentée
  • Sécurité : Communications sécurisées et contrôlées
  • Observabilité : Monitoring et alerting efficaces