APISIX: Migrar la operación de etcd de HTTP a gRPC

Zexuan Luo

Zexuan Luo

February 10, 2023

Products

Limitaciones de las operaciones HTTP de Apache APISIX basadas en etcd

Cuando etcd estaba en la versión 2.x, la interfaz API que exponía era HTTP 1 (a partir de ahora nos referiremos a ella como HTTP). Después de que etcd se actualizó a la versión 3.x, cambió el protocolo de HTTP a gRPC. Para los usuarios que no admiten gRPC, etcd proporciona gRPC-Gateway para redirigir solicitudes HTTP como gRPC y acceder a las nuevas API de gRPC.

Cuando APISIX comenzó a usar etcd, utilizó la API v2 de etcd. En APISIX 2.0 (2020), actualizamos el requisito de etcd de la versión 2.x a 3.x. La compatibilidad de etcd con HTTP nos ahorró esfuerzo en la actualización de la versión. Solo necesitamos modificar el código en los métodos de llamada y el procesamiento de respuestas. Sin embargo, a lo largo de los años, también hemos encontrado algunos problemas relacionados con la API HTTP de etcd. Todavía existen algunas diferencias sutiles. Nos dimos cuenta de que tener un gRPC-gateway no significa que pueda admitir perfectamente el acceso HTTP.

Aquí hay una lista de problemas relacionados que hemos encontrado con etcd en los últimos años:

  1. gRPC-gateway deshabilitado por defecto. Debido a la negligencia del mantenedor, la configuración predeterminada de etcd no habilita gRPC-gateway en algunos proyectos. Por lo tanto, tuvimos que agregar instrucciones en el documento para verificar si el etcd actual tiene habilitado gRPC-gateway. Ver https://github.com/apache/apisix/pull/2940.
  2. Por defecto, gRPC limita las respuestas a 4MB. etcd elimina esta restricción en el SDK que proporciona, pero no 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.
  3. 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 que un solo cliente puede enviar simultáneamente, con un valor predeterminado de 250. ¿Qué cliente enviaría normalmente más de 250 solicitudes al mismo tiempo? Por lo tanto, etcd siempre ha usado esta configuración. Sin embargo, gRPC-gateway, el "cliente" que redirige todas las solicitudes HTTP a la interfaz gRPC local, puede exceder este límite. Ver https://github.com/etcd-io/etcd/issues/14185.
  4. Después de habilitar mTLS en etcd, etcd usa el mismo certificado tanto como certificado del servidor como certificado del cliente: el certificado del servidor para gRPC-gateway y el certificado del 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 del cliente en este caso), pero APISIX no. Ver https://github.com/etcd-io/etcd/issues/9785.
  5. 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 del cliente es fijo y no se cambiará. Ver https://github.com/apache/apisix/issues/5608.

Podemos resumir los problemas en dos puntos:

  1. gRPC-gateway (y quizás otros intentos de convertir HTTP a gRPC) no es una solución mágica que resuelva todos los problemas.
  2. Los desarrolladores de etcd no ponen suficiente énfasis en el método HTTP a gRPC. Y su mayor usuario, Kubernetes, no usa esta función.

Para resolver este problema, necesitamos usar etcd directamente a través de gRPC, para no tener que pasar por la ruta HTTP de gRPC-Gateway reservada para la compatibilidad.

Superando los desafíos de la migración a gRPC

Error en lua-protobuf

Nuestro primer problema durante el proceso de migración fue un error inesperado en una biblioteca de terceros. Como la mayoría de las aplicaciones de OpenResty, usamos lua-protobuf para decodificar/codificar protobuf.

Después de integrar el archivo proto de etcd, encontramos que ocasionalmente se producían fallos en el código Lua, reportando un error de "desbordamiento de tabla". Debido a que este fallo no se puede reproducir de manera confiable, nuestro primer instinto fue buscar un ejemplo mínimo reproducible. Curiosamente, si usas el archivo proto de etcd solo, no puedes reproducir el problema en absoluto. Este fallo parece ocurrir solo cuando APISIX está en ejecución.

Después de algún depuración, localicé el problema en lua-protobuf al analizar el campo oneof del archivo proto. lua-protobuf intentaba preasignar el tamaño de la tabla al analizar, y el tamaño asignado se calcula según un valor particular. Había una cierta probabilidad de que este valor fuera un número negativo. Luego, LuaJIT convertiría este número en un número positivo grande al asignar, lo que resultaría en un error de "desbordamiento de tabla". Informé el problema al autor y mantuvimos un fork con una solución interna.

El autor de lua-protobuf respondió rápidamente, proporcionando una solución al día siguiente y lanzando una nueva versión unos días después. Resultó que cuando lua-protobuf limpiaba los archivos proto que ya no se usaban, omitió limpiar algunos campos, lo que resultó en un número negativo irrazonable cuando oneof se procesaba posteriormente. El problema solo ocurría de vez en cuando, y por qué no se podía reproducir cuando se usaba el archivo proto de etcd solo, porque omitía los pasos de limpiar estos campos.

Alineación con el comportamiento HTTP

Durante el proceso de migración, descubrí que la API existente no devuelve exactamente el resultado de la ejecución, sino una respuesta HTTP con el estado de la respuesta y el cuerpo. Luego, el llamador necesita procesar la respuesta HTTP por sí mismo.

Si las respuestas fueran en gRPC, necesitarían envolverse con una capa de respuesta HTTP para alinearse con la lógica de procesamiento. De lo contrario, el llamador necesitaría modificar el código en múltiples lugares para adaptarse al nuevo formato de respuesta (gRPC). Especialmente considerando que las operaciones antiguas basadas en HTTP de etcd también deben ser compatibles simultáneamente.

Aunque agregar una capa adicional para ser compatible con la respuesta HTTP no es deseado, tenemos que trabajar en ello. Además de esto, también necesitamos hacer algún procesamiento en la respuesta gRPC. Por ejemplo, cuando no hay datos correspondientes, HTTP no devuelve ningún dato, pero gRPC devuelve una tabla vacía. También necesita ajustarse para alinearse con los comportamientos HTTP.

De conexión corta a conexión larga

En las operaciones basadas en HTTP de etcd, APISIX usa conexiones cortas, por lo que no es necesario considerar la gestión de conexiones. Todo lo que necesitamos hacer es iniciar una nueva conexión cada vez que la necesitemos y cerrarla cuando hayamos terminado.

Pero gRPC no puede hacer esto. Uno de los principales propósitos de migrar a gRPC es lograr la multiplexación, lo cual no se puede lograr si se crea una nueva conexión gRPC para cada operación. Aquí debemos agradecer a gRPC-go, por su capacidad de gestión de conexiones integrada, que puede reconectar automáticamente una vez que la conexión se interrumpe. Por lo tanto, podemos usar gRPC-go para reutilizar la conexión. Y solo se deben considerar los requisitos comerciales en el nivel de APISIX.

Las operaciones de etcd de APISIX se pueden dividir en dos categorías: una son las operaciones CRUD (crear, eliminar, modificar, consultar) en los datos de etcd; la otra es sincronizar la configuración desde el plano de control. Aunque teóricamente estas dos operaciones de etcd podrían compartir la misma conexión gRPC, decidimos separarlas en dos conexiones por el bien de la separación de responsabilidades. Para la conexión de operaciones CRUD, dado que APISIX necesita tratarse por separado al inicio y después del inicio, se agrega una declaración if al obtener una nueva conexión. Si hay una discrepancia (es decir, la conexión actual se creó al inicio mientras necesitamos una conexión después del inicio), cerraremos la conexión actual y crearemos una nueva. He desarrollado un nuevo método de sincronización para la sincronización de la configuración, de modo que cada recurso use un stream bajo la conexión existente para observar etcd.

Beneficios de migrar a gRPC

Un beneficio obvio después de migrar a gRPC es que el número de conexiones necesarias para operar etcd se reduce enormemente. Al operar etcd a través de HTTP, APISIX solo podía usar conexiones cortas. Y al sincronizar la configuración, cada recurso tendría una conexión separada.

Después de cambiar a gRPC, podríamos usar la función de multiplexación de gRPC, y cada recurso solo usa un solo stream en lugar de una conexión completa. De esta manera, el número de conexiones ya no aumenta con el número de recursos. Considerando que el desarrollo posterior de APISIX introducirá más tipos de recursos, por ejemplo, la última versión 3.1 ha agregado secrets, la reducción en el número de conexiones al usar gRPC será más significativa.

Al usar gRPC para la sincronización, cada proceso tiene solo una (dos, si el subsistema de stream está habilitado) conexión para la sincronización de la configuración. En la siguiente figura, podemos ver que los dos procesos tienen cuatro conexiones, dos de las cuales son para la sincronización de la configuración, la API Admin usa una conexión, y la conexión restante es para que el agente privilegiado informe la información del servidor.

gRPC usa muchas menos conexiones

Para comparar, la siguiente figura muestra las 22 conexiones necesarias para usar el método original de sincronización de configuración mientras se mantienen los demás parámetros sin cambios. Además, estas conexiones son conexiones cortas.

demasiadas conexiones

La única diferencia entre estas dos configuraciones es si gRPC está habilitado para las operaciones de etcd:

  etcd:
    use_grpc: true
    host:
      - "http://127.0.0.1:2379"
    prefix: "/apisix"
    ...

Además de reducir el número de conexiones, usar gRPC para acceder a etcd directamente en lugar de gRPC-gateway puede resolver una serie de problemas limitados arquitectónicamente, como la autenticación mTLS mencionada al principio del artículo. También habrá menos problemas después de usar gRPC, porque Kubernetes usa gRPC para operar etcd. Si hay un problema, será descubierto por la comunidad de Kubernetes.

Por supuesto, dado que el método gRPC todavía es relativamente nuevo, APISIX inevitablemente tendrá algunos nuevos problemas al operar etcd a través de gRPC. Actualmente, el método predeterminado sigue siendo usar el método original basado en HTTP para operar etcd. Los usuarios tienen la opción de configurar use_grpc bajo etcd a true en config.yaml por sí mismos. Puedes probar si el método gRPC es mejor. También recopilaremos continuamente comentarios de varias fuentes para mejorar la operación de etcd basada en gRPC. Cuando consideremos que el enfoque gRPC es lo suficientemente maduro, lo convertiremos en el enfoque predeterminado.

Para maximizar APISIX, necesitas API7

Te encanta el rendimiento de Apache APISIX, no las cargas de gestionarlo. Puedes centrarte en tu negocio principal sin preocuparte por configurar, mantener y actualizar.

Nuestro equipo está compuesto por creadores y contribuidores de Apache APISIX, mantenedores principales de OpenResty y NGINX, miembros de Kubernetes y expertos en infraestructura en la nube. Obtienes a las mejores personas detrás de escena.

¿Quieres acelerar tu desarrollo con confianza? Para maximizar el soporte de APISIX, necesitas API7. ¡Proporcionamos soporte en profundidad para APISIX y soluciones de gestión de API basadas en tus necesidades!

Contáctanos ahora: https://api7.ai/contact.

Tags: