O que é gRPC? Como trabalhar com APISIX?

Zexuan Luo

Zexuan Luo

September 28, 2022

Ecosystem

O que é gRPC

gRPC é um framework RPC de código aberto desenvolvido pela Google que visa unificar a forma como os serviços se comunicam. O framework utiliza HTTP/2 como protocolo de transferência e Protocol Buffers como linguagem de descrição de interface. Ele pode gerar automaticamente código para chamadas entre serviços.

Dominância do gRPC

O gRPC se tornou o padrão dos frameworks RPC devido à influência excepcional da Google sobre desenvolvedores e ambientes de cloud-native.

Quer invocar funções do etcd? gRPC!

Quer enviar dados do OpenCensus? gRPC!

Quer usar RPC em um microsserviço implementado em Go? gRPC!

A dominância do gRPC é tão forte que, se você não escolher o gRPC como seu framework RPC, precisará dar uma razão sólida para isso. Caso contrário, alguém sempre perguntará: por que você não escolheu o gRPC, que é o padrão do mercado? Até mesmo a Alibaba, que promoveu vigorosamente seu framework RPC Dubbo, revisou drasticamente o design do protocolo na versão mais recente do Dubbo 3, transformando-o em uma variante do gRPC compatível tanto com o gRPC quanto com o Dubbo 2. Na verdade, em vez de dizer que o Dubbo 3 é uma atualização do Dubbo 2, é mais como um reconhecimento da supremacia do gRPC.

Muitos serviços que fornecem gRPC também oferecem interfaces HTTP correspondentes, mas essas interfaces geralmente são apenas para fins de compatibilidade. A versão gRPC oferece uma experiência de usuário muito melhor. Se você puder acessar via gRPC, pode importar diretamente o SDK correspondente. Se você só puder usar APIs HTTP comuns, geralmente será direcionado para uma página de documentação, e você precisará implementar as operações HTTP correspondentes por conta própria. Embora o acesso HTTP possa gerar o SDK correspondente por meio da especificação OpenAPI, poucos projetos levam os usuários HTTP tão a sério quanto o gRPC, já que o HTTP é uma prioridade baixa.

Devo usar gRPC?

APISIX usa etcd como centro de configuração. Desde a versão 3, o etcd migrou sua interface para o gRPC. No entanto, nenhum projeto no ecossistema OpenResty suporta gRPC, então o APISIX só pode chamar as APIs HTTP do etcd. As APIs HTTP do etcd são fornecidas por meio do gRPC-gateway. Essencialmente, o etcd executa um proxy HTTP para gRPC no lado do servidor, e então as requisições HTTP externas são convertidas em requisições gRPC por meio do gRPC-gateway. Após alguns anos de implantação desse método de comunicação, encontramos alguns problemas na interação entre a API HTTP e a API gRPC. Ter um gRPC-gateway não significa que o acesso HTTP seja perfeitamente suportado. Ainda existem diferenças sutis.

Aqui está uma lista de problemas relacionados que encontramos com o etcd nos últimos anos:

  1. gRPC-gateway desabilitado por padrão. Devido à negligência do mantenedor, a configuração padrão do etcd não habilita o gRPC-gateway em alguns projetos. Então, tivemos que adicionar instruções no documento para verificar se o etcd atual tem o gRPC-gateway habilitado. Veja https://github.com/apache/apisix/pull/2940.
  2. Limite de resposta de 4MB no gRPC. O etcd remove essa restrição no SDK que fornece, mas esqueceu de removê-la no gRPC-gateway. Descobrimos que o etcdctl oficial (baseado no SDK fornecido) funciona bem, mas o APISIX não. Veja https://github.com/etcd-io/etcd/issues/12576.
  3. Mesmo problema - desta vez com o número máximo de requisições para a mesma conexão. A implementação do HTTP2 em Go tem uma configuração MaxConcurrentStreams que controla o número de requisições simultâneas que um único cliente pode enviar, com um padrão de 250. Qual cliente normalmente enviaria mais de 250 requisições ao mesmo tempo? Então, o etcd sempre usou essa configuração. No entanto, o gRPC-gateway, o "cliente" que faz proxy de todas as requisições HTTP para a interface gRPC local, pode exceder esse limite. Veja https://github.com/etcd-io/etcd/issues/14185.
  4. Após habilitar mTLS no etcd, o etcd usa o mesmo certificado como certificado do servidor e do cliente, o certificado do servidor para o gRPC-gateway e o certificado do cliente quando o gRPC-gateway acessa a interface gRPC. Se a extensão de autenticação do servidor estiver habilitada no certificado, mas a extensão de autenticação do cliente não estiver, ocorrerá um erro na verificação do certificado. Mais uma vez, acessar diretamente com etcdctl funciona bem (já que o certificado não será usado como certificado do cliente nesse caso), mas o APISIX não. Veja https://github.com/etcd-io/etcd/issues/9785.
  5. Após habilitar mTLS, o etcd permite a configuração de políticas de segurança das informações do usuário nos certificados. Como mencionado acima, o gRPC-gateway usa um certificado de cliente fixo ao acessar a interface gRPC, em vez das informações do certificado usadas para acessar a interface HTTP no início. Assim, esse recurso não funcionará naturalmente, já que o certificado do cliente é fixo e não será alterado. Veja https://github.com/apache/apisix/issues/5608.

Podemos resumir os problemas em dois pontos:

  1. O gRPC-gateway (e talvez outras tentativas de converter HTTP para gRPC) não é uma solução mágica que resolve todos os problemas.
  2. Os desenvolvedores do etcd não dão ênfase suficiente ao método HTTP. E seu maior usuário, o Kubernetes, não usa esse recurso.

Não estamos falando dos problemas de um software específico aqui, o etcd é apenas um exemplo típico que usa gRPC. Todos os serviços que usam gRPC como seu framework RPC principal têm limitações semelhantes no suporte a HTTP.

Como o APISIX 3.0 resolve esse problema

Como diz o ditado, "se a montanha não vai a Maomé, Maomé vai à montanha". Se implementarmos um cliente gRPC no OpenResty, podemos nos comunicar diretamente com o serviço gRPC.

Considerando a carga de trabalho e a estabilidade, decidimos desenvolver com base na biblioteca gRPC comumente usada, em vez de reinventar a roda. Examinamos as seguintes bibliotecas gRPC:

  1. Serviço gRPC do NGINX. O NGINX não expõe o gRPC para usuários externos, nem mesmo uma API de alto nível. Se você quiser usá-lo, só pode copiar algumas funções de baixo nível e depois integrá-las em uma interface de alto nível. Integrá-las causará uma carga de trabalho adicional.
  2. A biblioteca oficial do gRPC para C++. Como nosso sistema é baseado no NGINX, pode ser um pouco complicado integrar bibliotecas C++. Além disso, a dependência dessa biblioteca é próxima de 2GB, o que será um grande desafio para a construção do APISIX.
  3. A implementação oficial do gRPC em Go. Go tem uma poderosa cadeia de ferramentas, e podemos construir projetos rapidamente. No entanto, é uma pena que o desempenho dessa implementação esteja longe da versão em C++. Então, olhamos para outra implementação em Go: https://github.com/bufbuild/connect-go/. Infelizmente, o desempenho desse projeto também não é melhor que a versão oficial.
  4. Implementação do gRPC em Rust. Essa biblioteca seria uma escolha natural para combinar gerenciamento de dependências e desempenho. Infelizmente, não estamos familiarizados com Rust e não apostaríamos nisso.

Considerando que as operações de um cliente gRPC são basicamente todas vinculadas a IO, o requisito de desempenho não é primordial. Após uma consideração cuidadosa, implementamos com base no Go-gRPC.

Para coordenar com o agendador de corrotinas do Lua, escrevemos um módulo C do NGINX: https://github.com/api7/grpc-client-nginx-module. Inicialmente, queríamos integrar o código Go nesse módulo C compilando-o em uma biblioteca estaticamente vinculada por meio do cgo. No entanto, descobrimos que, como o Go é uma aplicação multithread, o processo filho não herdará todas as threads do processo pai após o fork. Não há como adaptar isso à arquitetura de múltiplos processos master-worker do NGINX. Então, compilamos o código Go em uma biblioteca de link dinâmico (DLL) e a carregamos no processo worker em tempo de execução.

Implementamos um mecanismo de fila de tarefas para coordenar as corrotinas do Go com as corrotinas do Lua. Quando o código Lua inicia uma operação de IO gRPC, ele envia uma tarefa para o lado do Go e se suspende. Uma corrotina do Go executará essa tarefa, e o resultado da execução será escrito na fila. Um thread em segundo plano no lado do NGINX consome o resultado da execução da tarefa, reschedule a corrotina Lua correspondente e continua a execução do código Lua. Dessa forma, as operações de IO gRPC não são diferentes das operações de socket comuns aos olhos do código Lua.

Agora, a maior parte do trabalho do módulo C do NGINX está concluída. Tudo o que precisamos fazer é pegar o arquivo .proto do etcd (que define sua interface gRPC), modificá-lo e carregá-lo em Lua para obter o seguinte cliente etcd:

local gcli = require("resty.grpc")
assert(gcli.load("t/testdata/rpc.proto"))
local conn = assert(gcli.connect("127.0.0.1:2379"))
local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch",
                                        {create_request =
                                            {key = ngx.var.arg_key}},
                                        {timeout = 30000})
if not st then
    ngx.status = 503
    ngx.say(err)
    return
end
for i = 1, (ngx.var.arg_count or 10) do
    local res, err = st:recv()
    ngx.log(ngx.WARN, "received ", cjson.encode(res))
    if not res then
        ngx.status = 503
        ngx.say(err)
        break
    end
end

Essa implementação baseada em gRPC é melhor que o lua-resty-etcd, um projeto cliente HTTP do etcd com 1600 linhas de código apenas em Lua.

Claro, ainda estamos longe de substituir o lua-resty-etcd. Para se conectar totalmente ao etcd, o grpc-client-nginx-module também precisa completar as seguintes funções:

  1. Suporte a mTLS
  2. Suporte à configuração de metadados gRPC
  3. Suporte à configuração de parâmetros (por exemplo, MaxConcurrentStreams e MaxRecvMsgSize)
  4. Suporte a requisições de L4

Felizmente, estabelecemos a base, e suportar essas coisas é apenas uma questão de tempo.

O grpc-client-nginx-module será integrado ao APISIX 3.0, e então os usuários do APISIX poderão usar os métodos desse módulo no plugin do APISIX para se comunicar diretamente com serviços gRPC.

Com o suporte nativo ao gRPC, o APISIX terá uma experiência melhor com o etcd e abrirá as portas para possibilidades de recursos como verificação de saúde gRPC e relatórios de dados de telemetria aberta baseados em gRPC.

Estamos animados para ver mais recursos baseados em gRPC do APISIX no futuro!

Tags: