Spring cloud

Spring cloud fournit des outils répondant à des problématiques "classiques" en environnements distribués :

 

  • configurations distribuées (et versionnées)
  • annuaire de services
  • routage "intelligent"
  • monitoring
  • load balancing
  • etc.

 

Spring cloud @ DTSI

the big picture

 

Configurations distribuées et versionnées

 

Spring cloud config

 

Spring cloud config permet d'externaliser les configurations des applications.

 

Il fournit un serveur de configuration qui s'appuie sur un dépôt Git pour servir les configurations.

Le dépôt Git est organisé par branches, chacune d'entre-elles correspondant à un environnement du cycle de vie des applications :

 

  • développement

  • intégration

  • qualification

  • pré-production

  • production

Impact sur une application (backend) qui souhaîte utiliser le serveur de configuration :

Le fichier de configuration application.yml est :

- sorti du projet,

- renommé en {applicationName}.yml, et

- inscrit dans le dépôt Git dédié sur la branche correspondante.

 

Il est remplacé dans le projet par le fichier bootstrap.yml

 

 

Exemple :

 

# ----------------------------------------------------------------------
# Configuration
# ----------------------------------------------------------------------
spring:
  application:
    name: argos
---
spring:
  profiles: develop
  cloud:
    config:
      uri: https://themis.dev.appli-gestion.nc/api/
      label: develop
---
spring:
  profiles: validation
  cloud:
    config:
      uri: https://themis.valid-ref.gnc/api/
      label: validation      
---
spring:
  profiles: shadow
  cloud:
    config:
      uri: https://themis-shadow.ref.gnc/api/
      label: shadow      
---
spring:
  profiles: production
  cloud:
    config:
      uri: https://themis.ref.gnc/api/
      label: production      

La récupération de la configuration est faite par l'appel du modèle d'URI :

 

${spring.cloud.config.uri}/${spring.application.name}/{default | ${spring.profiles.active}}/${spring.cloud.config.label}

 

où : 

  • ${spring.cloud.config.uri} représente l'URL de base du serveur de configuration,

  • ${spring.application.name} représente le nom de l'application,

  • {default | ${spring.profiles.active}} représente éventuellement le profil spring activé au lancement de l'application, ou default s'il n'y en a pas, et

  • ${spring.cloud.config.label} représente le nom de la branche correspondant au cycle de vie (developpement, validation, production).

 

Le fichier pom.xml de l'application doit être enrichi :

<dependencies>
    (...)
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>
</dependencies>
 
(...)
 
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-parent</artifactId>
            <version>Brixton.RC1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

La récupération de la configuration peut être vérifiée au démarrage de l'application : 

10:38:27.932 [main] INFO  o.s.c.c.c.ConfigServicePropertySourceLocator - 
                          Fetching config from server at: https://themis.dev.appli-gestion.nc/api/
10:38:28.875 [main] INFO  o.s.c.c.c.ConfigServicePropertySourceLocator - 
                          Located environment: 
                            name=jess, 
                            profiles=[develop], 
                            label=develop, 
                            version=da8d1dbc3fa1e98b9f990d43192e6bdc1a5d28a0
10:38:28.876 [main] INFO  o.s.c.b.c.PropertySourceBootstrapConfiguration - 
                          Located property source: CompositePropertySource [
                            name='configService', 
                            propertySources=[
                              MapPropertySource [
                                name='https://gitlab.prod.appli-gestion.nc/themis/configurations.git/jess.yml']
                              ]
                            ]

Annuaire de services

 

Spring cloud Netflix eureka

 

L'enregistrement des services dans l'annuaire permettra à d'autres applications de découvrir ces derniers sans avoir besoin de savoir où ils sont réellement installés (cf. Routage Intelligent).

 

L'enregistrement des services dits "transverses" est donc obligatoire.

Enregistrement d'un service dans l'annuaire

 

Il faut vérifier, dans le fichier pom.xml de l'application, la nature du projet parent :

 

soit :

(...)
       <parent>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-dependencies</artifactId>
             (...)
       </parent>
(...)

soit :

(...)
       <parent>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-parent</artifactId>
       </parent>
(...)

Dans le second cas uniquement, ajouter :

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Brixton.RC1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Dans tous les cas, ajouter :

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    <dependency>
        <groupId>nc.gouv.dtsi.etudes.axi.trinity</groupId>
        <artifactId>EurekaUtils</artifactId>
        <version>1.1.0.RC1</version>
    </dependency>
</dependencies>

La dépendance EurekaUtils permet de résoudre les instances de services enregistrées dans Eureka via les reverses proxies. Pour son utilisation, il faut ajouter, dans la classe de bootstrap de l'application :

@Bean
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils pInetUtils) {
    EurekaInstanceAxiConfigBean config = new EurekaInstanceAxiConfigBean(pInetUtils);
    return config.getEurekaInstanceConfigBean(pInetUtils);
}

Enrichir la configuration de l'application :

eureka:
  client:
    serviceUrl:
      defaultZone: https://{instanceName#1|instanceName#2}/eureka/

où {instanceName#?} = 

cycle {instanceName#?}
développement - https://castor.dev.appli-gestion.nc
- https://pollux.dev.appli-gestion.nc
validation - https://castor.valid-ref.gnc
- https://pollux.valid-ref.gnc
shadow - https://castor-shadow.ref.gnc
- https://pollux-shadow.ref.gnc
production - https://castor.ref.gnc
- https://pollux.ref.gnc

Modifier la classe de bootstrap de l'application en lui ajoutant l'annotation :

@EnableEurekaClient

Une fois l'application démarrée, il est possible de vérifier qu'elle est bien enregistrée en visitant les URI du tableau précédent.

Routage intelligent

 

Spring cloud Zuul

 

Zuul va utiliser l'annuaire de services Eureka pour :

 

- découvrir les services disponibles, et

 

- fournir les "routes" d'appels aux services. 

La première étape est de configurer les applications pour qu'elles soient des clients Eureka (cf. Annuaire de services).

 

La deuxième étape est d'intégrer Zuul par ajout de la dépendance suivante dans le fichier pom.xml du projet :

 

<dependencies>
	(...)
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-zuul</artifactId>
	</dependency>
</dependencies>

Encore une fois, l'activation de Zuul dans le projet consiste simplement en l'ajout d'une annotation dans la classe de bootstrap de l'application :

package nc.gouv.dtsi.etudes.axi.example;

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

@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulIntegrationExampleApplication {

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

A ce stade, Zuul découvre tous les services enregistrés dans Eureka :


2016-06-20 15:08:16.658  
    INFO 17956 --- 
        [nio-8080-exec-1] o.s.c.n.zuul.web.ZuulHandlerMapping      : 
            Mapped URL path [/argos/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2016-06-20 15:08:16.659  
    INFO 17956 --- 
        [nio-8080-exec-1] o.s.c.n.zuul.web.ZuulHandlerMapping      : 
            Mapped URL path [/jess/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2016-06-20 15:08:16.659  
    INFO 17956 --- 
        [nio-8080-exec-1] o.s.c.n.zuul.web.ZuulHandlerMapping      : 
            Mapped URL path [/eureka/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]

Pour filtrer les seuls services nécessaires à une application, il faut ajouter à la configuration de l'application :

zuul:
  prefix: /api
  ignoredServices: '*'
  routes:
    editions:
      path: /editions/**
      serviceId: jess      

- l'option prefix permet de préfixer les URI.

- L'option ignoredServices permet de ne pas découvrir les services correspondant au pattern de la valeur renseignée.

- l'option routes permet de définir explicitement les routes pour un serviceId donné.

Dans l'exemple ci-dessus, toutes les requêtes /api/editions/** vers le backend de l'application seront redirigées vers le backend de Jess.

Monitoring

 

Spring boot admin server

 

Spring boot admin, comme son nom l'indique, est une interface d'administration d'applications Spring Boot.

Pour intégrer une application avec Spring boot admin, il faut :


Modifier le fichier pom.xml de l'application en y ajoutant les dépendances suivantes :

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>org.jolokia</groupId>
    <artifactId>jolokia-core</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Enrichir la configuration de votre application :

spring:
  boot:
    admin:
      url: https://argos.dev.appli-gestion.nc
      api-path: admin/api/applications
      hazelcast:
          enabled: false
      client:
        service-url: https://{application name}.dev.appli-gestion.nc
        management-url: https://{application name}.dev.appli-gestion.nc/supervision
        health-url: https://{application name}.dev.appli-gestion.nc/supervision/health

Circuit breaker pattern

En environnements distribués, l'enchaînement d'appels de services peut ne pas fonctionner correctement à cause de :

- timeout,

- serveur distant en erreur (surcharge par exemple)

...

 

Le pattern circuit breaker permet de ne pas appeler un service si l'état dudit service ne le permet pas, état basé sur la récupération de métriques.

Le circuit breaker peut être dans un des trois états suivants:

 

- fermé : dans ce cas, l'appel au service est effectué normalement.

 

- ouvert : dans ce cas, l'appel au service est rejeté immédiatement ; le but est de laissé le temps au serveur hébergeant le service appelé de revenir dans un état de fonctionnement normal.

 

- semi-ouvert : lorsque le circuit breaker passe à l'état ouvert, un timer est démarré à l'expiration duquel le circuit breaker passe à l'état semi-ouvert. Dans cet état certains appels au service sont effectués et si les métriques redeviennent correctes, le circuit breaker repasse à l'état fermé.

Pour que Hystrix puisse collecter les métriques, ajouter la dépendance :

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

Implémentation :

(...)
/*
 * Méthode d'appel à un(des) service(s) tiers à protéger
 * en cas d'erreur d'appel...
 */
@HystrixCommand(fallbackMethod = "laMethodeAExecuterEnCasDErreur")
public T methodeAProteger() {
    RestTemplate lRestTemplate = new RestTemplate();
    URI lURI = URI.create("https://some.service.uri/something");
    return lRestTemplate.getForObject(lURI, T);
}

/*
 * Méthode de fallback pour la methodeAProteger
 */
public T laMethodeAExecuterEnCasDErreur() {
    T defaultResultInCaseOfServiceFailure= ...;
    return defaultResultInCaseOfServiceFailure;
}
(...)
@EnableCircuitBreaker

Hystrix fournit également un tableau de bord de monitoring des circuits breakers :

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>

Ribbon

client-side load-balancing

L'objectif de ribbon est de répartir les requêtes vers les "bons" serveurs.

 

Ces derniers sont automatiquement identifiés à partir du moment où ils se sont enregistrés dans Eureka.

Merci de votre attention

Spring cloud bus,

Spring cloud sleuth,

etc.

Made with Slides.com