Hay
toneladas de artículos en el servicio de malla
en Internet, y aquí hay otro. ¡Hurra! Pero por que? Entonces, lo que quiero expresar mi opinión es que sería mejor que aparecieran mallas de servicio hace 10 años, antes de la aparición de plataformas de contenedores como Docker y Kubernetes. No afirmo que mi punto de vista sea mejor o peor que otros, pero como las mallas de servicio son animales bastante complejos, la multiplicidad de puntos de vista ayudará a comprenderlos mejor.
Hablaré sobre la plataforma dotCloud, que se creó en más de cien microservicios y admitió miles de aplicaciones en contenedores. Explicaré los problemas que encontramos durante su desarrollo y lanzamiento, y cómo las mallas de servicio podrían ayudar (o no).
Historia de dotCloud
Ya escribí sobre la historia de dotCloud y la elección de la arquitectura para esta plataforma, pero hablé un poco sobre el nivel de red. Si no desea sumergirse en la lectura del
artículo anterior sobre dotCloud, aquí hay un breve resumen: es una plataforma como servicio PaaS que permite a los clientes lanzar 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: carga su código en la plataforma, crea imágenes de contenedores y los implementa.
Te diré cómo se dirigió el tráfico a la plataforma dotCloud. No porque fuera especialmente 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 dicho diseño en poco tiempo si necesitan una forma de enrutar el tráfico entre un montón de microservicios o Un montón de aplicaciones. Por lo tanto, puede comparar las opciones: qué sucede si desarrolla todo usted mismo o utiliza la malla de servicio existente. Opción estándar: hágalo usted mismo o compre.
Enrutamiento de tráfico para aplicaciones alojadas
Las aplicaciones DotCloud pueden proporcionar puntos finales HTTP y TCP.
Los puntos finales HTTP se agregan dinámicamente a la configuración del clúster de equilibrador de carga de
Hipache . Esto es similar a lo que hacen hoy los recursos de Kubernetes
Ingress y un equilibrador de carga como
Traefik .
Los clientes se conectan a puntos finales HTTP a través de sus respectivos dominios, siempre que el nombre de dominio apunte a equilibradores de carga dotCloud. Nada especial
Los puntos finales TCP están asociados con un número de puerto, que luego se pasa a todos los contenedores de esta pila a través de variables de entorno.
Los clientes pueden conectarse a los 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 de
NodePort .
No había un equivalente de los servicios
ClusterIP en la
plataforma dotCloud : por simplicidad, el acceso a los servicios era el mismo tanto desde el interior como desde el exterior de la plataforma.
Todo se organizó de manera bastante simple: las implementaciones iniciales de las redes de enrutamiento HTTP y TCP, probablemente solo unos cientos de líneas de Python. Algoritmos simples (diría ingenuos) que se finalizaron con el crecimiento de la plataforma y la llegada de requisitos adicionales.
No se requirió una refactorización extensa del código existente. En particular,
las aplicaciones de 12 factores pueden usar directamente la dirección obtenida a través de variables de entorno.
¿Cómo difiere esto de una malla de servicio moderna?
Visibilidad limitada Generalmente no teníamos métricas para la cuadrícula de enrutamiento TCP. En cuanto al enrutamiento HTTP, las versiones posteriores tienen métricas HTTP detalladas con códigos de error y tiempos de respuesta, pero las mallas de servicio modernas van aún más lejos, proporcionando integración con sistemas de recopilación de métricas como Prometheus, por ejemplo.
La visibilidad es importante no solo desde un punto de vista operativo (para ayudar a solucionar problemas), sino también cuando se lanzan nuevas funciones. Se trata de un
despliegue seguro
azul-verde y el
despliegue de canarios .
La eficiencia del enrutamiento también
es limitada. En la cuadrícula de enrutamiento dotCloud, todo el tráfico tenía que pasar por un grupo de nodos de enrutamiento dedicados. Esto significó un posible cruce de varias fronteras AZ (zonas de accesibilidad) y un aumento significativo en el retraso. Recuerdo cómo solucioné problemas con el código que realizaba más de cien consultas SQL por página y para cada consulta se abría una nueva conexión al servidor SQL. Cuando se inicia localmente, la página se carga instantáneamente, pero en dotCloud, la carga tarda unos segundos, ya que toma decenas de milisegundos para cada conexión TCP (y la consulta SQL posterior). En este caso particular, las conexiones persistentes resolvieron el problema.
Las mallas de servicio modernas mejoran con tales problemas. En primer lugar, verifican que las conexiones se enrutan
en la 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ápida (microsegundos en lugar de milisegundos).
Las mallas de servicio modernas también implementan algoritmos de equilibrio de carga más inteligentes. Al controlar el rendimiento de los backends, pueden enviar más tráfico a backends más rápidos, lo que conduce a un aumento en el rendimiento general.
La seguridad también es mejor. La cuadrícula de enrutamiento dotCloud funcionó completamente en EC2 Classic y no cifró el tráfico (suponiendo que si alguien lograra poner un sniffer en el tráfico de la red EC2, ya tiene grandes problemas). Las mallas de servicio modernas protegen de forma transparente todo nuestro tráfico, por ejemplo, con autenticación mutua TLS y cifrado posterior.
Enrutamiento de tráfico para servicios de plataforma
Ok, discutimos el tráfico entre aplicaciones, pero ¿qué pasa con la plataforma dotCloud en sí?
La plataforma en sí consistía en aproximadamente un centenar de microservicios responsables de diversas funciones. Algunos recibieron solicitudes de otros, y algunos eran trabajadores de fondo que se conectaron a otros servicios pero no aceptaron conexiones. En cualquier caso, cada servicio debe conocer los puntos finales de las direcciones a las que es necesario conectarse.
Muchos servicios de alto nivel pueden usar la cuadrícula de enrutamiento descrita anteriormente. De hecho, muchos de los más de cientos de microservicios dotCloud se han implementado como aplicaciones regulares en la plataforma dotCloud. Pero una pequeña cantidad de servicios de bajo nivel (en particular, que implementan esta cuadrícula de enrutamiento) necesitaba algo más simple, con menos dependencias (ya que no podían depender de sí mismos para el trabajo, un buen viejo problema de huevo y gallina).
Estos servicios importantes de bajo nivel se implementaron ejecutando contenedores directamente en varios nodos clave. Al mismo tiempo, los servicios de plataforma estándar no estaban involucrados: el enlazador, el planificador y el corredor. Si desea comparar con plataformas de contenedores modernas, es como lanzar un plano de control con
docker run
directamente en los nodos, en lugar de delegar la tarea de Kubernetes. Esto es bastante similar al concepto de
módulos estáticos (hogares) que
usa kubeadm o
bootkube al cargar un clúster independiente.
Estos servicios fueron expuestos de una manera simple y cruda: sus nombres y direcciones se enumeraron en el archivo YAML; y cada cliente tuvo que tomar una copia de este archivo YAML para su implementación.
Por un lado, es extremadamente confiable porque no requiere el soporte de un almacén externo de clave / valor como Zookeeper (no lo olvide, en ese momento, etcd o Consul aún no existía). Por otro lado, esto dificultaba el traslado de servicios. Cada vez que se mueven, todos los clientes deberían haber recibido un archivo YAML actualizado (y posiblemente reiniciar). No muy conveniente!
Posteriormente, comenzamos a introducir un nuevo esquema, donde cada cliente se conectaba a un servidor proxy local. En lugar de la dirección y el puerto, le basta con saber solo el número de puerto del servicio y conectarse a través de
localhost
. El servidor proxy local procesa esta conexión y la enruta al servidor real. Ahora, al mover el back-end a otra máquina o escalar en lugar de actualizar todos los clientes, solo necesita actualizar todos estos servidores proxy locales; y ya no se requiere reiniciar.
(También se planeó encapsular el tráfico en las conexiones TLS y colocar 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 el
localhost
. Más sobre esto más adelante).
Esto es muy similar al
SmartStack de Airbnb, pero la diferencia significativa es que SmartStack se implementa y se implementa en producción, mientras que el sistema de enrutamiento interno dotCloud se colocó en una caja cuando dotCloud se convirtió en Docker.
Personalmente considero que SmartStack es uno de los predecesores de sistemas como Istio, Linkerd y Consul Connect, porque todos siguen el mismo patrón:
- Ejecutando proxies en cada nodo.
- Los clientes se conectan al proxy.
- El plano de administración actualiza la configuración del proxy al cambiar los backends.
- ... Beneficio!
Implementación moderna de una malla de servicios.
Si necesitamos implementar una grilla similar hoy, podemos usar principios similares. Por ejemplo, configure la zona DNS interna asignando nombres de servicio a direcciones en
127.0.0.0/8
. Luego ejecute HAProxy en cada nodo del clúster, acepte conexiones a cada dirección de servicio (
127.0.0.0/8
en esta subred) y redirija / equilibre la carga a los backends correspondientes. La configuración de HAProxy se puede controlar mediante
confd , lo que le permite almacenar información de back-end en etcd o Consul y enviar automáticamente la configuración actualizada a HAProxy cuando sea necesario.
¡Así es como funciona Istio! Pero con algunas diferencias:
- Utiliza Envoy Proxy en lugar de HAProxy.
- Guarda la configuración de back-end a través de la API de Kubernetes en lugar de etcd o Consul.
- Los servicios son direcciones asignadas en la subred interna (direcciones Kubernetes ClusterIP) en lugar de 127.0.0.0/8.
- Tiene un componente opcional (Ciudadela) para agregar autenticación mutua TLS entre el cliente y los servidores.
- Admite nuevas funciones como interrupción de circuitos, rastreo distribuido, despliegue de canarios, etc.
Echemos un vistazo rápido a algunas de las diferencias.
Enviado proxy
Enftoy Proxy fue escrito por Lyft [competidor de Uber en el mercado de taxis - aprox. trans.]. Es muy similar a otros servidores proxy en muchos sentidos (por ejemplo, HAProxy, Nginx, Traefik ...), pero Lyft escribió el suyo porque necesitaban funciones que no están en otros servidores proxy, y parecía más razonable hacer uno nuevo que expandir el existente.
Enviado se puede usar solo. Si tengo un servicio específico que debería 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 recibo muchas características adicionales excelentes, por ejemplo, visibilidad. En lugar de una biblioteca de cliente personalizada o incrustar el seguimiento de llamadas en el código, dirigimos el tráfico a Envoy y recopila métricas para nosotros.
Pero Envoy también puede funcionar como un plano de datos para una malla de servicio. Esto significa que para esta malla de servicios, Envoy ahora está configurado
por el plano de control.
Plano de control
En el plano de gestión, Istio se basa en la API de Kubernetes.
Esto no es muy diferente de usar confd , que se basa en etcd o Consul para ver un conjunto de claves en un almacén de datos. Istio, a través de la API de Kubernetes, visualiza el conjunto de recursos de Kubernetes.
Entre los casos : personalmente encontré
útil esta
descripción de la API de Kubernetes , que dice:
El Servidor API de Kubernetes es un "servidor tonto" que ofrece almacenamiento, control de versiones, verificación, actualización y semántica de los recursos API.
Istio está diseñado para trabajar con Kubernetes; y si desea usarlo fuera de Kubernetes, debe ejecutar una instancia del servidor API de Kubernetes (y el servicio auxiliar, etc.).
Direcciones de servicio
Istio se basa en las direcciones de ClusterIP que Kubernetes asigna, por lo que los servicios de Istio obtienen una dirección interna (no en el rango
127.0.0.0/8
).
El tráfico a la dirección de ClusterIP para un servicio específico en el clúster de Kubernetes sin Istio es interceptado por kube-proxy y enviado a la parte del servidor de este proxy. Si está interesado en los detalles técnicos, entonces kube-proxy establece las reglas de iptables (o equilibradores de carga IPVS, dependiendo de cómo lo configure) para reescribir las direcciones IP de destino de las conexiones que van a la dirección ClusterIP.
Después de instalar Istio en el clúster de Kubernetes, nada cambia hasta que se activa explícitamente para el consumidor dado o incluso para todo el espacio de nombres al introducir el contenedor del
sidecar
en hogares personalizados. Este contenedor iniciará una instancia de Envoy y establecerá una serie de reglas de iptables para interceptar el tráfico 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 el nombre del servicio, y todo "simplemente funciona". En otras palabras, nuestro código emite solicitudes como
http://api/v1/users/4242
, luego
api
resuelve la solicitud a
10.97.105.48
, las reglas de iptables interceptan conexiones desde 10.97.105.48 y las redirigen al proxy local de Envoy, y este proxy local dirigirá solicitud de la API de back-end real. Fuh!
Cositas extra
Istio también proporciona encriptación y autenticación de extremo a extremo a través de mTLS (TLS mutuo). El componente llamado
Ciudadela es responsable de esto.
También hay un componente de
Mezclador que Envoy puede solicitar para
cada solicitud para tomar una decisión especial sobre esta solicitud, dependiendo de varios factores, como encabezados, carga de back-end, etc. (no se preocupe: hay muchas maneras de asegurarse de que el Mezclador funcione, e incluso si se bloquea, Envoy continuará funcionando normalmente como un proxy).
Y, por supuesto, mencionamos la visibilidad: Envoy recopila una gran cantidad de métricas, al tiempo que proporciona un seguimiento distribuido. En la arquitectura de microservicios, si una solicitud de API debe pasar por los microservicios A, B, C y D, cuando inicie sesión en el sistema, la traza distribuida agregará un identificador único a la solicitud y guardará este identificador a través de subconsultas en todos estos microservicios, lo que le permitirá registrar todas las llamadas relacionadas, sus retrasos, etc.
Desarrollar o comprar
Istio tiene fama de ser un sistema complejo. En contraste, construir una cuadrícula de enrutamiento, que describí al principio de esta publicación, es relativamente simple usando las herramientas existentes. Entonces, ¿tiene sentido crear su propia malla de servicio?
Si tenemos necesidades modestas (no necesita visibilidad, un disyuntor y otras sutilezas), entonces pensamos en desarrollar su propia herramienta. Pero si usamos Kubernetes, puede que ni siquiera sea necesario, porque Kubernetes ya proporciona herramientas básicas para el descubrimiento de servicios y el equilibrio de carga.
Pero si tenemos requisitos avanzados, entonces "comprar" una malla de servicio parece ser una opción mucho mejor. (Esto no siempre es una "compra", porque Istio viene con código fuente abierto, pero aún necesitamos invertir tiempo de ingeniería para comprender su trabajo, implementarlo y administrarlo).
¿Qué elegir: Istio, Linkerd o Consul Connect?
Hasta ahora solo hemos hablado de Istio, pero este no es el único servicio de malla. Una alternativa popular es
Linkerd , y también hay
Consul Connect .
Que elegir
Honestamente, no lo se. Por el momento, no me considero lo suficientemente competente como para responder a esta pregunta. Hay algunos
artículos interesantes que comparan 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 servicio. En lugar de estudiar API específicas (y, en mi opinión, relativamente complejas) de varias mallas de servicio, podemos usar construcciones SuperGloo más simples, y cambiar fácilmente de una a otra, como si tuviéramos un formato de configuración intermedio que describa interfaces HTTP y backends capaces de generar la configuración real para Nginx, HAProxy, Traefik, Apache ...
Me entretuve 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 cuánto podrá hacer frente este último a su trabajo, es decir, le permite cambiar de una malla de servicio a otra sin reescribir las configuraciones.