L'architecture de microservices nécessite une communication fiable entre les services, la protection des requêtes API externes et la répartition de charge. Les serveurs proxy résolvent ces problèmes en agissant comme intermédiaires entre les services, les API externes et les clients. Dans ce guide, nous allons examiner comment intégrer correctement un proxy dans une infrastructure de microservices, quels types de proxys utiliser pour différents scénarios et comment configurer une communication sécurisée.
Rôle du proxy dans l'architecture de microservices
Dans l'architecture de microservices, les serveurs proxy remplissent plusieurs fonctions critiques qui diffèrent de l'utilisation traditionnelle des proxys pour l'anonymisation ou le contournement des blocages. Ici, les proxys deviennent une partie intégrante de l'infrastructure, garantissant une communication fiable et sécurisée entre les composants du système.
Principales fonctions des proxys dans les microservices :
- API Gateway — point d'entrée unique pour toutes les requêtes des clients, qui les route vers les microservices appropriés, masquant l'architecture interne du système
- Sidecar Proxy — conteneur proxy qui fonctionne à côté de chaque service (modèle Service Mesh), interceptant tout le trafic entrant et sortant
- Reverse Proxy — répartition de la charge entre plusieurs instances d'un même service, garantissant la tolérance aux pannes
- Forward Proxy — contrôle et protection des requêtes sortantes vers des API externes, masquant les adresses IP internes de l'infrastructure
- Proxy de sécurité — terminaison SSL/TLS, authentification, autorisation, protection contre les attaques DDoS et autres
Les proxys permettent de mettre en œuvre des modèles architecturaux importants : circuit breaker (déconnexion automatique des services non fonctionnels), retry logic (tentatives répétées en cas d'échec), rate limiting (limitation du nombre de requêtes), transformation de requêtes/réponses (transformation des formats de données). Tout cela rend le système plus résilient aux pannes et simplifie la gestion d'une infrastructure distribuée complexe.
Important : Dans l'architecture de microservices, les proxys fonctionnent à deux niveaux — comme passerelle externe pour les clients (API Gateway) et comme proxys internes entre les services (Service Mesh). Les deux niveaux sont critiques pour la sécurité et la fiabilité du système.
Types de proxys pour différents scénarios d'utilisation
Le choix du type de proxy dépend de la tâche spécifique dans l'architecture de microservices. Différents scénarios nécessitent différentes caractéristiques : vitesse, fiabilité, anonymat ou répartition géographique.
| Scénario | Type de proxy | Pourquoi |
|---|---|---|
| Communication interne entre services | Proxy HTTP/HTTPS (Envoy, NGINX) | Vitesse maximale, faible latence, support de HTTP/2 |
| Requêtes vers des API externes avec des limites | Proxys résidentiels | Contourner les limites de taux, adresses IP réelles des utilisateurs, faible risque de blocage |
| Extraction de données pour l'analyse | Proxys de centres de données | Haute vitesse, faible coût, adapté aux requêtes en masse |
| Travail avec des API mobiles | Proxys mobiles | Imitation des utilisateurs mobiles réels, accès aux API réservées aux mobiles |
| Répartition de charge | Reverse Proxy (HAProxy, NGINX) | Répartition du trafic, vérifications de santé, basculement automatique en cas d'échec |
| Système géographiquement distribué | Proxys résidentiels avec ciblage géographique | Accès aux API régionales, conformité aux exigences de localisation des données |
Pour la communication interne entre microservices, des solutions proxy spécialisées comme Envoy Proxy ou NGINX sont généralement utilisées, optimisées pour une faible latence et une haute capacité. Elles supportent les protocoles modernes (HTTP/2, gRPC) et s'intègrent aux systèmes Service Mesh.
Pour travailler avec des API externes, le choix dépend des exigences du service spécifique. Si l'API a des limites de taux strictes ou bloque les requêtes provenant d'adresses IP de centres de données, des proxys résidentiels sont nécessaires. Pour la collecte de données en masse, où la vitesse est plus importante que l'anonymat, des proxys de centres de données conviennent. Les proxys mobiles sont nécessaires lors de l'utilisation d'API qui vérifient le type de dispositif ou nécessitent des adresses IP mobiles.
Proxy en tant qu'API Gateway : protection et routage
L'API Gateway est un serveur proxy spécialisé qui sert de point d'entrée unique pour toutes les requêtes des clients vers le système de microservices. Au lieu que les clients s'adressent directement à des dizaines de services différents, ils envoient toutes les requêtes à une seule adresse API Gateway, qui les route vers les services appropriés.
Fonctions principales de l'API Gateway :
- Routage des requêtes — détermination du microservice qui doit traiter la requête, en fonction de l'URL, des en-têtes ou d'autres paramètres
- Authentification et autorisation — vérification des tokens (JWT, OAuth), gestion de l'accès à différents services
- Rate Limiting — limitation du nombre de requêtes d'un seul client pour se protéger contre la surcharge et les attaques DDoS
- Aggregation des réponses — combinaison des données de plusieurs services en une seule réponse pour le client
- Transformation des protocoles — conversion de REST en gRPC, HTTP/1.1 en HTTP/2
- Cache — stockage des données fréquemment demandées pour réduire la charge sur les services
- Journalisation et surveillance — collecte centralisée des métriques et des journaux de toutes les requêtes
Solutions populaires pour l'API Gateway : Kong, Tyk, AWS API Gateway, Azure API Management, NGINX Plus, Traefik. Le choix dépend de l'échelle du système, des exigences de performance et de la plateforme cloud utilisée.
// Exemple de configuration NGINX en tant qu'API Gateway
upstream auth_service {
server auth:8001;
}
upstream user_service {
server user:8002;
}
upstream order_service {
server order:8003;
}
server {
listen 80;
server_name api.example.com;
# Limitation du taux de requêtes
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
location /api/auth/ {
limit_req zone=api_limit burst=20;
proxy_pass http://auth_service/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/users/ {
# Vérification du token avant le proxy
auth_request /auth/verify;
proxy_pass http://user_service/;
}
location /api/orders/ {
auth_request /auth/verify;
proxy_pass http://order_service/;
}
# Endpoint interne pour vérifier les tokens
location = /auth/verify {
internal;
proxy_pass http://auth_service/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
}
L'API Gateway masque l'architecture interne du système des clients externes. Les clients ne savent pas combien de microservices existent et comment ils interagissent — ils ne voient qu'une API unique. Cela simplifie la gestion des versions, permet de modifier la structure interne sans affecter les clients et améliore la sécurité, car les services internes ne sont pas accessibles directement depuis Internet.
Intégration avec Service Mesh (Istio, Linkerd)
Le Service Mesh est une couche d'infrastructure qui gère la communication entre les microservices à l'aide de serveurs proxy déployés à côté de chaque service (modèle Sidecar). Contrairement à l'API Gateway, qui ne traite que le trafic externe, le Service Mesh contrôle tout le trafic interne entre les services.
Les solutions Service Mesh les plus populaires sont Istio (utilise Envoy Proxy comme sidecar) et Linkerd (utilise son propre proxy léger). Elles intègrent automatiquement un conteneur proxy à côté de chaque pod dans Kubernetes, interceptant tout le trafic entrant et sortant.
Fonctionnalités du Service Mesh via des proxys :
- Mutual TLS (mTLS) — cryptage automatique de tout le trafic entre les services avec authentification mutuelle
- Gestion du trafic — gestion du routage, déploiements canari, tests A/B
- Observabilité — collecte automatique des métriques, des traces et des journaux sans modifier le code des services
- Résilience — circuit breaking, logique de répétition, gestion des délais, injection de fautes pour les tests
- Découverte de services — découverte automatique des services et répartition de charge
# Exemple de configuration Istio VirtualService pour le routage
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- match:
- headers:
version:
exact: "v2"
route:
- destination:
host: user-service
subset: v2
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10 # Déploiement canari : 10% du trafic vers v2
---
# Circuit Breaker pour se protéger contre les pannes en cascade
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: user-service
spec:
host: user-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
Le Service Mesh résout le problème du "monolithe distribué" — lorsque la logique d'interaction entre les services (retry, timeout, circuit breaking) est dupliquée dans le code de chaque service. Au lieu de cela, toute cette logique est déplacée dans la couche proxy, simplifiant le code des services et assurant un comportement uniforme dans tout le système.
Un avantage important est la transparence totale du trafic. Chaque requête entre les services passe par un proxy qui journalise les métriques : temps de réponse, codes d'erreur, taille de la charge utile. Ces données sont automatiquement envoyées aux systèmes de surveillance (Prometheus, Grafana) et de traçage (Jaeger, Zipkin), créant une image complète du fonctionnement du système distribué sans avoir besoin d'ajouter de l'instrumentation dans le code de chaque service.
Protection des requêtes vers des API externes via un proxy
Les microservices interagissent souvent avec des API externes : systèmes de paiement, services de géolocalisation, API de réseaux sociaux, fournisseurs de données. Les requêtes directes vers des API externes posent plusieurs problèmes : divulgation des adresses IP internes de l'infrastructure, risque de blocage en cas de dépassement des limites de taux, absence de contrôle sur le trafic sortant.
L'utilisation de proxys pour les requêtes sortantes résout ces problèmes et ajoute des fonctionnalités supplémentaires :
- Masquage de l'infrastructure — les API externes voient les adresses IP des proxys, et non celles de vos serveurs
- Contourner les limites de taux — rotation des adresses IP pour répartir les requêtes
- Répartition géographique — accès aux API régionales via des proxys dans les pays nécessaires
- Gestion centralisée — point de contrôle unique pour toutes les requêtes sortantes
- Mise en cache des réponses — réduction du nombre de requêtes vers des API coûteuses
- Surveillance et journalisation — suivi de toutes les interactions avec des services externes
// Python : configuration d'un proxy pour les requêtes vers des API externes
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class ExternalAPIClient:
def __init__(self, proxy_url, proxy_rotation=False):
self.session = requests.Session()
# Configuration du proxy
self.proxies = {
'http': proxy_url,
'https': proxy_url
}
# Logique de répétition pour la résilience
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
def call_payment_api(self, data):
"""Requête vers l'API de paiement via un proxy"""
try:
response = self.session.post(
'https://api.payment-provider.com/charge',
json=data,
proxies=self.proxies,
timeout=10,
headers={'User-Agent': 'MyService/1.0'}
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
# Journalisation de l'erreur
print(f"Erreur API de paiement : {e}")
raise
# Utilisation avec un pool de proxys pour la rotation
class ProxyPool:
def __init__(self, proxy_list):
self.proxies = proxy_list
self.current = 0
def get_next(self):
proxy = self.proxies[self.current]
self.current = (self.current + 1) % len(self.proxies)
return proxy
# Initialisation
proxy_pool = ProxyPool([
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080'
])
# Pour chaque requête, utiliser le prochain proxy
client = ExternalAPIClient(proxy_pool.get_next())
Pour travailler avec des API externes qui ont des restrictions strictes ou bloquent les requêtes provenant d'adresses IP de centres de données, les proxys résidentiels deviennent une nécessité. Ils fournissent de véritables adresses IP d'utilisateurs domestiques, ce qui réduit le risque de blocage et permet de contourner les restrictions géographiques.
// Node.js : proxy pour des API externes avec rotation automatique
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
class ExternalAPIService {
constructor(proxyList) {
this.proxyList = proxyList;
this.currentProxyIndex = 0;
this.requestCounts = new Map(); // Compteur de requêtes pour la limitation de taux
}
getNextProxy() {
const proxy = this.proxyList[this.currentProxyIndex];
this.currentProxyIndex = (this.currentProxyIndex + 1) % this.proxyList.length;
return proxy;
}
async callAPI(endpoint, data, options = {}) {
const proxyUrl = this.getNextProxy();
const agent = new HttpsProxyAgent(proxyUrl);
// Limitation de taux : pas plus de 100 requêtes par minute sur le proxy
const proxyKey = proxyUrl;
const now = Date.now();
const count = this.requestCounts.get(proxyKey) || { count: 0, resetTime: now + 60000 };
if (count.count >= 100 && now < count.resetTime) {
// Passer au prochain proxy
return this.callAPI(endpoint, data, options);
}
try {
const response = await axios({
method: options.method || 'POST',
url: endpoint,
data: data,
httpsAgent: agent,
timeout: options.timeout || 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MyService/1.0)',
...options.headers
}
});
// Mettre à jour le compteur
if (now >= count.resetTime) {
this.requestCounts.set(proxyKey, { count: 1, resetTime: now + 60000 });
} else {
count.count++;
}
return response.data;
} catch (error) {
if (error.response?.status === 429) {
// Limite de taux dépassée - passer à un autre proxy
console.log(`Limite de taux sur ${proxyUrl}, changement de proxy`);
return this.callAPI(endpoint, data, options);
}
throw error;
}
}
}
// Utilisation
const apiService = new ExternalAPIService([
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080'
]);
module.exports = apiService;
Répartition de charge et tolérance aux pannes
Les serveurs proxy jouent un rôle clé dans la garantie d'une haute disponibilité du système de microservices grâce à la répartition de charge et au basculement automatique en cas d'échec. Lorsque vous avez plusieurs instances d'un même service en cours d'exécution (pour le dimensionnement horizontal), le proxy répartit les requêtes entre elles, garantissant une charge uniforme.
Principaux algorithmes de répartition de charge :
- Round Robin — envoi alternatif des requêtes à chaque serveur de la liste, simple et efficace pour des serveurs homogènes
- Least Connections — envoi de la requête au serveur avec le moins de connexions actives, adapté aux requêtes longues
- IP Hash — liaison du client à un serveur spécifique en fonction de son IP, garantissant des sessions persistantes
- Weighted Round Robin — répartition en tenant compte de la puissance des serveurs (les plus puissants reçoivent plus de requêtes)
- Random — choix aléatoire d'un serveur, adapté aux services sans état
# Configuration HAProxy pour la répartition avec vérifications de santé
global
maxconn 4096
log stdout format raw local0
defaults
mode http
timeout connect 5s
timeout client 50s
timeout server 50s
option httplog
frontend api_frontend
bind *:80
default_backend api_servers
backend api_servers
balance roundrobin
# Vérification de santé : vérification de /health toutes les 2 secondes
option httpchk GET /health
http-check expect status 200
# Logique de répétition
retries 3
option redispatch
# Serveurs avec poids (server3 est 2 fois plus puissant)
server server1 10.0.1.10:8080 check weight 1 maxconn 500
server server2 10.0.1.11:8080 check weight 1 maxconn 500
server server3 10.0.1.12:8080 check weight 2 maxconn 1000
# Serveur de secours (utilisé uniquement si les principaux sont indisponibles)
server backup1 10.0.2.10:8080 check backup
Les vérifications de santé sont une fonction critique pour la tolérance aux pannes. Le proxy vérifie régulièrement la disponibilité de chaque serveur (généralement via un endpoint HTTP /health ou /ready) et exclut automatiquement les serveurs non fonctionnels du pool de répartition. Lorsque le serveur se rétablit et commence à répondre aux vérifications de santé, il revient automatiquement dans le pool.
Stratégies de tolérance aux pannes via un proxy :
- Vérifications de santé actives — le proxy interroge activement les serveurs pour vérifier leur disponibilité
- Vérifications de santé passives — le proxy suit les requêtes réelles et exclut les serveurs en cas d'accumulation d'erreurs
- Circuit Breaker — désactivation temporaire d'un service problématique pour éviter des pannes en cascade
- Dégradation gracieuse — passage à un mode de fonctionnement simplifié ou à des données mises en cache en cas d'échec
- Basculement vers la sauvegarde — basculement automatique vers des serveurs ou des régions de secours
// Python : mise en œuvre d'un Circuit Breaker pour un proxy vers des services externes
from datetime import datetime, timedelta
from enum import Enum
class CircuitState(Enum):
CLOSED = "closed" # Fonctionnement normal
OPEN = "open" # Service indisponible, les requêtes sont bloquées
HALF_OPEN = "half_open" # Mode test après récupération
class CircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60, success_threshold=2):
self.failure_threshold = failure_threshold # Erreurs avant ouverture
self.timeout = timeout # Secondes avant tentative de récupération
self.success_threshold = success_threshold # Succès pour fermer
self.state = CircuitState.CLOSED
self.failures = 0
self.successes = 0
self.last_failure_time = None
def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if datetime.now() - self.last_failure_time > timedelta(seconds=self.timeout):
self.state = CircuitState.HALF_OPEN
print("Circuit breaker : passage en HALF_OPEN")
else:
raise Exception("Circuit breaker OPEN : service indisponible")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise e
def _on_success(self):
self.failures = 0
if self.state == CircuitState.HALF_OPEN:
self.successes += 1
if self.successes >= self.success_threshold:
self.state = CircuitState.CLOSED
self.successes = 0
print("Circuit breaker : récupération, passage en CLOSED")
def _on_failure(self):
self.failures += 1
self.last_failure_time = datetime.now()
if self.failures >= self.failure_threshold:
self.state = CircuitState.OPEN
print(f"Circuit breaker OPEN : {self.failures} erreurs consécutives")
# Utilisation
breaker = CircuitBreaker(failure_threshold=3, timeout=30)
def call_external_service():
# Votre code de requête vers une API externe via un proxy
pass
try:
result = breaker.call(call_external_service)
except Exception as e:
# Logique de secours : cache, valeurs par défaut, etc.
print(f"Service indisponible : {e}")
Sécurité des communications entre services
Dans l'architecture de microservices, les serveurs proxy assurent plusieurs niveaux de sécurité : cryptage du trafic, authentification des services, protection contre les attaques et isolation des segments réseau. Sans une configuration de sécurité appropriée, le trafic interne entre les services peut être intercepté ou falsifié.
Aspects clés de la sécurité via des proxys :
- Mutual TLS (mTLS) — authentification bidirectionnelle, où à la fois le client et le serveur vérifient les certificats de l'autre. Le Service Mesh configure automatiquement mTLS entre tous les services
- Terminaison TLS — le proxy déchiffre le trafic HTTPS à la frontière, le vérifie et le transmet aux services via un canal sécurisé
- Validation JWT — vérification des tokens d'accès au niveau du proxy, avant que la requête n'atteigne le service
- Liste blanche IP — restriction de l'accès aux services uniquement depuis des adresses IP autorisées
- Protection DDoS — limitation de taux, limites de connexion, protection contre les attaques SYN flood au niveau du proxy
- WAF (Web Application Firewall) — filtrage des requêtes malveillantes, protection contre les injections SQL, XSS
# Configuration NGINX avec SSL/TLS et sécurité
server {
listen 443 ssl http2;
server_name api.internal.example.com;
# Certificats SSL
ssl_certificate /etc/nginx/certs/api.crt;
ssl_certificate_key /etc/nginx/certs/api.key;
# Protocoles et chiffrages modernes
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Certificat client pour mTLS
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;
# En-têtes sécurisés
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
# Limitation de taux
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
limit_req zone=api burst=200 nodelay;
# Limitation de connexion
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn addr 10;
# Liste blanche IP
allow 10.0.0.0/8; # Réseau interne
allow 172.16.0.0/12; # VPC
deny all;
location / {
# Vérification du token JWT
auth_jwt "API restreinte";
auth_jwt_key_file /etc/nginx/jwt_key.json;
proxy_pass http://backend_service;
# Transmission des informations sur le certificat client
proxy_set_header X-Client-DN $ssl_client_s_dn;
proxy_set_header X-Client-Verify $ssl_client_verify;
}
}
Le Service Mesh simplifie considérablement la configuration de la sécurité, en générant et en faisant tourner automatiquement les certificats pour mTLS, en appliquant des politiques d'accès et en chiffrant tout le trafic entre les services. Par exemple, dans Istio, vous pouvez définir une politique selon laquelle le service "payment" ne peut accepter des requêtes que du service "order", et cela sera automatiquement appliqué au niveau du proxy sans modifier le code des services.
Important pour la production : Utilisez toujours mTLS pour la communication interne entre services, même s'ils se trouvent dans un même réseau privé. Cela protège contre les attaques de type man-in-the-middle et assure l'authentification au niveau des services, et non seulement au niveau du réseau.
Surveillance et journalisation du trafic proxy
Les serveurs proxy offrent une opportunité unique pour la surveillance centralisée de tout le trafic dans un système de microservices. Étant donné que tout le trafic passe par le proxy (à la fois externe via l'API Gateway et interne via le Service Mesh), vous obtenez une visibilité complète sur le fonctionnement du système sans avoir besoin d'instrumenter chaque service.
Métriques clés pour la surveillance au niveau du proxy :
- Latence — temps de traitement de la requête à chaque étape : proxy, service, API externes
- Débit — nombre de requêtes par seconde, volume de données transférées
- Taux d'erreur — pourcentage d'erreurs (4xx, 5xx), types d'erreurs, endpoints problématiques
- Métriques de connexion — nombre de connexions actives, utilisation du pool de connexions
- État des Circuit Breakers — état des disjoncteurs pour chaque service
- Métriques SSL/TLS — statut des certificats, versions des protocoles, erreurs de handshake
# Configuration NGINX pour l'exportation des métriques vers Prometheus
server {
listen 9113;
location /metrics {
stub_status;
access_log off;
allow 10.0.0.0/8; # Seulement pour le serveur Prometheus
deny all;
}
}
# Journalisation au format JSON pour le logging structuré
log_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status": "$status",'
'"body_bytes_sent":"$body_bytes_sent",'
'"request_time":"$request_time",'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_addr":"$upstream_addr",'
'"http_referrer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for"'
'}';
access_log /var/log/nginx/access.log json_combined;
Le traçage distribué est l'une des fonctionnalités les plus puissantes de la surveillance via des proxys. Chaque requête reçoit un identifiant de trace unique, que le proxy ajoute dans les en-têtes et transmet aux services en aval. Les systèmes de traçage (Jaeger, Zipkin) collectent des informations de tous les proxys et construisent le chemin complet de la requête à travers le système, montrant combien de temps elle a passé dans chaque service.
// Node.js : ajout de traçage dans le middleware proxy
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');
const app = express();
// Middleware pour ajouter l'identifiant de trace
app.use((req, res, next) => {
// Récupération de l'identifiant de trace depuis l'en-tête ou création d'un nouveau
const traceId = req.headers['x-trace-id'] || uuidv4();
const spanId = uuidv4();
// Ajout dans les en-têtes pour transmission ultérieure
req.traceId = traceId;
req.spanId = spanId;
res.setHeader('x-trace-id', traceId);
// Journalisation du début du traitement
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
// Journal structuré pour analyse
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
traceId: traceId,
spanId: spanId,
method: req.method,
path: req.path,
status: res.statusCode,
duration: duration,
userAgent: req.headers['user-agent'],
ip: req.ip
}));
});
next();
});
// Endpoint proxy avec transmission des en-têtes de traçage
app.all('/api/*', async (req, res) => {
const targetService = determineTargetService(req.path);
try {
const response = await axios({
method: req.method,
url: `http://${targetService}${req.path}`,
data: req.body,
headers: {
...req.headers,
'x-trace-id': req.traceId,
'x-parent-span-id': req.spanId,
'x-span-id': uuidv4() // Nouveau span pour la requête en aval
}
});
res.status(response.status).json(response.data);
} catch (error) {
console.error(JSON.stringify({
traceId: req.traceId,
error: error.message,
service: targetService
}));
res.status(500).json({ error: 'Service indisponible' });
}
});
function determineTargetService(path) {
if (path.startsWith('/api/users')) return 'user-service:8080';
if (path.startsWith('/api/orders')) return 'order-service:8080';
return 'default-service:8080';
}
app.listen(3000);
L'alerte basée sur les métriques du proxy permet de détecter rapidement les problèmes. Par exemple, vous pouvez configurer des alertes pour : une augmentation soudaine de la latence (un des services est peut-être en dégradation), un taux d'erreur supérieur à un seuil (problèmes de code ou de dépendances), des changements dans les motifs de trafic (possible attaque DDoS ou charge virale).
Exemples de mise en œuvre en Python et Node.js
Examinons des exemples pratiques d'intégration de proxys dans des microservices en Python et Node.js pour différents scénarios : communication interne, travail avec des API externes, répartition de charge.
Python : service avec proxy pour des API externes
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import asyncio
from typing import List, Optional
import logging
app = FastAPI()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ProxyConfig(BaseModel):
url: str
max_requests_per_minute: int = 60
class ProxyPool:
def __init__(self, proxies: List[ProxyConfig]):
self.proxies = proxies
self.current_index = 0
self.request_counts = {p.url: 0 for p in proxies}
self.reset_time = asyncio.get_event_loop().time() + 60
async def get_next_proxy(self) -> str:
# Réinitialisation des compteurs chaque minute
current_time = asyncio.get_event_loop().time()
if current_time >= self.reset_time:
self.request_counts = {p.url: 0 for p in self.proxies}
self.reset_time = current_time + 60
# Trouver un proxy avec des requêtes disponibles
for _ in range(len(self.proxies)):
proxy = self.proxies[self.current_index]
self.current_index = (self.current_index + 1) % len(self.proxies)
if self.request_counts[proxy.url] < proxy.max_requests_per_minute:
self.request_counts[proxy.url] += 1
return proxy.url
# Tous les proxys ont épuisé leur limite
raise HTTPException(status_code=429, detail="Tous les proxys sont limités")
# Initialisation du pool de proxys
proxy_pool = ProxyPool([
ProxyConfig(url="http://user:pass@proxy1.example.com:8080", max_requests_per_minute=100),
ProxyConfig(url="http://user:pass@proxy2.example.com:8080", max_requests_per_minute=100),
ProxyConfig(url="http://user:pass@proxy3.example.com:8080", max_requests_per_minute=100)
])
class ExternalAPIClient:
def __init__(self, proxy_pool: ProxyPool):
self.proxy_pool = proxy_pool
async def fetch_data(self, endpoint: str, params: dict = None) -> dict:
proxy_url = await self.proxy_pool.get_next_proxy()
async with httpx.AsyncClient(proxies={"http://": proxy_url, "https://": proxy_url}) as client:
try:
response = await client.get(
endpoint,
params=params,
timeout=10.0,
headers={"User-Agent": "MyMicroservice/1.0"}
)
response.raise_for_status()
logger.info(f"Données récupérées avec succès depuis {endpoint} via {proxy_url}")
return response.json()
except httpx.HTTPStatusError as e:
logger.error(f"Erreur HTTP {e.response.status_code} depuis {endpoint}")
raise HTTPException(status_code=e.response.status_code, detail=str(e))
except httpx.RequestError as e:
logger.error(f"Erreur de requête vers {endpoint} : {e}")
raise HTTPException(status_code=503, detail="API externe indisponible")
api_client = ExternalAPIClient(proxy_pool)
@app.get("/data/{resource_id}")
async def get_external_data(resource_id: str):
"""Endpoint qui récupère des données depuis une API externe via un proxy"""
external_endpoint = f"https://api.external-service.com/v1/resources/{resource_id}"
try:
data = await api_client.fetch_data(external_endpoint)
return {"status": "success", "data": data}
except HTTPException:
raise
except Exception as e:
logger.error(f"Erreur inattendue : {e}")
raise HTTPException(status_code=500, detail="Erreur interne du serveur")
@app.get("/health")
async def health_check():
return {"status": "healthy", "service": "external-api-proxy"}
# Démarrage : uvicorn main:app --host 0.0.0.0 --port 8000
Node.js : API Gateway avec répartition de charge
const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');
const app = express();
app.use(express.json());
// Configuration des microservices
const services = {
users: [
{ url: 'http://user-service-1:8001', healthy: true, activeConnections: 0 },
{ url: 'http://user-service-2:8001', healthy: true, activeConnections: 0 },
// Ajoutez d'autres services ici
],
orders: [
{ url: 'http://order-service-1:8002', healthy: true, activeConnections: 0 },
{ url: 'http://order-service-2:8002', healthy: true, activeConnections: 0 },
// Ajoutez d'autres services ici
]
};
// Logique de répartition de charge
app.use('/api/users', async (req, res) => {
// Logique pour choisir un service utilisateur en bonne santé
const service = chooseHealthyService(services.users);
try {
const response = await axios({
method: req.method,
url: `${service.url}${req.path}`,
data: req.body,
headers: { 'User-Agent': 'MyAPI/1.0' }
});
res.status(response.status).json(response.data);
} catch (error) {
res.status(500).json({ error: 'Service indisponible' });
}
});
// Fonction pour choisir un service sain
function chooseHealthyService(serviceList) {
// Logique pour choisir un service en bonne santé
return serviceList[0]; // Remplacez par une logique de sélection réelle
}
// Démarrage du serveur
app.listen(3000, () => {
console.log('API Gateway en cours d\'exécution sur le port 3000');
});