(Note: this transcript has been automatically generated with light editing. It may contain errors! When in doubt, please watch the original talk!)
[00:00 min] Bienvenidos a la introducción al service mesh con Linkerd. Antes que nada, quisiera dar un agradecimiento a los patrocinadores de este evento que hacen posible que eventos como DevOpsDays Medellín sean una realidad y también a todos los voluntarios que hacen posible este evento.
[00:29 min] Mi nombre es Alejandro Pedraza. Soy ingeniero de software en Buoyant. El día de hoy les voy a hablar sobre service mesh. Cómo surge, por qué razones, qué es, y de manera un poco más técnica y detallada, la implementación particular de service mesh que hace Linkerd. Estas son algunas de mis coordenadas en redes sociales por si quieren contactarme luego de la charla.
[00:58 min] Antes que nada, un poco de historia. Durante la década pasada, Twitter fue una de las compañías en primero implementar una migración exitosa a una arquitectura de microservicios. Antes de esto, Twitter estaba basada en un monolito en el entorno de Ruby on Rails y, con el tiempo, la cantidad de tráfico que empezaba a recibir y la popularidad que aumentaba del servicio, hizo que esto se volviera inmanejable.
[01:31 min] Ustedes recordarán que, en cualquier momento que hubiera un evento importante deportivo, en el mundo, o político y la gente empezaba a tweetear como loca, el sistema automáticamente se caía. Veía uno una ballena tirada por pajaritos. Era la fail whale.
[01:48 min] Entonces se tomó la decisión, para arreglar este problema, de migrar a una arquitectura de microservicios que fue un gran éxito. Pero antes de hablar de microservicios quisiera que habláramos un poco del monolito que tenían antes.
[02:04 min] Más o menos por esa época se hablaba de la metodología de Twelve Factor Apps que, en cierta forma, tenía mucha intersección con la filosofía de DevOps, en la que se listaban una serie de asuntos a tener en cuenta para ofrecer un servicio de manera exitosa online.
[02:25 min] Cosas como el manejo de la configuración y la integración continua, el despliegue continuo,los logs, el monitoreo, etc. Y, en el contexto de un monolito, era relativamente fácil proveer un checklist completo de todas estas cosas ya que, cada entorno por separado, ofrecía de manera autocontenida todas estas cosas. Ya bien sea en el mundo de Ruby on Rails, de Python, donde sea.
[02:56 min] Entonces se podría decir que el monolito era técnicamente superior. Sin embargo, a medida que aumentaban las necesidades de capacidad del servicio, empezaban a surgir otros problemas, principalmente de tipo organizacional. Ya con equipos de desarrollo muchos más grandes, era mucho más difícil que cada desarrollador tuviera en la cabeza el funcionamiento general y completo del sistema.
[03:23 min] Cada vez que se quería arreglar un bug o agregar una nueva funcionalidad, existía el gran riesgo de que interactuara de una forma inesperada con otra parte del sistema que no se comprendía bien y generara un error impredecible a futuro.
[03:41 min] Entonces, lo que hicieron los microservicios, fue dividir y compartimentar de la mayor manera posible, todos los aspectos del sistema en partes separadas asignadas a equipos relativamente independientes, que solamente tenían que saber sobre esa parte del sistema e interactuar con otras partes a través de interfaces bien definidas.
[04:10 min] Esto, sin embargo, trajo consigo otra serie de problemas ya a nivel técnico. Porque ya no se contaba con la certeza de llamados a funciones locales que iban a retornar exitosamente, sino, se tenía que pensar en: llamados remotos, en la red, qué hacer en caso de fallas, cuál era la política de reintentos, de timeout, etc.
[04:41 min] Twitter, en particular, para resolver ese problema, creó la librería Finagle en Scala, que se encargaba de la seguridad de las conexiones, de la confiabilidad, etc. Ya, los desarrolladores, responsables por sus servicios, tenían que pensar en la lógica de negocios, pero también tenían que saber un poco sobre la infraestructura subyacente y cómo configurar Finagle para poder comunicarse con los otros servicios.
[05:19 min] Para solventar este acoplamiento indebido entre lógica de negocios e infraestructura, se inventa el concepto del service mesh — esto ya es por fuera de Twitter — y se crea el producto Linkerd en su primera versión. No es otra cosa, sino el empaquetamiento de Finagle como un proxy, que es desplegado de manera independiente en cada uno de los nodos del clúster. Y todas las conexiones de manera automática tienen que pasar por ese proxy.
[05:47 min] Entonces, los desarrolladores de los servicios se enfocan 100% en su lógica de negocios, y el mantenimiento y la configuración de sus proxies son responsabilidades exclusiva de los administradores de la infraestructura.
[06:02 min] Como les había dicho, Finagle fue escrito en Scala, corre en la JVM, lo cual, a su vez, trae consigo ciertos retos cuando se despliega Scala, notablemente en cuanto al consumo de recursos. También tenemos el problema del garbage collection que es difícil de afinar y presenta problemas de latencia, sobre todo en la cola de la distribución.
[06:28 min] Puede que en el 99% de los casos no se observe una latencia grande cuando el proxy interviene en las conexiones, pero en el 1% de los casos que le tocó una garbage collection dura, el usuario final se ve afectado. Sobre todo si tenemos en cuenta que los servicios pueden estar encadenados uno detrás de otro y las latencias se componen multiplicativamente. Entonces, a final de cuentas, la experiencia del usuario puede ser terrible. Más adelante vamos a ver cómo se abordó ese problema.
[7:01 min] De manera simultánea, Kubernetes empieza a ganar mucha adopción y madurez. En pocas palabras, Kubernetes es un sistema de orquestación de contenedores que permite abstraer toda la infraestructura subyacente a un conjunto de microservicios y le facilita la vida a los operadores y a los desarrolladores, al poder declarar — es un sistema declarativo — las características de su sistema: Cuántas réplicas de tal servicio quieren, cuántos recursos máximo puede consumir cada servicio, cosas de ese estilo.
[07:46 min] Kubernetes se encarga de reconciliar esa información y llegar a ese estado deseado a través de consistencia eventual. Otro tipo de proyectos, como Prometheus y Helm junto con Kubernetes empiezan a ser parte de una fundación, Cloud Native Computing Foundation (CNCF), cuya misión primordial es agregar todos los proyectos en open source que giran en torno al ecosistema de Kubernetes. En el año 2017, Linkerd también empieza a ser parte de esa fundación
[08:19 min] Esos proyectos siguen una “carrera” en esa fundación, dependiendo de su grado de madurez. Empezando por sandbox, siguiendo por incubación y terminando por graduación. Casualmente, esta semana, anunciamos que Linkerd se graduó en la CNCF.
[08:40 min] Dado el gran éxito de Kubernetes, se toma la decisión de reescribir Linkerd en su segunda versión. Esta vez ya enfocado un 100% a Kubernetes. Esto, fundamentalmente, significa que ya el proxy no es desplegado de manera única en cada nodo, sino que se adopta el modelo de sidecar.
[09:04 min] La abstracción fundamental de Kubernetes son los pods que envuelven cada uno de los contenedores que conforman nuestro servicio. Y el modelo de sidecar, lo que hace es inyectar en cada uno de sus pods, una instancia de Linkerd, del proxy.
[09:22 min] La principal ventaja es que el proxy, como debe apropiarse de todas las conexiones entrantes y salientes, requiere privilegios a nivel administrativo [el proxy hace uso de iptables para enrutar las conexiones, lo cual requiere privilegios administrativos]. En el caso del pod, los privilegios se limitan al contexto del pod, mientras que en el caso anterior, se requieren privilegios administrativos al nivel del nodo, lo cualobviamente es menos deseable.
[9:52 min] Este proxy se reescribe para poder adoptar el modelo de sidecar en Kubernetes y se reescribe en el lenguaje de Rust — una apuesta que se hizo hace un par de años. En ese entonces, las librerías de Rust no estaban muy maduras, entonces se contribuyó mucho a su desarrollo. Todo eso está en el dominio del open source.
[10:23 min] Una característica importante de Rust es que es de compilación nativa, lo cual significa que tiene un excelente desempeño. Una compilación nativa tradicionalmente significa que no hay garbage collection, lo cual nos soluciona el problema que mencioné de la latencia en la cola de la distribución. Pero eso, usualmente significa que hay que hacer un manejo de memoria explícito durante el runtime, lo cual es muy difícil de hacer y, por lo general, es por ahí donde surgen los problemas de acceso indebido a la memoria, los problemas de seguridad, buffer overflows, cosas de ese tipo.
[11:06 min] Rust, una de las cosas que hizo diferente, es que permite al desarrollador, de forma declarativa, a través del modelo de ownership y borrowing, determinar las partes del código que van a ser responsables de los datos. Eso nos da una garantía de que, durante el runtime, no van a existir, o hay mucho menos posibilidad de que existan, esos problemas de seguridad. Eso es fundamental, ya que el proxy es una parte central del cluster, todo va pasar por los proxies y la seguridad es fundamental.
[11:48 min] El hecho de que esta compilación sea nativa, como les dije, significa que no hay un gran consumo de recursos y se puede efectivamente inyectar el proxy en todos pods y eso no significa que vaya a haber un consumo adicional apreciable de los recursos requeridos en el clúster.
[12:11 min] Qué significa service mesh, o malla de servicios en en español, exactamente? Ahí tenemos un diagrama de microservicios. Los microservicios ya no se comunican directamente entre sí, sino que ponemos como una “malla” encima del clúster y las conexiones se efectúan directamente entre proxies. Lo cual significa que el proxy está en un lugar privilegiado para entender todo lo que está pasando en el clúster. Así puede capturar una cantidad de métricas importantes, puede ser configurado para ser el responsable de la confiabilidad de esas conexiones — es decir, se le configura con las políticas de reintentos, de timeout.
[12:56 min] El proxy está enterado de la latencia de las conexiones, lo cual le permite establecer un balanceo de carga basado en latencia. Lo cual es mucho más efectivo que el balanceo de carga que Kubernetes ofrece por defecto. Todo este conjunto de proxies lo llamamos el plano de datos. Existe otro componente de Linkerd que es el plano de control a partir del cual se configuran los proxies y es el que también extrae toda la información, las métricas de los proxies para agregarla y mostrársela de manera general al operador para que sepa cuál es la salud general del clúster.
[13:39 min] Ahora vamos un poco más en detalle a ver cuál es la arquitectura de Linkerd. Comenzamos con un ejemplo muy sencillo. Un par de pods en nuestro clúster que contienen un contenedor que conforma el servicio. Aquí tenemos dos servicios. Antes de que se termine de crear el pod, hay un componente en el plano de control de Linkerd que captura ese evento — se llama el inyector — e inyecta el proxy en el pod antes que el pod se termine de crear.
[14:16 min] El proxy, a través de unas reglas de iptables, redirecciona todas las conexiones entrantes y salientes para que entren por el proxy. Luego, lo primero que hace el proxy, es comunicarse con el componente de identidad que le devuelve un certificado que le permite establecer conexiones de mTLS con todos los demás proxies del clúster.
[14:42 min] El protocolo TLS tradicional, que se conoce por lo general, es, por ejemplo, cuando un navegador se comunica con un servidor, el servidor auténtica su identidad mostrando un certificado y si el cliente quiere autenticar su identidad, ya se hace a nivel de la aplicación logueandose con un nombre de usuario y contraseña, por ejemplo.
[15:07 min] En contraste, con mTLS, tanto el servidor como el cliente, deben proveer una identidad criptográfica. Esto es lo que es el mutual TLS. Esto sucede de manera automática y totalmente transparente, de tal forma que la aplicación que está ahí en el pod, no tiene que hacer absolutamente nada para beneficiarse de ese sistema.
[15:29 min] A veces se piensa que una comunicación encriptada dentro de los elementos del clúster no es realmente necesaria y que simplemente es suficiente asegurar el perímetro del clúster. Pero, si alguno de los pods del cluster llegará a ver comprometida su seguridad, este pod puede servir como punto de entrada para comprometer los demás pods y, si existe una conexión encriptada entre ellos, el riesgo de comprometer otros posts es mucho menor.
[16:01 min] Cuando el proxy se va a comunicar con otro proxy, antes de eso se comunica con el componente “destination” que le informa cuál ruta tomar para llegar a ese proxy. En escenarios sencillos, esto cumple la misma labor que el componente de service discovery de Kubernetes. En escenarios más complejos, por ejemplo, si el pod o el proxy destino está en otro clúster, el componente destino le informa al proxy cuál es la ruta para llegar a ese destino.
[16:38 min] También le informa sobre las características de la conexión, como cuál es la política de reintentos, cuál la del timeouts. También si existe un split del tráfico, es decir si cierto porcentaje de las requests deben ir a cierto destino y el otro porcentaje a otro destino. Esto hace parte de la respuesta del componente de “destination”.
[17:05 min] La parte de abajo, como les dije, es el plano de datos, proxies en Rust. La parte de arriba, el plano de control. Estos son componentes escritos en Go, porque Go es el lenguaje de Kubernetes y nos permite usar las mismas librerías que usa Kubernetes.
[17:25 min] Sin embargo, hoy en día las librerías de Rust para Kubernetes han madurado bastante y ya estamos haciendo algunas pruebas con otros componentes escritos directamente en Rust.
[17:38 min] En la próxima versión, va a salir el componente de autorización, que permite configurar, digamos qué proxy puede comunicarse con qué otro proxy. Es parecido a lo que el recurso de NetworkPolicy en Kubernetes hace — es un poco más complejo. También, por ejemplo, permite que comunicaciones que no estén encriptadas, no sean aceptadas.
[18:02 min] Esto es lo que nos permite manejar la seguridad y la confiabilidad de las conexiones. Existe otro pilar importante que es la observabilidad del clúster. Esto se ofrece de manera opcional a través del componente Linkerd viz. Su primer elemento es una instancia de Prometheus que se comunica con cada uno de los proxies, extrae todas las métricas y se las expone a otro componente metrics API que las agrega.
[18:40 min] Esas métricas agregadas son consumidas por el web dashboard para que el operador vea, para cada componente o de manera agregada, la salud del sistema. También existe una línea de comandos que permite hacer consultas similares sobre el estado del sistema. Si se quiere ver el comportamiento histórico de esas métricas, también se instala por defecto una instancia de Grafana.
[19:35 min] Aquí vemos un pantallazo ejemplo del web dashboard. En la parte de abajo tenemos un par de conexiones con sus métricas en tiempo real. Como los proxies saben lo que está pasando dentro del cluster, nos permite mostrar diagramas de topología de los servicios (como aparece en la parte de arriba), se puede ver por donde pasan las conexiones, dónde están los problemas, etc.
[19:37 min] Para poder hacer diagramas de este estilo existen otros métodos sin el service mesh, por ejemplo con tracing. Pero el tracing tiene la desventaja de que es difícil de instalar y mantener, y las aplicaciones tienen que modificarse para poder enviar la información de esas trazas al sistema de tracing.
[20:05 min] En contraste, el service mesh, la ventaja fundamental es que no requiere cambio en absoluto de las aplicaciones. Las aplicaciones son completamente agnósticas al service mesh y a la infraestructura en la que están corriente. Por eso es muy fácil de implementar.
[20:22 min] Este es un ejemplo pantallazo de Grafana. Aquí vemos la tendencia histórica de la tasa de éxito, el throughput, la distribución de latencia. Por defecto, ofrecemos muchos gráficos como estos, pero la idea es que los operadores creen sus propios gráficos con todo el conjunto de métricas que ofrecemos.
[20:47 min] Otro elemento que hizo falta aquí, en la parte de visualización, es “tap” que se comunica con los proxies y nos permite hacer consultas en tiempo real sobre lo que está pasando en cada pod: las conexiones que están entrando y saliendo, sus características y tasas de éxito, headers, etc.
[21:08 min] En resumen, existe una concepción en la industria que el service mesh puede ser muy difícil de instalar y de manejar. En Linkerd, desde el principio nuestra misión ha sido ofrecer un producto fácil y minimalista. Las personas no tienen que ser expertos en service mesh para poderlo usar. Tenemos una documentación muy completa, si ustedes les interesa este tema, lo primero que les aconsejo es leer la guía de inicio que, en muy pocos minutos, les permite montar el service mesh en un cluster de prueba o en el que quieran e inmediatamente ver los resultados. Como les dije, durante esta semana anunciamos la graduación en la CNCF, publicamos un artículo — los invito a que lo lean. Es muy corto donde también se habla de cuál es la misión fundamental del proyecto, cuál es la filosofía y la manera de hacer las cosas.
[22:32 min] Muchas gracias, aquí les dejo unos links sobre el proyecto. El proyecto es totalmente open source en GitHub, tenemos un canal en Slack muy concurrido donde se responde de cualquier tipo de preguntas de principiantes o avanzadas y nuestra cuenta de Twitter. Muchas gracias!