Torna al blog

gRPC tramite proxy nell'architettura a microservizi: guida completa alla configurazione del tunneling HTTP/2

Guida dettagliata alla configurazione del traffico gRPC tramite server proxy nell'architettura a microservizi: dal tunneling HTTP/2 al bilanciamento del carico e alla terminazione TLS.

📅7 agosto 2026
```html

gRPC è un framework RPC ad alte prestazioni di Google, che funziona sopra HTTP/2 ed è diventato lo standard de facto per l'interazione tra servizi. Ma non appena si cerca di far passare il traffico gRPC attraverso un proxy, sorgono immediatamente problemi: la maggior parte dei proxy tradizionali non è in grado di gestire HTTP/2 e le connessioni di streaming a lungo termine. In questo articolo vedremo come configurare correttamente gRPC attraverso un proxy — dalla scelta dell'architettura fino alle configurazioni specifiche di Nginx, Envoy e HAProxy.

Perché gRPC non funziona bene con i proxy tradizionali

Per comprendere il problema, è necessario capire come gRPC è strutturato sotto il cofano. Il protocollo utilizza HTTP/2 come livello di trasporto, e questo lo differenzia radicalmente dal consueto REST su HTTP/1.1. La maggior parte dei proxy aziendali, dei firewall e dei bilanciatori di carico sono stati progettati nell'era di HTTP/1.1 e semplicemente non sono in grado di gestire flussi multiplexati di HTTP/2.

Ecco alcuni problemi specifici che si incontreranno nel tentativo di far passare gRPC attraverso un proxy HTTP tradizionale:

  • Downgrade a HTTP/1.1. Molti proxy abbassano automaticamente la versione del protocollo. gRPC richiede HTTP/2 — senza di esso la connessione non verrà stabilita e il client riceverà un errore UNAVAILABLE.
  • Interruzione delle connessioni a lungo termine. gRPC utilizza attivamente lo streaming server-side e bidirezionale. I proxy con timeout aggressivi (soprattutto AWS ELB Classic, alcune versioni di Squid) interrompono le connessioni che non trasferiscono dati per più di 60 secondi.
  • Problemi con il Content-Type. gRPC utilizza l'intestazione Content-Type: application/grpc. I proxy che non conoscono questo tipo possono rifiutare la richiesta o bufferizzare in modo errato il corpo.
  • Trailers. gRPC utilizza i trailers di HTTP/2 per trasmettere lo stato di completamento della chiamata. I proxy HTTP/1.1 non supportano i trailers — le informazioni sullo stato andranno perse.
  • Bufferizzazione del corpo. Alcuni proxy bufferizzano l'intero corpo della risposta prima di trasferirlo al client. Per le chiamate gRPC in streaming, ciò significa che il client non riceverà alcun messaggio fino al completamento dello stream.

Conclusione chiave:

Per far funzionare gRPC attraverso un proxy è necessario un proxy che supporti HTTP/2 end-to-end o una modalità di tunneling speciale. Un proxy HTTP tradizionale senza una configurazione adeguata non andrà bene.

Tunneling HTTP/2: come funziona

Esistono due approcci fondamentalmente diversi per il proxying del traffico gRPC, ed è importante comprendere la differenza tra di essi per scegliere la soluzione giusta per la propria architettura.

Approccio 1: HTTP/2 end-to-end (raccomandato)

Il proxy comprende HTTP/2 e stabilisce una connessione HTTP/2 sia con il client che con il backend. Può analizzare singoli stream, applicare il bilanciamento a livello di richiesta, aggiungere intestazioni e eseguire la terminazione TLS. Questa è l'opzione più funzionale — così funzionano Envoy, Nginx (a partire dalla versione 1.13.10) e i bilanciatori di carico a conoscenza di gRPC nei fornitori di cloud.

Approccio 2: Tunneling TCP (CONNECT)

Il proxy non analizza il traffico HTTP/2, ma crea semplicemente un tunnel TCP trasparente tramite il metodo CONNECT. Il client stabilisce una connessione TLS direttamente con il backend attraverso il tunnel. Il proxy vede solo un flusso di byte crittografato. Questo metodo è più semplice da configurare, ma ti priva della possibilità di bilanciamento a livello di richieste gRPC e di aggiungere intestazioni.

Caratteristica HTTP/2 end-to-end Tunnel TCP CONNECT
Bilanciamento per richieste ✅ Sì ❌ No (solo TCP)
Terminazione TLS sul proxy ✅ Sì ❌ No
Aggiunta di intestazioni ✅ Sì ❌ No
Difficoltà di configurazione Media Bassa
Supporto per lo streaming ✅ Completo ✅ Completo
Osservabilità (metriche) ✅ Dettagliata ❌ Solo TCP

Configurazione di Nginx come proxy gRPC

Nginx supporta il proxying gRPC a partire dalla versione 1.13.10 (febbraio 2018). Per funzionare è necessario il modulo ngx_http_grpc_module, che è incluso nella build standard. È importante: Nginx supporta HTTP/2 sul lato client (frontend), ma sul lato backend utilizza HTTP/2 solo per gRPC — l'upstream HTTP normale funziona su HTTP/1.1.

Configurazione di base del proxy gRPC su Nginx

server {
    listen 443 ssl http2;
    server_name grpc.example.com;

    # Certificati TLS
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # Parametri TLS moderni
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        # Direttiva grpc_pass invece di proxy_pass
        grpc_pass grpc://grpc_backend;

        # Timeout per gli stream a lungo termine
        grpc_read_timeout  3600s;
        grpc_send_timeout  3600s;
        grpc_connect_timeout 5s;

        # Trasmettere l'IP reale del client
        grpc_set_header X-Real-IP $remote_addr;
        grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

upstream grpc_backend {
    server backend-1:50051;
    server backend-2:50051;
    server backend-3:50051;

    # Keepalive per le connessioni HTTP/2
    keepalive 32;
}

Fai attenzione a diversi dettagli critici. In primo luogo, viene utilizzata la direttiva grpc_pass, non proxy_pass — sono moduli diversi con comportamenti diversi. In secondo luogo, i timeout grpc_read_timeout e grpc_send_timeout sono impostati a 3600 secondi (1 ora) — questo è importante per gli stream server-side che possono funzionare a lungo senza trasferire dati. In terzo luogo, keepalive 32 nell'upstream consente di riutilizzare le connessioni HTTP/2 con i backend.

Configurazione per gRPC non crittografato (grpc://)

server {
    listen 80 http2;
    server_name grpc-internal.example.com;

    location / {
        grpc_pass grpc://127.0.0.1:50051;

        # Gestione degli errori gRPC
        error_page 502 = /error502grpc;
    }

    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header content-length 0;
        return 204;
    }
}

Il blocco error502grpc è un dettaglio importante: in caso di indisponibilità del backend, restituisce un corretto stato gRPC UNAVAILABLE (14) invece di un HTTP 502, che il client gRPC non sarà in grado di gestire correttamente.

Envoy Proxy: la scelta migliore per gRPC nei microservizi

Envoy è stato creato da Lyft specificamente per architetture a microservizi, e il supporto per gRPC è implementato a un livello molto profondo. Comprende i Protocol Buffers, è in grado di trascrivere gRPC in REST, raccoglie metriche dettagliate per ogni metodo RPC ed è la base per le soluzioni di service mesh — Istio, AWS App Mesh e altre. Se stai costruendo un'architettura a microservizi seria, Envoy è lo standard del settore.

Configurazione di base di Envoy per gRPC

static_resources:
  listeners:
  - name: grpc_listener
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: grpc_proxy
          codec_type: HTTP2
          route_config:
            name: grpc_routes
            virtual_hosts:
            - name: grpc_services
              domains: ["*"]
              routes:
              - match:
                  prefix: "/com.example.UserService"
                route:
                  cluster: user_service
                  timeout: 30s
                  retry_policy:
                    retry_on: "reset,connect-failure,retriable-status-codes"
                    num_retries: 3
                    retriable_status_codes: [14]
              - match:
                  prefix: "/com.example.OrderService"
                route:
                  cluster: order_service
                  timeout: 60s
          http_filters:
          - name: envoy.filters.http.grpc_stats
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig
              emit_filter_state: true
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: user_service
    connect_timeout: 5s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    http2_protocol_options: {}
    load_assignment:
      cluster_name: user_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: user-service
                port_value: 50051

I principali vantaggi di questa configurazione: routing per servizi gRPC (il prefisso del percorso corrisponde al nome completo del servizio nel formato package.ServiceName), retry automatici in caso di stato UNAVAILABLE e raccolta di metriche gRPC tramite il filtro grpc_stats.

Trascrizione gRPC-Web in Envoy

Una delle killer feature di Envoy è il trascrittore gRPC-Web integrato. I browser non supportano gRPC direttamente (a causa delle limitazioni dell'API Fetch sui trailers HTTP/2), quindi viene utilizzato il protocollo gRPC-Web. Envoy può automaticamente convertire le richieste gRPC-Web dal browser in gRPC normale per il backend:

http_filters:
- name: envoy.filters.http.grpc_web
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.CorsPolicy
- name: envoy.filters.http.router
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

HAProxy e gRPC: configurazione con bilanciamento del carico

HAProxy supporta gRPC a partire dalla versione 1.9.2. Funziona a livello TCP o HTTP/2, è in grado di bilanciare il traffico gRPC e di eseguire health checks. HAProxy è una buona scelta se lo stai già utilizzando nella tua infrastruttura e vuoi aggiungere supporto gRPC senza introdurre un nuovo componente.

global
    maxconn 50000
    log stdout format raw local0

defaults
    log global
    timeout connect 5s
    timeout client  3600s
    timeout server  3600s

frontend grpc_frontend
    bind *:443 ssl crt /etc/haproxy/certs/server.pem alpn h2,http/1.1
    mode http
    option http-use-htx
    default_backend grpc_servers

backend grpc_servers
    mode http
    balance leastconn
    option http-use-htx

    # Health check tramite il gRPC Health Checking Protocol
    option httpchk GET /grpc.health.v1.Health/Check
    http-check expect status 200

    server grpc1 10.0.0.1:50051 check ssl verify none
    server grpc2 10.0.0.2:50051 check ssl verify none
    server grpc3 10.0.0.3:50051 check ssl verify none

Fai attenzione a diverse impostazioni importanti. alpn h2,http/1.1 nella direttiva bind indica che HAProxy accetta sia connessioni HTTP/2 che HTTP/1.1 tramite la negoziazione TLS ALPN. timeout client 3600s e timeout server 3600s sono parametri critici per gli stream gRPC a lungo termine. L'algoritmo leastconn è preferibile a roundrobin per gRPC, poiché le connessioni di streaming possono essere a lungo termine e sovraccaricare in modo non uniforme i backend.

Terminazione TLS e crittografia end-to-end per gRPC

gRPC per impostazione predefinita presuppone l'uso di TLS — questo fa parte della specifica. Nella pratica, nell'architettura a microservizi sorge la domanda: dove eseguire la terminazione TLS e come organizzare la crittografia tra i componenti? Esistono tre modelli principali.

Modello 1: Terminazione TLS sul proxy (Edge TLS)

Il proxy accetta il traffico crittografato dai client, lo decrittografa e lo trasmette ai backend su un canale non crittografato (o con un TLS separato). Questo è l'approccio più comune nelle reti aziendali. I backend possono utilizzare grpc.Insecure() per semplificare la configurazione.

Modello 2: Crittografia end-to-end (mTLS)

Mutual TLS (mTLS) è lo standard per le service mesh. Ogni servizio ha il proprio certificato, e ad ogni connessione entrambe le parti verificano i certificati reciproci. Questo garantisce l'autenticazione dei servizi e la crittografia del traffico all'interno del cluster. È così che funziona Istio con il proxy sidecar Envoy.

# Go: server gRPC con mTLS
import (
    "crypto/tls"
    "crypto/x509"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func createMTLSCredentials() credentials.TransportCredentials {
    cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
    
    caCert, _ := os.ReadFile("ca.crt")
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)
    
    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ClientAuth:   tls.RequireAndVerifyClientCert,
        ClientCAs:    caCertPool,
    }
    
    return credentials.NewTLS(tlsConfig)
}

// Creazione del server con mTLS
creds := createMTLSCredentials()
server := grpc.NewServer(grpc.Creds(creds))

Modello 3: TLS Passthrough

Il proxy opera a livello TCP e non decrittografa il traffico — semplicemente reindirizza i byte crittografati al backend. Questa è l'opzione più semplice dal punto di vista del proxy, ma priva della possibilità di analizzare il traffico gRPC, aggiungere intestazioni o eseguire il bilanciamento a livello di richieste.

Bilanciamento del carico per gli stream gRPC: caratteristiche e soluzioni

Il bilanciamento del carico per gRPC è un compito non banale, ecco perché. In HTTP/1.1 ogni richiesta è una connessione TCP separata (o una connessione da un pool), e il bilanciatore distribuisce facilmente le richieste tra i backend. In HTTP/2 una connessione TCP multiplexa più stream — se il bilanciatore opera a livello TCP, tutti gli stream di una connessione andranno a un solo backend.

Per gRPC questo significa: se il client stabilisce una connessione HTTP/2 e invia 100 richieste RPC attraverso di essa, con il bilanciamento TCP tutte e 100 le richieste verranno elaborate da un'istanza del backend. Gli altri backend rimarranno inattivi. La soluzione è il bilanciamento a livello di stream HTTP/2 (bilanciamento L7).

Algoritmi di bilanciamento per gRPC

Algoritmo Adatto per gRPC Commento
Round Robin ✅ Sì Buono per RPC unari con tempi di elaborazione simili
Least Connection ✅ Migliore scelta Considera gli stream attivi, distribuisce uniformemente il carico
Random ⚠️ Condizionato Funziona con un gran numero di richieste, non uniforme con poche
IP Hash ❌ Scarso Collega il client a un solo backend, non ha senso con L7
Pick First (gRPC integrato) ❌ Non per produzione Tutte le richieste vanno al primo server disponibile

Bilanciamento client-side in gRPC

gRPC supporta il bilanciamento del carico client-side — il client decide da solo a quale server inviare la richiesta. Questo consente di aggirare il problema del multiplexing TCP. Per la discovery dei servizi si utilizza DNS con più record A o resolver specializzati (Consul, etcd). Ecco un esempio di configurazione del bilanciamento client-side in Go:

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/balancer/roundrobin"
)

// Client con bilanciamento round-robin tramite DNS
conn, err := grpc.Dial(
    "dns:///grpc-service.internal:50051",
    grpc.WithDefaultServiceConfig(
        `{"loadBalancingConfig": [{"round_robin":{}}]}`
    ),
    grpc.WithTransportCredentials(creds),
)

// Client con least-connection (disponibile in gRPC >= 1.58)
conn, err := grpc.Dial(
    "dns:///grpc-service.internal:50051",
    grpc.WithDefaultServiceConfig(
        `{"loadBalancingConfig": [{"least_request":{}}]}`
    ),
    grpc.WithTransportCredentials(creds),
)

Proxy esterni per gRPC: quando e perché sono necessari nei microservizi

Oltre ai componenti proxy interni (Nginx, Envoy, HAProxy), nell'architettura a microservizi a volte sorge la necessità di utilizzare server proxy esterni — ad esempio, per instradare il traffico attraverso regioni specifiche, aggirare restrizioni di rete o isolare le connessioni in uscita. Esaminiamo i principali scenari.

Scenario 1: Microservizi geodistribuiti

Se i tuoi microservizi sono situati in diverse regioni (ad esempio, parte in Europa, parte negli Stati Uniti), e hai bisogno di controllare attraverso quale indirizzo IP vengono stabilite le connessioni gRPC tra le regioni, i proxy esterni possono aiutare a organizzare un instradamento prevedibile. Per tali compiti sono adatti proxy di data center — forniscono indirizzi IP stabili e alta velocità di connessione, il che è critico per gRPC con la sua sensibilità ai ritardi.

Scenario 2: Isolamento del traffico in uscita

In alcuni ambienti aziendali, tutto il traffico in uscita deve passare attraverso un proxy aziendale. Per i client gRPC che devono accedere a servizi gRPC esterni (ad esempio, Google Cloud APIs che utilizzano gRPC), questo crea complicazioni. La soluzione è configurare un tunnel CONNECT attraverso il proxy aziendale.

Ecco un esempio di configurazione di un client gRPC per funzionare tramite un proxy HTTP CONNECT in Go:

import (
    "net"
    "net/http"
    "golang.org/x/net/proxy"
    "google.golang.org/grpc"
)

// Utilizzo di un proxy SOCKS5 per gRPC
proxyDialer, _ := proxy.SOCKS5(
    "tcp",
    "proxy.example.com:1080",
    &proxy.Auth{User: "user", Password: "pass"},
    proxy.Direct,
)

conn, err := grpc.Dial(
    "grpc-service.example.com:443",
    grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
        return proxyDialer.Dial("tcp", addr)
    }),
    grpc.WithTransportCredentials(creds),
)

Scenario 3: Test e sviluppo

Durante lo sviluppo di microservizi, è spesso necessario testare il comportamento dei servizi da diversi ambienti di rete — verificare come il servizio risponde a ritardi elevati o testare la logica geo-dipendente. Per tali compiti sono comodi proxy residenziali, che consentono di simulare richieste da regioni specifiche con indirizzi IP reali di utenti domestici.

Errori comuni nella configurazione di gRPC attraverso un proxy e loro risoluzione

Esaminiamo i problemi più comuni che gli sviluppatori affrontano nella configurazione di gRPC attraverso un proxy e i modi specifici per risolverli.

Errore 1: "transport: received the unexpected content-type"

Sintomo:

Il client riceve un errore transport: received the unexpected content-type "text/html; charset=utf-8"

Motivo: Il proxy ha restituito una pagina HTML con un errore (ad esempio, 502 Bad Gateway) invece di una risposta gRPC. Il client gRPC non è in grado di gestire HTML e restituisce questo errore.
Soluzione: Assicurati che il backend sia disponibile. Aggiungi la gestione degli errori gRPC a livello di proxy (come mostrato nell'esempio di Nginx sopra con il blocco error502grpc).

Errore 2: La connessione si interrompe dopo 60-120 secondi

Sintomo:

Le connessioni gRPC in streaming si chiudono inaspettatamente con un errore UNAVAILABLE: transport is closing dopo un intervallo di tempo simile.

Motivo: Il proxy chiude le connessioni inattive dopo un timeout. Valori classici: AWS ELB — 60 secondi, Nginx per impostazione predefinita — 60 secondi.
Soluzione 1: Aumenta i timeout sul proxy (come mostrato negli esempi sopra).
Soluzione 2: Configura il keepalive sul lato client gRPC:

import "google.golang.org/grpc/keepalive"

kaParams := keepalive.ClientParameters{
    Time:                30 * time.Second, // Ping ogni 30 secondi
    Timeout:             10 * time.Second, // Aspetta la risposta per 10 secondi
    PermitWithoutStream: true,             // Ping anche senza RPC attive
}

conn, err := grpc.Dial(
    "grpc-service:50051",
    grpc.WithKeepaliveParams(kaParams),
    grpc.WithTransportCredentials(creds),
)

Errore 3: HTTP/2 non concordato (ALPN failure)

Sintomo:

Errore transport: failed to dial: context deadline exceeded o no application protocol

Motivo: Il proxy o l'hardware intermedio non supportano ALPN (Application-Layer Protocol Negotiation) o h2 non è incluso nell'elenco dei protocolli supportati.
Soluzione: Assicurati che nella configurazione TLS del proxy sia esplicitamente indicato il protocollo h2: alpn h2,http/1.1 (HAProxy) o listen 443 ssl http2 (Nginx).

Errore 4: Lo streaming si blocca — i dati non arrivano

Sintomo:

Lo stream server-side funziona sul backend (visibile nei log), ma il client non riceve messaggi fino al completamento dello stream.

Motivo: Il proxy bufferizza la risposta e la invia al client solo dopo il completamento. Problema tipico per i proxy configurati con proxy_buffering on.
Soluzione per Nginx:

location / {
    grpc_pass grpc://backend;
    
    # Disabilita la bufferizzazione per gli stream gRPC
    grpc_buffer_size 0;
    
    # Oppure per il normale proxy_pass:
    proxy_buffering off;
    proxy_cache off;
}

Checklist di diagnostica gRPC attraverso un proxy

✅ Checklist: diagnostica gRPC-proxy

  • Il proxy supporta HTTP/2 (controlla con curl --http2 -v)
  • ALPN h2 è abilitato nelle configurazioni TLS del proxy
  • I timeout del client e del server sono impostati a non meno di 300 secondi
  • La bufferizzazione delle risposte è disabilitata per gli endpoint gRPC
  • Il keepalive gRPC è configurato sul client (Time: 30s, Timeout: 10s)
  • Gli errori del backend vengono restituiti come stati gRPC, non come codici HTTP
  • Il controllo di integrità utilizza il gRPC Health Checking Protocol
  • Il bilanciamento funziona su L7 (stream HTTP/2), non su L4 (TCP)

Conclusione

Configurare gRPC attraverso un proxy richiede di comprendere le differenze chiave tra HTTP/2 e HTTP/1.1: multiplexing degli stream, connessioni a lungo termine, trailers HTTP/2 e negoziazione ALPN. I proxy HTTP tradizionali senza una configurazione speciale non funzionano con gRPC — è necessario un proxy L7 con supporto per HTTP/2 (Nginx 1.13.10+, Envoy, HAProxy 1.9.2+) o tunneling TCP CONNECT.

Per l'ambiente di produzione, si raccomanda Envoy — è stato creato specificamente per architetture a microservizi, ha supporto nativo per gRPC, è in grado di raccogliere metriche dettagliate per ogni metodo RPC ed è la base della maggior parte delle soluzioni di service mesh. Nginx è una buona scelta se lo stai già utilizzando come API Gateway e vuoi aggiungere supporto gRPC senza introdurre un nuovo componente. HAProxy è adatto se la performance è critica e sei già familiare con la sua configurazione.

Indipendentemente dal proxy scelto, tre regole rimangono invariate: aumenta i timeout per gli stream a lungo termine, disabilita la bufferizzazione delle risposte e configura il keepalive sul client. Queste tre impostazioni risolvono l'80% dei problemi con gRPC attraverso un proxy.

Se nella tua architettura i servizi gRPC devono interagire attraverso reti esterne o hai bisogno di instradare il traffico attraverso regioni specifiche, considera i proxy di data center — forniscono indirizzi IP stabili, bassa latenza e alta larghezza di banda, il che è particolarmente importante per gRPC con il suo protocollo binario e la sensibilità alla latenza.

```