Torna al blog

Proxy per architettura a microservizi: protezione API, bilanciamento e sicurezza

Guida completa all'integrazione dei proxy nell'architettura a microservizi: protezione delle API, bilanciamento del carico, sicurezza delle comunicazioni tra i servizi e esempi di configurazione.

📅18 febbraio 2026
```html

L'architettura a microservizi richiede una comunicazione affidabile tra i servizi, la protezione delle richieste API esterne e il bilanciamento del carico. I server proxy risolvono questi problemi fungendo da intermediari tra i servizi, le API esterne e i clienti. In questa guida vedremo come integrare correttamente un proxy nell'infrastruttura a microservizi, quali tipi di proxy utilizzare per diversi scenari e come configurare una comunicazione sicura.

Il ruolo del proxy nell'architettura a microservizi

Nei microservizi, i server proxy svolgono diverse funzioni critiche che si differenziano dall'uso tradizionale del proxy per l'anonimizzazione o per bypassare i blocchi. Qui, i proxy diventano una parte integrante dell'infrastruttura, garantendo una comunicazione affidabile e sicura tra i componenti del sistema.

Principali ruoli del proxy nei microservizi:

  • API Gateway — un unico punto di accesso per tutte le richieste dei clienti, che le instrada ai microservizi appropriati, nascondendo l'architettura interna del sistema
  • Sidecar Proxy — un contenitore proxy che opera accanto a ciascun servizio (pattern Service Mesh), intercettando tutto il traffico in entrata e in uscita
  • Reverse Proxy — distribuzione del carico tra più istanze di un servizio, garantendo resilienza
  • Forward Proxy — controllo e protezione delle richieste in uscita verso API esterne, nascondendo gli indirizzi IP interni dell'infrastruttura
  • Proxy di sicurezza — terminazione SSL/TLS, autenticazione, autorizzazione, protezione da attacchi DDoS e altri attacchi

I proxy consentono di implementare importanti pattern architetturali: circuit breaker (disconnessione automatica dei servizi non funzionanti), retry logic (tentativi ripetuti in caso di errori), rate limiting (limitazione della frequenza delle richieste), request/response transformation (trasformazione dei formati dei dati). Tutto ciò rende il sistema più resiliente agli errori e semplifica la gestione di un'infrastruttura distribuita complessa.

Importante: Nell'architettura a microservizi, i proxy operano su due livelli: come gateway esterno per i clienti (API Gateway) e come proxy interni tra i servizi (Service Mesh). Entrambi i livelli sono critici per la sicurezza e l'affidabilità del sistema.

Tipi di proxy per diversi scenari di utilizzo

La scelta del tipo di proxy dipende dal compito specifico nell'architettura a microservizi. Diversi scenari richiedono caratteristiche diverse: velocità, affidabilità, anonimato o distribuzione geografica.

Scenario Tipo di proxy Perché
Comunicazione interna tra servizi Proxy HTTP/HTTPS (Envoy, NGINX) Massima velocità, bassa latenza, supporto per HTTP/2
Richieste a API esterne con limiti Proxy residenziali Bypassare i limiti di frequenza, IP reali degli utenti, basso rischio di blocchi
Parsing dei dati per analisi Proxy di data center Alta velocità, basso costo, adatto per richieste di massa
Lavorare con API mobili Proxy mobili Simulazione di utenti mobili reali, accesso a API solo per mobile
Bilanciamento del carico Reverse Proxy (HAProxy, NGINX) Distribuzione del traffico, controlli di salute, commutazione automatica in caso di guasti
Sistema distribuito geograficamente Proxy residenziali con geo-targeting Accesso a API regionali, conformità ai requisiti di localizzazione dei dati

Per la comunicazione interna tra microservizi si utilizzano solitamente soluzioni proxy specializzate come Envoy Proxy o NGINX, ottimizzate per bassa latenza e alta capacità. Supportano protocolli moderni (HTTP/2, gRPC) e si integrano con sistemi Service Mesh.

Per lavorare con API esterne, la scelta dipende dai requisiti del servizio specifico. Se l'API ha limiti di frequenza rigidi o blocca le richieste dagli IP dei data center, sono necessari proxy residenziali. Per la raccolta di dati di massa, dove la velocità è più importante dell'anonimato, vanno bene i proxy di data center. I proxy mobili sono necessari quando si lavora con API che verificano il tipo di dispositivo o richiedono indirizzi IP mobili.

Proxy come API Gateway: protezione e instradamento

L'API Gateway è un server proxy specializzato che funge da unico punto di accesso per tutte le richieste dei clienti al sistema a microservizi. Invece di contattare direttamente decine di diversi servizi, i clienti inviano tutte le richieste a un unico indirizzo API Gateway, che le instrada ai servizi necessari.

Funzioni principali dell'API Gateway:

  • Instradamento delle richieste — determinazione di quale microservizio deve elaborare la richiesta, in base all'URL, agli header o ad altri parametri
  • Autenticazione e autorizzazione — verifica dei token (JWT, OAuth), gestione dell'accesso ai diversi servizi
  • Rate Limiting — limitazione del numero di richieste da un singolo cliente per proteggere da sovraccarichi e DDoS
  • Aggregazione delle risposte — unione dei dati provenienti da più servizi in un'unica risposta per il cliente
  • Trasformazione dei protocolli — conversione da REST a gRPC, da HTTP/1.1 a HTTP/2
  • Cache — memorizzazione dei dati frequentemente richiesti per ridurre il carico sui servizi
  • Registrazione e monitoraggio — raccolta centralizzata di metriche e log di tutte le richieste

Soluzioni popolari per API Gateway: Kong, Tyk, AWS API Gateway, Azure API Management, NGINX Plus, Traefik. La scelta dipende dalla scala del sistema, dai requisiti di prestazione e dalla piattaforma cloud utilizzata.

// Esempio di configurazione NGINX come 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;

    # Limitazione della frequenza delle richieste
    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/ {
        # Verifica del token prima del proxy
        auth_request /auth/verify;
        proxy_pass http://user_service/;
    }

    location /api/orders/ {
        auth_request /auth/verify;
        proxy_pass http://order_service/;
    }

    # Endpoint interno per la verifica dei token
    location = /auth/verify {
        internal;
        proxy_pass http://auth_service/verify;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
    }
}

L'API Gateway nasconde l'architettura interna del sistema ai clienti esterni. I clienti non sanno quanti microservizi esistono e come interagiscono: vedono solo un'unica API. Ciò semplifica la gestione delle versioni, consente di modificare la struttura interna senza influenzare i clienti e migliora la sicurezza, poiché i servizi interni non sono accessibili direttamente da Internet.

Integrazione con Service Mesh (Istio, Linkerd)

Il Service Mesh è uno strato infrastrutturale che gestisce la comunicazione tra i microservizi tramite server proxy distribuiti accanto a ciascun servizio (pattern Sidecar). A differenza dell'API Gateway, che gestisce solo il traffico esterno, il Service Mesh controlla tutto il traffico interno tra i servizi.

Le soluzioni più popolari per il Service Mesh sono Istio (che utilizza Envoy Proxy come sidecar) e Linkerd (che utilizza un proprio proxy leggero). Questi implementano automaticamente un contenitore proxy accanto a ciascun pod in Kubernetes, intercettando tutto il traffico in entrata e in uscita.

Funzionalità del Service Mesh tramite proxy:

  • Mutual TLS (mTLS) — crittografia automatica di tutto il traffico tra i servizi con autenticazione reciproca
  • Gestione del traffico — gestione dell'instradamento, distribuzioni canary, test A/B
  • Osservabilità — raccolta automatica di metriche, tracce e log senza modificare il codice dei servizi
  • Resilienza — circuit breaking, retry logic, gestione dei timeout, fault injection per test
  • Service Discovery — scoperta automatica dei servizi e bilanciamento del carico
# Esempio di configurazione Istio VirtualService per l'instradamento
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  # Distribuzione canary: 10% del traffico su v2

---
# Circuit Breaker per proteggere da guasti a cascata
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

Il Service Mesh risolve il problema del "monolite distribuito" — quando la logica di interazione tra i servizi (retry, timeout, circuit breaking) è duplicata nel codice di ciascun servizio. Invece, tutta questa logica viene spostata nello strato proxy, semplificando il codice dei servizi e garantendo un comportamento uniforme dell'intero sistema.

Un importante vantaggio è la completa trasparenza del traffico. Ogni richiesta tra i servizi passa attraverso un proxy, che registra metriche: tempo di risposta, codici di errore, dimensione del payload. Questi dati vengono automaticamente inviati ai sistemi di monitoraggio (Prometheus, Grafana) e di tracciamento (Jaeger, Zipkin), creando un quadro completo del funzionamento del sistema distribuito senza la necessità di aggiungere strumentazione nel codice di ciascun servizio.

Protezione delle richieste alle API esterne tramite proxy

I microservizi interagiscono spesso con API esterne: sistemi di pagamento, servizi di geolocalizzazione, API dei social media, fornitori di dati. Le richieste dirette alle API esterne creano diversi problemi: esposizione degli indirizzi IP interni dell'infrastruttura, rischio di blocco in caso di superamento dei limiti di frequenza, mancanza di controllo sul traffico in uscita.

L'uso di proxy per le richieste in uscita risolve questi problemi e aggiunge ulteriori funzionalità:

  • Nascondere l'infrastruttura — le API esterne vedono gli indirizzi IP dei proxy, non dei vostri server
  • Bypassare i limiti di frequenza — rotazione degli indirizzi IP per distribuire le richieste
  • Distribuzione geografica — accesso a API regionali tramite proxy nei paesi necessari
  • Gestione centralizzata — un unico punto di controllo per tutte le richieste in uscita
  • Cache delle risposte — riduzione del numero di richieste a costose API
  • Monitoraggio e registrazione — tracciamento di tutte le interazioni con i servizi esterni
// Python: configurazione del proxy per le richieste a API esterne
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()
        
        # Configurazione del proxy
        self.proxies = {
            'http': proxy_url,
            'https': proxy_url
        }
        
        # Logica di retry per resilienza
        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):
        """Richiesta all'API di pagamento tramite 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:
            # Registrazione dell'errore
            print(f"Errore API di pagamento: {e}")
            raise

# Utilizzo con un pool di proxy per rotazione
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

# Inizializzazione
proxy_pool = ProxyPool([
    'http://user:pass@proxy1.example.com:8080',
    'http://user:pass@proxy2.example.com:8080',
    'http://user:pass@proxy3.example.com:8080'
])

# Per ogni richiesta utilizziamo il prossimo proxy
client = ExternalAPIClient(proxy_pool.get_next())

Per lavorare con API esterne che hanno restrizioni severe o bloccano le richieste dagli IP dei data center, i proxy residenziali diventano una necessità. Forniscono indirizzi IP reali di utenti domestici, riducendo il rischio di blocchi e consentendo di bypassare le restrizioni geografiche.

// Node.js: proxy per API esterne con rotazione automatica
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');

class ExternalAPIService {
  constructor(proxyList) {
    this.proxyList = proxyList;
    this.currentProxyIndex = 0;
    this.requestCounts = new Map(); // Contatore delle richieste per il rate limiting
  }

  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);

    // Rate limiting: non più di 100 richieste al minuto per 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) {
      // Passiamo al prossimo 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
        }
      });

      // Aggiorniamo il contatore
      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 di frequenza superato - passiamo a un altro proxy
        console.log(`Limite di frequenza su ${proxyUrl}, switching proxy`);
        return this.callAPI(endpoint, data, options);
      }
      throw error;
    }
  }
}

// Utilizzo
const apiService = new ExternalAPIService([
  'http://user:pass@proxy1.example.com:8080',
  'http://user:pass@proxy2.example.com:8080'
]);

module.exports = apiService;

Bilanciamento del carico e resilienza

I server proxy svolgono un ruolo chiave nel garantire l'alta disponibilità del sistema a microservizi attraverso il bilanciamento del carico e la commutazione automatica in caso di guasti. Quando hai più istanze di un servizio (per la scalabilità orizzontale), il proxy distribuisce le richieste tra di esse, garantendo un carico uniforme.

Algoritmi principali di bilanciamento del carico:

  • Round Robin — invio sequenziale delle richieste a ciascun server nella lista, semplice ed efficace per server omogenei
  • Least Connections — invio della richiesta al server con il minor numero di connessioni attive, adatto per richieste lunghe
  • IP Hash — associazione del cliente a un server specifico in base al suo IP, garantendo sessioni sticky
  • Weighted Round Robin — distribuzione tenendo conto della potenza dei server (i server più potenti ricevono più richieste)
  • Random — scelta casuale del server, adatto per servizi stateless
# Configurazione HAProxy per bilanciamento con controlli di salute
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
    
    # Controllo di salute: verifica /health ogni 2 secondi
    option httpchk GET /health
    http-check expect status 200
    
    # Logica di retry
    retries 3
    option redispatch
    
    # Server con pesi (server3 è 2 volte più potente)
    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
    
    # Server di riserva (utilizzato solo se i principali non sono disponibili)
    server backup1 10.0.2.10:8080 check backup

I controlli di salute sono una funzione critica per la resilienza. Il proxy verifica regolarmente la disponibilità di ciascun server (di solito tramite l'endpoint HTTP /health o /ready) ed esclude automaticamente i server non funzionanti dal pool di bilanciamento. Quando un server si riprende e inizia a rispondere ai controlli di salute, torna automaticamente nel pool.

Strategie di resilienza tramite proxy:

  • Active Health Checks — il proxy interroga attivamente i server per verificarne la disponibilità
  • Passive Health Checks — il proxy monitora le richieste reali ed esclude i server in caso di accumulo di errori
  • Circuit Breaker — disconnessione temporanea di un servizio problematico per prevenire guasti a cascata
  • Graceful Degradation — passaggio a una modalità semplificata o a dati memorizzati in cache in caso di guasti
  • Failover to Backup — commutazione automatica a server o regioni di riserva
// Python: implementazione del Circuit Breaker per proxy a servizi esterni
from datetime import datetime, timedelta
from enum import Enum

class CircuitState(Enum):
    CLOSED = "closed"      # Funzionamento normale
    OPEN = "open"          # Servizio non disponibile, le richieste vengono bloccate
    HALF_OPEN = "half_open"  # Modalità di test dopo il ripristino

class CircuitBreaker:
    def __init__(self, failure_threshold=5, timeout=60, success_threshold=2):
        self.failure_threshold = failure_threshold  # Errori prima di aprire
        self.timeout = timeout  # Secondi prima di tentare il ripristino
        self.success_threshold = success_threshold  # Successi per chiudere
        
        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: passaggio a HALF_OPEN")
            else:
                raise Exception("Circuit breaker OPEN: servizio non disponibile")
        
        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: ripristino, passaggio a 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} errori consecutivi")

# Utilizzo
breaker = CircuitBreaker(failure_threshold=3, timeout=30)

def call_external_service():
    # Il tuo codice per la richiesta a un'API esterna tramite proxy
    pass

try:
    result = breaker.call(call_external_service)
except Exception as e:
    # Logica di fallback: cache, valori di default, ecc.
    print(f"Servizio non disponibile: {e}")

Sicurezza della comunicazione tra i servizi

Nell'architettura a microservizi, i server proxy forniscono diversi livelli di sicurezza: crittografia del traffico, autenticazione dei servizi, protezione da attacchi e isolamento dei segmenti di rete. Senza una corretta configurazione della sicurezza, il traffico interno tra i servizi può essere intercettato o falsificato.

Aspetti chiave della sicurezza tramite proxy:

  • Mutual TLS (mTLS) — autenticazione bidirezionale, in cui sia il client che il server verificano i certificati l'uno dell'altro. Il Service Mesh configura automaticamente mTLS tra tutti i servizi
  • Terminazione TLS — il proxy decrittografa il traffico HTTPS al confine, lo verifica e lo trasmette ai servizi tramite un canale protetto
  • Validazione JWT — verifica dei token di accesso a livello di proxy, prima che la richiesta raggiunga il servizio
  • IP Whitelisting — limitazione dell'accesso ai servizi solo da indirizzi IP autorizzati
  • Protezione DDoS — limitazione della frequenza, limiti di connessione, protezione da SYN flood a livello di proxy
  • WAF (Web Application Firewall) — filtraggio delle richieste dannose, protezione da SQL injection, XSS
# Configurazione NGINX con SSL/TLS e sicurezza
server {
    listen 443 ssl http2;
    server_name api.internal.example.com;

    # Certificati SSL
    ssl_certificate /etc/nginx/certs/api.crt;
    ssl_certificate_key /etc/nginx/certs/api.key;
    
    # Protocolli e cifrature moderne
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    
    # Certificato client per mTLS
    ssl_client_certificate /etc/nginx/certs/ca.crt;
    ssl_verify_client on;
    
    # Intestazioni sicure
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    
    # Limitazione della frequenza
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
    limit_req zone=api burst=200 nodelay;
    
    # Limitazione delle connessioni
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    limit_conn addr 10;
    
    # Whitelisting IP
    allow 10.0.0.0/8;      # Rete interna
    allow 172.16.0.0/12;   # VPC
    deny all;
    
    location / {
        # Verifica del token JWT
        auth_jwt "API riservata";
        auth_jwt_key_file /etc/nginx/jwt_key.json;
        
        proxy_pass http://backend_service;
        
        # Trasmissione delle informazioni sul certificato client
        proxy_set_header X-Client-DN $ssl_client_s_dn;
        proxy_set_header X-Client-Verify $ssl_client_verify;
    }
}

Il Service Mesh semplifica notevolmente la configurazione della sicurezza, generando e ruotando automaticamente i certificati per mTLS, applicando politiche di accesso e crittografando tutto il traffico tra i servizi. Ad esempio, in Istio è possibile impostare una politica che il servizio "payment" può ricevere richieste solo dal servizio "order", e questo sarà applicato automaticamente a livello di proxy senza modificare il codice dei servizi.

Importante per la produzione: Utilizzare sempre mTLS per la comunicazione interna tra i servizi, anche se si trovano nella stessa rete privata. Questo protegge dagli attacchi di tipo man-in-the-middle e garantisce l'autenticazione a livello di servizi, non solo a livello di rete.

Monitoraggio e registrazione del traffico proxy

I server proxy offrono un'opportunità unica per il monitoraggio centralizzato di tutto il traffico nel sistema a microservizi. Poiché tutto il traffico passa attraverso il proxy (sia esterno tramite API Gateway che interno tramite Service Mesh), si ottiene una visibilità completa sul funzionamento del sistema senza la necessità di strumentare ogni servizio.

Metriche chiave per il monitoraggio a livello di proxy:

  • Latency (latenza) — tempo di elaborazione della richiesta in ciascuna fase: proxy, servizio, API esterne
  • Throughput (capacità) — numero di richieste al secondo, volume di dati trasmessi
  • Error Rate — percentuale di errori (4xx, 5xx), tipi di errori, endpoint problematici
  • Connection Metrics — numero di connessioni attive, utilizzo del pool di connessioni
  • Circuit Breaker State — stato dei circuit breaker per ciascun servizio
  • SSL/TLS Metrics — stato dei certificati, versioni dei protocolli, errori di handshake
# Configurazione NGINX per l'esportazione delle metriche in Prometheus
server {
    listen 9113;
    location /metrics {
        stub_status;
        access_log off;
        allow 10.0.0.0/8;  # Solo per il server Prometheus
        deny all;
    }
}

# Registrazione in formato JSON per logging strutturato
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;

Il Distributed Tracing è una delle funzionalità più potenti del monitoraggio tramite proxy. Ogni richiesta riceve un ID di traccia unico, che il proxy aggiunge agli header e trasmette ulteriormente lungo la catena di servizi. I sistemi di tracciamento (Jaeger, Zipkin) raccolgono informazioni da tutti i proxy e costruiscono il percorso completo della richiesta attraverso il sistema, mostrando quanto tempo ha trascorso in ciascun servizio.

// Node.js: aggiunta di tracciamento nel middleware proxy
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');

const app = express();

// Middleware per aggiungere l'ID di traccia
app.use((req, res, next) => {
  // Otteniamo l'ID di traccia dall'header o ne creiamo uno nuovo
  const traceId = req.headers['x-trace-id'] || uuidv4();
  const spanId = uuidv4();
  
  // Aggiungiamo agli header per la trasmissione successiva
  req.traceId = traceId;
  req.spanId = spanId;
  res.setHeader('x-trace-id', traceId);
  
  // Registriamo l'inizio dell'elaborazione
  const startTime = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    
    // Log strutturato per l'analisi
    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 con trasmissione degli header di tracciamento
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()  // Nuovo span per la richiesta downstream
      }
    });
    
    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: 'Servizio non disponibile' });
  }
});

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'alerting basato sulle metriche del proxy consente di rilevare rapidamente i problemi. Ad esempio, è possibile impostare avvisi per: un improvviso aumento della latenza (potrebbe essere che uno dei servizi stia degradando), un aumento della percentuale di errori oltre una soglia (problemi con il codice o le dipendenze), cambiamenti nei pattern di traffico (possibile attacco DDoS o carico virale).

Esempi di implementazione in Python e Node.js

Esaminiamo esempi pratici di integrazione del proxy nei microservizi in Python e Node.js per diversi scenari: comunicazione interna, lavoro con API esterne, bilanciamento del carico.

Python: servizio con proxy per API esterne

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:
        # Reset dei contatori ogni minuto
        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
        
        # Trovare un proxy con richieste disponibili
        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
        
        # Tutti i proxy hanno esaurito il limite
        raise HTTPException(status_code=429, detail="Tutti i proxy limitati")

# Inizializzazione del pool di proxy
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"Recuperato con successo da {endpoint} tramite {proxy_url}")
                return response.json()
            
            except httpx.HTTPStatusError as e:
                logger.error(f"Errore HTTP {e.response.status_code} da {endpoint}")
                raise HTTPException(status_code=e.response.status_code, detail=str(e))
            
            except httpx.RequestError as e:
                logger.error(f"Errore di richiesta a {endpoint}: {e}")
                raise HTTPException(status_code=503, detail="API esterna non disponibile")

api_client = ExternalAPIClient(proxy_pool)

@app.get("/data/{resource_id}")
async def get_external_data(resource_id: str):
    """Endpoint che recupera dati da un'API esterna tramite 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"Errore imprevisto: {e}")
        raise HTTPException(status_code=500, detail="Errore interno del server")

@app.get("/health")
async def health_check():
    return {"status": "healthy", "service": "external-api-proxy"}

# Esecuzione: uvicorn main:app --host 0.0.0.0 --port 8000

Node.js: API Gateway con bilanciamento del carico

const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');

const app = express();
app.use(express.json());

// Configurazione dei microservizi
const services = {
  users: [
    { url: 'http://user-service-1:8001', healthy: true, activeConnections: 0 },
    { url: 'http://user-service-2:8001', healthy: true, activeConnections: 0 },
    // Altri servizi...
  ],
  orders: [
    { url: 'http://order-service-1:8002', healthy: true, activeConnections: 0 },
    { url: 'http://order-service-2:8002', healthy: true, activeConnections: 0 },
    // Altri servizi...
  ],
};

// Middleware per il bilanciamento del carico
app.use('/api/users', async (req, res) => {
  const service = services.users.find(s => s.healthy);
  if (!service) return res.status(503).send('Servizio non disponibile');
  
  try {
    const response = await axios({
      method: req.method,
      url: service.url + req.originalUrl,
      data: req.body,
      headers: req.headers,
    });
    res.status(response.status).json(response.data);
  } catch (error) {
    res.status(500).send('Errore nella comunicazione con il servizio');
  }
});

// Avvio del server
app.listen(3000, () => {
  console.log('API Gateway in ascolto sulla porta 3000');
});
```