IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Apprendre à développer une application basée sur une architecture microservices avec Docker et le langage Java

L'objectif de cette quatrième série d'exercices est d'apprendre à construire une application en respectant une architecture à base de microservices en se focalisant sur le langage Java. Nous utiliserons pour cela plusieurs technologies : la bibliothèque KumuluzEE pour packager et exécuter une application microservice respectant la spécification MicroProfile, l'outil Docker pour l'isolation des microservices, la bibliothèque et le serveur RabbitMQ pour la gestion d'un bus d'événements afin que les microservices communiquent de manière asyncrhone et finalement l'outil Docker Compose pour la composition des microservices.

La grande majorité du code contenu dans les microservices vous sera donnée comme support. En effet, ces exercices se focaliseront principalement sur les problématiques de déploiement.

Dans la suite, on appelle « microservice », un programme qui implémente une fonctionnalité dans un langage donné (par exemple Java) et qui est isolé dans un conteneur Docker.

Nous invitons les lecteurs à consulter les deux supports de cours qui présentent les concepts mis en avant dans cette série d'exercices :

Les codes pour les exercices sont disponibles sur le dépôt Git suivant : https://github.com/mickaelbaron/javamicroservices-tutorial (pour récupérer le code faire : $ git clone https://github.com/mickaelbaron/javamicroservices-tutorial).

La solution de tous les exercices est disponible sur la branche solution : $ git checkout solution

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum 8 commentaires Donner une note à l´article (5).

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Prérequis logiciels

Avant de démarrer cette série d'exercices, veuillez préparer votre environnement de développement en installant les outils suivants :

Eclipse, Java et Maven sont nécessaires uniquement pour la partie liée à KumuluzEE. Même si ces outils ne sont pas installés, vous pourrez sans problème réaliser les exercices puisque nous ferons la compilation Java lors de la création d'une image Docker.

Toutes les expérimentations se feront sur un poste en local à partir d'un système d'exploitation Linux. Vous pouvez sans problème réaliser ces expérimentations depuis les systèmes OS X ou Windows en utilisant Docker Toolbox (voir l'installation de Docker pour ces systèmes) ou via Vagrant par exemple. Dans tous les cas où vous utiliserez une virtualisation, vous aurez à rediriger les ports réseaux suivants :

  • 6379 : port utilisé par le serveur Redis ;
  • 15672 et 5672 : ports utilisés par RabbitMQ (interface de gestion de RabbitMQ et serveur RabbitMQ) ;
  • 80: pour la redirection du port http.

II. Présentation de l'étude de cas

L'étude de cas utilisée est une application permettant de diffuser des messages « HelloWorld ». Elle fournit une interface web pour la saisie et pour la consultation des messages (voir figure ci-dessous).

Interface web pour la saisie et l'affichage

Sur le schéma proposé ci-dessous, nous détaillons la décomposition en microservices de cette application.

Architecture de l'application HelloWorld

L'architecture dispose de sept microservices.

  • Le microservice Web (contenu dans le projet helloworldwebmicroservice) fournit à l'utilisateur une interface web. La technologie utilisée sera du HTML/JavaScript pour le client et NodeJS pour créer un petit serveur web.
  • Le microservice Rest (contenu dans le projet helloworldrestmicroservice) a pour rôle de fournir une API de type service web pour le microservice Web. Lors de la réception d'un message « HelloWorld » celui-ci est envoyé au microservice Redis à des fins de stockage. Il publie également un événement vers le microservice RabbitMQ.
  • Le microservice Redis fournit un serveur Redis pour le stockage des messages « HelloWorld ».
  • Le microservice RabbitMQ fournit un bus d'événements basé sur RabbitMQ.
  • Le microservice Log (contenu dans le projet helloworldlogmicroservice) s'abonne au bus d'événements et affiche sur la sortie console les événements envoyés.
  • Les microservices Email et Twitter s'abonnent au bus d'événement et envoient respectivement un email ou un tweet. Ces deux microservices ne seront pas traités.

III. Exercice 1 : préparer le programme Java du service web HelloWorld (Rest)

III-A. But

  • Configurer un microservice respectant MicroProfile avec KumuluzEE.
  • Configurer un fichier pom.xml pour gérer les dépendances Maven.
  • Exécuter un microservice Java en ligne de commande.

III-B. Description

Le projet Java (celui qui servira pour le microservice Rest) du service web HelloWorld a été développé en utilisant les spécifications JAX-RS et CDI. Ces deux spécifications font parties du modèle de programmation MicroProfile permettant le développement de microservice avec le langage Java. Nous utiliserons KumuluzEE comme solution d'implémentation à MicroProfile. À noter que KumuluzEE utilise l'implémentation JERSEY pour JAX-RS.

Le code du projet Java est assez commun. Un package service pour la gestion des services web REST et un package dao pour la gestion des données avec la base de données Redis. Dans cet exercice on va partir d'un code déjà tout prêt et nous allons nous attacher à le configurer pour l'utiliser avec KumuluzEE.

III-C. Étapes à suivre

  • Démarrer l'environnement de développement Eclipse.
  • Importer le projet Maven helloworldrestmicroservice (File -> Import -> General -> Existing Projects into Workspace, choisir le répertoire du projet puis faire Finish).
  • Examiner les différents packages et classes. Vous remarquerez que le projet contient des erreurs de compilation dues à l'absence des dépendances vers KumuluzEE et indirectement vers JERSEY.
  • Ouvrir le fichier pom.xml et compléter le contenu de la balise <dependencies> par les dépendances suivantes :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
...
    <dependencies>
        <dependency>
            <groupId>com.kumuluz.ee</groupId>
            <artifactId>kumuluzee-core</artifactId>
            <version>${kumuluzee.version}</version>
        </dependency>
        <dependency>
            <groupId>com.kumuluz.ee</groupId>
            <artifactId>kumuluzee-servlet-jetty</artifactId>
            <version>${kumuluzee.version}</version>
        </dependency>
        <dependency>
            <groupId>com.kumuluz.ee</groupId>
            <artifactId>kumuluzee-jax-rs-jersey</artifactId>
            <version>${kumuluzee.version}</version>
        </dependency>
        <dependency>
            <groupId>com.kumuluz.ee</groupId>
            <artifactId>kumuluzee-cdi-weld</artifactId>
            <version>${kumuluzee.version}</version>
        </dependency>
        ...
    </dependencies>
...
  • Compléter dans la balise <properties> le numéro de version de KumuluzEE par la valeur 3.0.0 (version octobre 2018)
 
Sélectionnez
1.
2.
3.
4.
5.
6.
...
    <properties>
        <kumuluzee.version>3.0.0</kumuluzee.version>
        ...
    </properties>
...

Désormais le projet ne contient plus d'erreur et peut être compilé en totalité (Eclipse s'en charge via la compilation incrémentale).

  • Pour exécuter le projet depuis Eclipse, créer une configuration d'exécution que vous appellerez HelloworldRESTMicroservice et dont la classe principale (Main class) sera com.kumuluz.ee.EeApplication puis faire Run.

Votre programme s'exécute par l'intermédiaire de KumuluzEE. Pour tester, nous pourrions utiliser l'adresse http://localhost:8080/helloworld, mais comme le serveur Redis n'est pas encore opérationnel nous ne pourrons pas à cet instant aller plus loin dans les tests.

  • Avant de continuer, arrêter l'exécution du programme depuis le bouton Terminate (bouton rouge) de la console Eclipse.

Notre programme Java doit s'exécuter en ligne de commande et non pas depuis Eclipse quand il sera exécuté depuis un conteneur Docker. Nous allons donc réaliser une dernière modification sur le fichier pom.xml afin de préparer le terrain. Nous allons préciser à Maven que l'on souhaite que toutes les dépendances soient présentes dans le répertoire target du projet.

  • Ouvrir le fichier pom.xml.
  • Ajouter dans la balise <plugins> le plugin maven-dependency-plugin comme montré sur le code suivant.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
...
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.10</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
  • Depuis Eclipse, exécuter la configuration d'exécution appelée alldependencies (package). Si vous rencontrez des soucis avec l'intégration Maven sous Eclipse, exécuter la ligne de commande suivante à la racine de votre projet $ mvn package. Toutes les dépendances seront copiées par le plugin maven-dependency-plugin et localisées dans le répertoire helloworldrestmicroservice/target/dependency.
  • Ouvrir une invite de commande shell à la racine du projet helloworldrestmicroservice, puis exécuter la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
$ java -cp 'target/classes:target/dependency/*' com.kumuluz.ee.EeApplication
2018-12-28 18:56:55.562 INFO -- com.kumuluz.ee.configuration.sources.FileConfigurationSource -- Unable to load configuration from file. No configuration files were found.
2018-12-28 18:56:55.580 INFO -- com.kumuluz.ee.EeApplication -- Initialized configuration source: EnvironmentConfigurationSource
2018-12-28 18:56:55.580 INFO -- com.kumuluz.ee.EeApplication -- Initialized configuration source: SystemPropertyConfigurationSource
2018-12-28 18:56:55.581 INFO -- com.kumuluz.ee.EeApplication -- Initialized configuration source: FileConfigurationSource
2018-12-28 18:56:55.581 INFO -- com.kumuluz.ee.EeApplication -- Initializing KumuluzEE
2018-12-28 18:56:55.581 INFO -- com.kumuluz.ee.EeApplication -- Checking for requirements
2018-12-28 18:56:55.583 INFO -- com.kumuluz.ee.EeApplication -- KumuluzEE running in an exploded class and dependency runtime.
...

Votre microservice Rest est désormais opérationnel et l'instruction pour la démarrer fonctionne.

  • Arrêter l'exécution du programme en faisant simplement un CTRL-C.
  • Avant de passer à l'exercice suivant qui nous permettra de disposer d'un serveur Redis, essayons de comprendre comment la communication est réalisée entre l'application Java EE et le serveur Redis. Ouvrir la classe fr.mickaelbaron.helloworldrestmicroservice.dao.redis.JedisFactory et examiner la méthode URI getRedisURI().
 
Sélectionnez
1.
2.
3.
4.
5.
private static final String REDIS_HOST_ENV = "REDIS_HOST";
private URI getRedisURI() {
  String redisPort = System.getenv(REDIS_HOST_ENV);
  return URI.create(redisPort != null && !redisPort.isEmpty() ? redisPort : "tcp://localhost:6379");
}

Vous remarquerez que l'accès à l'hôte de Redis se fait par une variable d'environnement REDIS_HOST (qui sera utilisée plus tard quand le projet sera un microservice) ou se fait via l'adresse localhost (pratique pour les tests).

IV. Exercice 2 : préparer le microservice Redis (créer un conteneur à partir d'une image existante)

IV-A. But

  • Récupérer depuis Docker Hub une image prête à l'emploi.
  • Créer un conteneur depuis une image avec un volume.
  • Lister des images Docker.
  • Lister les conteneurs disponibles et inspecter un conteneur.

IV-B. Description

Le microservice Redis a pour objectif de conserver l'état des différents messages « HelloWorld » traités par le microservice Rest. Cette conservation de données se fera par l'intermédiaire d'un serveur de données NoSQL Redis. Ce serveur permettra au microservice Rest de s'y connecter afin de conserver la création des messages « HelloWorld ».

IV-C. Étapes à suivre

  • Allez sur le site Docker Hub et faire une recherche avec Redis. Vous remarquerez un nombre important de dépôts (Repositories) destiné à Redis. De manière générale, veuillez privilégier les dépôts officiels.
  • Ouvrir une invite de commande et se placer à la racine du dossier helloworldmicroservices.
  • Saisir la ligne de commande suivante pour télécharger la dernière version de l'image Docker Redis. Cela prendra un petit peu de temps, car toutes les couches (layers) de l'image doivent être téléchargées. À noter que chaque couche est une image qui fait référence à une image parente.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
$ docker pull redis
Using default tag: latest
latest: Pulling from library/redis
a5a6f2f73cd8: Pull complete
a6d0f7688756: Pull complete
53e16f6135a5: Pull complete
f52b0cc4e76a: Pull complete
e841feee049e: Pull complete
ccf45e5191d0: Pull complete
Digest: sha256:bf65ecee69c43e52d0e065d094fbdfe4df6e408d47a96e56c7a29caaf31d3c35
Status: Downloaded newer image for redis:latest
  • S'assurer que l'image a été correctement téléchargée en utilisant la commande images de l'outil docker.
 
Sélectionnez
1.
2.
3.
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
redis               latest              ce25c7293564        2 weeks ago         95MB
  • Les différentes couches de l'image peuvent être consultées en utilisant la commande history de l'outil Docker.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
$ docker history redis
ce25c7293564        2 weeks ago         /bin/sh -c #(nop)  CMD ["redis-server"]         0B
<missing>           2 weeks ago         /bin/sh -c #(nop)  EXPOSE 6379/tcp              0B
<missing>           2 weeks ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B
<missing>           2 weeks ago         /bin/sh -c #(nop) COPY file:b63bb2d2b8d09598…   374B
...
<missing>           6 weeks ago         /bin/sh -c groupadd -r redis && useradd -r -…   329kB
<missing>           6 weeks ago         /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>           6 weeks ago         /bin/sh -c #(nop) ADD file:dab9baf938799c515…   55.3MB
  • Créer un conteneur à partir de l'image Docker de Redis en saisissant la ligne de commande suivante.
 
Sélectionnez
1.
2.
$ docker run --name redis -v $(pwd)/data:/data -d redis redis-server --appendonly yes
c95b96730f1e3ff7e99d5d380b7d871fcd1a7dab2883be41b50e237f8012763c

Un conteneur appelé redis sera créé. L'option -v $(pwd)/data:/data permet de partager le répertoire $(pwd)/data de l'hôte avec le répertoire /data du conteneur. L'option -d permet de créer un conteneur en mode détaché (similaire au classique &). redis permet de désigner l'image et redis-server --apprendonly yes sont des options pour démarrer le serveur Redis.

  • Exécuter la ligne de commande suivante pour vérifier que le conteneur a été créé.
 
Sélectionnez
1.
2.
3.
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
c95b96730f1e        redis               "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        6379/tcp            redis

Nous venons de créer dans un conteneur une instance d'un serveur Redis. Nous allons pouvoir l'utiliser pour tester notre programme Java.

V. Exercice 3 : tester le service web HelloWorld (Rest) avec le microservice Redis

V-A. But

  • Rediriger des ports avec Docker.
  • Inspecter des métadonnées d'un conteneur Docker.

V-B. Description

Revenons un instant sur le test du projet helloworldrestmicroservice (voir premier exercice). Pour assurer la communication entre ce projet (pas encore isolé dans un conteneur Docker) et le microservice Redis la solution est de rediriger le port 6379 de l'hôte vers le port 6379 du conteneur (le port par défaut de Redis).

V-C. Étapes à suivre

Pour la redirection du port 6379 de l'hôte vers le port 6379 du conteneur vous devez ajouter un paramètre lors de la construction du conteneur Redis : -p 6379:6379, mais il faut avant tout supprimer le conteneur existant créé depuis l'exercice 2.

  • Supprimer le conteneur nommé redis via la ligne de commande suivante.
 
Sélectionnez
1.
2.
docker rm -f redis
redis
  • Créer un nouvelle fois le conteneur redis en ajoutant le paramètre -p 6379:6379 permettant de rediriger le port 6379 de l'hôte vers le port 6379 du conteneur.
 
Sélectionnez
1.
2.
docker run --name redis -v $(pwd)/data:/data -p 6379:6379 -d redis redis-server --appendonly yes
a1f4e49c2ea5af796012ca5665bb57daba47158bb855cf6dcd4bca79382d025f
  • Exécuter la ligne de commande suivante pour vérifier que le conteneur a été créé et que le port 6379 a été redirigé. Une nouvelle colonne PORTS fait son apparition et précise que le port de l'hôte est redirigé vers le port 6379 du conteneur.
 
Sélectionnez
1.
2.
3.
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
a1f4e49c2ea5        redis               "docker-entrypoint.s…"   8 hours ago         Up 8 hours          0.0.0.0:6379->6379/tcp   redis
  • Exécuter la ligne de commande suivante pour obtenir plus d'information sur le conteneur nommé redis.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
$ docker inspect redis
[
    {
        "Id": "a1f4e49c2ea5af796012ca5665bb57daba47158bb855cf6dcd4bca79382d025f",
        "Created": "2018-12-28T21:49:21.1928527Z",
        "Path": "docker-entrypoint.sh",
        "Args": [
            "redis-server",
            "--appendonly",
            "yes"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 14589,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2018-12-28T21:49:21.83339Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        ...
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "aa5a1e2b2b1937acf1cf9f43ea1851a350481197620a628a15cd89fd32f299bb",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {
                "6379/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "6379"
                    }
                ]
            },
        ...
    }
]

Désormais nous pouvons démarrer le projet Java helloworldrestmicroservice afin qu'il se connecte au serveur Redis.

  • Depuis la configuration d'exécution, ajouter une variable d'environnement appelée (onglet Environment) REDIS_HOST avec la valeur tcp://0.0.0.0:6379, puis faire Run.
  • Pour tester le service web HelloWorld, nous utiliserons l'outil cURL. Exécuter les deux lignes de commandes suivantes afin de poster un message « HelloWorld » et de récupérer les messages « HelloWorld » envoyés.
 
Sélectionnez
1.
2.
3.
4.
5.
# Création d'un message HelloWorld à partir d'un contenu JSON
$ curl -H "Content-Type: application/json" -X POST -d '{"message":"Mon HelloWorld"}' http://localhost:8080/helloworld
# Lister les messages HelloWorld
$ curl http://localhost:8080/helloworld
[{"rid":1,"message":"Mon HelloWorld","startDate":"Sat Dec 29 07:38:01 CET 2018"}]

Tout fonctionne parfaitement. Notre programme Java du service web HelloWorld est prêt à être isolé dans un conteneur Docker afin de devenir le microservice Rest.

VI. Exercice 4 : préparer le microservice Rest (écrire un Dockerfile et créer sa propre image Docker)

VI-A. But

  • Écrire un fichier Dockerfile.
  • Construire une image Docker.
  • Isoler un programme Java.
  • Réduire la taille d'une image Docker avec le Multi-Stage Build.

VI-B. Description

Le microservice Rest a pour objectif d'isoler dans un conteneur Docker le projet Java du service web HelloWorld. Une image Docker à base de Java et de Maven sera utilisée. Elle sera construite en s'assurant que le projet Maven compile correctement.

VI-C. Étapes à suivre

  • Créer un fichier Dockerfile à la racine du projet helloworldrestmicroservice.
  • Ouvrir un éditeur de texte et saisir le contenu présenté ci-dessous :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
FROM java:openjdk-8-jdk
MAINTAINER Mickael BARON

ENV MAVEN_VERSION 3.3.9
RUN curl -fsSLk https://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz | tar xzf - -C /usr/share \
    && mv /usr/share/apache-maven-$MAVEN_VERSION /usr/share/maven \
    && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn
ENV MAVEN_HOME /usr/share/maven

ADD pom.xml /work/pom.xml
WORKDIR /work
RUN ["mvn", "dependency:go-offline"]

ADD ["src", "/work/src"]
RUN ["mvn", "package"]
EXPOSE 8080
ENTRYPOINT ["java", "-cp", "target/classes:target/dependency/*", "com.kumuluz.ee.EeApplication"]

Donnons quelques détails sur le contenu de ce fichier. À la ligne 1, il est précisé que l'image que nous souhaitons construire se basera sur une image fournissant Java 8. De la ligne 4 à la ligne 8, nous précisons comment doit être installé Maven. La ligne 10 précise que le fichier pom.xml sera copié dans le répertoire /work/pom.xml de l'image. La ligne 11 indique que le répertoire courant sera /work. La ligne 12 demande à Maven de télécharger tout ce dont il a besoin. L'avantage de procéder de cette manière c'est que si vous modifiez le code source de votre projet, les dépendances n'auront pas à être de nouveau téléchargées puisqu'elles auront été faites avant. Toutefois, si vous modifiez le fichier pom.xml, la reconstruction de l'image se fera à partir de la ligne 10. La ligne 14 précise que le répertoire src sera copié dans le répertoire /work/src de l'image. À la ligne 15, il est demandé de faire une construction de package via Maven. À la ligne 17, il est indiqué que le port 8080 sera exposé. Enfin à la ligne 18, il est indiqué comment démarrer le programme Java. La ligne de commande est identique à celle que nous avions vue dans l'exercice 1.

  • Nous allons construire l'image à partir du fichier Dockerfile, exécuter la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
$ docker build -t mickaelbaron/helloworldrestmicroservice .
...
Step 8/12 : RUN ["mvn", "dependency:go-offline"]
 ---> Running in 32928666e2c3
[INFO] Scanning for projects...
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-dependency-plugin/2.10/maven-dependency-plugin-2.10.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-dependency-plugin/2.10/maven-dependency-plugin-2.10.pom (12 KB at 15.6 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-plugins/27/maven-plugins-27.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-plugins/27/maven-plugins-27.pom (12 KB at 217.6 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/maven-parent/26/maven-parent-26.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/maven-parent/26/maven-parent-26.pom (39 KB at 607.1 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/org/apache/apache/16/apache-16.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/apache/16/apache-16.pom (16 KB at 300.7 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-dependency-plugin/2.10/maven-dependency-plugin-2.10.jar
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-dependency-plugin/2.10/maven-dependency-plugin-2.10.jar (157 KB at 1174.3 KB/sec)
...
Step 11/12 : EXPOSE 8080
 ---> Running in 6f6725702850
Removing intermediate container 6f6725702850
 ---> 2dc65427ed4f
Step 12/12 : ENTRYPOINT ["java", "-cp", "target/classes:target/dependency/*", "com.kumuluz.ee.EeApplication"]
 ---> Running in a4290ac7fa93
Removing intermediate container a4290ac7fa93
 ---> e04b1b2fc0aa
Successfully built e04b1b2fc0aa
Successfully tagged mickaelbaron/helloworldrestmicroservice:latest

Remarquer le téléchargement des dépendances Java (les fichiers Jar) réalisées lors de la commande $mvn dependendy:go-offline.

  • S'assurer que l'image a été correctement construite en exécutant la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
$ docker images
REPOSITORY                                       TAG                 IMAGE ID            CREATED             SIZE
mickaelbaron/helloworldrestmicroservice          latest              e04b1b2fc0aa        9 minutes ago       706MB
redis                                            latest              ce25c7293564        2 weeks ago         95MB
java                                             openjdk-8-jdk       d23bdf5b1b1b        23 months ago       643MB

Vous remarquerez que l'image java est disponible puisque l'image que nous venons de construire est basée sur celle-ci. Noter également la taille de notre image 706 Mo. En fait l'image mickaelbaron/helloworldrestmicroservice ne pèse que 60 Mo, car toute la partie Java 8 est déjà présente dans l'image de base java.

  • Vérifions que les dépendances Java (les fichiers Jar) ont correctement été stockées dans les couches de notre image. Exécuter la ligne de commande shell suivante :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
$ docker history mickaelbaron/helloworldrestmicroservice
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
e04b1b2fc0aa        13 minutes ago      /bin/sh -c #(nop)  ENTRYPOINT ["java" "-cp" …   0B
2dc65427ed4f        13 minutes ago      /bin/sh -c #(nop)  EXPOSE 8080                  0B
f2be75fce3d1        13 minutes ago      mvn package                                     18.4MB
7ecda19aca78        14 minutes ago      /bin/sh -c #(nop) ADD dir:6c964c0442e33433ff…   11.1kB
0f237d437944        14 minutes ago      mvn dependency:go-offline                       34.1MB
55ee0829d3eb        15 minutes ago      /bin/sh -c #(nop) WORKDIR /work                 0B
194da6ab61a8        15 minutes ago      /bin/sh -c #(nop) ADD file:3ac8f7547512fa916…   2.44kB
0fcad87e84f6        15 minutes ago      /bin/sh -c #(nop)  ENV MAVEN_HOME=/usr/share…   0B
0da24f3c9e32        15 minutes ago      /bin/sh -c curl -fsSLk https://archive.apach…   10MB
6ba73ad7b2d7        15 minutes ago      /bin/sh -c #(nop)  ENV MAVEN_VERSION=3.3.9      0B
e7bd770a86bd        15 minutes ago      /bin/sh -c #(nop)  LABEL maintener=Mickael B…   0B
d23bdf5b1b1b        23 months ago       /bin/sh -c /var/lib/dpkg/info/ca-certificate…   419kB
<missing>           23 months ago       /bin/sh -c set -x  && apt-get update  && apt…   352MB

Nous remarquons à ligne 7, l'exécution de notre commande $mvn dependency:go-offline dont le résultat a fait augmenter la taille de l'image de 34 Mo. Ceci est dû aux dépendances Java qui ont été téléchargées. Par conséquent, si nous modifions uniquement le code source de notre projet, seule la compilation des sources sera réalisée.

  • Éditer la classe fr.mickaelbaron.helloworldrestmicroservice.service.HelloWorldResource et faire une modification (ajouter par exemple un espace), puis relancer la construction de l'image.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
$ docker build -t mickaelbaron/helloworldrestmicroservice .
...
Step 9/12 : ADD ["src", "/work/src"]
 ---> 0b5eb5a40241
Step 10/12 : RUN ["mvn", "package"]
 ---> Running in 39c1681dcef3
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building helloworldrestmicroservice 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ helloworldrestmicroservice ---
...

Vous noterez que la construction de l'image se fait plus rapidement, car les étapes précédant l'étape 9 sont en cache.

À cet étape, nous constatons que nous venons de construire une image Docker qui fournit Java dans sa version JDK, l'outil Maven, les dépendances Maven nécessaires à son fonctionnement, les classes compilées et les dépendances Maven pour l'exécution du projet *helloworldrestmicroservice*. Or, seuls ces deux deniers sont nécessaires pour la suite. Par ailleurs, la taille de l'image est quand même assez imposante : 706 Mo.

Pour résoudre ces problèmes, nous allons utiliser le Multi-Stage Build de Docker qui permet de découper un fichier Dockerfile en plusieurs phases. Dans notre cas, nous aurons ainsi une phase de compilation et une phase d'exécution. La phase exécution qui sera finale fournira une image Docker plus réduite puisqu'elle sera basée sur une image JRE et non JDK.

  • Créer un fichier *DockerfileMSB* à la racine du projet *helloworldrestmicroservice*, éditer le fichier et saisir le contenu présenté ci-dessous.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
# Build env
FROM maven:3-jdk-8 AS build-java-stage
LABEL maintainer="Mickael BARON"

ADD pom.xml /work/pom.xml
WORKDIR /work
RUN ["mvn", "dependency:go-offline"]

ADD ["src", "/work/src"]
RUN ["mvn", "package"]

# Run env
FROM openjdk:8-jre-slim
COPY --from=build-java-stage /work/target/classes /classes/
COPY --from=build-java-stage /work/target/dependency/*.jar /dependency/
EXPOSE 8080
ENTRYPOINT ["java", "-cp", "classes:dependency/*", "com.kumuluz.ee.EeApplication"]

Donnons quelques détails sur le contenu de ce fichier. De la ligne 1 à la ligne 10, les instructions décrivent la phase de compilation qui porte le nom de build-java-stage. De la ligne 12 à la ligne 18, les instructions décrivent la phase d'exécution. Pour la phase de compilation, contrairement à notre premier fichier Dockerfile, nous sommes partis d'une image de base Maven. Il n'est donc pas nécessaire d'installer Maven. À la fin de cette phase de compilation, les classes compilées et les dépendances seront disponibles. Dans la phase d'exécution, ces fichiers et dépendances seront copiés depuis la première phase vers les répertoires classes/ et dependency/ (lignes 14 et 15).

  • Nous allons construire l'image à partir du fichier DockerfileMSB, exécuter la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
$ docker build -t mickaelbaron/helloworldrestmicroservice -f DockerfileMSB .
Sending build context to Docker daemon  110.6kB
Step 1/12 : FROM maven:3-jdk-8 AS build-java-stage
 ---> c77985f27eaf
Step 2/12 : LABEL maintainer="Mickael BARON"
 ---> Using cache
 ---> 18f92fa6c3db
Step 3/12 : ADD pom.xml /work/pom.xml
 ---> Using cache
 ---> 3fe1ae36df19
Step 4/12 : WORKDIR /work
 ---> Using cache
 ---> 5e8d3303b024
Step 5/12 : RUN ["mvn", "dependency:go-offline"]
 ---> Using cache
 ---> cf79c4db5ad8
Step 6/12 : ADD ["src", "/work/src"]
 ---> Using cache
 ---> f414fc12fca2
Step 7/12 : RUN ["mvn", "package"]
 ---> Using cache
 ---> 77d18dc14f6b
Step 8/12 : FROM openjdk:8-jre-slim
 ---> 7c6b62cf60ee
Step 9/12 : COPY --from=build-java-stage /work/target/classes /classes/
 ---> Using cache
 ---> e5c2fcda00f0
Step 10/12 : COPY --from=build-java-stage /work/target/dependency/*.jar /dependency/
 ---> Using cache
 ---> 973604af82e1
Step 11/12 : EXPOSE 8080
 ---> Using cache
 ---> 78950f5169e1
Step 12/12 : ENTRYPOINT ["java", "-cp", "classes:dependency/*", "com.kumuluz.ee.EeApplication"]
 ---> Using cache
 ---> 4f6445d0688f
Successfully built 4f6445d0688f
Successfully tagged mickaelbaron/helloworldrestmicroservice:latest
  • S'assurer que l'image a été correctement construite en exécutant la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
$ docker images
REPOSITORY                                       TAG                 IMAGE ID            CREATED             SIZE
mickaelbaron/helloworldrestmicroservice          latest              e04b1b2fc0aa        1 minutes ago       184MB
redis                                            latest              ce25c7293564        2 weeks ago         95MB
java                                             openjdk-8-jdk       d23bdf5b1b1b        23 months ago       643MB

Nous constatons que l'image construite a diminué en taille. Elle est passée de 706 Mo à 184 Mo.

VII. Exercice 5 : Lier les microservices Redis et Rest

VII-A. But

  • Créer un réseau Docker.
  • Associer un conteneur existant à un réseau Docker.
  • Créer un conteneur en spécifiant un réseau Docker.
  • Définir des variables d'environnement à la création d'un conteneur Docker.
  • Exécuter une commande Linux à partir d'un conteneur Docker en cours.

VII-B. Description

À cette étape nous disposons d'un conteneur correspondant au microservice Redis et d'une image Docker pour le futur microservice Rest dont le code se trouve dans le projet helloworldrestmicroservice. Nous allons nous intéresser dans cet exercice à créer le conteneur du microservice Rest et lui associer le conteneur du microservice Redis. Pour réaliser cette association, nous utilisons des réseaux Docker.

VII-C. Étapes à suivre

  • Créer un réseau Docker appelé helloworldnetwork en exécutant la ligne de commande suivante.
 
Sélectionnez
1.
2.
$ docker network create helloworldnetwork
14109f963b014b322fbbd3094af191517cd95d42244115d491e9abf8063d1d16
  • Afficher la liste des réseaux Docker afin d'assurer que votre réseau précédent créé à bien été créé.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
61f6b732863b        bridge              bridge              local
3017d43b85a0        helloworldnetwork   bridge              local
4523411e4251        host                host                local
50afa90b1502        none                null                local

Vous remarquerez que des réseaux Docker sont déjà existants (bridge, host et none). Ils sont créés par défaut lors de l'installation de Docker. Par exemple le réseau bridge est le réseau par défaut. Si aucun réseau n’est spécifié à la construction d’un conteneur, le conteneur se connectera sur le réseau par défaut bridge. Le conteneur nommé redis créé dans les précédents exercices utilise ce réseau. Les conteneurs connectés sur le réseau par défaut bridge se voient tous mais sont UNIQUEMENT accessibles en utilisant les IPs (pas les noms). Le réseau par défaut bridge n’est pas RECOMMANDÉ pour la mise en production.

Chaque conteneur connecté à un réseau Docker se verra identifié dans ce réseau par son nom. Dans le réseau Docker helloworldnetwork le conteneur redis sera identifié par redis.
  • Connecter le conteneur Redis précédemment créé au réseau Docker helloworldnetwork.
 
Sélectionnez
1.
docker network connect helloworldnetwork redis
  • Pour vérifier que le conteneur Redis est bien connecté au réseau Docker helloworldnetwork deux solutions sont possibles : en interrogeant soit le conteneur soit le réseau Docker.
  1. Afficher les informations du conteneur redis via l'option inspect.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
$ docker inspect redis
...
"Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "61f6b732863bb4652e386b60eea6ae1b1c06a76ced9f6d0b2e81779c275ef5f7",
                    "EndpointID": "302fb6762520789065c3b7430fe3ec63fac1db90dae1487bd13829c6c46e3ce4",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                },
                "helloworldnetwork": {
                    "IPAMConfig": {},
                    "Links": null,
                    "Aliases": [
                        "f9a0f6d81967"
                    ],
                    "NetworkID": "3017d43b85a088fa605e74dc9122b69a02ea4bd2f4fb0cd3286f60a11ddee7df",
                    "EndpointID": "feb4cd85c146b58d728b47848de6f000d7c23e74a5a550fdc06cff8d5220b5c9",
                    "Gateway": "172.19.0.1",
                    "IPAddress": "172.19.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:13:00:02",
                    "DriverOpts": null
                }
            }
...
  1. Afficher les informations du réseau Docker helloworldnetwork.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
$ docker network inspect helloworldnetwork
[
    {
        "Name": "helloworldnetwork",
        "Id": "3017d43b85a088fa605e74dc9122b69a02ea4bd2f4fb0cd3286f60a11ddee7df",
        "Created": "2018-12-30T15:49:18.1104193Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "f9a0f6d81967414672aba2edecf7e570010f1de7a61f17424287430753212d8a": {
                "Name": "redis",
                "EndpointID": "feb4cd85c146b58d728b47848de6f000d7c23e74a5a550fdc06cff8d5220b5c9",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]
  • Créer un conteneur pour le microservice Rest en exécutant la ligne de commande suivante.
 
Sélectionnez
1.
2.
$ docker run --name rest -p 8080:8080 -d --network helloworldnetwork --env REDIS_HOST=tcp://redis:6379 mickaelbaron/helloworldrestmicroservice
28eba01ed267a1618019df724cbc7686b574c2f73b0575ea1352f58cdaa4afc5

Cette instruction crée un conteneur Docker appelé Rest (--name rest), dont le port 8080 est redirigé sur le port 8080 du hôte (-p 8080:8080), lancé en tâche de fond (-d), connecté au réseau Docker helloworldnetwork à partir de l'image appelée mickaelbaron/helloworldrestmicroservice. Enfin, l'option --env REDIS_HOST=tcp://redis:6379 permet de créer une variable d'environnement lors de la création du conteneur. Cette variable utilisée dans notre projet helloworldrestmicroservice sert à identifer l'accès au serveur Redis. Ici la valeur donnée est redis puisque les conteneurs dans un réseau Docker sont identifiés par leur nom de création.

  • Assurons-nous que les deux conteneurs redis et rest sont connectés dans le réseau Docker helloworldnetwork.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
$ docker network inspect helloworldnetwork
...
        "Containers": {
            "28eba01ed267a1618019df724cbc7686b574c2f73b0575ea1352f58cdaa4afc5": {
                "Name": "rest",
                "EndpointID": "79c6e4ce51b6d57caddd25c9bf9f2e5bcb83d298b42477840cfeb479af1bf249",
                "MacAddress": "02:42:ac:13:00:03",
                "IPv4Address": "172.19.0.3/16",
                "IPv6Address": ""
            },
            "f9a0f6d81967414672aba2edecf7e570010f1de7a61f17424287430753212d8a": {
                "Name": "redis",
                "EndpointID": "feb4cd85c146b58d728b47848de6f000d7c23e74a5a550fdc06cff8d5220b5c9",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
            }
        },
...
  • Vérifions également que depuis le conteneur rest le dialogue peut s'opérer via le conteneur redis, exécuter la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
$ docker exec -it rest /bin/sh -c 'ping redis'
PING redis (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: icmp_seq=0 ttl=64 time=0.115 ms
64 bytes from 172.19.0.2: icmp_seq=1 ttl=64 time=0.213 ms
64 bytes from 172.19.0.2: icmp_seq=2 ttl=64 time=0.212 ms
...
  • Il nous reste plus qu'à tester le service web contenu dans le conteneur rest. Exécuter les deux lignes de commandes suivantes afin de poster un message « HelloWorld » et de récupérer les messages « HelloWorld » envoyés.
 
Sélectionnez
1.
2.
3.
4.
5.
# Création d'un message « HelloWorld » à partir d'un contenu JSON
$ curl -H "Content-Type: application/json" -X POST -d '{"message":"Mon HelloWorld"}' http://localhost:8080/helloworld
# Lister les messages « HelloWorld »
$ curl http://localhost:8080/helloworld
[{"rid":1,"message":"Mon HelloWorld","startDate":"Sat Dec 29 07:38:01 CET 2018"},{"rid":1,"message":"Mon HelloWorld","startDate":"Sat Dec 29 07:38:01 CET 2018"}]

VIII. Exercice 6 : émettre et recevoir des événements

VIII-A. But

  • Publier un message « HelloWorld » vers RabbitMQ.
  • Afficher le contenu d'un conteneur Docker.

VIII-B. Description

Nous allons enrichir le projet Java du service web HelloWorld de façon à ce qu'un message « HelloWorld » puisse être publié sur le bus d'événements de RabbitMQ à chaque fois que le service web de création est appelé. Nous allons également créer un nouveau microservice appelé Log (contenu dans le projet helloworldlogmicroservice) qui se chargera de réceptionner les événements envoyés au bus d'événements de RabbitMQ. Pour cela, nous allons utiliser un nouveau projet Java pour l'affichage des logs sur la console.

VIII-C. Étapes à suivre

  • Avant de continuer, nous allons arrêter et supprimer les conteneurs rest et redis. Exécuter les lignes de commande suivantes.
 
Sélectionnez
1.
2.
3.
$ docker rm -f rest
$ docker rm -f redis
redis
  • Depuis l'environnement de développement Eclipse, modifier la classe HelloWorldResource pour ajouter l'attribut currentProduceer et le contenu dans la méthode addHelloWorld.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
@Path("/helloworld")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class HelloWorldResource {

    @Inject
    @Named("redis")
    private IHelloWorldDAO currentDAO;

    @Inject
    private IHelloWorldEventProducer currentProducer;

    @GET
    public Response getHelloWorlds() {
        return Response.ok(currentDAO.getHelloWorlds()).build();
    }

    @POST
    public Response addHelloWorld(HelloWorld newHelloWorld) {
        if (newHelloWorld != null) {
            newHelloWorld.setStartDate(new Date().toString());
        }

        currentDAO.addHelloWorld(newHelloWorld);
        currentProducer.sendMessage(newHelloWorld);
        return Response.status(Status.CREATED).build();
    }
}
  • Mettre à jour l'image du microservice Rest défini dans le projet helloworldrestmicroservice, se placer à la racine du projet helloworldrestmicroservice et exécuter la ligne de commande suivante.
 
Sélectionnez
1.
2.
$ docker build -t mickaelbaron/helloworldrestmicroservice .
...
  • Importer le projet Maven helloworldlogmicroservice (File -> Import -> General -> Existing Maven Projects, choisir le répertoire du projet puis faire Finish).
  • Examiner la classe HelloWorldLogMicroservice. Les événements reçus sont récupérés et affichés sur la sortie console.
  • Créer un fichier Dockerfile à la racine du projet helloworldlogmicroservice.
  • Ouvrir un éditeur de texte et saisir le contenu présenté ci-dessous.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
FROM java:openjdk-8-jdk
LABEL MAINTAINER="Mickael BARON"

ENV MAVEN_VERSION 3.3.9
RUN curl -fsSLk https://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz | tar xzf - -C /usr/share \
	&& mv /usr/share/apache-maven-$MAVEN_VERSION /usr/share/maven \
	&& ln -s /usr/share/maven/bin/mvn /usr/bin/mvn
ENV MAVEN_HOME /usr/share/maven

ADD pom.xml /work/pom.xml
WORKDIR /work
RUN ["mvn", "dependency:go-offline"]

ADD ["src", "/work/src"]
RUN ["mvn", "package"]
ENTRYPOINT ["java", "-cp", "target/classes:target/dependency/*", "fr.mickaelbaron.helloworldlogmicroservice.HelloWorldLogMicroservice"]
CMD [localhost]
  • Nous allons construire l'image à partir de fichier Dockerfile, exécuter la ligne de commande suivante depuis la racine du projet helloworldlogmicroservice :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
$ docker build -t mickaelbaron/helloworldlogmicroservice .
... // Des tonnes de lignes
Step 6/12 : ADD pom.xml /work/pom.xml
 ---> bc656dde42fc
Step 7/12 : WORKDIR /work
 ---> Running in 33d8f9fb4cf8
Removing intermediate container 33d8f9fb4cf8
...

Vous remarquerez que la construction de l'image commence réellement à partir de l'étape 6. Tout ce qui est avant cette étape est déjà présent dans le cache de Docker.

  • Nous ne pouvons pas continuer tant que le serveur RabbitMQ n'est pas mis en place. Nous allons donc l'installer en l'isolant dans un conteneur. Le microservice résultat s'appellera Rabbitmq. Nous utiliserons une image de RabbitMQ contenant une interface web pour la gestion des événements reçus et envoyés (rabbitmq:management). Exécuter la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
docker pull rabbitmq:management
management: Pulling from library/rabbitmq
f17d81b4b692: Pull complete
...
Digest: sha256:3eb2fa0f83914999846f831f14b900c0c85cea8e5d2db48ff73cf7defa12fe96
Status: Downloaded newer image for rabbitmq:management
  • Il ne nous reste plus qu'à créer tous les conteneurs rest, redis et rabbitmq et de les connecter au réseau Docker helloworldnetwork. Exécuter les lignes de commande suivantes en faisant attention d'être à la racine du répertoire workspace.
 
Sélectionnez
1.
2.
3.
4.
$ docker run --name redis -d --network helloworldnetwork -v $(pwd)/data:/data redis redis-server --appendonly yes
$ docker run --name rabbitmq -d --network helloworldnetwork -p 5672:5672 -p 15672:15672 --hostname my-rabbit rabbitmq:management
$ docker run --name log -d --network helloworldnetwork mickaelbaron/helloworldlogmicroservice rabbitmq
$ docker run --name rest -d --network helloworldnetwork -p 8080:8080 --env REDIS_HOST=tcp://redis:6379 --env RABBITMQ_HOST=rabbitmq  mickaelbaron/helloworldrestmicroservicee
  • Assurons-nous que tous les conteneurs soient opérationnels en affichant le statut des conteneurs. Exécuter la ligne de commande shell suivante :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
$ docker ps
CONTAINER ID        IMAGE                                     COMMAND                  CREATED             STATUS              PORTS                                     NAMES
d3a1e7f0594b        mickaelbaron/helloworldrestmicroservice   "java -cp target/cla…"   4 minutes ago       Up 4 minutes        0.0.0.0:8080->8080/tcp                    rest
3b6430dd7c62        mickaelbaron/helloworldlogmicroservice    "java -cp target/cla…"   13 minutes ago      Up 13 minutes                                                 log
ac2314ccc4bf        rabbitmq:management                       "docker-entrypoint.s…"   16 minutes ago      Up 16 minutes       4369/tcp, ..., 0.0.0.0:15672->15672/tcp   rabbitmq
57f69c0deabe        redis                                     "docker-entrypoint.s…"   16 minutes ago      Up 16 minutes       6379/tcp                                  redis
  • Assurons-nous que l'interface d'administration de RabbitMQ fonctionne. Ouvrir un navigateur et saisissir l'adresse http://localhost:15672 (utilisateur : guest, mot de passe : guest)
Interface de gestion de RabbitMQ
  • Appeler le service web HelloWorld pour tester la chaîne complète des microservices en exécutant les commandes suivantes.
 
Sélectionnez
1.
2.
3.
4.
5.
# Création d'un message « HelloWorld » à partir d'un contenu JSON
$ curl -H "Content-Type: application/json" -X POST -d '{"message":"Mon HelloWorld"}' http://localhost:8080/helloworld
# Lister les messages « HelloWorld »
$ curl http://localhost:8080/helloworld
[{"rid":3,"message":"Mon HelloWorld avec presque tous les microservices","startDate":"Sun Dec 30 21:10:09 UTC 2018"},{"rid":2,"message":"Mon HelloWorld","startDate":"Sat Dec 29 07:38:01 CET 2018"},{"rid":1,"message":"Mon HelloWorld","startDate":"Sat Dec 29 07:30:01 CET 2018"}]
  • Afficher le contenu des logs du conteneur log en exécutant la commande suivante.
 
Sélectionnez
1.
2.
$ docker logs log
[x] Received '{"rid":2,"message":"Mon HelloWorld avec presque tous les microservices","startDate":"Mon Feb 01 16:30:08 UTC 2016"}'

IX. Exercice 7 : composer tous les microservices avec DockerCompose

IX-A. But

  • Ecrire un fichier docker-compose.yml.
  • Utiliser l'outil Docker Compose pour composer des conteneurs Docker.

IX-B. Description

Dans les précédents exercices, nous avons procédé à la création de chaque image et nous avons ensuite créé les conteneurs associés. Toutes ces tâches ont été réalisées via les commandes docker pull, docker build et docker run. Comme nous avons pu le constater, cela reste utilisable quand il y a peu de conteneurs, mais lorsqu'il y a plus de deux conteneurs cela devient difficile de tout gérer. C'est pour cette raison que nous allons employer l'outil Docker Compose.

IX-C. Étapes à suivre

  • Avant de commencer, faire « table rase » en supprimant tous les conteneurs précédemment créés. Exécuter la ligne commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
$ docker rm -f $(docker ps -q)
d3a1e7f0594b
3b6430dd7c62
ac2314ccc4bf
57f69c0deabe
  • S'assurer que tous les conteneurs ont été supprimés en exécutant la commande shell suivante.
 
Sélectionnez
1.
2.
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Nous allons également introduire un nouveau microservice qui se chargera de fournir une interface web à notre application : le microservice Web. Le code de cette application est dans le projet helloworldwebmicroservice. Le contenu du code est réalisé en HTML, JavaScript, Bootstrap et JQuery. Les dépendances des bibliothèques JavaScript sont gérées par l'outil Bower et la version distribuable du projet est obtenue par des tâches Grunt.

  • Parcourir les fichiers contenus dans le projet helloworldwebmicroservice. Vous remarquerez dans le fichier resthosts.js une variable restHostUrl qui prend la valeur de l'URL du microservice Rest. Dans notre cas, il s'agit de l'URL http://localhost:8080/helloworld. Modifier la valeur de cette variable.
  • Créer un fichier docker-compose.yml à la racine du workspace.
  • Éditer le fichier et compléter comme ci-dessous.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
version: '3'

services:
  redis:
    image: redis
    command: redis-server --appendonly yes
    volumes:
      - ./data:/data
    networks:
      - helloworldnet

  rabbitmq:
    image: rabbitmq:management
    hostname: my-rabbit
    ports:
      - 5672:5672
      - 15672:15672
    networks:
      - helloworldnet

  rest:
    build: helloworldrestmicroservice/
    image: mickaelbaron/helloworldrestmicroservice:latest
    depends_on:
      - rabbitmq
      - redis
    ports:
      - 8080:8080
    environment:
      REDIS_HOST: tcp://redis:6379
      RABBITMQ_HOST: rabbitmq
    networks:
      - helloworldnet

  web:
    build: helloworldwebmicroservice/
    image: mickaelbaron/helloworldwebmicroservice:latest
    ports:
      - 80:8080
    networks:
      - helloworldnet

  log:
    build: helloworldlogmicroservice/
    image: mickaelbaron/helloworldlogmicroservice:latest
    depends_on:
      - rabbitmq
    command: rabbitmq
    networks:
      - helloworldnet
networks:
  helloworldnet:
    external:
      name: helloworldnetwork

Veuillez noter deux choses.

  1. toutes les options passées en paramètres de la commande docker run des exercices précédents sont réutilisées ;
  2. le paramètre build permet de construire l'image en se basant sur les fichiers Dockerfile présents dans les répertoires des projets.
  • Nous pouvons donc exécuter ce fichier en utilisant la commande docker-compose up comme précisé ci-dessous (s'assurer d'être à la racine du répertoire workspace).
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
$ docker-compose up -d
Building web
Step 1/10 : FROM node:latest
latest: Pulling from library/node
cd8eada9c7bb: Pull complete
c2677faec825: Pull complete
fcce419a96b1: Pull complete
045b51e26e75: Pull complete
83aa5374cd04: Pull complete
bb9752e24b3a: Pull complete
04b2f7baa231: Pull complete
4dad5ba692b4: Pull complete
Digest: sha256:fa9ac2ee6b4bab070e669f5a88da97cad75e9ed6fa785a5b3dc22b42d6c41149
Status: Downloaded newer image for node:latest
 ---> 2718f90558b7
Step 2/10 : LABEL MAINTAINER="Mickael BARON"
 ---> Running in 8875e6cecd05
Removing intermediate container 8875e6cecd05
 ---> e8634d57c589
Step 3/10 : RUN npm install -g grunt-cli && npm install -g http-server
 ---> Running in 95993e32cc3f
/usr/local/bin/grunt -> /usr/local/lib/node_modules/grunt-cli/bin/grunt
...
Creating workspace_web_1      ... done
Creating workspace_redis_1    ... done
Creating workspace_rabbitmq_1 ... done
Creating workspace_log_1      ... done
Creating workspace_rest_1     ... done
  • Affichez les logs des conteneurs en exécutant la commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
$ docker-compose logs
redis_1     | 1:M 31 Dec 2018 14:34:55.652 * Ready to accept connections
web_1       | Starting up http-server, serving /workdir/site
web_1       | Available on:
web_1       |   http://127.0.0.1:8080
web_1       |   http://172.19.0.4:8080
web_1       | Hit CTRL-C to stop the server
...

Pour différencier de quelle sortie les logs sont issus, il est précisé sur la zone de gauche le nom du conteneur en suivant la convention <nom>_<indice>. <nom> précise le nom du conteneur et <nom> précise de quelle instance du conteneur il s'agit. Cette information est importante quand vous faites usage de docker-compose scale pour augmenter le nombre d'instances d'un conteneur. Nous étudierons ce point dans un prochain tutoriel.

  • Vérifier que tous les conteneurs ont été correctement créés en exécutant la ligne commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
$ docker-compose ps
        Name                      Command               State                                 Ports
---------------------------------------------------------------------------------------------------------------------------------
workspace_log_1        java -cp target/classes:ta ...   Exit 1
workspace_rabbitmq_1   docker-entrypoint.sh rabbi ...   Up       15671/tcp, 0.0.0.0:15672->15672/tcp, ..., 0.0.0.0:5672->5672/tcp
workspace_redis_1      docker-entrypoint.sh redis ...   Up       6379/tcp
workspace_rest_1       java -cp target/classes:ta ...   Up       0.0.0.0:8080->8080/tcp
workspace_web_1        http-server /workdir/site  ...   Up       0.0.0.0:80->8080/tcp

Nous remarquons que le conteneur microservices_log_1 est arrêté (State = Exit 1).

  • Examinons les logs du conteneur du microservice Log pour comprendre la raison de son arrêt. Exécuter la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
$ docker-compose logs log
Attaching to workspace_log_1
log_1       | Exception in thread "main" java.net.ConnectException: Connection refused (Connection refused)
log_1       | 	at java.net.PlainSocketImpl.socketConnect(Native Method)
log_1       | 	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
log_1       | 	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
log_1       | 	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
log_1       | 	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
log_1       | 	at java.net.Socket.connect(Socket.java:589)
log_1       | 	at com.rabbitmq.client.impl.FrameHandlerFactory.create(FrameHandlerFactory.java:32)
log_1       | 	at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:676)
log_1       | 	at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:722)
log_1       | 	at fr.mickaelbaron.helloworldlogmicroservice.HelloWorldLogMicroservice.<init>(HelloWorldLogMicroservice.java:26)
log_1       | 	at fr.mickaelbaron.helloworldlogmicroservice.HelloWorldLogMicroservice.main(HelloWorldLogMicroservice.java:72)

Le microservice Log n'arrive pas à se connecter au serveur RabbitMQ puisque ce dernier a un temps de démarrage relativement long. Par conséquent la connexion au serveur RabbitMQ est faite trop tôt. Cette problématique est connue chez les utilisateurs de l'outil Docker Compose https://docs.docker.com/compose/faq/#how-do-i-get-compose-to-wait-for-my-database-to-be-ready-before-starting-my-application. Pour pallier ce problème, nous allons modifier le programme Java afin de pouvoir tenter une nouvelle connexion en cas d'échec. Cette solution est connue sous le nom de healthcheck.

  • Ouvrir et éditer la classe HelloWorldLogMicroservice et compléter/ajouter le code suivant.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
public HelloWorldLogMicroservice(String rabbitMQHosts) ... {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(rabbitMQHosts);

        final Connection connection = createConnection(factory);
        final Channel channel = connection.createChannel();
        ...
    }

    private Connection createConnection(ConnectionFactory factory) ... {
        // We implement an healthcheck.

        boolean connectionIsReady = false;
        Connection connection = null;
        while(!connectionIsReady) {
            try {
                connection = factory.newConnection();
                connectionIsReady = true;
            } catch (Exception e) {
                System.out.println("Problem:" + e.getMessage());
                System.out.println("We will try to connect to RabbitMQ in 5s.");
                Thread.sleep(5000);
            }
        }

        System.out.println("Great !! Connected to RabbitMQ.");
        return connection;
    }
  • Pour recompiler uniquement l'image du conteneur *log*, exécuter la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
$ docker-compose build log
Building log
Step 1/12 : FROM java:openjdk-8-jdk
 ---> d23bdf5b1b1b
Step 2/12 : LABEL MAINTAINER="Mickael BARON"
 ---> Using cache
 ---> 0daf28a8fb12
Step 3/12 : ENV MAVEN_VERSION 3.3.9
 ---> Using cache
 ---> e9a99812c33e
Step 4/12 : RUN curl -fsSLk https://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz | tar xzf - -C /usr/share 	&& mv /usr/share/apache-maven-$MAVEN_VERSION /usr/share/maven 	&& ln -s /usr/share/maven/bin/mvn /usr/bin/mvn
 ---> Using cache
 ---> 72d4cf747bb2
...
Step 11/12 : ENTRYPOINT ["java", "-cp", "target/classes:target/dependency/*", "fr.mickaelbaron.helloworldlogmicroservice.HelloWorldLogMicroservice"]
 ---> Running in 0f31033988a7
Removing intermediate container 0f31033988a7
 ---> 048016e32f90
Step 12/12 : CMD [localhost]
 ---> Running in 2cab3b4ecd76
Removing intermediate container 2cab3b4ecd76
 ---> 95abc3ec1549
Successfully built 95abc3ec1549
Successfully tagged mickaelbaron/helloworldlogmicroservice:latest
  • Pour recréer les conteneurs, exécuter la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
$ docker-compose up -d
workspace_web_1 is up-to-date
workspace_redis_1 is up-to-date
workspace_rabbitmq_1 is up-to-date
workspace_rest_1 is up-to-date
Recreating workspace_log_1 ... done
  • Vérifier que les conteneurs ont correctement été créés en exécutant la ligne de commande suivante.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
$ docker-compose ps
        Name                      Command               State                                Ports
--------------------------------------------------------------------------------------------------------------------------------
workspace_log_1        java -cp target/classes:ta ...   Up
workspace_rabbitmq_1   docker-entrypoint.sh rabbi ...   Up      15671/tcp, 0.0.0.0:15672->15672/tcp, ..., 0.0.0.0:5672->5672/tcp
workspace_redis_1      docker-entrypoint.sh redis ...   Up      6379/tcp
workspace_rest_1       java -cp target/classes:ta ...   Up      0.0.0.0:8080->8080/tcp
workspace_web_1        http-server /workdir/site  ...   Up      0.0.0.0:80->8080/tcp
  • Il ne nous reste plus qu'à tester. Ouvrir un navigateur et se rendre à cette adresse : http://localhost.
Interface web de l'application HelloWorld

X. Conclusion, perspectives et remerciements

Nous avons vu au travers de huit exercices comment construire une application en respectant une architecture à base de microservices. Nous avons utilisé un ensemble de technologies dont notamment Docker pour l'isolation, RabbitMQ pour la communication asynchrone via un bus d'événements, Docker Compose pour la composition et des services web REST pour la communication synchrone.

Dans un prochain atelier, nous montrerons les aspects de montée en charge et notamment l'avantage d'utiliser Docker Compose pour le pilotage du nombre de conteneurs d'une même image. Nous insisterons aussi sur l'intérêt d'utiliser un reverse proxy et un load balancer dans ce cas d'usage.

Pour aller plus loin, vous pouvez consulter les ressources suivantes :

Vous pouvez retrouver cette série d'exercices sur mon compte Github : https://github.com/mickaelbaron/javamicroservices-tutorialApprendre à développer une application basée sur une architecture microservices avec Docker et le langage Java.

Je tiens à remercier Claude Leloup pour la relecture orthographique de cet article et Logan Mauzaize pour sa relecture technique attentive et ses nombreuses bonnes pratiques.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Mickael Baron. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.