gRPC est un framework RPC haute performance de Google qui fonctionne au-dessus de HTTP/2 et devient le standard de facto pour l'interaction entre services. Mais dès que vous essayez de faire passer le trafic gRPC à travers un proxy, des problèmes surgissent immédiatement : la plupart des serveurs proxy classiques ne savent pas gérer HTTP/2 et les connexions de streaming à long terme. Dans cet article, nous allons examiner comment configurer correctement gRPC via un proxy — du choix de l'architecture aux configurations spécifiques de Nginx, Envoy et HAProxy.
Pourquoi gRPC fonctionne mal avec les proxies classiques
Pour comprendre le problème, il faut examiner comment gRPC est conçu en interne. Le protocole utilise HTTP/2 comme couche de transport, ce qui le différencie radicalement du REST habituel sur HTTP/1.1. La plupart des proxies d'entreprise, des pare-feu et des répartiteurs de charge ont été conçus à l'époque de HTTP/1.1 et ne savent tout simplement pas gérer les flux multiplexés de HTTP/2.
Voici quelques problèmes spécifiques que vous rencontrerez en essayant de faire passer gRPC à travers un proxy HTTP classique :
- Downgrade à HTTP/1.1. De nombreux proxies rétrogradent automatiquement la version du protocole. gRPC nécessite HTTP/2 — sans cela, la connexion ne pourra tout simplement pas être établie, et le client recevra une erreur
UNAVAILABLE. - Interruption des connexions à long terme. gRPC utilise activement le streaming serveur et bidirectionnel. Les proxies avec des délais d'attente agressifs (en particulier AWS ELB Classic, certaines versions de Squid) interrompent les connexions qui ne transmettent pas de données pendant plus de 60 secondes.
- Problèmes de Content-Type. gRPC utilise l'en-tête
Content-Type: application/grpc. Un proxy qui ne connaît pas ce type peut rejeter la requête ou mettre en mémoire tampon le corps de manière incorrecte. - Trailers. gRPC utilise les trailers HTTP/2 pour transmettre le statut de fin d'appel. Les proxies HTTP/1.1 ne prennent pas en charge les trailers — l'information sur le statut sera perdue.
- Mise en mémoire tampon du corps. Certains proxies mettent en mémoire tampon tout le corps de la réponse avant de le transmettre au client. Pour les appels gRPC en streaming, cela signifie que le client ne recevra aucun message tant que le flux ne sera pas terminé.
Conclusion clé :
Pour faire fonctionner gRPC via un proxy, il faut un proxy prenant en charge HTTP/2 de bout en bout ou un mode de tunneling spécial. Un proxy HTTP classique sans configuration supplémentaire ne conviendra pas.
Tunneling HTTP/2 : comment ça fonctionne
Il existe deux approches fondamentalement différentes pour le proxying du trafic gRPC, et il est important de comprendre la différence entre elles pour choisir la bonne solution pour votre architecture.
Approche 1 : HTTP/2 de bout en bout (recommandée)
Le proxy comprend HTTP/2 et établit une connexion HTTP/2 à la fois avec le client et avec le backend. Il peut analyser des flux individuels, appliquer une répartition de charge au niveau des requêtes, ajouter des en-têtes et effectuer une terminaison TLS. C'est l'option la plus fonctionnelle — c'est ainsi que fonctionnent Envoy, Nginx (à partir de la version 1.13.10) et les répartiteurs de charge compatibles gRPC chez les fournisseurs de cloud.
Approche 2 : Tunneling TCP (CONNECT)
Le proxy n'analyse pas le trafic HTTP/2, mais crée simplement un tunnel TCP transparent via la méthode CONNECT. Le client établit une connexion TLS directement avec le backend via le tunnel. Le proxy ne voit que le flux de bytes chiffrés. Cette méthode est plus simple à configurer, mais vous prive des possibilités de répartition de charge au niveau des requêtes gRPC et d'ajout d'en-têtes.
| Caractéristique | HTTP/2 de bout en bout | Tunnel TCP CONNECT |
|---|---|---|
| Répartition par requêtes | ✅ Oui | ❌ Non (uniquement par TCP) |
| Terminaison TLS sur le proxy | ✅ Oui | ❌ Non |
| Ajout d'en-têtes | ✅ Oui | ❌ Non |
| Complexité de configuration | Moyenne | Faible |
| Support du streaming | ✅ Complet | ✅ Complet |
| Observabilité (métriques) | ✅ Détail | ❌ Seulement TCP |
Configurer Nginx comme proxy gRPC
Nginx prend en charge le proxying gRPC à partir de la version 1.13.10 (février 2018). Pour cela, le module ngx_http_grpc_module est nécessaire, qui fait partie de la distribution standard. Il est important de noter que Nginx prend en charge HTTP/2 côté client (frontend), mais utilise HTTP/2 côté backend uniquement pour gRPC — le upstream HTTP classique fonctionne sur HTTP/1.1.
Configuration de base du proxy gRPC sur Nginx
server {
listen 443 ssl http2;
server_name grpc.example.com;
# Certificats TLS
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# Paramètres TLS modernes
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
# Directive grpc_pass au lieu de proxy_pass
grpc_pass grpc://grpc_backend;
# Délais d'attente pour les flux à long terme
grpc_read_timeout 3600s;
grpc_send_timeout 3600s;
grpc_connect_timeout 5s;
# Transmission de l'IP réelle du 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 pour les connexions HTTP/2
keepalive 32;
}
Faites attention à quelques détails critiques. Tout d'abord, la directive grpc_pass est utilisée au lieu de proxy_pass — ce sont des modules différents avec des comportements différents. Deuxièmement, les délais d'attente grpc_read_timeout et grpc_send_timeout sont fixés à 3600 secondes (1 heure) — c'est important pour les flux serveur qui peuvent fonctionner longtemps sans transmettre de données. Troisièmement, keepalive 32 dans le upstream permet de réutiliser les connexions HTTP/2 avec les backends.
Configuration pour gRPC non chiffré (grpc://)
server {
listen 80 http2;
server_name grpc-internal.example.com;
location / {
grpc_pass grpc://127.0.0.1:50051;
# Gestion des erreurs gRPC
error_page 502 = /error502grpc;
}
location = /error502grpc {
internal;
default_type application/grpc;
add_header grpc-status 14;
add_header content-length 0;
return 204;
}
}
Le bloc error502grpc est un détail important : en cas d'indisponibilité du backend, il retourne le statut gRPC correct UNAVAILABLE (14) au lieu de HTTP 502, que le client gRPC ne pourra pas traiter correctement.
Envoy Proxy : le meilleur choix pour gRPC dans les microservices
Envoy a été créé par Lyft spécifiquement pour l'architecture des microservices, et le support de gRPC y est implémenté à un niveau très profond. Il comprend les Protocol Buffers, peut transcoder gRPC en REST, collecte des métriques détaillées pour chaque méthode RPC et constitue la base des solutions de service mesh — Istio, AWS App Mesh et d'autres. Si vous construisez une architecture de microservices sérieuse, Envoy est le standard de l'industrie.
Configuration de base d'Envoy pour 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
Les principaux avantages de cette configuration : routage par services gRPC (le préfixe du chemin correspond au nom complet du service au format package.ServiceName), réessais automatiques en cas de statut UNAVAILABLE et collecte de métriques gRPC via le filtre grpc_stats.
Transcodage gRPC-Web dans Envoy
L'une des fonctionnalités clés d'Envoy est le transcoder gRPC-Web intégré. Les navigateurs ne prennent pas en charge gRPC directement (en raison des limitations de l'API Fetch sur les trailers HTTP/2), donc le protocole gRPC-Web est utilisé. Envoy peut automatiquement convertir les requêtes gRPC-Web du navigateur en gRPC normal pour le 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 et gRPC : configuration avec répartition de charge
HAProxy prend en charge gRPC à partir de la version 1.9.2. Il fonctionne au niveau TCP ou HTTP/2, peut équilibrer le trafic gRPC et effectuer des vérifications de santé. HAProxy est un bon choix si vous l'utilisez déjà dans votre infrastructure et souhaitez ajouter le support gRPC sans introduire un nouveau composant.
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
# Vérification de santé via le protocole gRPC Health Checking
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
Faites attention à quelques paramètres importants. alpn h2,http/1.1 dans la directive bind indique que HAProxy accepte à la fois les connexions HTTP/2 et HTTP/1.1 via la négociation TLS ALPN. timeout client 3600s et timeout server 3600s sont des paramètres critiques pour les flux gRPC à long terme. L'algorithme leastconn est préférable à roundrobin pour gRPC, car les connexions de streaming peuvent être à long terme et déséquilibrer les backends.
Terminaison TLS et chiffrement de bout en bout pour gRPC
gRPC suppose par défaut l'utilisation de TLS — c'est une partie de la spécification. En pratique, dans une architecture de microservices, la question se pose : où effectuer la terminaison TLS et comment organiser le chiffrement entre les composants ? Il existe trois modèles principaux.
Modèle 1 : Terminaison TLS sur le proxy (Edge TLS)
Le proxy accepte le trafic chiffré des clients, le déchiffre et le transmet aux backends par un canal non chiffré (ou avec un TLS séparé). C'est l'approche la plus courante dans les réseaux d'entreprise. Les backends peuvent utiliser grpc.Insecure() pour simplifier la configuration.
Modèle 2 : Chiffrement de bout en bout (mTLS)
Le TLS mutuel (mTLS) est la norme pour les services mesh. Chaque service a son propre certificat, et à chaque connexion, les deux parties vérifient les certificats de l'autre. Cela assure l'authentification des services et le chiffrement du trafic à l'intérieur du cluster. C'est ainsi qu'Istio fonctionne avec le proxy sidecar Envoy.
# Go : serveur gRPC avec 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)
}
// Création d'un serveur avec mTLS
creds := createMTLSCredentials()
server := grpc.NewServer(grpc.Creds(creds))
Modèle 3 : TLS Passthrough
Le proxy fonctionne au niveau TCP et ne déchiffre pas le trafic — il redirige simplement les bytes chiffrés vers le backend. C'est l'option la plus simple du point de vue du proxy, mais elle prive de la possibilité d'analyser le trafic gRPC, d'ajouter des en-têtes ou d'effectuer une répartition de charge au niveau des requêtes.
Répartition de charge pour les flux gRPC : particularités et solutions
La répartition de charge pour gRPC est une tâche non triviale, et voici pourquoi. Dans HTTP/1.1, chaque requête est une connexion TCP distincte (ou une connexion d'un pool), et le répartiteur de charge répartit facilement les requêtes entre les backends. Dans HTTP/2, une seule connexion TCP multiplexe de nombreux flux — si le répartiteur de charge fonctionne au niveau TCP, tous les flux d'une connexion iront vers un seul backend.
Pour gRPC, cela signifie que si le client établit une seule connexion HTTP/2 et envoie 100 requêtes RPC à travers celle-ci, avec une répartition TCP, toutes les 100 requêtes seront traitées par une seule instance de backend. Les autres backends resteront inactifs. La solution est de répartir la charge au niveau des flux HTTP/2 (répartition L7).
Algorithmes de répartition pour gRPC
| Algorithme | Adapté pour gRPC | Commentaire |
|---|---|---|
| Round Robin | ✅ Oui | Bon pour les RPC unaires avec des temps de traitement à peu près égaux |
| Least Connection | ✅ Meilleur choix | Prend en compte les flux actifs, répartit la charge de manière uniforme |
| Random | ⚠️ Conditionnel | Fonctionne avec un grand nombre de requêtes, déséquilibré avec peu |
| IP Hash | ❌ Mauvais | Lie le client à un seul backend, pas de sens en L7 |
| Pick First (intégré à gRPC) | ❌ Pas pour la production | Toutes les requêtes vont au premier serveur disponible |
Répartition de charge côté client dans gRPC
gRPC prend en charge la répartition de charge côté client — le client décide lui-même sur quel serveur envoyer la requête. Cela permet de contourner le problème de multiplexage TCP. Pour la découverte de services, DNS est utilisé avec plusieurs enregistrements A ou des résolveurs spéciaux (Consul, etcd). Exemple de configuration de la répartition de charge côté client en Go :
import (
"google.golang.org/grpc"
"google.golang.org/grpc/balancer/roundrobin"
)
// Client avec répartition round-robin via DNS
conn, err := grpc.Dial(
"dns:///grpc-service.internal:50051",
grpc.WithDefaultServiceConfig(
`{"loadBalancingConfig": [{"round_robin":{}}]}`
),
grpc.WithTransportCredentials(creds),
)
// Client avec least-connection (disponible dans gRPC >= 1.58)
conn, err := grpc.Dial(
"dns:///grpc-service.internal:50051",
grpc.WithDefaultServiceConfig(
`{"loadBalancingConfig": [{"least_request":{}}]}`
),
grpc.WithTransportCredentials(creds),
)
Proxies externes pour gRPC : quand et pourquoi en avoir besoin dans les microservices
En plus des composants proxy internes (Nginx, Envoy, HAProxy), il arrive parfois dans une architecture de microservices qu'il soit nécessaire d'utiliser des serveurs proxy externes — par exemple, pour router le trafic à travers des régions spécifiques, contourner des restrictions réseau ou isoler des connexions sortantes. Examinons les principaux scénarios.
Scénario 1 : Microservices géo-distribués
Si vos microservices sont situés dans différentes régions (par exemple, une partie en Europe, une partie aux États-Unis), et que vous devez contrôler par quelle adresse IP les connexions gRPC sont établies entre les régions, des proxies externes peuvent aider à organiser un routage prévisible. Pour de telles tâches, des proxies de centre de données sont adaptés — ils fournissent des adresses IP stables et une vitesse de connexion élevée, ce qui est critique pour gRPC avec sa sensibilité aux latences.
Scénario 2 : Isolation du trafic sortant
Dans certains environnements d'entreprise, tout le trafic sortant doit passer par un proxy d'entreprise. Pour les clients gRPC qui doivent accéder à des services gRPC externes (par exemple, les API Google Cloud qui utilisent gRPC), cela crée des difficultés. La solution consiste à configurer un tunnel CONNECT via le proxy d'entreprise.
Exemple de configuration d'un client gRPC pour fonctionner via un proxy HTTP CONNECT en Go :
import (
"net"
"net/http"
"golang.org/x/net/proxy"
"google.golang.org/grpc"
)
// Utilisation d'un proxy SOCKS5 pour 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),
)
Scénario 3 : Test et développement
Lors du développement de microservices, il est souvent nécessaire de tester le comportement des services depuis différents environnements réseau — vérifier comment un service répond en cas de latence élevée, ou tester une logique dépendante de la géographie. Pour de telles tâches, des proxies résidentiels sont pratiques, car ils permettent de simuler des requêtes depuis des régions spécifiques avec de vraies adresses IP d'utilisateurs domestiques.
Erreurs typiques lors de la configuration de gRPC via un proxy et leur résolution
Examinons les problèmes les plus fréquents rencontrés par les développeurs lors de la configuration de gRPC via un proxy et les moyens spécifiques de les résoudre.
Erreur 1 : "transport: received the unexpected content-type"
Symptôme :
Le client reçoit l'erreur transport: received the unexpected content-type "text/html; charset=utf-8"
Cause : Le proxy a renvoyé une page HTML avec une erreur (par exemple, 502 Bad Gateway) au lieu d'une réponse gRPC. Le client gRPC ne sait pas traiter le HTML et génère cette erreur.
Solution : Assurez-vous que le backend est accessible. Ajoutez la gestion des erreurs gRPC au niveau du proxy (comme montré dans l'exemple Nginx ci-dessus avec le bloc error502grpc).
Erreur 2 : La connexion se coupe après 60-120 secondes
Symptôme :
Les connexions gRPC en streaming se ferment de manière inattendue avec l'erreur UNAVAILABLE: transport is closing après un intervalle de temps à peu près égal.
Cause : Le proxy ferme les connexions inactives après un délai d'attente. Valeurs classiques : AWS ELB — 60 secondes, Nginx par défaut — 60 secondes.
Solution 1 : Augmentez les délais d'attente sur le proxy (comme montré dans les exemples ci-dessus).
Solution 2 : Configurez le keepalive côté client gRPC :
import "google.golang.org/grpc/keepalive"
kaParams := keepalive.ClientParameters{
Time: 30 * time.Second, // Ping toutes les 30 secondes
Timeout: 10 * time.Second, // Attendre une réponse pendant 10 secondes
PermitWithoutStream: true, // Pinguer même sans RPC actifs
}
conn, err := grpc.Dial(
"grpc-service:50051",
grpc.WithKeepaliveParams(kaParams),
grpc.WithTransportCredentials(creds),
)
Erreur 3 : HTTP/2 ne s'accorde pas (échec ALPN)
Symptôme :
Erreur transport: failed to dial: context deadline exceeded ou no application protocol
Cause : Le proxy ou le matériel intermédiaire ne prend pas en charge ALPN (Application-Layer Protocol Negotiation) ou h2 n'est pas inclus dans la liste des protocoles pris en charge.
Solution : Assurez-vous que dans la configuration TLS du proxy, le protocole h2 est explicitement indiqué : alpn h2,http/1.1 (HAProxy) ou listen 443 ssl http2 (Nginx).
Erreur 4 : Le streaming se fige — les données n'arrivent pas
Symptôme :
Le streaming serveur fonctionne sur le backend (visible dans les logs), mais le client ne reçoit pas de messages jusqu'à la fin du streaming.
Cause : Le proxy met en mémoire tampon la réponse et l'envoie au client uniquement après la fin. Problème typique pour les proxies configurés avec proxy_buffering on.
Solution pour Nginx :
location / {
grpc_pass grpc://backend;
# Désactiver la mise en mémoire tampon pour les flux gRPC
grpc_buffer_size 0;
# Ou pour un proxy_pass classique :
proxy_buffering off;
proxy_cache off;
}
Liste de contrôle de diagnostic pour gRPC via proxy
✅ Liste de contrôle : diagnostic gRPC-proxy
- Le proxy prend en charge HTTP/2 (vérifiez avec
curl --http2 -v) - ALPN h2 est activé dans les configurations TLS du proxy
- Les délais d'attente client et serveur sont fixés à au moins 300 secondes
- La mise en mémoire tampon des réponses est désactivée pour les points de terminaison gRPC
- Le keepalive gRPC est configuré sur le client (Time: 30s, Timeout: 10s)
- Les erreurs du backend sont renvoyées sous forme de statuts gRPC, et non de codes HTTP
- La vérification de santé utilise le protocole gRPC Health Checking
- La répartition fonctionne en L7 (flux HTTP/2), et non en L4 (TCP)
Conclusion
La configuration de gRPC via un proxy nécessite de comprendre les différences clés entre HTTP/2 et HTTP/1.1 : multiplexage des flux, connexions à long terme, trailers HTTP/2 et négociation ALPN. Les proxies HTTP classiques sans configuration spéciale ne fonctionnent pas avec gRPC — il faut soit un proxy L7 prenant en charge HTTP/2 (Nginx 1.13.10+, Envoy, HAProxy 1.9.2+), soit un tunneling TCP CONNECT.
Pour un environnement de production, Envoy est recommandé — il a été conçu spécifiquement pour l'architecture des microservices, a un support natif pour gRPC, peut collecter des métriques détaillées pour chaque méthode RPC et constitue la base de la plupart des solutions de service mesh. Nginx est un bon choix si vous l'utilisez déjà comme API Gateway et souhaitez ajouter le support gRPC sans introduire un nouveau composant. HAProxy conviendra si la performance est critique et que vous êtes déjà familier avec sa configuration.
Quelle que soit la solution proxy choisie, trois règles demeurent inchangées : augmentez les délais d'attente pour les flux à long terme, désactivez la mise en mémoire tampon des réponses et configurez le keepalive côté client. Ces trois réglages résolvent 80 % des problèmes avec gRPC via un proxy.
Si dans votre architecture, les services gRPC doivent interagir à travers des réseaux externes ou si vous avez besoin de router le trafic à travers des régions spécifiques, envisagez des proxies de centre de données — ils fournissent des adresses IP stables, une faible latence et une grande capacité, ce qui est particulièrement important pour gRPC avec son protocole binaire et sa sensibilité à la latence.
```