gRPC é um framework RPC de alto desempenho do Google que opera sobre HTTP/2 e se torna o padrão de fato para a interação entre serviços. Mas assim que você tenta passar o tráfego gRPC através de um proxy, surgem problemas: a maioria dos proxies clássicos não consegue lidar com HTTP/2 e conexões de streaming de longa duração. Neste artigo, vamos discutir como configurar corretamente o gRPC através de um proxy — desde a escolha da arquitetura até configurações específicas do Nginx, Envoy e HAProxy.
Por que o gRPC não funciona bem com proxies comuns
Para entender o problema, é necessário compreender como o gRPC funciona por trás das cenas. O protocolo utiliza HTTP/2 como camada de transporte, o que o diferencia radicalmente do REST convencional sobre HTTP/1.1. A maioria dos proxies corporativos, firewalls e balanceadores de carga foram projetados na era do HTTP/1.1 e simplesmente não conseguem lidar com streams multiplexados de HTTP/2.
Aqui estão alguns problemas específicos que você encontrará ao tentar passar gRPC através de um proxy HTTP clássico:
- Downgrade para HTTP/1.1. Muitos proxies automaticamente rebaixam a versão do protocolo. O gRPC requer HTTP/2 — sem isso, a conexão simplesmente não será estabelecida, e o cliente receberá um erro
UNAVAILABLE. - Interrupção de conexões de longa duração. O gRPC utiliza ativamente streaming bidirecional e do servidor. Proxies com timeouts agressivos (especialmente AWS ELB Classic, algumas versões do Squid) interrompem conexões que não transmitem dados por mais de 60 segundos.
- Problemas com Content-Type. O gRPC utiliza o cabeçalho
Content-Type: application/grpc. Proxies que não conhecem esse tipo podem rejeitar a solicitação ou bufferizar o corpo de forma inadequada. - Trailers. O gRPC utiliza trailers HTTP/2 para transmitir o status de conclusão da chamada. Proxies HTTP/1.1 não suportam trailers — a informação de status será perdida.
- Bufferização do corpo. Alguns proxies bufferizam todo o corpo da resposta antes de enviá-lo ao cliente. Para chamadas gRPC de streaming, isso significa que o cliente não receberá nenhuma mensagem até que o stream seja concluído.
Conclusão chave:
Para que o gRPC funcione através de um proxy, é necessário um proxy que suporte HTTP/2 de ponta a ponta ou um modo especial de tunelamento. Um proxy HTTP clássico sem ajustes na configuração não servirá.
Tunelamento HTTP/2: como funciona
Existem duas abordagens fundamentalmente diferentes para o proxy de tráfego gRPC, e é importante entender a diferença entre elas para escolher a solução correta para sua arquitetura.
Abordagem 1: HTTP/2 de ponta a ponta (recomendada)
O proxy entende HTTP/2 e estabelece uma conexão HTTP/2 tanto com o cliente quanto com o backend. Ele pode analisar streams individuais, aplicar balanceamento no nível de solicitações, adicionar cabeçalhos e realizar terminação TLS. Esta é a opção mais funcional — assim funcionam o Envoy, Nginx (a partir da versão 1.13.10) e balanceadores de carga cientes de gRPC em provedores de nuvem.
Abordagem 2: Tunelamento TCP (CONNECT)
O proxy não analisa o tráfego HTTP/2, mas simplesmente cria um túnel TCP transparente através do método CONNECT. O cliente estabelece uma conexão TLS diretamente com o backend através do túnel. O proxy vê apenas um fluxo de bytes criptografados. Este método é mais simples de configurar, mas o priva das capacidades de balanceamento no nível de solicitações gRPC e adição de cabeçalhos.
| Característica | HTTP/2 de ponta a ponta | Túnel TCP CONNECT |
|---|---|---|
| Balanceamento por solicitações | ✅ Sim | ❌ Não (apenas por TCP) |
| Terminação TLS no proxy | ✅ Sim | ❌ Não |
| Adição de cabeçalhos | ✅ Sim | ❌ Não |
| Dificuldade de configuração | Média | Baixa |
| Suporte a streaming | ✅ Completo | ✅ Completo |
| Observabilidade (métricas) | ✅ Detalhada | ❌ Apenas TCP |
Configurando o Nginx como um proxy gRPC
O Nginx suporta o proxy de gRPC a partir da versão 1.13.10 (fevereiro de 2018). Para funcionar, é necessário o módulo ngx_http_grpc_module, que está incluído na compilação padrão. Importante: o Nginx suporta HTTP/2 no lado do cliente (frontend), mas no lado do backend utiliza HTTP/2 apenas para gRPC — upstream HTTP comum opera sobre HTTP/1.1.
Configuração básica do proxy gRPC no Nginx
server {
listen 443 ssl http2;
server_name grpc.example.com;
# Certificados TLS
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# Parâmetros TLS modernos
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
# Diretiva grpc_pass em vez de proxy_pass
grpc_pass grpc://grpc_backend;
# Timeouts para streams de longa duração
grpc_read_timeout 3600s;
grpc_send_timeout 3600s;
grpc_connect_timeout 5s;
# Passando o IP real do cliente
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 para conexões HTTP/2
keepalive 32;
}
Preste atenção em alguns detalhes críticos. Primeiro, a diretiva grpc_pass é usada em vez de proxy_pass — são módulos diferentes com comportamentos distintos. Em segundo lugar, os timeouts grpc_read_timeout e grpc_send_timeout estão definidos para 3600 segundos (1 hora) — isso é importante para streams de servidor que podem operar por longos períodos sem transmitir dados. Em terceiro lugar, keepalive 32 no upstream permite reutilizar conexões HTTP/2 com os backends.
Configuração para gRPC não criptografado (grpc://)
server {
listen 80 http2;
server_name grpc-internal.example.com;
location / {
grpc_pass grpc://127.0.0.1:50051;
# Tratamento de erros gRPC
error_page 502 = /error502grpc;
}
location = /error502grpc {
internal;
default_type application/grpc;
add_header grpc-status 14;
add_header content-length 0;
return 204;
}
}
O bloco error502grpc é um detalhe importante: quando o backend não está acessível, ele retorna o status gRPC correto UNAVAILABLE (14) em vez do HTTP 502, que o cliente gRPC não conseguirá processar corretamente.
Envoy Proxy: a melhor escolha para gRPC em microsserviços
O Envoy foi criado pela Lyft especificamente para arquitetura de microsserviços, e o suporte ao gRPC é implementado em um nível muito profundo. Ele entende Protocol Buffers, pode transcodificar gRPC em REST, coleta métricas detalhadas para cada método RPC e é a base para soluções de service mesh — Istio, AWS App Mesh e outras. Se você está construindo uma arquitetura de microsserviços séria, o Envoy é o padrão da indústria.
Configuração básica do Envoy para 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
As principais vantagens desta configuração incluem: roteamento por serviços gRPC (o prefixo do caminho corresponde ao nome completo do serviço no formato package.ServiceName), tentativas automáticas em caso de status UNAVAILABLE e coleta de métricas gRPC através do filtro grpc_stats.
Transcodificação gRPC-Web no Envoy
Uma das características mais atraentes do Envoy é o transcodificador gRPC-Web embutido. Os navegadores não suportam gRPC diretamente (devido a limitações da Fetch API em relação a trailers HTTP/2), portanto, o protocolo gRPC-Web é utilizado. O Envoy pode automaticamente converter solicitações gRPC-Web do navegador em gRPC normal para o 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: configuração com balanceamento de carga
O HAProxy suporta gRPC a partir da versão 1.9.2. Ele opera no nível TCP ou HTTP/2, consegue balancear tráfego gRPC e realizar health checks. O HAProxy é uma boa escolha se você já o utiliza na infraestrutura e deseja adicionar suporte a gRPC sem introduzir um novo 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 via 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
Preste atenção em algumas configurações importantes. alpn h2,http/1.1 na diretiva bind indica que o HAProxy aceita conexões tanto HTTP/2 quanto HTTP/1.1 através da negociação ALPN TLS. timeout client 3600s e timeout server 3600s são parâmetros críticos para streams gRPC de longa duração. O algoritmo leastconn é preferível ao roundrobin para gRPC, pois conexões de streaming podem ser de longa duração e sobrecarregar desigualmente os backends.
Terminação TLS e criptografia de ponta a ponta para gRPC
O gRPC, por padrão, presume o uso de TLS — isso faz parte da especificação. Na prática, na arquitetura de microsserviços, surge a questão: onde realizar a terminação TLS e como organizar a criptografia entre os componentes? Existem três padrões principais.
Padrão 1: Terminação TLS no proxy (Edge TLS)
O proxy aceita tráfego criptografado dos clientes, descriptografa e o transmite para os backends através de um canal não criptografado (ou com TLS separado). Esta é a abordagem mais comum em redes corporativas. Os backends podem usar grpc.Insecure() para simplificar a configuração.
Padrão 2: Criptografia de ponta a ponta (mTLS)
O Mutual TLS (mTLS) é o padrão para service mesh. Cada serviço possui seu próprio certificado, e em cada conexão ambas as partes verificam os certificados uma da outra. Isso garante a autenticação dos serviços e a criptografia do tráfego dentro do cluster. É assim que o Istio funciona com o proxy sidecar do Envoy.
# Go: servidor gRPC com 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)
}
// Criando o servidor com mTLS
creds := createMTLSCredentials()
server := grpc.NewServer(grpc.Creds(creds))
Padrão 3: TLS Passthrough
O proxy opera no nível TCP e não descriptografa o tráfego — simplesmente redireciona os bytes criptografados para o backend. Esta é a opção mais simples do ponto de vista do proxy, mas priva a capacidade de analisar o tráfego gRPC, adicionar cabeçalhos ou realizar balanceamento no nível de solicitações.
Balanceamento de carga para streams gRPC: características e soluções
O balanceamento de carga para gRPC é uma tarefa não trivial, e aqui está o porquê. No HTTP/1.1, cada solicitação é uma conexão TCP separada (ou uma conexão de um pool), e o balanceador distribui facilmente as solicitações entre os backends. No HTTP/2, uma única conexão TCP multiplexa múltiplos streams — se o balanceador opera no nível TCP, todos os streams de uma conexão irão para um único backend.
Para o gRPC, isso significa que se um cliente estabeleceu uma única conexão HTTP/2 e envia 100 solicitações RPC através dela, com balanceamento TCP, todas as 100 solicitações serão processadas por uma instância do backend. Os outros backends ficarão ociosos. A solução é o balanceamento no nível de streams HTTP/2 (balanceamento L7).
Algoritmos de balanceamento para gRPC
| Algoritmo | Adequado para gRPC | Comentário |
|---|---|---|
| Round Robin | ✅ Sim | Bom para RPC unários com tempos de processamento aproximadamente iguais |
| Least Connection | ✅ Melhor escolha | Considera streams ativos, distribui a carga uniformemente |
| Random | ⚠️ Condicional | Funciona bem com um grande número de solicitações, mas é desigual com um pequeno |
| IP Hash | ❌ Ruim | Vincula o cliente a um único backend, não faz sentido em L7 |
| Pick First (gRPC embutido) | ❌ Não para produção | Todas as solicitações vão para o primeiro servidor disponível |
Balanceamento de cliente em gRPC
O gRPC suporta balanceamento de carga no cliente — o cliente decide para qual servidor enviar a solicitação. Isso permite contornar o problema de multiplexação TCP. Para descoberta de serviços, utiliza-se DNS com vários registros A ou resolvers especiais (Consul, etcd). Exemplo de configuração de balanceamento de cliente em Go:
import (
"google.golang.org/grpc"
"google.golang.org/grpc/balancer/roundrobin"
)
// Cliente com balanceamento round-robin via DNS
conn, err := grpc.Dial(
"dns:///grpc-service.internal:50051",
grpc.WithDefaultServiceConfig(
`{"loadBalancingConfig": [{"round_robin":{}}]}`
),
grpc.WithTransportCredentials(creds),
)
// Cliente com least-connection (disponível no gRPC >= 1.58)
conn, err := grpc.Dial(
"dns:///grpc-service.internal:50051",
grpc.WithDefaultServiceConfig(
`{"loadBalancingConfig": [{"least_request":{}}]}`
),
grpc.WithTransportCredentials(creds),
)
Proxies externos para gRPC: quando e por que são necessários em microsserviços
Além dos componentes de proxy internos (Nginx, Envoy, HAProxy), na arquitetura de microsserviços às vezes surge a necessidade de usar servidores proxy externos — por exemplo, para roteamento de tráfego através de regiões específicas, contornar restrições de rede ou isolar conexões de saída. Vamos considerar os principais cenários.
Cenário 1: Microsserviços geograficamente distribuídos
Se seus microsserviços estão localizados em diferentes regiões (por exemplo, parte na Europa, parte nos EUA), e você precisa controlar através de qual endereço IP as conexões gRPC são estabelecidas entre as regiões, proxies externos podem ajudar a organizar o roteamento previsível. Para tais tarefas, proxies de data center são adequados — eles fornecem endereços IP estáveis e alta velocidade de conexão, o que é crítico para gRPC devido à sua sensibilidade a latências.
Cenário 2: Isolamento do tráfego de saída
Em alguns ambientes corporativos, todo o tráfego de saída deve passar por um proxy corporativo. Para clientes gRPC que precisam se conectar a serviços gRPC externos (por exemplo, Google Cloud APIs que usam gRPC), isso cria dificuldades. A solução é configurar um túnel CONNECT através do proxy corporativo.
Exemplo de configuração de um cliente gRPC para operar através de um proxy HTTP CONNECT em Go:
import (
"net"
"net/http"
"golang.org/x/net/proxy"
"google.golang.org/grpc"
)
// Usando um proxy SOCKS5 para 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),
)
Cenário 3: Testes e desenvolvimento
Ao desenvolver microsserviços, muitas vezes é necessário testar o comportamento dos serviços a partir de diferentes ambientes de rede — verificar como o serviço responde a altas latências ou testar a lógica dependente de geolocalização. Para tais tarefas, proxies residenciais são convenientes, pois permitem simular solicitações de regiões específicas com endereços IP reais de usuários domésticos.
Erros comuns ao configurar gRPC através de proxy e suas soluções
Vamos discutir os problemas mais frequentes que os desenvolvedores enfrentam ao configurar gRPC através de proxies e as maneiras específicas de resolvê-los.
Erro 1: "transport: received the unexpected content-type"
Sintoma:
O cliente recebe o erro transport: received the unexpected content-type "text/html; charset=utf-8"
Causa: O proxy retornou uma página HTML de erro (por exemplo, 502 Bad Gateway) em vez da resposta gRPC. O cliente gRPC não consegue processar HTML e gera esse erro.
Solução: Verifique se o backend está acessível. Adicione tratamento de erros gRPC no nível do proxy (como mostrado no exemplo do Nginx acima com o bloco error502grpc).
Erro 2: Conexão é encerrada após 60-120 segundos
Sintoma:
As conexões de streaming gRPC são encerradas inesperadamente com o erro UNAVAILABLE: transport is closing após um intervalo de tempo aproximadamente igual.
Causa: O proxy encerra conexões ociosas após um timeout. Valores clássicos: AWS ELB — 60 segundos, Nginx por padrão — 60 segundos.
Solução 1: Aumente os timeouts no proxy (como mostrado nos exemplos acima).
Solução 2: Configure keepalive no lado do cliente gRPC:
import "google.golang.org/grpc/keepalive"
kaParams := keepalive.ClientParameters{
Time: 30 * time.Second, // Ping a cada 30 segundos
Timeout: 10 * time.Second, // Aguardar resposta por 10 segundos
PermitWithoutStream: true, // Ping mesmo sem RPCs ativas
}
conn, err := grpc.Dial(
"grpc-service:50051",
grpc.WithKeepaliveParams(kaParams),
grpc.WithTransportCredentials(creds),
)
Erro 3: HTTP/2 não se negocia (falha ALPN)
Sintoma:
Erro transport: failed to dial: context deadline exceeded ou no application protocol
Causa: O proxy ou equipamento intermediário não suporta ALPN (Application-Layer Protocol Negotiation) ou o h2 não está incluído na lista de protocolos suportados.
Solução: Verifique se na configuração TLS do proxy o protocolo h2 está explicitamente especificado: alpn h2,http/1.1 (HAProxy) ou listen 443 ssl http2 (Nginx).
Erro 4: Streaming trava — dados não chegam
Sintoma:
O stream do servidor está funcionando no backend (visível nos logs), mas o cliente não recebe mensagens até que o stream seja concluído.
Causa: O proxy bufferiza a resposta e a envia ao cliente apenas após a conclusão. Um problema típico para proxies configurados com proxy_buffering on.
Solução para Nginx:
location / {
grpc_pass grpc://backend;
# Desativar bufferização para streams gRPC
grpc_buffer_size 0;
# Ou para proxy_pass comum:
proxy_buffering off;
proxy_cache off;
}
Checklist de diagnóstico gRPC através de proxy
✅ Checklist: diagnóstico de proxy gRPC
- O proxy suporta HTTP/2 (verifique com
curl --http2 -v) - ALPN h2 está habilitado nas configurações TLS do proxy
- Os timeouts do cliente e do servidor estão definidos para pelo menos 300 segundos
- A bufferização das respostas está desativada para endpoints gRPC
- O keepalive gRPC está configurado no cliente (Time: 30s, Timeout: 10s)
- Os erros do backend são retornados como status gRPC, e não códigos HTTP
- O health check utiliza o gRPC Health Checking Protocol
- O balanceamento opera em L7 (streams HTTP/2), e não em L4 (TCP)
Conclusão
Configurar gRPC através de um proxy requer compreensão das principais diferenças entre HTTP/2 e HTTP/1.1: multiplexação de streams, conexões de longa duração, trailers HTTP/2 e negociação ALPN. Proxies HTTP clássicos sem configuração especial não funcionam com gRPC — é necessário um proxy L7 que suporte HTTP/2 (Nginx 1.13.10+, Envoy, HAProxy 1.9.2+) ou tunelamento TCP CONNECT.
Para ambientes de produção, o Envoy é recomendado — ele foi projetado especificamente para arquitetura de microsserviços, possui suporte nativo a gRPC, pode coletar métricas detalhadas para cada método RPC e é a base da maioria das soluções de service mesh. O Nginx é uma boa escolha se você já o utiliza como API Gateway e deseja adicionar suporte a gRPC sem introduzir um novo componente. O HAProxy é adequado se a performance é crítica e você já está familiarizado com sua configuração.
Independentemente do proxy escolhido, três regras permanecem inalteradas: aumente os timeouts para streams de longa duração, desative a bufferização das respostas e configure o keepalive no cliente. Essas três configurações resolvem 80% dos problemas com gRPC através de proxies.
Se na sua arquitetura os serviços gRPC devem interagir através de redes externas ou você precisa de roteamento de tráfego através de regiões específicas, considere usar proxies de data center — eles fornecem endereços IP estáveis, baixa latência e alta largura de banda, o que é especialmente importante para gRPC com seu protocolo binário e sensibilidade à latência.
```