¿Qué es gRPC? ¿Cómo trabajar con APISIX?
September 28, 2022
¿Qué es gRPC?
gRPC es un marco de RPC de código abierto desarrollado por Google que tiene como objetivo unificar la forma en que los servicios se comunican. El marco utiliza HTTP/2 como su protocolo de transferencia y Protocol Buffers como el lenguaje de descripción de interfaces. Puede generar automáticamente código para llamadas entre servicios.
Dominio de gRPC
gRPC se ha convertido en el estándar de los marcos de RPC debido a la excepcional influencia de Google en los desarrolladores y los entornos nativos de la nube.
¿Quieres invocar funciones de etcd? ¡gRPC!
¿Quieres enviar datos de OpenCensus? ¡gRPC!
¿Quieres usar RPC en un microservicio implementado en Go? ¡gRPC!
El dominio de gRPC es tan fuerte que si no eliges gRPC como tu marco de RPC, tendrás que dar una razón sólida para no hacerlo. De lo contrario, siempre habrá alguien que pregunte: ¿por qué no eliges el gRPC, que es el estándar? Incluso Alibaba, que ha promovido vigorosamente su marco de RPC Dubbo, ha modificado drásticamente el diseño del protocolo en la última versión de Dubbo 3, cambiándolo a una variante de gRPC compatible tanto con gRPC como con Dubbo 2. De hecho, en lugar de decir que Dubbo 3 es una actualización de Dubbo 2, es más como un reconocimiento de la supremacía de gRPC.
Muchos servicios que ofrecen gRPC también proporcionan interfaces HTTP correspondientes, pero tales interfaces a menudo son solo para fines de compatibilidad. La versión de gRPC ofrece una experiencia de usuario mucho mejor. Si puedes acceder a través de gRPC, puedes importar directamente el SDK correspondiente. Si solo puedes usar las API HTTP normales, generalmente te dirigirán a una página de documentación y tendrás que implementar las operaciones HTTP correspondientes tú mismo. Aunque el acceso HTTP puede generar el SDK correspondiente a través de la especificación OpenAPI, pocos proyectos toman a los usuarios de HTTP tan en serio como gRPC, ya que HTTP es una prioridad baja.
¿Debería usar gRPC?
APISIX utiliza etcd como centro de configuración. Desde la versión 3, etcd ha migrado su interfaz a gRPC. Sin embargo, ningún proyecto en el ecosistema de OpenResty admite gRPC, por lo que APISIX solo puede llamar a las API HTTP de etcd. Las API HTTP de etcd se proporcionan a través de gRPC-gateway. En esencia, etcd ejecuta un proxy de HTTP a gRPC en su lado del servidor, y luego las solicitudes HTTP externas se convierten en solicitudes gRPC a través de gRPC-gateway. Después de implementar este método de comunicación durante algunos años, hemos encontrado algunos problemas en la interacción entre la API HTTP y la API gRPC. Tener un gRPC-gateway no significa que el acceso HTTP esté perfectamente soportado. Todavía hay diferencias sutiles.
Aquí hay una lista de problemas relacionados que hemos encontrado con etcd en los últimos años:
- gRPC-gateway deshabilitado por defecto. Debido a la negligencia del mantenedor, la configuración predeterminada de etcd no habilita gRPC-gateway en algunos proyectos. Así que tuvimos que agregar instrucciones en el documento para verificar si el etcd actual tiene gRPC-gateway habilitado. Ver https://github.com/apache/apisix/pull/2940.
- Por defecto, gRPC limita las respuestas a 4MB. etcd elimina esta restricción en el SDK que proporciona, pero olvidó eliminarla en gRPC-gateway. Resulta que el etcdctl oficial (construido sobre el SDK que proporciona) funciona bien, pero APISIX no. Ver https://github.com/etcd-io/etcd/issues/12576.
- El mismo problema, esta vez con el número máximo de solicitudes para la misma conexión. La implementación de HTTP2 de Go tiene una configuración
MaxConcurrentStreams
que controla el número de solicitudes simultáneas que un solo cliente puede enviar, con un valor predeterminado de 250. ¿Qué cliente enviaría normalmente más de 250 solicitudes al mismo tiempo? Así que etcd siempre ha usado esta configuración. Sin embargo, gRPC-gateway, el "cliente" que proxy todas las solicitudes HTTP a la interfaz gRPC local, puede exceder este límite. Ver https://github.com/etcd-io/etcd/issues/14185. - Después de que etcd habilita mTLS, etcd usa el mismo certificado tanto como certificado de servidor como certificado de cliente, el certificado de servidor para gRPC-gateway y el certificado de cliente cuando gRPC-gateway accede a la interfaz gRPC. Si la extensión de autenticación del servidor está habilitada en el certificado, pero la extensión de autenticación del cliente no está habilitada, se producirá un error en la verificación del certificado. Una vez más, acceder directamente con etcdctl funciona bien (ya que el certificado no se usará como certificado de cliente en este caso), pero APISIX no. Ver https://github.com/etcd-io/etcd/issues/9785.
- Después de habilitar mTLS, etcd permite la configuración de políticas de seguridad de la información del usuario en los certificados. Como se mencionó anteriormente, gRPC-gateway usa un certificado de cliente fijo cuando accede a la interfaz gRPC en lugar de la información del certificado utilizada para acceder a la interfaz HTTP al principio. Por lo tanto, esta característica no funcionará naturalmente ya que el certificado de cliente es fijo y no se cambiará. Ver https://github.com/apache/apisix/issues/5608.
Podemos resumir los problemas en dos puntos:
- gRPC-gateway (y quizás otros intentos de convertir HTTP a gRPC) no es una bala de plata que resuelva todos los problemas.
- Los desarrolladores de etcd no ponen suficiente énfasis en el método HTTP. Y su mayor usuario, Kubernetes, no usa esta característica.
No estamos hablando de los problemas de un software específico aquí, etcd es solo un ejemplo típico que usa gRPC. Todos los servicios que usan gRPC como su marco de RPC principal tienen limitaciones similares en su soporte para HTTP.
¿Cómo resuelve APISIX 3.0 este problema?
Como dice el refrán, "si la montaña no viene a Mahoma, Mahoma debe ir a la montaña". Si implementamos un cliente gRPC bajo OpenResty, podemos comunicarnos directamente con el servicio gRPC.
Considerando la carga de trabajo y la estabilidad, decidimos desarrollar basándonos en la biblioteca gRPC comúnmente utilizada en lugar de reinventar la rueda. Examinamos las siguientes bibliotecas gRPC:
- El servicio gRPC de NGINX. NGINX no expone gRPC a usuarios externos, ni siquiera una API de alto nivel. Si quieres usarlo, solo puedes copiar algunas funciones de bajo nivel y luego integrarlas en una interfaz de alto nivel. Integrarlas causará cargas de trabajo adicionales.
- La biblioteca oficial de gRPC para C++. Dado que nuestro sistema se basa en NGINX, puede ser un poco complicado integrar bibliotecas de C++. Además, la dependencia de esta biblioteca es cercana a 2GB, lo que será un gran desafío para la construcción de APISIX.
- La implementación oficial de gRPC en Go. Go tiene una potente cadena de herramientas, y podemos construir proyectos rápidamente en él. Sin embargo, es una pena que el rendimiento de esta implementación esté lejos de la versión de C++. Así que miramos otra implementación en Go: https://github.com/bufbuild/connect-go/. Desafortunadamente, el rendimiento de este proyecto tampoco es mejor que la versión oficial.
- Implementación de la biblioteca gRPC en Rust. Esta biblioteca sería una elección natural para combinar la gestión de dependencias y el rendimiento. Desafortunadamente, no estamos familiarizados con Rust y no apostaríamos por ella.
Considerando que las operaciones de un cliente gRPC están básicamente todas limitadas por IO, el requisito de rendimiento no es primordial. Después de una cuidadosa consideración, lo implementamos basándonos en Go-gRPC.
Para coordinar con el planificador de corrutinas de Lua, escribimos un módulo C de NGINX: https://github.com/api7/grpc-client-nginx-module. Al principio, queríamos integrar el código de Go en este módulo C compilándolo en una biblioteca enlazada estáticamente a través de cgo. Sin embargo, descubrimos que, dado que Go es una aplicación multi-hilo, el proceso hijo no heredará todos los hilos del proceso padre después de bifurcarse. No hay forma de adaptarse a la arquitectura multi-proceso master-worker de NGINX. Así que compilamos el código de Go en una biblioteca de enlace dinámico (DLL) y luego la cargamos en el proceso worker en tiempo de ejecución.
Implementamos un mecanismo de cola de tareas para coordinar las corrutinas de Go con las corrutinas de Lua. Cuando el código Lua inicia una operación de IO gRPC, envía una tarea al lado de Go y se suspende. Una corrutina de Go ejecutará esta tarea, y el resultado de la ejecución se escribirá en la cola. Un hilo en segundo plano en el lado de NGINX consume el resultado de la ejecución de la tarea, reprograma la corrutina Lua correspondiente y continúa ejecutando el código Lua. De esta manera, las operaciones de IO gRPC no son diferentes de las operaciones de socket ordinarias en los ojos del código Lua.
Ahora, la mayor parte del trabajo del módulo C de NGINX está hecho. Todo lo que necesitamos hacer es tomar el archivo .proto
de etcd (que define su interfaz gRPC), modificarlo y luego cargar el archivo en Lua para obtener el siguiente 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
Esta implementación basada en gRPC es mejor que lua-resty-etcd, un proyecto de cliente etcd-HTTP con 1600 líneas de código solo en Lua.
Por supuesto, todavía estamos lejos de reemplazar lua-resty-etcd. Para conectarse completamente con etcd, grpc-client-nginx-module también necesita completar las siguientes funciones:
- Soporte para mTLS
- Soporte para la configuración de metadatos gRPC
- Soporte para configuraciones de parámetros (por ejemplo,
MaxConcurrentStreams
yMaxRecvMsgSize
) - Soporte para solicitudes desde L4
Afortunadamente, hemos sentado las bases, y apoyar estas cosas es solo cuestión de tiempo.
grpc-client-nginx-module se integrará en APISIX 3.0, luego los usuarios de APISIX podrán usar los métodos de este módulo en el plugin de APISIX para comunicarse directamente con los servicios gRPC.
Con el soporte nativo para gRPC, APISIX obtendrá una mejor experiencia con etcd y abre la puerta a posibilidades para características como la verificación de salud gRPC y la generación de informes de datos de telemetría abierta basados en gRPC.
¡Estamos emocionados de ver más características basadas en gRPC de APISIX en el futuro!