La mise en place d'applications microservices ou d'architectures frontend/backend conteneurisées sur un même hôte nécessite une gestion adéquate de la communication inter-conteneurs. Une erreur fréquente est de tenter de joindre des services en utilisant des adresses IP qui ne sont pas appropriées dans le contexte réseau de Docker.
Le Défi Initial : Connexion Frontend-Backend
Considérons une configuration courante où un conteneur Nginx sert d'interface frontend et doit acheminer des requêtes vers un conteneur backend (API) s'exécutant sur le même hôte Docker.
Tentative 1 : Proxy via Adresse IP Publique
Une première approche, souvent intuitive mais incorrecte pour la communication interne, consiste à configurer Nginx pour proxifier les requêtes vers l'adresse IP publique de l'hôte, et le port exposé du service backend. Cela implique que le trafic quitte le réseau Docker pour y revenir, ce qui est inefficace et potentiellement risqué.
Voici un exemple de configuraton Nginx pour ce scénario :
# Fichier: nginx.conf (extrait)
http {
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
index index.html;
}
location /api/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://<IP_PUBLIQUE_HOTE>:8080/; # IP publique ou externe
client_max_body_size 1024m;
}
}
}
Et la définition des services dans Docker Compose :
# Fichier: docker-compose.yml
version: '3.8'
services:
backend-api:
container_name: mon_api
image: mon-api:latest
ports:
- "8080:8080" # Expose le port 8080 de l'API sur l'hôte
environment:
- API_ENV=production
volumes:
- "./data:/app/data"
restart: "unless-stopped"
frontend-web:
container_name: mon_frontend
image: mon-frontend:latest
ports:
- "80:80" # Expose le port 80 de Nginx
volumes:
- "./nginx.conf:/etc/nginx/nginx.conf"
restart: "unless-stopped"
Cette configuration fonctionne, mais elle nécessite que le port 8080 de l'API soit exposé publiquement sur l'hôte, ce qui est rarement souhaitable pour un service backend interne.
Tentative 2 : Proxy via l'Adresse Locale (Loopback)
Face à la nécessité de ne pas exposer l'API, on pourrait être tenté d'utiliser l'adresse de bouclage (loopback) 127.0.0.1 dans la configuration Nginx, en pensant qu'elle ferait référence à l'hôte Docker. Cependant, à l'intérieur d'un conteneur Docker, 127.0.0.1 pointe vers le conteneur lui-même, et non vers l'hôte ou un autre conteneur.
Si nous modifions la configuration Nginx comme suit :
# Fichier: nginx.conf (extrait, modification du bloc /api/)
location /api/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080/; # Incorrect: pointe vers le conteneur Nginx lui-même
client_max_body_size 1024m;
}
La communication échouera car le conteneur Nginx essaiera de joindre le port 8080 sur lui-même, où aucun service API n'écoute.
La Solusion Élégante : Utilisation des Noms de Services Docker Compose
Docker Compose crée un réseau interne par défaut pour tous les services définis dans le même fichier docker-compose.yml. Dans ce réseau, chaque service est accessible par son nom de service défini dans le fichier Compose, qui agit comme un nom d'hôte résolvable via le DNS interne de Docker.
La solution correcte et recommandée consiste à utiliser le nom du service backend (ici backend-api) comme hôte dans la directive proxy_pass de Nginx.
La configuraton Nginx est alors mise à jour comme suit :
# Fichier: nginx.conf (extrait, modification du bloc /api/)
location /api/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend-api:8080/; # Utilise le nom du service Docker Compose
client_max_body_size 1024m;
}
Le fichier docker-compose.yml reste le même que dans la première tentative, à l'exception que l'exposition des ports pour backend-api n'est plus strictement nécessaire pour la communication interne, bien qu'elle puisse être utile pour le débogage ou l'accès externe contrôlé. Cependant, pour une communication purement interne entre les services définis dans le même Compose, les ports n'ont pas besoin d'être exposés sur l'hôte.
Cette approche offre plusieurs avantages :
- Isolation : Le service API n'a pas besoin d'exposer son port sur l'hôte, améliorant la sécurité.
- Simplicité : Utilise des noms de services faciles à lire et à gérer.
- Portabilité : La configuration est agnostique à l'adresse IP de l'hôte et fonctionnera dans différents environnements Docker.
- Découverte de services : Docker Compose gère automatiquement la découverte des services au sein du réseau défini.