Spring cloud fournit des outils répondant à des problématiques "classiques" en environnements distribués :
the big picture
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']
]
]
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.
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.
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.
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
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>
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.
Spring cloud bus,
Spring cloud sleuth,
etc.