Contenedores, microservicios y mallas de servicios

En Internet montón artículos о mallas de servicio (malla de servicio), y aquí hay otro. ¡Hurra! ¿Pero por qué? Luego, quiero expresar mi opinión de que las mallas de servicios habrían sido mejores hace 10 años, antes de la llegada de plataformas de contenedores como Docker y Kubernetes. No digo que mi punto de vista sea mejor o peor que otros, pero como las mallas de servicios son animales bastante complejos, múltiples puntos de vista ayudarán a comprenderlas mejor.

Hablaré de la plataforma dotCloud, que se basó en más de cien microservicios y admitió miles de aplicaciones en contenedores. Explicaré los desafíos que encontramos al desarrollarlo y lanzarlo, y cómo las mallas de servicios podrían (o no) ayudar.

historia de la nube de puntos

Ya escribí sobre la historia de dotCloud y la elección de la arquitectura para esta plataforma, pero no hablé mucho sobre la capa de red. Si no quieres leer ultimo articulo En cuanto a dotCloud, esto es lo esencial: es una plataforma como servicio PaaS que permite a los clientes ejecutar una amplia gama de aplicaciones (Java, PHP, Python...), con soporte para una amplia gama de servicios de datos ( MongoDB, MySQL, Redis...) y un flujo de trabajo como Heroku: usted carga su código en la plataforma, ella crea imágenes de contenedores y las implementa.

Te contaré cómo se envió el tráfico a la plataforma dotCloud. No porque fuera particularmente genial (¡aunque el sistema funcionó bien para su época!), sino principalmente porque con la ayuda de herramientas modernas, un equipo modesto puede implementar fácilmente un diseño de este tipo en poco tiempo si necesita una forma de enrutar tráfico entre un grupo de microservicios o un grupo de aplicaciones. Por lo tanto, puede comparar opciones: qué sucede si desarrolla todo usted mismo o utiliza una red de servicios existente. Elección estándar: haz el tuyo propio o cómpralo.

Enrutamiento de tráfico para aplicaciones alojadas

Las aplicaciones en dotCloud pueden exponer puntos finales HTTP y TCP.

Puntos finales HTTP agregado dinámicamente a la configuración del clúster del balanceador de carga hipache. Esto es similar a lo que están haciendo los recursos hoy. Ingreso en Kubernetes y un balanceador de carga como Traefik.

Los clientes se conectan a puntos finales HTTP a través de los dominios apropiados, siempre que el nombre de dominio apunte a los balanceadores de carga de dotCloud. Nada especial.

Puntos finales TCP asociado con un número de puerto, que luego se pasa a todos los contenedores en esa pila a través de variables de entorno.

Los clientes pueden conectarse a puntos finales TCP utilizando el nombre de host apropiado (algo así como gateway-X.dotcloud.com) y el número de puerto.

Este nombre de host se resuelve en el clúster de servidores "nats" (no relacionado con NATS) que enrutará las conexiones TCP entrantes al contenedor correcto (o, en el caso de servicios con equilibrio de carga, a los contenedores correctos).

Si está familiarizado con Kubernetes, esto probablemente le recordará los servicios Puerto de nodo.

No había servicios equivalentes en la plataforma dotCloud IP de clúster: para simplificar, el acceso a los servicios se produjo de la misma manera tanto desde dentro como desde fuera de la plataforma.

Todo estaba organizado de forma bastante sencilla: las implementaciones originales de las redes de enrutamiento HTTP y TCP probablemente contaban con sólo unos pocos cientos de líneas de Python. Algoritmos simples (yo diría, ingenuos) que se han ido perfeccionando con el crecimiento de la plataforma y la aparición de requisitos adicionales.

No fue necesaria una refactorización extensa del código existente. En particular, Aplicaciones de 12 factores Puede utilizar directamente la dirección obtenida a través de variables de entorno.

¿En qué se diferencia esto de una red de servicios moderna?

Limitado visibilidad. No teníamos ninguna métrica para la malla de enrutamiento TCP. Cuando se trata de enrutamiento HTTP, las versiones más recientes tienen métricas HTTP detalladas con códigos de error y tiempos de respuesta, pero las mallas de servicios modernas van aún más allá y brindan integración con sistemas de recopilación de métricas como Prometheus, por ejemplo.

La visibilidad es importante no sólo desde un punto de vista operativo (para ayudar a solucionar problemas), sino también cuando se lanzan nuevas funciones. hablando de seguridad despliegue azul-verde и despliegue de canarios.

Eficiencia de enrutamiento también es limitado. En la malla de enrutamiento de dotCloud, todo el tráfico tenía que pasar a través de un grupo de nodos de enrutamiento dedicados. Esto significó cruzar potencialmente múltiples límites AZ (zona de disponibilidad) y un aumento significativo en la latencia. Recuerdo un código de solución de problemas que realizaba más de cien consultas SQL por página y abría una nueva conexión al servidor SQL para cada consulta. Cuando se ejecuta localmente, la página se carga instantáneamente, pero en dotCloud tarda unos segundos en cargarse porque cada conexión TCP (y la consulta SQL posterior) tarda decenas de milisegundos. En este caso particular, las conexiones persistentes solucionaron el problema.

Las redes de servicios modernas son mejores para abordar estos problemas. En primer lugar, comprueban que las conexiones estén enrutadas. en fuente. El flujo lógico es el mismo: клиент → меш → сервис, pero ahora la malla funciona localmente y no en nodos remotos, por lo que la conexión клиент → меш es local y muy rápido (microsegundos en lugar de milisegundos).

Las mallas de servicios modernas también implementan algoritmos de equilibrio de carga más inteligentes. Al monitorear el estado de los backends, pueden enviar más tráfico a backends más rápidos, lo que resulta en un mejor rendimiento general.

seguridad también es mejor. La malla de enrutamiento dotCloud se ejecutó completamente en EC2 Classic y no cifró el tráfico (bajo el supuesto de que si alguien logró poner un rastreador en el tráfico de la red EC2, ya está en un gran problema). Las redes de servicios modernas protegen de forma transparente todo nuestro tráfico, por ejemplo, con autenticación TLS mutua y posterior cifrado.

Enrutamiento de tráfico para servicios de plataforma

Bien, hemos hablado del tráfico entre aplicaciones, pero ¿qué pasa con la plataforma dotCloud en sí?

La plataforma en sí constaba de unos cien microservicios responsables de diversas funciones. Algunos aceptaban solicitudes de otros y otros eran trabajadores en segundo plano que se conectaban a otros servicios pero no aceptaban conexiones ellos mismos. En cualquier caso, cada servicio debe conocer los puntos finales de las direcciones a las que necesita conectarse.

Muchos servicios de alto nivel pueden utilizar la malla de enrutamiento descrita anteriormente. De hecho, muchos de los más de XNUMX microservicios de dotCloud se han implementado como aplicaciones normales en la propia plataforma dotCloud. Pero un pequeño número de servicios de bajo nivel (en particular, aquellos que implementan esta malla de enrutamiento) necesitaban algo más simple, con menos dependencias (porque no podían depender de sí mismos para funcionar: el viejo problema del huevo y la gallina).

Estos servicios esenciales de bajo nivel se implementaron ejecutando contenedores directamente en algunos nodos clave. Al mismo tiempo, los servicios estándar de la plataforma no estaban involucrados: enlazador, programador y corredor. Si lo comparamos con las modernas plataformas de contenedores, es como lanzar un avión de control con docker run directamente en los nodos, en lugar de delegar la tarea a Kubernetes. Es bastante similar en concepto. módulos estáticos (pods), que utiliza kubeadm o cubo de arranque al iniciar un clúster independiente.

Estos servicios fueron expuestos de una manera simple y cruda: un archivo YAML enumeraba sus nombres y direcciones; y cada cliente tuvo que tomar una copia de este archivo YAML para su implementación.

Por un lado, esto es extremadamente confiable porque no requiere el soporte de un almacén de clave/valor externo, como Zookeeper (recuerde, etcd o Consul no existían en ese momento). Por otro lado, dificultó el movimiento de servicios. Cada vez que se realizaba un movimiento, todos los clientes tenían que obtener un archivo YAML actualizado (y potencialmente recargarlo). ¡No muy cómodo!

Posteriormente, comenzamos a implementar un nuevo esquema, donde cada cliente se conectaba a un servidor proxy local. En lugar de la dirección y el puerto, sólo necesita conocer el número de puerto del servicio y conectarse a través de localhost. El proxy local maneja esta conexión y la reenvía al servidor real. Ahora, al mover el backend a otra máquina o escalar, en lugar de actualizar todos los clientes, solo es necesario actualizar todos estos servidores proxy locales; y ya no es necesario reiniciar.

(También se planeó encapsular el tráfico en conexiones TLS e instalar otro servidor proxy en el lado receptor, así como verificar los certificados TLS sin la participación del servicio receptor, que está configurado para aceptar conexiones solo en localhost. Más sobre esto más adelante).

Esto es muy similar a pila inteligente de Airbnb, pero la diferencia significativa es que SmartStack se implementa y se implementa en producción, mientras que el sistema de enrutamiento interno de dotCloud quedó bloqueado cuando dotCloud se convirtió en Docker.

Personalmente considero a SmartStack uno de los predecesores de sistemas como Istio, Linkerd y Consul Connect porque todos siguen el mismo patrón:

  • Ejecute un proxy en cada nodo.
  • Los clientes se conectan al proxy.
  • El plano de control actualiza la configuración del proxy cuando cambian los backends.
  • … ¡Ganancia!

Implementación de malla de servicios moderna

Si necesitamos implementar una red similar hoy, podemos usar principios similares. Por ejemplo, configure una zona DNS interna asignando nombres de servicios a direcciones en el espacio 127.0.0.0/8. Luego ejecute HAProxy en cada nodo del clúster, aceptando conexiones en cada dirección de servicio (en esa subred 127.0.0.0/8) y redirigir/equilibrar la carga a los backends apropiados. La configuración de HAProxy se puede gestionar confidencia, lo que le permite almacenar información de backend en etcd o Consul y enviar automáticamente la configuración actualizada a HAProxy cuando sea necesario.

¡Así funciona Istio! Pero con algunas diferencias:

  • Usos Apoderado del enviado en lugar de HAProxy.
  • Guarda la configuración del backend a través de la API de Kubernetes en lugar de etcd o Consul.
  • A los servicios se les asignan direcciones en la subred interna (direcciones IP de Kubernetes Cluster) en lugar de 127.0.0.0/8.
  • Tiene un componente adicional (Citadel) para agregar autenticación TLS mutua entre el cliente y los servidores.
  • Admite nuevas funciones como interrupción de circuitos, seguimiento distribuido, implementación canary, etc.

Echemos un vistazo rápido a algunas de las diferencias.

Apoderado del enviado

Envoy Proxy fue escrito por Lyft [el competidor de Uber en el mercado de taxis - aprox. por.]. Es similar en muchos aspectos a otros proxies (por ejemplo, HAProxy, Nginx, Traefik...), pero Lyft escribió el suyo propio porque necesitaba características que otros proxies no tienen y parecía más sensato crear uno nuevo en lugar de ampliarlo. uno existente.

Envoy se puede utilizar solo. Si tengo un servicio específico que necesita conectarse a otros servicios, puedo configurarlo para que se conecte a Envoy y luego configurar y reconfigurar dinámicamente Envoy con la ubicación de otros servicios, mientras obtengo muchos extras excelentes como visibilidad. En lugar de una biblioteca de cliente personalizada o una inyección en el código de seguimiento de llamadas, enviamos tráfico a Envoy y este recopila métricas por nosotros.

Pero Envoy también puede trabajar como plano de datos (plano de datos) para la malla de servicios. Esto significa que ahora para esta malla de servicios, Envoy está configurado plano de control (plano de control).

Plano de control

En el plano de control, Istio se basa en la API de Kubernetes. Esto no es muy diferente de usar confd., que depende de etcd o Consul para buscar un conjunto de claves en el almacén de datos. Istio analiza un conjunto de recursos de Kubernetes a través de la API de Kubernetes.

Entre esto y luego: Personalmente encontré esto útil Descripción de la API de Kubernetesque dice:

El servidor API de Kubernetes es un "servidor estúpido" que ofrece almacenamiento, control de versiones, validación, actualización y semántica de recursos API.

Istio está diseñado para funcionar con Kubernetes; y si desea usarlo fuera de Kubernetes, debe iniciar una instancia del servidor API de Kubernetes (y el servicio auxiliar etcd).

Direcciones de servicio

Istio depende de las direcciones ClusterIP que asigna Kubernetes, por lo que los servicios de Istio obtienen una dirección interna (no en el rango 127.0.0.0/8).

Kube-proxy intercepta el tráfico a una dirección ClusterIP para un servicio específico en un clúster de Kubernetes sin Istio y lo envía al back-end del proxy. Si está interesado en los detalles técnicos, kube-proxy configura reglas de iptables (o balanceadores de carga IPVS, según cómo esté configurado) para reescribir las direcciones IP de destino de las conexiones que van a la dirección ClusterIP.

Una vez que Istio está instalado en un clúster de Kubernetes, nada cambia hasta que se habilita explícitamente para un consumidor determinado, o incluso para todo el espacio de nombres, mediante la introducción de un contenedor. sidecar a cápsulas personalizadas. Este contenedor iniciará una instancia de Envoy y configurará un conjunto de reglas de iptables para interceptar el tráfico que va a otros servicios y redirigir ese tráfico a Envoy.

Cuando se integra con Kubernetes DNS, esto significa que nuestro código puede conectarse por nombre de servicio y todo "simplemente funciona". En otras palabras, nuestro código emite consultas como http://api/v1/users/4242, entonces api resolver la solicitud de 10.97.105.48, las reglas de iptables interceptan conexiones desde 10.97.105.48 y las redirigen al proxy Envoy local, que reenviará la solicitud al backend API real. ¡Uf!

Adornos adicionales

Istio también proporciona cifrado y autenticación de un extremo a otro a través de mTLS (TLS mutuo). El componente llamado Ciudadela.

También hay un componente Mezclador, que Envoy puede solicitar para cada solicitud para tomar una decisión especial sobre esa solicitud dependiendo de varios factores como encabezados, carga de backend, etc... (no te preocupes: hay muchas herramientas para mantener Mixer funcionando, e incluso si falla, Envoy seguirá funcionando como apoderado).

Y, por supuesto, mencionamos la visibilidad: Envoy recopila una gran cantidad de métricas al mismo tiempo que proporciona seguimiento distribuido. En una arquitectura de microservicios, si una única solicitud de API debe pasar por los microservicios A, B, C y D, al iniciar sesión, el rastreo distribuido agregará un identificador único a la solicitud y almacenará este identificador a través de subsolicitudes a todos estos microservicios, lo que permitirá para capturar todas las llamadas relacionadas, sus retrasos, etc.

Desarrollar o comprar

Istio tiene fama de ser un sistema complejo. Por el contrario, construir la malla de enrutamiento que describí al principio de esta publicación es relativamente fácil con las herramientas existentes. Entonces, ¿tiene sentido crear su propia red de servicios?

Si tenemos necesidades modestas (no necesitamos visibilidad, un disyuntor y otras sutilezas), entonces se nos ocurre desarrollar nuestra propia herramienta. Pero si usamos Kubernetes, es posible que ni siquiera sea necesario porque Kubernetes ya proporciona las herramientas básicas para el descubrimiento de servicios y el equilibrio de carga.

Pero si tenemos requisitos avanzados, entonces "comprar" una malla de servicios parece una opción mucho mejor. (Esto no siempre es una "compra", ya que Istio es de código abierto, pero aún necesitamos invertir tiempo de ingeniería para comprenderlo, implementarlo y administrarlo).

¿Qué elegir: Istio, Linkerd o Consul Connect?

Hasta ahora sólo hemos hablado de Istio, pero no es la única malla de servicios. La alternativa popular es Linkerd, pero hay más Conexión Cónsul.

¿Qué elegir?

Para ser honesto, no lo sé. Por el momento no me considero lo suficientemente competente para responder a esta pregunta. Hay algunos interesante artículos con una comparación de estas herramientas e incluso puntos de referencia.

Un enfoque prometedor es utilizar una herramienta como supergloo. Implementa una capa de abstracción para simplificar y unificar las API proporcionadas por las mallas de servicios. En lugar de aprender las API específicas (y, en mi opinión, relativamente complejas) de varias mallas de servicios, podemos usar las construcciones SuperGloo más simples y cambiar fácilmente de una a otra, como si tuviéramos un formato de configuración intermedio que describiera las interfaces HTTP y los backends. capaz de generar la configuración real para Nginx, HAProxy, Traefik, Apache…

Jugué un poco con Istio y SuperGloo, y en el próximo artículo quiero mostrar cómo agregar Istio o Linkerd a un clúster existente usando SuperGloo, y cómo este último hará su trabajo, es decir, te permitirá cambiar de una malla de servicio a otra sin reescribir configuraciones.

Fuente: habr.com

Añadir un comentario