Aujourd’hui, le mot d’ordre est livraison rapide, haute disponibilité et scalabilité.
Des microservices !
Du failover !!!
La vie est imprévisible ! Alors autant faire un backup ! Un backup c’est bien, mais un système actif / actif, c’est mieux ! Le premier reflexe est donc d’avoir un minimum de deux instances pour offrir un service.
Le secret résidera donc dans la capacité à ces services à répondre aux requêtes qui leur sont addressé de manière transparente pour les clients.
Un service sans état constitue la solution la plus adapté pour répartir une charge sur plusieurs instance. Si la requête addressé à un service est sans état, nous avons la garantie que n’importe qui peut prétendre y répondre.
Une fois que nous avons la certitude que les services de notre architectures
sont stateless, rien ne nous empêche de nous lancer avec des solutions de
Load Balacing. Un répartiteur de charge assura que notre plateforme pourra
scaler à volonté sans contrainte. Attention, ceci se limite à la couche
applicative. Les systèmes de persistences utilisé par la plateforme doivent
eux aussi être pensé pour scaler à leur manière.
Inévitablement, une plateforme orienté micro service finit par englober une myriade de service éparpillé partout. Ultimement, certains de ces services ont pour but d'être exposé à des tiers. Par conséquent, il faut offrir un mécanisme d’abstraction de l’architecture à ces tiers. Un client ne doit pas et ne peut pas connaître la topologie physique des services déployés.
Et bien non ! Eclater un service à travers le réseau n’a pas que des avantages. Une architecture orienté micro service est certe plus élastique et maléable, cependant elle introduit des contraintres pour lesquels des solutions sont nécessaire.
Source : http://trustmeiamadeveloper.com/
Imaginons une platforme de vente avec deux modules nécessitant de communiquer entre eux.
Avec cette configuration, chaque instance de service doit connaitre les adresses permettant de rejoindre ses dépendances. Très rapidement, ce modèle ne tient pas la route. Spécialement dans un context où des instances sont déployées à la demande.
La solution pour ce type de problème consiste à mettre à disposition un registre de service hautement disponible. Chaque instance de la plateform peut donc contacter le composant Service Discovery pour s’enregistrer et récupérer les adresses de tous les services composant la plateforme. Les applications clientes d’un registre de services doivent donc être penser pour s’adapter dynamiquement aux adresses de leur dépendances.
1 - Enregistrement au registre de service.
2 - Communications avec les services reçus du registre.
Derrière le problème solutionné par le Service Discovery se cache un problème fondamentalement plus large. Il s’agit de la gestion de configuration.
Sur des applications legacy, les configurations sont généralement gérées dans des fichiers. Encore une fois, une gestion manuelle ne permet pas de de scaler l’application. Des outils de provisionning tel que Puppet ou Chef peuvent se présenter comme une solution mais ne présentent pas la plus élégante.
Sur ce type de système, chaque service porte lui même sa configuration. Chaque évolution de configuration nécessite donc de mettre à jour tous ses fichiers ou encore de redéployer les fichiers à travers les outils de provisionning.
Pas très pratique lorsqu’on parle d’application cloud.
La meilleur pratique qui soit dans ce domaine se traduit par l’utilisation d’un service de configuration. Ce service est responsable d’héberger les configurations et de les rendre accessible aux différents services de la plateforme.
Ainsi, la seule configuration nécessaire à déployer en dur pour chaque service concerne les instructions pour communiquer avec le service de configuration.
Dans un système distribué, on retrouve systématiquement plusieurs instances pour un unique service. Avant même de parler de scalabilité, l’argument premier restera la redondance des services. Deux approches sont possibles pour gérer un Load Balancing.
Le service discovery et la gestion centralisée de configuration permettent à nos composant de communiqué entre eux de manière efficace. Cependant, ces solutions impliquent une complexité et des contraintent qui ne peut être imposée aux clients externes de notre application.
La solution réside donc dans un Reverse Proxy frontal responsable d’abstraire la complexité interne du système aux clients externes.
Do not beat a dead horse !
Rien se sert de s’engouffrer dans une queue qui s’empille et ne répond pas.
Les requêtes REST fonctionnent très bien pour des demandes de Request / Reply.
Parfois, certaines fonctionnalités s’implémente naturellement mieux avec un système de notification par messagerie.
Il faut tenir compte que les traces qui étaient historiquement centralisés dans les logs d’un unique service monolitique seront maintenant éclaté à travers tous le parc de serveurs. Il est aussi à prendre en compte que certains logs seront généré de manière aléatoire sur les différentes instances d’un même service.
Il faut donc prévoir une solution pour tracer le parcours d’une requête à travers tout le système.
java -jar ou sous un war conventionnel.
| Nom | Description |
|---|---|
spring-boot-starter-web |
Support for développement de la pile web complête en incluant Tomcat et spring-webmvc. |
spring-boot-starter-data-jpa |
Support pour “Java Persistence API” en incluant spring-data-jpa, spring-orm et Hibernate. |
spring-boot-starter-security |
Support pour spring-security. |
spring-boot-starter-data-mongodb |
Support pour la base de données NoSQL MongoDB en incluant spring-data-mongodb. |
|
|
Liste complête de tous les starters http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-starter |
<parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>1.4.0.RELEASE</version> </parent>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies>
package com.invivoo.springboot.plain; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.stereotype.Service; @SpringBootApplication public class PlanApplication { public static void main(String[] args) { System.out.println( SpringApplication.run(PlanApplication.class, args) .getBean(SuperService.class) .ping() ); } @Service public class SuperService { public String ping() { return "pong"; } } }
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
$ mvn package
$ java -jar target/my-app.jar
|
|
A mettre en favoris Gestion de configuration avec Spring Boot |
|
|
Pour aider à l’atelier MySQL avec DockerScripts SQL |
|
|
A mettre en favoris Reference SQL avec Spring BootReference Spring Data JPA |
|
|
A lire tous les soirs Configurations par défaut de Spring Boot |
Spring Boot est un outil formidable permettant d’obtenir une productivité difficile à retrouver ailleurs. Naturellement, beaucoup de système basé sur les microservices ont émergés sur une base de Spring Boot.
Ces systèmes ont tous été confrontés aux problématiques des architectures distribués. Pivotal et Netflix ont donc travaillé en compération pour offrir des solutions à ces problèmes basé sur leur expérience en production.
Spring Cloud est extension de Spring Boot offrant des solutions aux différentes problématiques que représentent les systèmes distribués (par exemple : gestion de configuration, annuaire de service, load balancing, routage, coupe circuits, etc). Le framework facilite la communication inter process tout en assurant que les différents services ne soit pas couplés autre que par le model de données.
Pour résumer, Spring Cloud facilite la communication entre applications développé avec Spring Boot.
Rien de plus simple ! Il suffit de créer une application Spring Boot qui importe des dépendances Spring Cloud.
... <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> ...
Une gestion de configuration centralisé se présente comme un des première problématique à laquelle nous devons répondre sur une architecture microservice.
Le projet Spring Cloud mêt donc à disposition des modules Spring Boot qui permettent construire un serveur de configuration ainsi que des clients pouvant être utilisé par des applications Spring Boot.
Le module Spring Cloud Config Server va servir d’interface HTTP à un backend de
configuration. Par défaut, le Config Server utilise un backend git. Des
implémentations peuvent être fourni pour supporter n’importe quel autre type
de repository. Cependant, git offre des fonctionnalités naturel aux concepts
suivies par le Config Server.
L’avantage du Spring Cloud Config Server résident dans les configurations
chargé depuis le service sont disponible à l’injection pour l’application.
Ce mécanisme s’insère nativement dans la gestion de configuration de Spring
Boot. Les ConfigurationProperties et @Value peuvent donc être utilisés sans
égard sur la provenance des configurations.
Un client sur Config Server doit reseigner le nom de son application avec la
propriété spring.application.name. Ce nom sera utilisé dans la résolution de
configuration sur le serveur.
Un profil est une information complémentaire qui se grèffe dans la demande de configuration. En pratique, le profile peut être utilisé pour spécifier l’environment de l’instance application (ex : dev, test, hom, prod).
Le chargement des configurations servit par le serveur se fera en chargant par ordre de priorité les fichiers suivants :
Implémenter un config server est très simple. Il s’agit d’une simple application
Spring Boot avec des dépendances spécifique et sur laquelle l’annotation
@EnableConfigServer a été déclaré. C’est le minimum requis pour obtenir un
serveur de configuration.
org.springframework.cloud:spring-cloud-config-server.
@EnableConfigServer.
application.properties.
... <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> ...
package com.invivoo.springcloud.configserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@EnableConfigServer
@SpringBootApplication
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}server.port: 8888
spring.cloud.config.server.git.uri: file://${user.home}/config-repo
|
|
Repository de configuration https://github.com/daniellavoie/formation-spring-cloud.git |
|
|
Pour tester votre serveur : http://localhost:8888/atelier-5/prod/config |
|
|
Pour un affichage JSON formaté depuis votre navigateur : https://jsonview.com/ |
A son démarrage, le client tentera de localiser un fichier
bootstrap.properties dans la racine du classpath de l’application.
Dans ce fichier doit figurer toutes les configurations nécessaire pour que le
service puisse contacter le Config Server et récupérer ses configurations.
spring.cloud.config.uri= # URL du Config Server
spring.cloud.config.label= # Branche sur laquelle les configurations
# sont situées
spring.cloud.config.username= # Utilisateur Git
spring.cloud.config.password= # Mot de passe Git
spring.cloud.config.failFast= # Empêche le service de démarrer si à
# 'true'.
... <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> </dependencies> ...
Quelques caractéristiques sur le serveur Eureka :
... <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> ...
@EnableEurekaServer @SpringBootApplication public class EurekaServer { public static void main(String[] args) { SpringApplication.run(EurekaServer.class, args); } }
server.port=8761
spring.application.name=eureka-server
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
Le client Eureka mêt à notre disposition une implémentation de l’interface
EurekaClient. Cette dernière sera initialisé par l’AutoConfiguration du
module spring-cloud-starter-eureka.
|
|
C’est possible sans Eureka ? Spring Cloud a construit une abstraction au dessus du client Eureka. Il s’agit
du DiscoveryClient. Toutes implémentations du DiscoveryClient assure une
compatibilité aux autres solution de Service Discovery avec la stack Spring
Cloud. |
... <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> ...
spring.application.name= # Le nom de l'application doit être renseigné.
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
Le Spring Cloud Dashboard est un projet Open Source piloté par Julien Roy.
Il propose une interface non officiel pour Eureka spécialement adapté aux applications développé avec Spring Boot qui sont enregistré sur Eureka.
|
|
Url du projet https://github.com/VanRoy/spring-cloud-dashboard |
Ribbon se présente comme un load balancer client. Sa particuliarité est de
venir s’incruster dans le RestTemplate de Spring. Ribbon prend son
intérêt lorsqu’il est utilisé avec un client Eureka. La configuration
par défaut des starter Spring Cloud s’occupera de gérer pour nous la service
discovery directement au sein du RestTemplate.
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency>
|
|
Aller plus loin avec Ribbon https://spring.io/guides/gs/client-side-load-balancing/ |
Pour une utilisation de Ribbon avec gestion du service discovery, il est
nécessaire que l’application ait activé l’annotation @EnableDiscoveryClient.
Une fois ce prérequis pris en compte, il suffit de déclarer un RestTemplate
avec @LoadBalanced.
@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }
Avec Spring Cloud, le retry automatique sur les différents noeuds du Load Balancer
Ribbon sera possible seulement si l’application a intégré Hystrix et activé
l’annotation @EnableCircuitBreaker. Voir la section Hystrix pour plus de
détails.
Le code de gestion autour du RestTemplate peut rapidement devenir répétitif. Par conséquent, la librairie Feign permet d’apporter une approche déclarative à l’implémentation de client REST.
Des annotations Spring MVC ou Jersey permettent de déclarer à Feign comment il devra binder notre client aux Web Service de notre choix.
Feign utilise Ribbon, par conséquent le nom du client feign doit correspondre à un service déclarer avec Ribbon ou bien récupéré depuis un Service Discovery.
@FeignClient("PRODUCT-SERVER") public interface ProductClient { @RequestMapping("/product/{ean13}") Product findOne(@PathVariable(name = "ean13") String ean13); }
... <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> ....
@EnableFeignClients @SpringBootApplication @EnableDiscoveryClient public class InvoiceServer { public static void main(String[] args) { SpringApplication.run(InvoiceServer.class, args); } }
Les systèmes sont malheureusement tous imprévisible. Peu importe l’effort investi sur à rentre le système réslient, il y aura toujours un cas où une dépendance ne sera pas joignable. Hystrix permet de prendre le controle de manière élégante sur ces dépendances et nous donnent l’opportunité de prévoir un traitement de secours lorsque qu’une dépendance ne répond plus.
|
|
Le Circuit Breaker en détails http://martinfowler.com/bliki/CircuitBreaker.html |
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency>
@HystrixCommand(fallbackMethod = "fallback") public List<String> method() { return dependency.method(); } public List<String> fallback( { return Arrays.asList(); }
Zuul est un Reverse Proxy HTTP architecture. Il est utilisé par Netflix pour les besoins suivant :
Zuul supporte un système de filtre pouvant être écrit en Java ou en Groovy.
L’utilisation principale au sein de Spring Cloud pour Zuul se résume à un role de reverse proxy pour abstraire la complexité d’une architecture aux clients externe. L’intégration avec le Service Discovery de Spring Cloud et l’utilisation de Ribbon rend ce travail de redirection presque transparant.
Par défaut, Zuul expoera tous les services Eureka à travers l’url
/servierId/**. Une fois la requête passé le proxy, la première partie de son
URL sera supprimé afin que le reste de la requête soit redirigé au service
cible.
|
|
Le retry automatique de Zuul après une première erreur se doit d'être configuré
manuellement avec la configuration zuul.routes.route-name.retryable=true.
Autrement Zuul retournera des erreurs lorsque l’un des microservice qu’il
tente d’appeller est indisponible. |
Sleuth vise à répondre à la problématique des services de tracage distribué imposé par les architecture microservice. De manière transparante,
Sleuth consiste en une implémentation de l’essaie Dapper rédigé par Google
|
|
Etude sur le tracage distribué par google http://research.google.com/pubs/pub36356.html |
filter {
# pattern matching logback pattern
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
}
}
filter {
# pattern matching logback pattern
grok {
match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
}
}
Spring Cloud Stream se veut comme une boite à outil pour simplifier la messagerie entre application de type microservice. Les services en fesant usage auront besoin de se reposer sur un broker externe.
Le projet vise à standardiser la déclaration et la configuration des queues de production et de consommation.
@SpringBootApplication public class StreamApplication { public static void main(String[] args) { SpringApplication.run(StreamApplication.class, args); } } @EnableBinding(Sink.class) public class TimerSource { ... @StreamListener(Sink.INPUT) public void processVote(Vote vote) { votingService.recordVote(vote); } }
|
|
Pour plus de détails sur Spring Cloud Stream http://docs.spring.io/spring-cloud-stream/docs/current/reference/htmlsingle/index.html |
Spring Cloud préconise l’utilisation de OAuth pour l’authentification à travers votre plateforme. Par conséquent la projet Spring Cloud Security offre les fonctionnalités suivantes :
Module plutot expériental qui permet aux application Spring Cloud de mettre à jour leur configuration Spring dynamiquement. Ce système se base sur Kafka ou AMQP. Dans l’esprit, le bus sert à communiquer des messages à tous les noeuds qui répondent au même service Eureka.
|
|
Plus de détails sur Spring Cloud Bus http://cloud.spring.io/spring-cloud-static/spring-cloud.html#_spring_cloud_bus |