Lien vers l'énoncé du TP: https://insatunisia.github.io/TP-eServices/tp4/
Le développement d’applications Web côté serveur a beaucoup évolué depuis les débuts de Docker. Grâce à Docker, il est maintenant plus facile de construire des applications évolutives et gérables construites à partir de microservices.
Dans ce contexte, nous allons nus intéresser à la conteneurisation d'une application à base de microservices dont l'architecture est donnée par la figure ci-dessous :
Les concepts que nous allons utiliser ici pour le déploiement sont les suivants:
1. Dockerfile: Il s'agit d'un document texte contenant toutes les instructions nécessaires à la création d'une image Docker. En utilisant le jeu d'instructions d'un fichier Dockerfile, nous pouvons écrire des étapes pour copier des fichiers, effectuer l'installation, etc...
2. Docker Compose: C'est un outil qui peut créer et générer plusieurs conteneurs. Il est utile pour créer l'environnement requis avec une seule commande.
Le répertoire de configuration doit être un répertoire git. Pour cela, placez-vous dans config-service\src\main\resources\myConfig et saisir les commandes ci-dessous :
- git init
- git add .
- git commit -m "First commit"
- git remote add origin [remote repository URL]
- git push origin master
Dans Config-service, ajouter la propriété suivante au niveau de proxy-service.properties ainsi que product-service.properties :
eureka.client.serviceUrl.defaultZone=http://discovery-service:8761/eureka/
Cette propriété permet de s'assurer que le proxy-service et product-service s'enregistrent automatiquement auprès du discovery-service en définissant la zone par défaut.
Note : discovery-service est le nom du service défini dans docker-compose.yml par la suite.
Créer en premier lieu un fichier Dockerfile qui permettra de construire l'image de base contenant Java.
FROM alpine:edge
LABEL maintainer="Oussama_Imen"
RUN apk add --no-cache openjdk8
Exécuter la commande ci-dessous pour créer l'image de base de Docker:
docker build --tag=alpine-jdk:base --rm=true .
Une fois que l'image de base est générée avec succès, il est temps de créer les images docker des différents services :
Créez un fichier appelé Dockerfile-configservice avec le contenu ci-dessous:
FROM alpine-jdk:base
LABEL maintainer="Oussama_Imen"
COPY config-service/target/config-service-0.0.1-SNAPSHOT.jar /opt/lib/
ENV SPRING_APPLICATION_JSON='{"spring": {"cloud": {"config": {"server": {"git": {"uri": "https://github.com/OussamaSghaier/e-services_configurations"}}}}}}'
ENTRYPOINT ["/usr/bin/java"]
CMD ["-jar", "/opt/lib/config-service-0.0.1-SNAPSHOT.jar"]
EXPOSE 8888
Ici, nous avons mentionné la construction de l'image à partir de l'image alpine-jdk créée précédemment.
Nous copierons le fichier jar nommé config-service-0.0.1-SNAPSHOT.jar à l’emplacement /opt/lib.
Lorsque le conteneur démarre, nous voulons que le serveur de configuration commence à s'exécuter. Par conséquent, ENTRYPOINT et CMD sont configurés pour exécuter la commande Java.
Le serveur de configuration doit être accessible avec le port 8888; C'est pourquoi nous avons EXPOSE 8888.
De même, nous devons créer un fichier Docker pour DiscoveryService, qui s'exécutera sur le port 8761. Le fichier Dockerfile-DiscoveryService devrait être comme suit:
FROM alpine-jdk:base
LABEL maintainer="Oussama_Imen"
RUN apk --no-cache add netcat-openbsd
COPY discovery-service/target/discovery-service-0.0.1-SNAPSHOT.jar /opt/lib/
COPY discoveryservice_wait-for-it.sh /opt/bin/
RUN chmod 755 /opt/bin/discoveryservice_wait-for-it.sh
EXPOSE 8761
Nous devons nous rappeler que DiscoveryService dépend de ConfigService. Il faut donc nous assurer que, avant de démarrer le DiscoveryService, le ConfigServie est opérationnel.
Pour cela, créons le script discoveryservice_wait-for-it ci-dessous :
#!/bin/sh
while ! nc -z config-service 8888 ; do
echo "Waiting for upcoming Config Service"
sleep 2
done
java -jar /opt/lib/discovery-service-0.0.1-SNAPSHOT.jar
ProxyService s'exécute sur le port 9999. Le fichier Dockerfile-ProxyService sera donc comme suit :
FROM alpine-jdk:base
LABEL maintainer="Oussama_Imen"
RUN apk --no-cache add netcat-openbsd
COPY proxy-service/target/proxy-service-0.0.1-SNAPSHOT.jar /opt/lib/
COPY proxyservice_wait-for-it.sh /opt/bin/
RUN chmod 755 /opt/bin/proxyservice_wait-for-it.sh
EXPOSE 9999
Ici, le ProxyService dépend du ConfigService ansi que le DiscoveryService. Il faut donc nous assurer que, avant de démarrer le ProxyService, les deux précédents sont opérationnels. Le script proxyservice_wait-for-it.sh sera comme suit
#!/bin/sh
while ! nc -z config-service 8888 ; do
echo "Waiting for upcoming Config Service"
sleep 2
done
while ! nc -z discovery-service 8761 ; do
echo "Waiting for the Discovery Service"
sleep 2
done
java -jar /opt/lib/proxy-service-0.0.1-SNAPSHOT.jar
ProductService s'exécute sur le port 8080. Le fichier Dockerfile-ProductService sera donc comme suit :
FROM alpine-jdk:base
LABEL maintainer="Oussama_Imen"
RUN apk --no-cache add netcat-openbsd
COPY product-service/target/product-service-0.0.1-SNAPSHOT.jar /opt/lib/
COPY productservice_wait-for-it.sh /opt/bin/
RUN chmod 755 /opt/bin/productservice_wait-for-it.sh
EXPOSE 8080
Tout comme le ProxyService, le ProductService dépend du ConfigService ansi que le DiscoveryService. Il faut donc nous assurer que, avant de le démarrer, les deux précédents sont opérationnels. Le script productservice_wait-for-it.sh sera comme suit
#!/bin/sh
while ! nc -z config-service 8888 ; do
echo "Waiting for the Config Service"
sleep 3
done
while ! nc -z discovery-service 8761 ; do
echo "Waiting for the Eureka Service"
sleep 3
done
while ! nc -z proxy-service 9999 ; do
echo "Waiting for the Proxy Service"
sleep 3
done
java -jar /opt/lib/product-service-0.0.1-SNAPSHOT.jar
Créons maintenant un fichier appelé docker-compose.yml, qui utilisera tous ces Dockerfiles pour créer notre environnement requis. Il s'assurera également que les conteneurs requis générés maintiennent le bon ordre et qu'ils sont interconnectés.
Ce dernier sera comme suit :
version: '2.0'
services:
config-service:
container_name: config-service
build:
context: .
dockerfile: Dockerfile-configservice
image: config-service:latest
expose:
- 8888
ports:
- 8888:8888
networks:
- spring-cloud-network
logging:
driver: json-file
discovery-service:
container_name: discovery-service
build:
context: .
dockerfile: Dockerfile-DiscoveryService
image: discovery-service:latest
entrypoint: /opt/bin/discoveryservice_wait-for-it.sh
environment:
SPRING_APPLICATION_JSON: '{"spring":{"cloud":{"config":{"uri":"http://config-service:8888"}}}}'
EUREKA_INSTANCE_PREFER_IP_ADDRESS: "false"
expose:
- 8761
ports:
- 8761:8761
networks:
- spring-cloud-network
links:
- config-service:config-service
depends_on:
- config-service
logging:
driver: json-file
proxy-service:
container_name: proxy-service
build:
context: .
dockerfile: Dockerfile-ProxyService
image: proxy-service:latest
entrypoint: /opt/bin/proxyservice_wait-for-it.sh
environment:
SPRING_APPLICATION_JSON: '{"spring":{"cloud":{"config":{"uri":"http://config-service:8888"}}}}'
expose:
- 9999
ports:
- 9999:9999
networks:
- spring-cloud-network
links:
- config-service:config-service
- discovery-service:discovery-service
depends_on:
- config-service
- discovery-service
logging:
driver: json-file
product-service-0:
container_name: product-service-0
build:
context: .
dockerfile: Dockerfile-ProductService
image: product-service:latest
entrypoint: /opt/bin/productservice_wait-for-it.sh
environment:
SPRING_APPLICATION_JSON: '{"spring":{"cloud":{"config":{"uri":"http://config-service:8888"}}}}'
expose:
- 8080
ports:
- 8080:8080
networks:
- spring-cloud-network
links:
- config-service:config-service
- discovery-service:discovery-service
depends_on:
- config-service
- discovery-service
logging:
driver: json-file
product-service-1:
container_name: product-service-1
build:
context: .
dockerfile: Dockerfile-ProductService
image: product-service:latest
entrypoint: /opt/bin/productservice_wait-for-it.sh
environment:
SPRING_APPLICATION_JSON: '{"spring":{"cloud":{"config":{"uri":"http://config-service:8888"}}}}'
expose:
- 8080
ports:
- 8081:8080
networks:
- spring-cloud-network
links:
- config-service:config-service
- discovery-service:discovery-service
depends_on:
- config-service
- discovery-service
logging:
driver: json-file
product-service-2:
container_name: product-service-2
build:
context: .
dockerfile: Dockerfile-ProductService
image: product-service:latest
entrypoint: /opt/bin/productservice_wait-for-it.sh
environment:
SPRING_APPLICATION_JSON: '{"spring":{"cloud":{"config":{"uri":"http://config-service:8888"}}}}'
expose:
- 8080
ports:
- 8082:8080
networks:
- spring-cloud-network
links:
- config-service:config-service
- discovery-service:discovery-service
depends_on:
- config-service
- discovery-service
logging:
driver: json-file
product-service-3:
container_name: product-service-3
build:
context: .
dockerfile: Dockerfile-ProductService
image: product-service:latest
entrypoint: /opt/bin/productservice_wait-for-it.sh
environment:
SPRING_APPLICATION_JSON: '{"spring":{"cloud":{"config":{"uri":"http://config-service:8888"}}}}'
expose:
- 8080
ports:
- 8083:8080
networks:
- spring-cloud-network
links:
- config-service:config-service
- discovery-service:discovery-service
depends_on:
- config-service
- discovery-service
logging:
driver: json-file
networks:
spring-cloud-network:
driver: bridge
Le fichier de composition Docker ci-dessous contient quelques entrées importantes:
1. version: un champ obligatoire dans lequel nous devons conserver la version du format Docker Compose.
2. services: chaque entrée définit le conteneur que nous devons générer.
3. build: si mentionné, alors Docker Compose devrait construire une image à partir du fichier Docker indiqué.
4. image: le nom de l'image qui sera créée.
5. network: le nom du réseau à utiliser. Ce nom devrait être présent dans la section réseaux.
6. links: cela créera un lien interne entre le service et le service mentionné. Ici, le ProductService doit par exemple accéder au CongifService aisni qu'au DiscoveryService.
7. depends: cela est nécessaire pour maintenir l'ordre. Le conteneur ProductService dépend de DiscoveryService et de ConfigService. Par conséquent, Docker s'assure que les conteneurs DiscoveryService et ConfigService sont créés avant le conteneur ProductService.
Après avoir créé ces différents fichiers, construisons nos images, créons les conteneurs requis et démarrons avec la seule commande:
docker-compose up --build
On a ainsi une architecture fonctionnelle de microservices composée des élements suivants:
- Proxy Service
- Config Service
- Discovery Service
- 3 Product Services
Pour arrêter l'environnement complet, nous pouvons utiliser cette commande:
docker-compose down

