Accueil / Articles / Un dépôt de configuration central pour les microservices : intégration et synchronisation en un seul commandement

Un dépôt de configuration central pour les microservices : intégration et synchronisation en un seul commandement

Créez un répertoire de configuration central avec des scripts d’installation, de synchronisation et de mise à jour quotidienne afin que chaque service NestJS et React partage les paramètres de linting, d’éditeur et de Docker.

2452 mots

Lorsqu’une équipe gère une douzaine de microservices Node.js, la partie la plus difficile de la première semaine d’un nouvel employé n’est souvent pas le code mais l’environnement : la bonne version de Node, des règles ESLint et Prettier compatibles, les valeurs correctes du .env ainsi qu’une configuration Docker fonctionnant comme celle des autres services. Chaque service a tendance à s’éloigner un peu de cette configuration, ce qui oblige les ingénieurs seniors à jouer le rôle d’un service d’assistance. Cet article montre comment résoudre ce problème grâce à un seul répertoire de configuration accompagné de quelques scripts Bash qui permettent de démarrer une machine en un seul commandement, de copier les paramètres partagés dans chaque service et de les maintenir à jour. Vous verrez également où ces scripts, tels qu’ils sont actuellement écrits, nécessitent des améliorations de sécurité avant de pouvoir être utilisés en toute confiance.

Comment l’écart de configuration se développe progressivement

Imaginez un développeur expérimenté qui rejoint une équipe gérant douze services. La première journée se passe à installer Node et Docker, à définir les règles de style ESLint propres à l’équipe, à deviner quelles variables doivent figurer dans tel ou tel fichier .env, et à demander en chat quelle version de NestJS sont utilisées par les services. Ensuite, le auth-service échoue en raison d’une erreur CORS causée par une configuration locale légèrement différente, et plus tard la première demande de pull request échoue au test d’intégration car la configuration Prettier partagée a été modifiée il y a un mois, sans que la copie locale ne soit mise à jour.

Aucun de ces échecs n’est dramatique, mais pris ensemble ils ont un coût important. Lorsque chaque service dispose de ses propres règles de lint légèrement différentes, de fichiers Dockerfiles, d’une structure de dossiers et de paramètres d’édition uniques, cela se traduit par des jours perdus pour les nouveaux arrivants et des heures perdues chaque semaine pour les ingénieurs seniors qui doivent répondre aux questions relatives à la configuration.

Un seul répertoire en tant que source de vérité

La solution consiste en un répertoire dédié, ici nommé dev-environment-config, qui contient tout ce dont a besoin une machine de développement : des modèles de projet, des règles de linting et de formatage, des paramètres VS Code, une configuration de base Docker, des scripts d’automatisation et une bibliothèque partagée. Lorsqu quelqu’un le met à jour, chaque machine de développeur intègre automatiquement la modification dès le lendemain matin.

La structure globale est la suivante, avec le répertoire central qui alimente chaque service :

dev-environment-config (Central Repo)
├── templates/          → Project scaffolding (NestJS, React)
├── shared/             → ESLint, Prettier, Git configs
├── vscode/             → Extensions & settings
├── docker/             → Base Docker configs
├── scripts/            → Automation (setup, sync, update)
├── common-lib/         → Shared NPM package
└── manifest.json       → Version tracking

         │  syncs to
         ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ user-svc │ │ auth-svc │ │ pay-svc  │  ... 12 services
│ .eslint  │ │ .eslint  │ │ .eslint  │
│ .vscode  │ │ .vscode  │ │ .vscode  │
│ Docker   │ │ Docker   │ │ Docker   │
└──────────┘ └──────────┘ └──────────┘

Construction du système étape par étape

Structure du répertoire central

Dans le répertoire, chaque fonctionnalité dispose de son propre dossier. Les modèles contiennent des starters complets pour NestJS et React, shared/ abrite les fichiers dotfiles pour ESLint, Prettier, EditorConfig, les règles Git ignore et commitlint, vscode/ contient les paramètres de l’éditeur, et scripts/ regroupe les scripts d’automatisation.

dev-environment-config/
├── templates/
│   ├── nestjs-service/    # Complete NestJS starter
│   └── reactjs-service/   # Complete React starter
├── shared/
│   ├── .eslintrc.js
│   ├── .prettierrc
│   ├── .editorconfig
│   ├── .gitignore
│   └── commitlint.config.js
├── vscode/
│   ├── extensions.json
│   ├── settings.json
│   └── launch.json
├── scripts/
│   ├── setup-new-developer.sh
│   ├── sync-config.sh
│   ├── auto-update.sh
│   └── create-new-service.sh
├── common-lib/            # Shared NPM package
└── manifest.json          # Version tracker

Le fichier manifest.json est ce qui rend possible le reste de l’automatisation. Il enregistre la version globale, la date et l’auteur de la dernière modification, les versions pour chaque zone de configuration ainsi que la liste des services appartenant à l’espace de travail.

{
  "version": "1.2.0",
  "lastUpdated": "2024-01-15T10:00:00Z",
  "updatedBy": "senior-dev",
  "configs": {
    "eslint": "3.2.0",
    "prettier": "2.1.0",
    "docker": "1.5.0"
  },
  "services": [
    "user-service",
    "auth-service",
    "payment-service"
  ]
}

La règle est simple : toute modification de la configuration partagée entraîne une augmentation de la version de niveau supérieur. Le script de mise à jour compare ensuite les versions pour déterminer s’il y a des éléments à synchroniser ; oublier d’augmenter la version signifie que la modification ne se propage pas silencieusement. Une vérification CI qui échoue lorsque des fichiers situés dans shared/ sont modifiés sans augmentation de version constitue une mesure de sécurité peu coûteuse.

Intégration en une seule commande

Le plus grand avantage réside dans le script de démarrage. Un nouveau développeur installe VS Code puis exécute une seule commande :

curl -sSL https://raw.githubusercontent.com/company/dev-environment-config/main/scripts/setup-new-developer.sh | bash

Environ dix minutes plus tard, l’espace de travail est prêt. Il est pratique d’exécuter directement un script distant depuis bash, mais cela exige prudence : le script s’exécute avec les permissions de l’utilisateur et ce qui se trouve sur la branche à ce moment-là. Cela est généralement acceptable pour un outil interne, mais de nombreuses équipes préfèrent que les personnes clonent le répertoire et exécutent le script localement afin de pouvoir le lire d’abord.

Au niveau technique, le script fonctionne selon une séquence fixe :

#!/bin/bash
set -e

WORKSPACE_DIR="$HOME/company-workspace"
CONFIG_REPO="https://github.com/company/dev-environment-config.git"

echo "🚀 Setting up your development environment..."

# 1. Check & install prerequisites
check_and_install "git" "Git"
check_and_install "node" "Node.js" "nvm install 20"
check_and_install "docker" "Docker"

# 2. Create workspace & clone everything
mkdir -p "$WORKSPACE_DIR" && cd "$WORKSPACE_DIR"
git clone "$CONFIG_REPO"

# 3. Clone all service repos from manifest
SERVICES=$(cat dev-environment-config/manifest.json | \
  python3 -c "import sys,json; \
  [print(s) for s in json.load(sys.stdin)['services']]")

for service in $SERVICES; do
  git clone "https://github.com/company/${service}.git"
done

# 4. Install VS Code extensions
EXTENSIONS=$(cat dev-environment-config/vscode/extensions.json | \
  python3 -c "import sys,json; \
  [print(e) for e in json.load(sys.stdin)['recommendations']]")

for ext in $EXTENSIONS; do
  code --install-extension "$ext" --force
done

# 5. Sync shared configs to all services
bash dev-environment-config/scripts/sync-config.sh

# 6. Install dependencies everywhere
for service in $SERVICES; do
  cd "$service" && npm install && cp .env.example .env && cd ..
done

# 7. Set up daily auto-update cron (8 AM weekdays)
CRON="0 8 * * 1-5 cd $WORKSPACE_DIR && \
  bash dev-environment-config/scripts/auto-update.sh"
(crontab -l 2>/dev/null; echo "$CRON") | crontab -

echo "✅ Setup complete! Open VS Code: code $WORKSPACE_DIR"

Quelques détails méritent d’être notés. set -e interrompt l’exécution dès la première commande qui échoue, ce qui empêche un espace de travail partiellement configuré d’avoir l’air complet. L’aide-mémoire check_and_install n’est pas montré et doit être écrit spécifiquement pour chaque système d’exploitation. La parsing de JSON à l’aide d’une ligne de commande Python intégrée fonctionne, mais le script de mise à jour utilise ensuite jq ; donc standardiser sur un seul outil réduit le nombre de prérequis. La copie de .env.example fournit des valeurs par défaut sûres, mais cela ne fonctionne que si les fichiers d’exemple restent complets et ne contiennent aucune information secrète réelle.

Synchronisation de la configuration partagée dans chaque service

Le script de synchronisation parcourt les services un par un et copie les fichiers dotfiles partagés ainsi que les paramètres de VS Code dans chacun d’eux, écrasant ce qui s’y trouve déjà.

#!/bin/bash
# sync-config.sh

CONFIG_ROOT="$(dirname "$SCRIPT_DIR")"
WORKSPACE_DIR="$(dirname "$CONFIG_ROOT")"

for service in $SERVICES; do
  SERVICE_DIR="$WORKSPACE_DIR/$service"

  # Copy shared configs
  cp -f "$CONFIG_ROOT/shared/.eslintrc.js" "$SERVICE_DIR/"
  cp -f "$CONFIG_ROOT/shared/.prettierrc" "$SERVICE_DIR/"
  cp -f "$CONFIG_ROOT/shared/.editorconfig" "$SERVICE_DIR/"
  cp -f "$CONFIG_ROOT/shared/.gitignore" "$SERVICE_DIR/"

  # Copy VS Code settings
  mkdir -p "$SERVICE_DIR/.vscode"
  cp -f "$CONFIG_ROOT/vscode/settings.json" "$SERVICE_DIR/.vscode/"
  cp -f "$CONFIG_ROOT/vscode/extensions.json" "$SERVICE_DIR/.vscode/"

  echo "✅ $service synced"
done

Ce fragment suppose que SCRIPT_DIR et SERVICES sont déjà définis, ce qui signifie que le script réel doit les déterminer lui-même (par exemple à partir de $0 et du manifeste). Les appels à cp -f sont intentionnels : l’écrasement des fichiers permet d’éliminer les écarts. Le compromis est que toute modification locale de ces fichiers sera perdue lors du prochain exécution, c’est pourquoi la personnalisation par service doit se faire dans des fichiers séparés, comme expliqué ci-dessous. Envisagez également si un fichier tel que .gitignore devrait vraiment être identique partout, étant donné que les services ont souvent besoin de leurs propres entrées.

Tâche d’auto-mise à jour quotidienne

C’est cette partie qui maintient les machines en synchronisation sans que personne n’ait à se souvenir de faire quoi que ce soit. Chaque jour ouvré à 8 heures du matin, la tâche cron exécute auto-update.sh:

#!/bin/bash
# auto-update.sh — Runs daily at 8 AM

cd "$CONFIG_ROOT"

# 1. Check for config updates
OLD_VERSION=$(cat manifest.json | jq -r '.version')
git pull origin main
NEW_VERSION=$(cat manifest.json | jq -r '.version')

if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
  echo "🆕 Config updated: $OLD_VERSION → $NEW_VERSION"
  bash "$SCRIPT_DIR/sync-config.sh"
fi

# 2. Pull latest code for all services
for service in $SERVICES; do
  cd "$WORKSPACE_DIR/$service"

  # Stash local changes safely
  git stash push -m "auto-update-$(date +%Y%m%d)" 2>/dev/null

  # Pull latest
  git pull origin $(git branch --show-current) --rebase

  # Update dependencies if package.json changed
  if git diff HEAD~1 --name-only | grep -q "package.json"; then
    npm install
  fi

  # Update shared library
  npm update @company/common-lib
done

# 3. Notify the developer
notify-send "Dev Environment" "✅ Updated to config v$NEW_VERSION"

Considérez ce script comme un point de départ et non comme quelque chose à déployer tel quel :

  • Les modifications sont mises de côté mais ne sont jamais récupérées, ce qui oblige un développeur ayant quitté son poste sans valider ses changements à savoir exécuter git stash pop ; sinon, ces modifications semblent disparaître.
  • git diff HEAD~1 ne vérifie que le dernier commit. Si la récupération a intégré plusieurs commits, une modification apportée à package.json dans un commit antérieur peut être manquée ; comparer avec le commit existant avant la récupération est plus fiable.
  • Un rebase rencontrant des conflits laisse le répertoire en cours de rebase sans que personne ne le surveille. Utiliser git pull --ff-only et ignorer le service en cas d’échec est plus sûr pour un travail non supervisé.
  • notify-send n’existe que sur les ordinateurs de bureau Linux, et cron ne s’exécute pas sur une machine en veille à 8 heures du matin, donc les utilisateurs de macOS peuvent avoir besoin de launchd ou d’un déclencheur différent.
  • Le flux de bout en bout relie la action d’un ingénieur senior à chaque développeur le lendemain matin :

    Senior Dev pushes config change
             │
             ▼
       GitHub Actions
             │
        ┌────┴────┐
        │ Slack   │ Notify team
        │ Publish │ common-lib to NPM
        └────┬────┘
             │
        Next 8 AM
             │
             ▼
      Every developer's cron runs
             │
        ┌────┴────────────────┐
        │ Pull config repo    │
        │ Sync shared configs │
        │ Pull service repos  │
        │ Update dependencies │
        │ Desktop notification│
        └─────────────────────┘
    

    Une bibliothèque partagée plutôt que du copier-coller

    Les gardiens, les intercepteurs, les filtres d’exception et les DTO ont tendance à être dupliqués entre les services avant de diverger. En les regroupant dans un module npm privé, @company/common-lib, chaque service dispose d’une seule importation :

    // In any service, just import and use:
    import {
      AuthGuard,
      RolesGuard,
      ResponseInterceptor,
      LoggingInterceptor,
      HttpExceptionFilter,
      PaginationDto,
      McpService,        // Qwen MCP integration
    } from '@company/common-lib';
    
    @Controller('users')
    @UseGuards(AuthGuard, RolesGuard)
    @UseInterceptors(ResponseInterceptor)
    @UseFilters(HttpExceptionFilter)
    export class UserController {
      constructor(private mcpService: McpService) {}
    
      @Get()
      findAll(@Query() pagination: PaginationDto) {
        return this.userService.findAll(pagination);
      }
    
      @Post('analyze')
      async analyze(@Body() dto: AnalyzeDto) {
        return this.mcpService.callTool('text-analysis', {
          text: dto.text,
        });
      }
    }
    

    McpService pour une intégration Qwen MCP. Lorsque la bibliothèque intègre un nouveau mécanisme de protection ou une correction pour le filtre d’exception, la tâche d’auto-mise à jour l’applique à tous les services.

    Il convient de noter que npm update respecte la plage semver indiquée dans chaque package.json ; par conséquent, une version majeure ne sera pas automatiquement prise en compte, ce qui est généralement souhaitable. Les bibliothèques partagées créent également un couplage entre les services ; l’article sur les cas où du code partagé doit exister explique comment déterminer ce qui appartient à l’une ou l’autre catégorie.

    Création de nouveaux services

    Créer un nouveau service signifiait autrefois passer une journée à copier celui existant et à supprimer ce qui n’était pas applicable. Un script générateur transforme cela en une courte commande interactive :

    bash scripts/create-new-service.sh
    
    # Prompts:
    # Service name: order-service
    # Description: Handles order processing
    # Port: 3002
    # Type: nestjs
    
    # ✅ Done! Complete service with:
    # - src/common/ (filters, guards, interceptors, DTOs)
    # - Docker setup (Dockerfile + docker-compose)
    # - VS Code settings
    # - ESLint + Prettier
    # - Health check endpoint
    # - Swagger docs
    # - MCP integration
    # - Auto-update scripts
    

    Comme chaque nouveau service part du même modèle, il commence sans dérive.

    Avantages pour l’équipe

    Une équipe ayant adopté cette configuration a constaté les effets suivants :

    • Intégration en quelques minutes : une seule commande a remplacé une page wiki de quarante étapes, réduisant le temps nécessaire pour lancer chaque service de environ trois jours à une quinzaine de minutes, et les questions relatives à la configuration en chat ont presque disparu.
    • Aucune dérive : les services risquaient autrefois de diverger silencieusement (ESLint 8 dans l’un, 7 dans un autre, fichiers Docker obsolètes) ; les écrans de sauvegarde planifiés les maintiennent identiques.
  • Rétour à un temps de travail accru : environ une heure par jour consacrée au support technique et aux échecs de CI liés uniquement au formatage, soit environ cinq heures par semaine, réallouées aux travaux fonctionnels.
  • Signaux de qualité constants : mêmes corrections de linting et formatage au moment du sauvegarde, messages de commit imposés par Husky ainsi que extensions installées automatiquement, permettant aux revues de porter sur la conception plutôt que sur l’espacement.
  • Mises à jour sans annonce préalable : une nouvelle règle, une image de base Docker, une correction d’extension ou de bibliothèque parvient à tous le jour suivant.
  • Structure prévisible : mêmes schémas dans src/common/, configuration Docker et chemin de vérification de l’état dans chaque service.
  • Leçons pour sa mise en œuvre progressive

    Commencer par la plus petite partie utile

    Il n’est pas nécessaire de mettre en place tout cela d’un coup. Synchroniser uniquement les paramètres d’ESLint et de Prettier apporte rapidement des avantages visibles. La configuration Docker, les paramètres de l’éditeur, la bibliothèque partagée et les mises à jour planifiées peuvent être ajoutées une fois que l’équipe fait confiance à cette approche.

    Laisser de la place pour des modifications locales

    Une base commune ne doit pas être contraignante. Un service ayant besoin d’une règle supplémentaire peut ajouter un fichier .eslintrc.local.js qui étend la configuration partagée ; comme le script de synchronisation ne touche jamais ce fichier, la modification locale reste en vigueur à chaque exécution.

    Rendre les mises à jour visibles

    La notification sur l’ordinateur semble être un détail mineur, mais elle informe les développeurs que le système existe et qu’il y a eu un changement. Les mises à jour silencieuses ont tendance à troubler les gens lorsque leurs outils se comportent différemment du jour au lendemain.

    Versionner tout

    Sans version manifeste, la tâche de mise à jour ne peut pas déterminer s’il y a eu des changements et effectuerait une synchronisation à chaque exécution. La versionning vous fournit également un référent concret lorsque des modifications causent des problèmes.

    La technologie sous-jacente à l’exemple

    L’environnement d’exemple combine NestJS et React (tous deux en TypeScript), Qwen accessible via MCP, Docker et Docker Compose, Node.js 20 LTS, PostgreSQL 16, Redis 7, GitHub Actions, VS Code, ainsi que Bash et cron pour la synchronisation. Ces versions correspondent à l’environnement de l’époque ; vérifiez les dernières versions LTS avant de les reproduire.

    Points clés

    • Lorsque vous disposez de plus d’une poignée de services et de développeurs, un répertoire de configuration centralisé s’avère rapidement rentable.
  • Les éléments essentiels sont un dépôt de configuration contenant des modèles et des fichiers partagés, un script d’installation en une seule commande, un script de synchronisation, un mises à jour planifiée, une bibliothèque npm partagée et un générateur de services.
  • La suppression des fichiers partagés permet d’éviter les écarts, il faut donc concevoir dès le début des fichiers de remplacement explicites.
  • Les opérations Git sans surveillance représentent la partie la plus risquée ; privilégiez les récupérations de type fast-forward, restaurez les fichiers temporaires et exposez les échecs plutôt que de les cacher.
  • Le résultat est que les développeurs passent leur temps à travailler sur le code du produit plutôt qu’à reconstruire les environnements des uns et des autres.