M2TIW : Intergiciels et Services
TP microservices
Objectifs pédagogiques
- Concevoir et construire une architecture applicative à base de microservices.
- Approfondir vos connaissances sur Docker
Outils
Dans ce TP, vous utiliserez l'infrastructure OpenStack et les VMs que vous avez mises au point dans le TP Docker de PAI. Les autres images dont vous partirez sont à aller chercher sur le Docker hub, de même que les docs correspondantes.
Vous utiliserez Apache Bench pour tester votre application depuis une autre machine sur le réseau. Pour l'installer en Debian/Ubuntu : sudo apt-get install apache2-utils. Pour tester : ab -n 10000 -c 10 http://adresse-à-tester.
Indications
Vous trouverez beaucoup d'indications utiles pour déployer vos containers sur l'architecture OpenStack dans les TPs Swarm 1 et Swarm 2 de TIW7.
Préambule
Dans ce TP, vous allez reprendre votre application agenda et la décomposer en microservices. Pour cela, vous devez disposer d'une version :
- tournant dans un framework (Java EE, Spring ou OSGi)
- avec des agendas dans des compossants séparés et une interface standardisée pour y accéder
- avec une interface web (exemple de JSP cliente ici)
- avec la possibilité de passer facilement d'un support de persistance en XML à une BD (cf. question "Gestionnaire d'entités" du TP1, exemples de fichiers de persistence là et là)
Infrastructure du TP
Vous commencerez par instancier 3 VMs sur l'infrastructure OpenStack, avec la configuration standard sur laquelle vous avez fait les TPs Docker de PAI. Nous les nommerons M1, M2 et M3. Pour ne pas surcharger le réseau de l'université, vous installerez Apache Bench sur M1 et ferez tous les tests depuis cette machine.
Mise en place de l'application
Dans cette section, vous allez "dockeriser" votre application et la faire tourner dans plusieurs conteneurs. Toutes les manipulations suivantes sont à effectuer sur la machine M2 :
- Récupérez une version complète de votre application - avec la persistence dans un fichier XML et le client dans une interface web - et faites-la tourner dans un conteneur. Vous aurez besoin de mapper les fichiers de configuration et l'agenda de déploiement avec des emplacements spécifiques du système de fichiers de la machine hôte ; pour cela, utilisez des data volumes. Faites la redirection de ports pour pouvoir tester depuis une machine de TP.
- Ajoutez un conteneur nginx en front et configurez nginx pour qu'il redirige les requêtes sur le port d'écoute du conteneur applicatif (exposez le port d'écoute d'nginx sur la machine hôte et supprimez l'exposition de celui du conteneur applicatif).
- Créez un nouveau conteneur avec le SGBD de votre choix (postgreSQL, mySQL...) et modifiez la persistence de l'application pour qu'elle fonctionne avec un entity manager qui utilise ce SGBD. Pour ne pas exposer le SGBD sur le réseau (et surtout parce que c'est plus simple), faites communiquer les conteneurs avec un Docker link.
Remarques :
- Vous pouvez utiliser un autre conteneur, par exemple contenant un PHPMyAdmin, pour configurer votre base avant de la relier au reste de l'application, mais le plus simple est de déclarer la configuration dans les variables d'environnement d'un Dockerfile
- En toute logique, vous ne devriez pas stocker la BD de votre application dans un conteneur, mais utiliser un data volume pour mapper les éléments de votre SGBD qui servent à conserver les données sur le système de fichier de la machine hôte...
- Une fois le lien entre les deux conteneurs mis en place, vous avez deux solutions pour configurer votre application : utiliser le nom du conteneur directement dans l'URL JDBC du persistence.xml (exemple) ou créer une ressource dans la configuration de Tomcat, qui sera référencée dans les fichiers web.xml et persistence.xml de l'application (exemple, doc). Vous indiquerez dans votre rapport la solution choisie et les raisons de ce choix.
Testez à l'aide d'Apache Bench et notez le nombre de requêtes que votre application peut accepter par seconde, pour chaque type de requête.
Remarque : plus vous mettrez de config dans des Dockerfile plutôt que dans des commandes docker run, plus cela vous simplifiera la vie pour la question suivante...
Configuration de l'application
Vous allez maintenant utiliser Docker compose pour déclarer la structure globale de votre application et en automatiser le démarrage. Pour cela, effectuez la procédure d'installation mentionnée ici.
Remarques :
- À l'étape 4 de l'installation, la commande curl qui a marché pour moi (novembre 2015, Ubuntu server 14.04) : curl -L https://github.com/docker/compose/releases/download/1.5.0/docker-compose-Linux-x86_64 > docker-compose puis sudo mv docker-compose /usr/local/bin
- Le lien vers la doc des fichiers docker-compose.yml ne fonctionne pas pour moi. Il y en a une autre ici : https://docs.docker.com/v1.6/compose/yml/
Une fois la commande docker-compose installée, créez un fichier docker-compose.yml qui regroupe toutes les commandes de lancement de vos conteneurs, ainsi que les ports et les Docker links de ceux-ci. Testez.
Séparation des tâches dans plusieurs conteneurs applicatifs
Actuellement, la totalité du métier de votre application s'exécute dans le même conteneur. Si celle-ci est soumise à de nombreuses requêtes, ce conteneur peut être répliqué mais il occupera nécessairement beaucoup d'espace, par rapport à la tâche qu'il remplira. Vous allez donc rendre votre application plus "microservice-compliant", en morcelant la logique applicative dans plusieurs conteneurs, que vous pourrez répliquer indépendamment les uns des autres.
- Séparez vos agendas pour qu'ils s'exécutent dans des conteneurs différents. Redirigez les requêtes depuis le front sur les conteneurs adéquats et pensez aux accès concurrents au support de persistance.
Re-testez et re-notez les performances pour chaque type de requête.
- Recherchez les patterns dupliqués (DAO, contexte...) dans les différents conteneurs et "sortez-les" dans des conteneurs différents. Vous pouvez également créer des conteneurs ambassadeurs pour améliorer la communication entre les conteneurs sur différents hôtes.
À nouveau, testez les différentes configurations et adoptez la plus performante d'entre elles.
Poussez toutes vos images sur votre repository Docker hub. Alternativement, faites un snapshot de votre VM M2 et clonez-le pour instancier M3.
Mise en place d'un cluster
Attention : Docker compose n'est pas compatible avec les outils mis en place dans cette question. Vous reviendrez donc à partir d'ici au lancement de vos conteneurs en ligne de commande (pour l'instant).
Migration des conteneurs
Actuellement, tous les conteneurs de votre application tournent sur la machine 2. Vous allez tirer profit de l'infrastructure de cloud.
- Faites en sorte que le front et le stockage soient sur M1 et que la charge applicative soit répartie entre M2 et M3 (par exemple, 2 sur chaque noeud). Pour cela, vous migrerez vos conteneurs en utilisant Docker hub.
- Pour permettre à vos conteneurs de communiquer entre eux, vous ne pouvez plus utiliser de Docker links. Vous devez mettre au point un overlay network. Pour cela, vous installerez le key-value store (un annuaire version Docker) sur M1.
- dans votre docker-compose.yml, remplacez les links par des connexions réseau entre les conteneurs.
- Testez et affinez le positionnement de vos conteneurs en fonction des résultats
Passage à l'échelle
Vous allez maintenant utiliser Swarm pour gérer la charge de votre application.
- À l'aide de Docker Machine, créez et lancez un Swarm manager qui va observer et répartir la charge des différents conteneurs sur les noeuds de votre infrastructure.
- Mettez en place un service de découverte pour référencer les noeuds de votre réseau.
- Déployez ensuite une stratégie de planification pour permettre à Swarm de démarrer et d'arrêter des conteneurs en fonction de la charge.
Mesurez à nouveau les performances de l'application et utilisez ces résultats et ceux des mesures précédentes pour modifier au besoin votre décomposition en conteneurs et votre stratégie de planification. L'objectif est d'équilibrer le temps de traitement de tous les types de requêtes.
Cache Redis
Dans cette partie on mettra en place un cache
Redis pour accélérer la mise à disposition des données.
Afin de mettre en valeur l'effet du cache, on va
artificiellement augmenter le temps de traitement des composants métier (agendas) en ajoutant une instruction
Thread.sleep dans les méthodes du DAO.
Utilisation d'un cache redis à une machine
Dans un premier temps, on utilisera un cache redis situé sur une seule machine.
Il existe une image officielle redis qui peut être utilisée à cette fin.
Modifier votre code de façon à utiliser la bibliothèque Jedis pour accéder à votre instance Redis.
On implémentera une stratégie de cache de type pull: c'est lors des accès en lecture que les données sont mises en cache.
Les points suivants sont laissés à votre discrétion:
- Granularité du cache: quelles clés utiliser et quelles données leur associer.
- Positionnement de l'appel au cache lors du traitement d'une requête: front controller, code métier, DAO...
- Stratégie d'éviction et maintien de la cohérence du cache
Cluster Redis
On souhaite à présent utiliser plusieurs machines Redis pour le cache.
Comme expliqué dans la documentation sur le partionnement Redis, plusieurs technologies sont utilisables: Redis Cluster, Twemproxy et enfin le support du hashage côté client. On utilisera ici cette dernière technologie, plus simple à mettre en place.
Démarrer plusieurs conteneurs Redis (un sur chaque machine hôte openstack) et modifier la mise en place de la connection Jedis pour implémenter le hashage cohérent côté client via ShardedJedis.
Modalités de rendu
Ce TP est à rendre pour le dimanche 15 janvier 2017 à 23h59.
Vous rendrez sur Tomuss dans la case "Rendu TP Docker" de l'UE TIW1.
- Les IP de vos instances
- Les mesures de charges effectuées à chaque étape
- Les choix architecturaux effectués
- Les éléments de configuration
- Une conclusion
- Tout autre élément qu'il vous paraîtra pertinent de rajouter