A medida que pasamos de una aplicación monolítica a una arquitectura de microservicios, enfrentamos nuevos desafíos.
En una aplicación monolítica, suele ser bastante fácil determinar en qué parte del sistema se produjo el error. Lo más probable es que el problema esté en el código del propio monolito o en la base de datos. Pero cuando empezamos a buscar un problema en una arquitectura de microservicios, ya no todo es tan obvio. Necesitamos encontrar la ruta completa que tomó la solicitud de principio a fin y seleccionarla entre cientos de microservicios. Además, muchos de ellos también tienen sus propias instalaciones de almacenamiento, lo que también puede causar errores lógicos, así como problemas de rendimiento y tolerancia a fallos.
He estado buscando durante mucho tiempo una herramienta que me ayudara a afrontar este tipo de problemas (escribí sobre ello en Habré:
El rastreo distribuido es una solución común al problema de encontrar errores en sistemas distribuidos. Pero, ¿qué pasa si este enfoque para recopilar información sobre las interacciones de la red aún no se ha implementado en el sistema o, peor aún, en parte del sistema ya funciona correctamente, pero en parte no, ya que no se ha agregado a los servicios antiguos? ? Para determinar la causa raíz exacta de un problema, es necesario tener una imagen completa de lo que está sucediendo en el sistema. Es especialmente importante comprender qué microservicios están involucrados en rutas clave y críticas para el negocio.
Aquí puede venir en nuestra ayuda el enfoque de malla de servicios, que se ocupará de toda la maquinaria para recopilar información de la red a un nivel inferior al que operan los propios servicios. Este enfoque nos permite interceptar todo el tráfico y analizarlo sobre la marcha. Además, las aplicaciones ni siquiera tienen que saber nada al respecto.
Enfoque de malla de servicios
La idea principal del enfoque de malla de servicios es agregar otra capa de infraestructura a la red, lo que nos permitirá hacer cualquier cosa con la interacción entre servicios. La mayoría de las implementaciones funcionan de la siguiente manera: a cada microservicio se agrega un contenedor adicional con un proxy transparente, a través del cual pasa todo el tráfico entrante y saliente del servicio. Y este es precisamente el lugar donde podemos equilibrar los clientes, aplicar políticas de seguridad, imponer restricciones en la cantidad de solicitudes y recopilar información importante sobre la interacción de los servicios en producción.
Решения
Ya existen varias implementaciones de este enfoque:
Como resultado, analizamos exactamente qué capacidades necesitábamos en este momento y decidimos que la razón principal por la que comenzamos a implementar dichas soluciones era la capacidad de recopilar información de seguimiento de todo el sistema de forma transparente. También queríamos tener control sobre la interacción de los servicios y realizar diversas manipulaciones con los encabezados que se transfieren entre servicios.
Como resultado, llegamos a nuestra decisión:
netramesh
Los principales objetivos de la nueva solución eran una baja sobrecarga de recursos y un alto rendimiento. Entre las características principales, inmediatamente quisimos poder enviar de forma transparente tramos de seguimiento a nuestro sistema Jaeger.
Hoy en día, la mayoría de las soluciones en la nube se implementan en Golang. Y, por supuesto, hay razones para ello. Escribir aplicaciones de red en Golang que funcionen de forma asincrónica con E/S y se escalen entre núcleos según sea necesario es conveniente y bastante simple. Y, lo que también es muy importante, el rendimiento es suficiente para solucionar este problema. Por eso también elegimos Golang.
Rendimiento
Hemos centrado nuestros esfuerzos en conseguir la máxima productividad. Para una solución que se implementa junto a cada instancia del servicio, se requiere un pequeño consumo de RAM y tiempo de CPU. Y, por supuesto, el retraso en la respuesta también debería ser pequeño.
Veamos qué resultados obtuvimos.
RAM
Netramesh consume ~10Mb sin tráfico y 50Mb como máximo con una carga de hasta 10000 RPS por instancia.
El proxy enviado de Istio siempre consume ~300 Mb en nuestros clústeres con miles de instancias. Esto no permite escalarlo a todo el clúster.
Con Netramesh obtuvimos una reducción de aproximadamente 10 veces en el consumo de memoria.
CPU
El uso de la CPU es relativamente igual bajo carga. Depende del número de solicitudes por unidad de tiempo al sidecar. Valores a 3000 solicitudes por segundo en pico:
Hay un punto más importante: Netramesh: una solución sin plano de control y sin carga no consume tiempo de CPU. Con Istio, los sidecars siempre actualizan los puntos finales del servicio. Como resultado, podemos ver esta imagen sin carga:
Usamos HTTP/1 para la comunicación entre servicios. El aumento en el tiempo de respuesta de Istio cuando se realiza proxy a través de Envoy fue de hasta 5 a 10 ms, lo cual es bastante para servicios que están listos para responder en un milisegundo. Con Netramesh este tiempo se ha reducido a 0.5-2 ms.
Escalabilidad
La pequeña cantidad de recursos que consume cada proxy permite colocarlo al lado de cada servicio. Netramesh fue creado intencionalmente sin un componente de plano de control para simplemente mantener cada sidecar liviano. A menudo, en las soluciones de malla de servicios, el plano de control distribuye información de descubrimiento de servicios a cada sidecar. Junto con esto viene información sobre tiempos de espera y configuraciones de equilibrio. Todo esto te permite hacer muchas cosas útiles, pero, desafortunadamente, aumenta el tamaño de los sidecars.
Descubrimiento de servicio
Netramesh no agrega ningún mecanismo adicional para el descubrimiento de servicios. Todo el tráfico se envía de forma transparente a través de netra sidecar.
Netramesh admite el protocolo de aplicación HTTP/1. Para definirlo se utiliza una lista configurable de puertos. Normalmente, el sistema tiene varios puertos a través de los cuales se produce la comunicación HTTP. Por ejemplo, usamos 80, 8890, 8080 para la interacción entre servicios y solicitudes externas. En este caso, se pueden configurar usando una variable de entorno. NETRA_HTTP_PORTS
.
Si utiliza Kubernetes como orquestador y su mecanismo de entidad de servicio para la comunicación dentro del clúster entre servicios, entonces el mecanismo sigue siendo exactamente el mismo. Primero, el microservicio obtiene una dirección IP de servicio utilizando kube-dns y abre una nueva conexión. Esta conexión se establece primero con el netra-sidecar local y todos los paquetes TCP llegan inicialmente a netra. A continuación, netra-sidecar establece una conexión con el destino original. NAT en la IP del pod en el nodo sigue siendo exactamente igual que sin netra.
Seguimiento distribuido y reenvío de contexto
Netramesh proporciona la funcionalidad necesaria para enviar tramos de seguimiento sobre interacciones HTTP. Netra-sidecar analiza el protocolo HTTP, mide los retrasos en las solicitudes y extrae la información necesaria de los encabezados HTTP. En última instancia, obtenemos todas las trazas en un único sistema Jaeger. Para una configuración detallada, también puede utilizar las variables de entorno proporcionadas por la biblioteca oficial.
Pero hay un problema. Hasta que los servicios generen y envíen un encabezado uber especial, no veremos tramos de seguimiento conectados en el sistema. Y esto es lo que necesitamos para encontrar rápidamente la causa de los problemas. Aquí nuevamente Netramesh tiene una solución. Los servidores proxy leen los encabezados HTTP y, si no contienen el ID de seguimiento de Uber, generan uno. Netramesh también almacena información sobre solicitudes entrantes y salientes en un sidecar y las compara enriqueciéndolas con los encabezados de solicitudes salientes necesarios. Todo lo que necesitas hacer en los servicios es enviar solo un encabezado. X-Request-Id
, que se puede configurar mediante una variable de entorno NETRA_HTTP_REQUEST_ID_HEADER_NAME
. Para controlar el tamaño del contexto en Netramesh, puede configurar las siguientes variables de entorno: NETRA_TRACING_CONTEXT_EXPIRATION_MILLISECONDS
(el tiempo durante el cual se almacenará el contexto) y NETRA_TRACING_CONTEXT_CLEANUP_INTERVAL
(frecuencia de limpieza de contexto).
También es posible combinar varias rutas en su sistema marcándolas con un token de sesión especial. Netra te permite instalar HTTP_HEADER_TAG_MAP
para convertir los encabezados HTTP en etiquetas de intervalo de seguimiento correspondientes. Esto puede resultar especialmente útil para realizar pruebas. Luego de pasar la prueba funcional, se puede ver qué parte del sistema fue afectada filtrando por la clave de sesión correspondiente.
Determinar el origen de la solicitud
Para determinar de dónde proviene la solicitud, puede utilizar la función de agregar automáticamente un encabezado con la fuente. Usando una variable de entorno NETRA_HTTP_X_SOURCE_HEADER_NAME
Puede especificar un nombre de encabezado que se instalará automáticamente. Mediante el uso NETRA_HTTP_X_SOURCE_VALUE
puede establecer el valor en el que se establecerá el encabezado X-Source para todas las solicitudes salientes.
Esto permite que la distribución de este útil encabezado se distribuya de manera uniforme por toda la red. Luego puedes usarlo en servicios y agregarlo a registros y métricas.
Enrutamiento de tráfico y elementos internos de Netramesh
Netramesh consta de dos componentes principales. El primero, netra-init, establece reglas de red para interceptar el tráfico. Él usa INBOUND_INTERCEPT_PORTS, OUTBOUND_INTERCEPT_PORTS
.
La herramienta también tiene una característica interesante: el enrutamiento probabilístico. Si utiliza Netramesh exclusivamente para recopilar tramos de seguimiento, en un entorno de producción puede ahorrar recursos y habilitar el enrutamiento probabilístico utilizando variables. NETRA_INBOUND_PROBABILITY
и NETRA_OUTBOUND_PROBABILITY
(de 0 a 1). El valor predeterminado es 1 (se intercepta todo el tráfico).
Después de una intercepción exitosa, netra sidecar acepta la nueva conexión y utiliza SO_ORIGINAL_DST
opción de socket para obtener el destino original. Luego, Netra abre una nueva conexión a la dirección IP original y establece una comunicación TCP bidireccional entre las partes, escuchando todo el tráfico que pasa. Si el puerto está definido como HTTP, Netra intenta analizarlo y rastrearlo. Si el análisis de HTTP falla, Netra recurre a TCP y transfiere los bytes de forma transparente.
Construyendo un gráfico de dependencia
Después de recibir una gran cantidad de información de seguimiento en Jaeger, quiero obtener un gráfico completo de las interacciones en el sistema. Pero si su sistema está bastante cargado y se acumulan miles de millones de tramos de seguimiento por día, agregarlos no se convierte en una tarea tan fácil. Hay una forma oficial de hacer esto:
Si está utilizando Elasticsearch para almacenar intervalos de seguimiento, puede utilizar
Cómo utilizar Netramesh
Netra se puede agregar fácilmente a cualquier servicio que ejecute cualquier orquestador. Puedes ver un ejemplo.
Por el momento, Netra no tiene la capacidad de implementar sidecars automáticamente en los servicios, pero hay planes para su implementación.
El futuro de Netramesh
Propósito principal
En el futuro, Netramesh admitirá otros protocolos de capa de aplicación además de HTTP. La ruta L7 estará disponible en un futuro próximo.
Utilice Netramesh si encuentra problemas similares y escríbanos con preguntas y sugerencias.
Fuente: habr.com