Contactez-nous

Utilisation de Docker et Docker Compose pour orchestrer les microservices

Apprenez à utiliser Docker pour conteneuriser vos microservices Node.js et Docker Compose pour les définir, les exécuter et les connecter localement en développement.

Le défi de l'exécution locale des microservices

Nous avons défini nos microservices (Utilisateurs, Produits, Commandes) et réfléchi à leur communication. Cependant, gérer l'exécution simultanée de ces multiples services indépendants sur une machine de développement peut rapidement devenir complexe. Chaque service pourrait nécessiter une version spécifique de Node.js, avoir ses propres dépendances, écouter sur un port différent, et dépendre d'autres services (comme une base de données ou un message broker).

Démarrer manuellement chaque service dans son propre terminal, gérer les ports et s'assurer que tout communique correctement est fastidieux et source d'erreurs. C'est là que Docker et Docker Compose entrent en jeu pour simplifier radicalement l'environnement de développement et de test des architectures microservices.

Docker nous permet de packager chaque microservice et ses dépendances dans un conteneur isolé et reproductible. Docker Compose nous permet ensuite de définir et d'exécuter l'ensemble de notre application multi-conteneurs (tous nos microservices et leurs éventuelles dépendances) avec une seule commande, en gérant automatiquement le réseau entre eux.

Etape 1 : Dockeriser chaque microservice

La première étape consiste à créer un `Dockerfile` pour chaque microservice (par exemple, un dans le répertoire `users-service`, un dans `products-service`, etc.). Ce fichier décrira comment construire l'image Docker spécifique à ce service.

Le `Dockerfile` pour chaque service Node.js sera probablement très similaire à celui que nous avons vu précédemment pour conteneuriser une application Node.js standard. L'important est d'assurer la cohérence et d'inclure uniquement ce qui est nécessaire pour exécuter ce service particulier.

Exemple de `Dockerfile` type (à placer dans chaque répertoire de service) :

# Utiliser une image Node.js de base
FROM node:18-alpine

# Définir le répertoire de travail dans le conteneur
WORKDIR /usr/src/app

# Copier package.json et package-lock.json (ou yarn.lock)
# Le `.` à la fin indique la racine du contexte de build (le répertoire du service)
COPY package*.json ./

# Installer uniquement les dépendances de production
# Utiliser 'ci' pour une installation basée sur le lockfile
RUN npm ci --only=production

# Copier le reste du code source du service
COPY . .

# Exposer le port sur lequel ce service spécifique écoutera (ex: 3001 pour users, 3002 pour products...)
# La valeur réelle sera souvent passée via une variable d'environnement
# Mais EXPOSE est utile pour la documentation et certains outils.
EXPOSE 3000 # Mettez ici le port par défaut de votre service

# Commande pour démarrer le service
CMD [ "node", "server.js" ] # Adaptez 'server.js' si nécessaire

N'oubliez pas d'ajouter un fichier `.dockerignore` dans chaque répertoire de service pour exclure `node_modules`, `.git`, `.env`, etc., de l'image Docker.

Etape 2 : Orchestration avec Docker Compose (`docker-compose.yml`)

Une fois que chaque service peut être 'dockerisé', nous utilisons Docker Compose pour définir comment tous ces services doivent s'exécuter ensemble. Créez un fichier nommé `docker-compose.yml` à la racine de votre projet global (le répertoire contenant les dossiers de chaque microservice).

Ce fichier YAML décrit les `services` qui composent votre application, comment les construire (à partir de leurs Dockerfiles), comment les connecter en réseau, et comment leur passer des configurations (variables d'environnement, ports exposés).

Exemple de `docker-compose.yml` pour nos trois services :

version: '3.8' # Spécifier une version de la syntaxe Compose

services:
  # Définition du service Utilisateurs
  users-service:
    build: ./users-service # Chemin vers le répertoire contenant le Dockerfile
    container_name: users_service_container # Nom optionnel du conteneur
    ports:
      - "3001:3001" # Mapper port hôte 3001 vers port conteneur 3001
    environment:
      - PORT=3001
      - MONGODB_URL=mongodb://mongo_db:27017/users_db # URL vers le service MongoDB (défini ci-dessous)
      # Autres variables nécessaires (ex: JWT_SECRET)
    depends_on:
      - mongo_db # Indiquer que ce service dépend de MongoDB
    networks:
      - app-network # Connecter au réseau partagé

  # Définition du service Produits
  products-service:
    build: ./products-service
    container_name: products_service_container
    ports:
      - "3002:3002"
    environment:
      - PORT=3002
      - MONGODB_URL=mongodb://mongo_db:27017/products_db
      # Variables pour communiquer avec d'autres services si nécessaire
    depends_on:
      - mongo_db
    networks:
      - app-network

  # Définition du service Commandes
  orders-service:
    build: ./orders-service
    container_name: orders_service_container
    ports:
      - "3003:3003"
    environment:
      - PORT=3003
      - MONGODB_URL=mongodb://mongo_db:27017/orders_db
      # URLs pour appeler les autres services (via leur nom de service Docker Compose)
      - USERS_SERVICE_URL=http://users-service:3001
      - PRODUCTS_SERVICE_URL=http://products-service:3002
    depends_on:
      - mongo_db
      - users-service # S'assurer que les dépendances sont au moins démarrées
      - products-service
    networks:
      - app-network

  # Définition d'une base de données MongoDB (si utilisée par les services)
  mongo_db:
    image: mongo:latest # Utiliser une image officielle MongoDB
    container_name: mongo_db_container
    ports:
      - "27017:27017" # Exposer le port MongoDB sur l'hôte
    volumes:
      - mongo-data:/data/db # Persister les données MongoDB dans un volume Docker
    networks:
      - app-network

# Définir le réseau partagé
networks:
  app-network:
    driver: bridge # Type de réseau par défaut

# Définir les volumes pour la persistance
volumes:
  mongo-data:

Points clés de la configuration Docker Compose

  • `services` : Chaque clé sous `services` (`users-service`, `products-service`, etc.) définit un microservice ou une dépendance (comme `mongo_db`).
  • `build` : Indique à Docker Compose de construire l'image à partir du Dockerfile situé dans le répertoire spécifié. Alternativement, on peut utiliser `image:` pour utiliser une image déjà construite et poussée vers un registre.
  • `ports` : Mappe les ports entre la machine hôte et le conteneur (`HOST:CONTAINER`). Cela permet d'accéder aux services depuis l'extérieur (par exemple, via `http://localhost:3001` pour le service utilisateurs).
  • `environment` : Permet de définir des variables d'environnement pour chaque service. C'est crucial pour passer la configuration comme le port d'écoute, les URL de base de données, et surtout les URL des autres services.
  • `networks` : Nous définissons un réseau personnalisé (`app-network`) et y connectons tous nos services. C'est ce qui permet la communication inter-services.
  • Communication Inter-Services : Grâce au réseau défini par Docker Compose, les services peuvent communiquer entre eux en utilisant simplement le nom du service défini dans `docker-compose.yml` comme nom d'hôte. Par exemple, depuis `orders-service`, on peut atteindre `users-service` via `http://users-service:3001`. Docker Compose gère la résolution DNS interne.
  • `depends_on` : Permet de spécifier un ordre de démarrage basique (Compose attendra que les conteneurs listés soient démarrés avant de démarrer le service courant). Attention, cela ne garantit pas que l'application *à l'intérieur* du conteneur dépendant soit prête. Vos services doivent idéalement être résilients aux démarrages dans un ordre quelconque (par exemple, avec des retries de connexion à la base de données).
  • `volumes` : Utilisé ici pour persister les données de MongoDB en dehors du cycle de vie du conteneur.

Exécuter la stack microservices localement

Avec le fichier `docker-compose.yml` en place, gérer l'ensemble de l'application devient très simple :

  • Démarrer tous les services : Ouvrez un terminal à la racine du projet (là où se trouve `docker-compose.yml`) et exécutez :
    docker-compose up
    Cela va construire les images si elles n'existent pas (ou si les Dockerfiles ont changé) et démarrer tous les conteneurs définis. Vous verrez les logs de tous les services s'afficher dans ce terminal. Ajoutez `-d` (`docker-compose up -d`) pour démarrer en mode détaché (en arrière-plan).
  • Voir les logs : Si démarré en mode détaché, utilisez :
    docker-compose logs -f # Afficher les logs en continu
    docker-compose logs users-service # Logs d'un service spécifique
  • Arrêter tous les services :
    docker-compose down
    Cela arrête et supprime les conteneurs et le réseau. Ajoutez `-v` pour supprimer également les volumes (attention, cela supprime les données persistées comme celles de la base MongoDB).
  • Reconstruire les images : Si vous modifiez un Dockerfile ou le code source qu'il copie, vous devez reconstruire l'image avant de redémarrer :
    docker-compose build # Reconstruit les images des services modifiés
    docker-compose up -d --build # Reconstruit puis redémarre

Grâce à Docker et Docker Compose, vous pouvez maintenant lancer et gérer l'ensemble de votre architecture microservices Node.js avec quelques commandes simples, en assurant un environnement cohérent et en facilitant la communication inter-services pour le développement et les tests locaux.